How to study C && ASM Code(3)

来源:互联网 发布:淘宝几单才有2颗心 编辑:程序博客网 时间:2021/12/08 08:22

Download:
http://www.cisrg.cn/doc/lesson3.rar
                    ==www.cisrg.cn.==
                            
                    How to study C && ASM Code(3)

|=---------------=[ 运算符、表达式的逆向 ]=------------------------=|
|=-----------------------------------------------------------------=|
|=---------------=[ 7all<7all7_at_163.com> ]=----------------------=|
|=-----------------------------------------------------------------=|
|=---------------=[ 版权所有:www.cisrg.cn ]=-----------------------=|

--[ Let's Go
  一个C程序由运算符、表达式组成,表达式还包括一些循环语句。C的运算符比较多,
也比较有意思,而且其定义没有C++那般的无耻,所以这可能是有些老道的程序员喜欢
C而不喜欢C++的原因之一。一般来说C的程序都比较的随意,随意的结果就是允许程序
员自己实现更多的东西,这种随意的结果使得很多的代码狂成了C的偏执者。有那么一
段时间,本人徘徊在C、C++、JAVA与C#之间,直到某个月黑人静的晚上,我逆向了C、
C++的代码后就再也没有碰过C++,虽然偶尔也会写MFC的代码,但基本上MFC程序的核心
代码我都试图摆脱MFC的封装类。如果不是人懒惰的原因,我想我会直接拿SDK来写界面
程序的。那个月黑人静的夜晚,是什么原因要我放弃了C++呢?原因是比较简单的,我
发现我拿函数封装实现某个功能的C代码,与用类来实现同样功能的C++代码相比,其汇
编代码相差很大,这里我的意思是说C++逆向后的汇编代码要比C的多了那么十几行,同
样的Release模式编译,实现同样功能的代码居然会相差这么多!虽然现在电脑运行速度
快了很多,很难想象,如果一个超过2W行的代码使用C++书写的话,其汇编代码要比C多
多少?做为一个只有小学文化的程序员,我在考虑的差不多头皮发麻的那一瞬间,毅然
的抛弃了C++:)当然这里仅仅是个人的观点,毕竟C++还有众多的FANS存在,我可不想走
在大街上都被别人扔臭鸡蛋。

--[ 运算符
  如果您不知道C的运算符是啥子东东,您可以去论坛的FAQ那个板块提问,如果在那里有
人给您了回答,但是您还是不理解运算符是啥东东,那么您可以给我发封EMail过来,我
会找我身边对C非常熟悉的同事给您一个很专业的答案,如果您在看到我同事的回答后还
是不知道运算符是啥东东,那么您可以选择这样的一条汇编语句,以表达我们对您的崇拜
之情。汇编语句是:
mov eax, 黄河
jmp eax
  C的运算符有赋值运算符(=)、算数运算符、其它运算符,我们该小节会简单的说明下
这些运算符,本文的内容也会针对这些运算符进行我们的逆向过程。
A: 赋值运算符
例如: int i = 9;即把整数9赋值给int类型的变量i,这里需要注意的是符号=,不是真正
等于的意思,而是赋值的意思。虽然也可以称呼为等于,但在C里面的标准却是赋值。
B: 算数运算符
+、-、*、/、%、++、--,我们这里直接书写一段C代码来演示下这些运算符,当然后面会
直接拿这个代码进行我们可爱的逆向。
[code]
#include <stdio.h>
#include <stdlib.h>

int main()
{
 int i = 9;
 int j = 8;
 i = i + j;
 printf("i+j = %d/n", i);
 i = i - j;
 printf("i-j = %d/n", i);
 i = i * j;
 printf("i*j = %d/n", i);
 i = i / j;
 printf("i/j = %d/n", i);
 i = i % j;
 printf("i%%j = %d/n", i);
 i = i++;
 printf("i++ = %d/n", i);
 i = i--;
 printf("i-- = %d/n", i);
 exit(0);
}
[/code]

C: 其它运算符
sizeof是个很有意思的运算符,sizeof以字节为单位返回其操作数的大小,
sizeof、(类型)
[code]
#include <stdio.h>
#include <stdlib.h>

int main()
{
 int  i = 1;
 char *ptr = "abc";
 char str[12] = "aaa";
 float flt;
 ptr = (char *)malloc(8);
 printf("sizeof(i) = %d/n", sizeof(i));
 printf("sizeof(*ptr) = %d/n", sizeof(ptr));
 printf("sizeof(str) = %d/n", sizeof(str));
 printf("sizeof(flt) = %d/n", sizeof(flt));
 exit(0);
}
[/code]
小节:运算符的概念是很容易理解的,看过C的朋友都知道,这里也就不再加以
累赘的说明。在下面的章节我们会针对这两个代码进行逆向。

--[ 算数运算符的逆向
  按照上面的那个算数运算符的例子代码来看,该代码最后打印的结果会是:
i+j = 17
i-j = 9
i*j = 72
i/j = 9
i%j = 1
i++ = 2
i-- = 1
下面我们来剖析下汇编代码是如何完成这个工作的:)下面的代码是关于例子1的
汇编代码实现。
[code]
6:        int i = 9;
//把整数9放到ebp-4的位置,前面的文章曾详细的讲解过这种类型的赋值
0040D798   mov         dword ptr [ebp-4],9 
7:        int j = 8;
//把整数8放到ebp-8的位置
0040D79F   mov         dword ptr [ebp-8],8
8:        i = i + j;
/*这里的add指令在汇编里为加法指令*/
0040D7A6   mov         eax,dword ptr [ebp-4]--|->实现了i+j的操作
0040D7A9   add         eax,dword ptr [ebp-8]--|->而后把相加的值
0040D7AC   mov         dword ptr [ebp-4],eax--|->赋值给变量i
9:        printf("i+j = %d/n", i);
0040D7AF   mov         ecx,dword ptr [ebp-4]
0040D7B2   push        ecx
0040D7B3   push        offset string "i+j = %d/n" (00422fe8)
0040D7B8   call        printf (004012c0)
0040D7BD   add         esp,8
10:       i = i - j;
/*这里的sub指令在汇编里为减法指令*/
0040D7C0   mov         edx,dword ptr [ebp-4]--|->实现了i-j的操作
0040D7C3   sub         edx,dword ptr [ebp-8]--|->而后把相减的值
0040D7C6   mov         dword ptr [ebp-4],edx--|->赋值给变量i
11:       printf("i-j = %d/n", i);
0040D7C9   mov         eax,dword ptr [ebp-4]
0040D7CC   push        eax
0040D7CD   push        offset string "i-j = %d/n" (00422fdc)
0040D7D2   call        printf (004012c0)
0040D7D7   add         esp,8
12:       i = i * j;
/*这里的imul指令在汇编里为乘法指令*/
0040D7DA   mov         ecx,dword ptr [ebp-4]-|->实现了i*j的操作
0040D7DD   imul        ecx,dword ptr [ebp-8]-|->而后把相乘的值
0040D7E1   mov         dword ptr [ebp-4],ecx-|->赋值给变量i
13:       printf("i*j = %d/n", i);
0040D7E4   mov         edx,dword ptr [ebp-4]
0040D7E7   push        edx
0040D7E8   push        offset string "i*j = %d/n" (00422fd0)
0040D7ED   call        printf (004012c0)
0040D7F2   add         esp,8
14:       i = i / j;
/*
这里的idiv指令在汇编里为除法指令,可能大家注意到了,在idiv指令前
增加了cdq指令,cdq指令会把EAX寄存器中的符号位扩展到EDX中,可能
大家猜测到这里会做什么了:)
如果这里修改为mov dword ptr [ebp-4],edx。那么就是把进行%运算后
的数值赋值给变量i。不过即使你不去主动的使用cdq指令,Intel的处理
办法也会自动把除法的余数放到edx上。
*/
0040D7F5   mov         eax,dword ptr [ebp-4]-|->实现了i/j的操作
0040D7F8   cdq               -|->而后把相除的值
0040D7F9   idiv        eax,dword ptr [ebp-8]-|->赋值给变量i
0040D7FC   mov         dword ptr [ebp-4],eax-|
15:       printf("i/j = %d/n", i);
0040D7FF   mov         eax,dword ptr [ebp-4]
0040D802   push        eax
0040D803   push        offset string "i/j = %d/n" (00422040)
0040D808   call        printf (004012c0)
0040D80D   add         esp,8
16:       i = i % j;
/*
正如前面我们猜测的结果,cdq指令会把eax的有符号位扩展到edx上,
此时对变量i的赋值已经不是 mov [ebp-4],eax,而是mov [ebp-4],edx
此时被扩展到edx上的值就是取模后的数值:)
*/
0040D810   mov         eax,dword ptr [ebp-4]-|->实现了i%j的操作
0040D813   cdq               -|->而后把取模的值
0040D814   idiv        eax,dword ptr [ebp-8]-|->赋值给变量i
0040D817   mov         dword ptr [ebp-4],edx-|->
17:       printf("i%%j = %d/n", i);
0040D81A   mov         ecx,dword ptr [ebp-4]
0040D81D   push        ecx
0040D81E   push        offset string "i%%j = %d/n" (00422034)
0040D823   call        printf (004012c0)
0040D828   add         esp,8
18:       i = i++;
/*
  i++ = i + 1;所以这里会采取add eax,1,其它与上面我们的解释一致。
*/
0040D82B   mov         edx,dword ptr [ebp-4]
0040D82E   mov         dword ptr [ebp-4],edx
0040D831   mov         eax,dword ptr [ebp-4]
0040D834   add         eax,1
0040D837   mov         dword ptr [ebp-4],eax
19:       printf("i++ = %d/n", i);
0040D83A   mov         ecx,dword ptr [ebp-4]
0040D83D   push        ecx
0040D83E   push        offset string "i++ = %d/n" (00422028)
0040D843   call        printf (004012c0)
0040D848   add         esp,8
20:       i = i--;
/*
  i-- = i - 1;所以这里会采取sub eax,1,其它与上面我们的解释一致。
*/
0040D84B   mov         edx,dword ptr [ebp-4]
0040D84E   mov         dword ptr [ebp-4],edx
0040D851   mov         eax,dword ptr [ebp-4]
0040D854   sub         eax,1
0040D857   mov         dword ptr [ebp-4],eax
21:       printf("i-- = %d/n", i);
0040D85A   mov         ecx,dword ptr [ebp-4]
0040D85D   push        ecx
0040D85E   push        offset string "i-- = %d/n" (0042201c)
0040D863   call        printf (004012c0)
0040D868   add         esp,8
22:       exit(0);
0040D86B   push        0
0040D86D   call        exit (00401130)
23:   }
[/code]
  通过上面我们对汇编语言的理解,我们基本上可以把这个代码从C代码转换为
汇编代码了。至于怎么详细的实现了这个转换过程,这里就不再累赘了,前面
的文章比较详细的说明了整个的逆向流程。
汇编代码实现的与C代码相同的程序:)
[code]
#include <stdio.h>
#include <stdlib.h>

int main()
{
 int i = 9;
 int j = 8;
 printf("i = 9/nj = 8/nStart.../n/n");

 /*实现i+j的汇编代码*/
 __asm{
  mov eax,i;
  add eax,j;
  mov i,eax;
 }
 printf("i+j = %d/n", i);

 /*实现i-j的汇编代码*/
 __asm{
  mov eax,i;
  sub eax,j;
  mov i,eax;
 }
 printf("i-j = %d/n", i);
 
 /*实现i*j的汇编代码*/
 __asm{
  mov  eax,i;
  imul eax,j;
  mov  i,eax;
 }
 printf("i*j = %d/n", i);

 /*实现i/j的汇编代码*/
 /*__asm{
  xor edx,edx;//该段代码使用了div指令完成,其用法与idiv类似.
  mov eax,i;  //至于为什么需要xor edx,edx呢?那是因为被除数达到了双精度值          
  mov ecx,j;  //所以不能用符号扩展,而只能将高16位送0
  div ecx;
  mov i,eax;
 }*/
 /*
 大家仔细观察下这里的汇编代码与上面反编译的汇编代码有啥区别?
 上面的指令为idiv eax,dword ptr [ebp-8],而实际情况是idiv这个指令,
 在Intel的声明中只支持一个操作数而已,令人头疼的问题,但这里也不可能
 是伪指令,如果是的话,按照常理编译器应该认识我的语法...
 可怜的程序员又被MS给爽了一把,窃喜ing...
 */
 __asm{
  xor edx,edx;
  mov eax,i;
  mov ecx,j
  //cdq;
  idiv ecx;
  mov i,eax;
 }
 printf("i/j = %d/n", i);

 /*实现i%j的汇编代码*/
 __asm{
  xor edx,edx;
  mov eax,i;
  mov ebx,j;
  //cdq;
  idiv ebx;
  mov  i,edx;
 }
 printf("i%%j = %d/n", i);

 /*实现i++的汇编代码*/
 __asm{
  mov eax,i;
  add eax,0x01;
  mov i,eax;
 }
 printf("i++ = %d/n", i);
 
 /*实现i--的汇编代码*/
 //请自行书写,当家庭作业吧:)

 exit(0);
}
[/code]

--[ 其它运算符的逆向
  可爱的sizeof是我们的第一个选择,写代码写多了,于是遇到了这样的一个
问题,关于把变量置空的问题。我一般置空的办法为:
char str[12];
memset(&str, 0, sizeof(&str));这样的情况下数组一般可以被我置空,但是
那天同事突然感觉我这种置空的办法很不可思议,于是认真的考虑了下这个问题。
同事当时给了我另外一种memset置空的方法,搞的我也是莫名其妙......,今天
算是把这个问题给彻底的搞定了:)不过不知道自己这么想到底对还是不对?
memset函数原型
函数名: memset
功  能: 设置s中的所有字节为ch, s数组的大小由n给定
用  法: void *memset(void *s, char ch, unsigned n);
首先,sizeof(&str)的值肯定是12个字节的,虽然我这里使用了取地址,但是
取来取去还是str所指向的内存空间,道理还是相同的。
其次,调用memset函数时,会把&str的内存空间置空,但仔细考虑下的话,str
取地址的话,不正与&str是相同的嘛?
最后,证明我的这边办法是可行的。不过,也得出一个结论,不是学计算机专业
难免会存在很多的弊端。

  为了更好的书写这里的代码,我把上面的代码简单的修改了下,修改后的C
代码为:
[code]
int main()
{
 printf("sizeof(int) = %d/n", sizeof(int));
 printf("sizeof(char *) = %d/n", sizeof(char *));
 printf("sizeof(char) = %d/n", sizeof(char));
 printf("sizeof(float) = %d/n", sizeof(float));
 exit(0);
}
[/code]
得到的汇编代码为:
[code]
6:        printf("sizeof(int) = %d/n", sizeof(int));
0040D708   push        4 //sizeof直接返回了int类型的长度
0040D70A   push        offset string "sizeof(i) = %d/n" (00423004)
0040D70F   call        printf (00401230)
0040D714   add         esp,8
7:        printf("sizeof(char *) = %d/n", sizeof(char *));
0040D717   push        4 //sizeof直接返回了char *类型的长度
0040D719   push        offset string "sizeof(*ptr) = %d/n" (00422fcc)
0040D71E   call        printf (00401230)
0040D723   add         esp,8
8:        printf("sizeof(char) = %d/n", sizeof(char));
0040D726   push        1 //sizeof直接返回了char类型的长度
0040D728   push        offset string "sizeof(str) = %d/n" (00422fb8)
0040D72D   call        printf (00401230)
0040D732   add         esp,8
9:        printf("sizeof(float) = %d/n", sizeof(float));
0040D735   push        4 //sizeof直接返回了float类型的长度
0040D737   push        offset string "sizeof(flt) = %d/n" (0042201c)
0040D73C   call        printf (00401230)
0040D741   add         esp,8
10:       exit(0);
0040D744   push        0
0040D746   call        exit (004010a0)
[/code]
  因此,我们没有必要去逆向sizeof到底做了啥事情,他只是处理了返回的字节大小。
但在实际编程中,sizeof却是如此的重要,极少的C代码会不使用sizeof。当然也有人
会说自己写C就没有使用过sizeof,那样的话,的确得好好的佩服一下了。

--] 总结
  运算符、表达式多么美好的组合,有了这些就可以象码砖一样的建个大楼出来,
不过在你学会了这些的同时,你已经朝程序员的旅程迈进了一步了。对了,不要
以为别人喊你是程序员你就开心的不得了,那其实是在骂你而已:),最起码国内
的程序员是这样的。
 

原创粉丝点击