[教程]逆向反汇编第七课

来源:互联网 发布:cocos2d js 源码 编辑:程序博客网 时间:2024/04/28 14:15

在以C为代表的高级语言中用if-then-else,switch-case等高级语句来构成程序的判断流程,
不仅条理清晰且维护性还是不错的.而汇编语言的代码则复杂得多,会看到cmp等指令的后面跟着 各类的跳转指令jz jnz等.识别关键跳转是软件破解的一个重要技能,许多软件用一个或多个跳转 来实现注册或非注册.
下面先说说if-then-else语句
将语句if-then-else语句编译代码后,整数用cmp指令比较,而浮点值则是使用fcom fcomp比 较.语句if-then-else编译后,其汇编代码形式如下:
cmp    a,b    ;a和b只是代表
jz(jnz)    xxxx    ;xxxx是一个地址
cmp指令不修改操作符,根据两个操作数的相减,影响处理的几个标志,如零标志 进位标志 符号标志和溢出标志,jz等指令就是条件跳转指令,根据a,b的值大小决定跳转方向.
实际上,许多情况下编译器都用test或or之类的较短的逻辑指令来替换cmp指令.一般形式是"test eax,eax",如eax如果为0,则逻辑与运算结果为零,否则设为0.
来看一个C反汇编的实例.

 

view plaincopy to clipboardprint?
  1. #include <stdio.h>    
  2. int main()    
  3. {    
  4.     int a,b=5;    
  5.     scanf("%d",&a);    
  6.     if (a==0)    
  7.      a=8;    
  8.     return a+b;    
  9. }   

 

看反汇编的代码:

 

view plaincopy to clipboardprint?
  1. push    ecx            ;为局部变量分配内存相当于sub esp,4    
  2. lea    eax,dword ptr ss:[esp]    ;eax指向局部变量空间    
  3. push    eax                
  4. push    00407030        ;指向字符串"%d"    
  5. call    00401030        ;c语言的scanf函数    
  6. mov    eax,dword ptr [esp+8]    ;将输入的字符传出    
  7. add    esp,8            ;由于是cdecl调用,所以在函数外调用    
  8. test    eax,eax            ;若eax为0,则ZF置1,否则ZF置0    
  9. jnz    00401020        ;若ZF=1不跳转,否则跳转    
  10. mov    eax,8    
  11. add    eax,8            ;return a+b    
  12. pop    ecx            ;释放局部变量用到的内存,相当于add esp,4    
  13. retn   

 

好,看完if-then-else了,下面砍switch-case语句.
SWITCH语句是多分支选择语句.SWITCH语句编译后,实质就是多个IF-THEN语句嵌套组合.编译 器会将SWITCH编译成一组不同关系运算组成的语句.具体点,来看一个例子.

 

view plaincopy to clipboardprint?
  1. #include <stdio.h>    
  2. int main(void)    
  3. {    
  4. int a    
  5. scanf("%d",&a);    
  6. switch(a)    
  7. {    
  8.     case1:printf("a=1");    
  9.     break;    
  10.     case2:printf("a=2");    
  11.      break;    
  12.     case3:printf("a=10");    
  13.     break;    
  14.     default:printf("a=default");    
  15.     break;    
  16. }    
  17.     return 0;    
  18. }   

 

把它编译,然后反汇编看看汇编代码:

 

view plaincopy to clipboardprint?
  1. push    ebp    
  2. mov    ebp,esp    
  3. sub    esp,8        ;为局部变量分配空间    
  4. lea    eax,[ebp-04]        
  5. push    eax        ;指向字符("%d")    
  6. call    004010A2    ;scanf("%d",&a)    
  7. add    esp,8        ;    
  8. mov    ecx,[ebp-04]    ;输入的结果给ecx    
  9. mov    [ebp-08],ecx    
  10. cmp    [ebp-08],1    ;如果是1    
  11. je    00401031    
  12. cmp    [ebp-08],2    ;如果是2    
  13. je    00401040        
  14. cmp    [ebp-08],0a    ;如果是10    
  15. je    0040104F    
  16. jmp    0040105E    
  17. push    00408034    
  18. call    00401071    ;printf("a=1")    
  19. add    esp,04    
  20. jmp    0040106B    
  21. push    00408038        
  22. call    00401071    ;printf("a=2")    
  23. add    esp,04    
  24. jmp    0040106B    
  25. push    0040803C    
  26. call    00401071    ;printf ("a=10")    
  27. jmp    0040106B    
  28. push    00408044    
  29. call    00401071    ;printf("a=default")    
  30. add    esp,04    
  31. xor    eax,eax    
  32. mov    esp,ebp    
  33. pop    ebp    
  34. ret  

 

上面的是未优化的反汇编,再看看优化过后的:

 

view plaincopy to clipboardprint?
  1. push    ecx        ;为局部变量分配内存,相当于sub    esp,4    
  2. lea    eax,[esp]    
  3. push    eax    
  4. push    0040804c    
  5. call    004010A1    ;scanf("%d",&a)    
  6. mov    eax,[esp+08]    ;scanf输入的结果传给eax    
  7. add    esp,08        
  8. dec    eax        ;检测eax是否为1,如果是下面的那句就跳转    
  9. je    00401055    ;相当于case    1    
  10. dec    eax        ;eax再减一,即eax的值是2    
  11. je    00401044    ;相当于ease    2    
  12. sub    eax,8        ;eax两次减1后的值为8,所以值为18    
  13. je    00401033   

 

编译器优化后用"dec eax"指令代替cmp指令,这样指令更短,并且执行速度更快,并且优化后,编译器会合理排列switch后各case节点,有最优化方式找到所需要的节点.
     如果case取值表示一个算数级数,那么编译器会利用一个跳转表来实现例如"

 

view plaincopy to clipboardprint?
  1. switch(a)    
  2. {    
  3.     case 1:printf("a=1");break;    
  4.     case 2:printf("a=2");break;    
  5.     case 3:printf("a=3");break;    
  6.     case 4:printf("a=3");break;    
  7.     case 5:printf("a=3");break;    
  8.     case 6:printf("a=3");break;    
  9.     case 7:printf("a=3");break;    
  10.     default :printf("a=default"):break;    
  11. }   

 

编译器编译后,"jmp dword ptr [4*eax+004010B0]"指令就相当于switch(a),其根据eax的值进行索引,计算出指向相应case处理代码的指针.汇编代码如下:

 

view plaincopy to clipboardprint?
  1. lea    eax,dword ptr [ecx-0]    
  2. cmp    eax,6            ;判断是否为default节点    
  3. ja    0040109D    
  4. jmp    dword ptr [4*eax+004010B0]    ;跳转表    
  5. push    00408054        ;case1:printf("a=1")    
  6. call    004010D0    
  7. add    esp,04    
  8. xor    eax,eax    
  9. pop    ecx    
  10. ret   

 

下面的我就不写了,反正大概就是这样.

原创粉丝点击