switch语句详细逆向分析

首先需要明确一点switch语句在游戏当中至关重要,而且基本都会使用它来提高效率!

因为我们在找call的时候,如果能够识别出来switch语句,只要找到一个call,后面的就都搞定了

switch:case必须是整数,也必须是常量

拦截服务器发回来的数据包,找到加红,就找到了其他的一连串技能函数

switch语句详细逆向分析_第1张图片

switch当中几个需要注意的细节:

1、如果没有匹配到就直接跳出break

3、不加break就从匹配的case一直往后执行,直到遇到break为止

ebp-4 -8是局部变量,ebp+8 +C是函数的参数

1、当分支条件比较少的时候,switch和if else效率相当:

前面的部分都是一样的函数调用流程: 

switch语句详细逆向分析_第2张图片

当case的个数很少的时候,不会生成大表,和if..else语句没有任何区别 

n是传进来的函数的参数:n=3

006E1845  mov         eax,dword ptr [n]  
006E1848  mov         dword ptr [ebp-0C4h],eax  
006E184E  cmp         dword ptr [ebp-0C4h],1  
006E1855  je          __$EncStackInitStart+3Fh (06E186Bh)  
006E1857  cmp         dword ptr [ebp-0C4h],2  
006E185E  je          __$EncStackInitStart+4Eh (06E187Ah)  
006E1860  cmp         dword ptr [ebp-0C4h],3  
006E1867  je          __$EncStackInitStart+5Dh (06E1889h)  
006E1869  jmp         __$EncStackInitStart+6Ah (06E1896h)  
006E186B  push        offset string "111\n" (06E7B30h)  
006E1870  call        _printf (06E10CDh)  
006E1875  add         esp,4  
006E1878  jmp         __$EncStackInitStart+6Ah (06E1896h)  
006E187A  push        offset string "222\n" (06E7B38h)  
006E187F  call        _printf (06E10CDh)  
006E1884  add         esp,4  
006E1887  jmp         __$EncStackInitStart+6Ah (06E1896h)  
006E1889  push        offset string "111\n" (06E7B30h)  
006E188E  call        _printf (06E10CDh)  
006E1893  add         esp,4 
006E1896  pop         edi  
006E1897  pop         esi  
006E1898  pop         ebx  
006E1899  add         esp,0C4h  
006E189F  cmp         ebp,esp  
006E18A1  call        __RTC_CheckEsp (06E1244h)  
006E18A6  mov         esp,ebp  
006E18A8  pop         ebp  
006E18A9  ret  

 2、当case的数量超过一定的数值之后,就会生成大表:

这里传进来的n=3

00651845  mov         eax,dword ptr [n]  
00651848  mov         dword ptr [ebp-0C4h],eax  
0065184E  mov         ecx,dword ptr [ebp-0C4h]  
00651854  sub         ecx,1  
00651857  mov         dword ptr [ebp-0C4h],ecx  
0065185D  cmp         dword ptr [ebp-0C4h],3  
00651864  ja          $LN7+0Dh (06518ADh)  
00651866  mov         edx,dword ptr [ebp-0C4h]  
0065186C  jmp         dword ptr [edx*4+6518C4h]
00651873  push        offset string "111\n" (0657B30h)  
00651878  call        _printf (06510CDh)  
0065187D  add         esp,4  
00651880  jmp         $LN7+0Dh (06518ADh)  
00651882  push        offset string "222\n" (0657B38h)  
00651887  call        _printf (06510CDh)  
0065188C  add         esp,4  
0065188F  jmp         $LN7+0Dh (06518ADh)  
00651891  push        offset string "333\n" (0657B40h)  
00651896  call        _printf (06510CDh)  
0065189B  add         esp,4  
0065189E  jmp         $LN7+0Dh (06518ADh)  
006518A0  push        offset string "444\n" (0657B48h)  
006518A5  call        _printf (06510CDh)  
006518AA  add         esp,4  
006518AD  pop         edi  
006518AE  pop         esi  
006518AF  pop         ebx  
006518B0  add         esp,0C4h  
006518B6  cmp         ebp,esp  
006518B8  call        __RTC_CheckEsp (0651244h)  
006518BD  mov         esp,ebp  
006518BF  pop         ebp  
006518C0  ret 

先把ecx-1和第二大的值比较,如果大于就直接跳转到default

那么为什么要先让ecx-1然后去和倒数第二大的数比较,这不是多此一举吗?直接使用ecx和最大的数进行比较他不香吗?,这里先留个悬念,供大家思考。

否则就跳转到 dword ptr [edx*4+6518c4]的位置 

那么6518C4的位置存的什么呢?

switch语句详细逆向分析_第3张图片

如图所示,他存储的是651873,也就是我们需要跳转的地址

你使用edx*4一次跳4个字节,对应的正好是这个case在大表里的位置

这样,你一次比较都不需要,直接就可以用edx*4+651873查表来跳转

到这里,你应该明白为什么ecx要-1了吧?》不就是为了定位它在大表当中的位置吗!

3、交换case的顺序,改成4 2 3 1 ,观察大表的生成是否会发生变化?

009F1845  mov         eax,dword ptr [n]  
009F1848  mov         dword ptr [ebp-0C4h],eax  
009F184E  mov         ecx,dword ptr [ebp-0C4h]  
009F1854  sub         ecx,1  
009F1857  mov         dword ptr [ebp-0C4h],ecx  
009F185D  cmp         dword ptr [ebp-0C4h],3  
009F1864  ja          $LN7+0Dh (09F18ADh)  
009F1866  mov         edx,dword ptr [ebp-0C4h]  
009F186C  jmp         dword ptr [edx*4+9F18C4h]
009F1873  push        offset string "444\n" (09F7B30h)  
009F1878  call        _printf (09F10CDh)  
009F187D  add         esp,4  
009F1880  jmp         $LN7+0Dh (09F18ADh)  
009F1882  push        offset string "222\n" (09F7B38h)  
009F1887  call        _printf (09F10CDh)  
009F188C  add         esp,4  
009F188F  jmp         $LN7+0Dh (09F18ADh)  
009F1891  push        offset string "333\n" (09F7B40h)  
009F1896  call        _printf (09F10CDh)  
009F189B  add         esp,4  
009F189E  jmp         $LN7+0Dh (09F18ADh)  
009F18A0  push        offset string "111\n" (09F7B48h)  
009F18A5  call        _printf (09F10CDh)  
009F18AA  add         esp,4  
009F18AD  pop         edi  
009F18AE  pop         esi  
009F18AF  pop         ebx  
009F18B0  add         esp,0C4h  
009F18B6  cmp         ebp,esp  
009F18B8  call        __RTC_CheckEsp (09F1244h)  
009F18BD  mov         esp,ebp  
009F18BF  pop         ebp  
009F18C0  ret  

 switch语句详细逆向分析_第4张图片

switch语句详细逆向分析_第5张图片 

比如你是第一个case 1,edx就是0,然后跳转到f918a0,执行的正好是printf("111")

可以看到switch语句和顺序没有关系,大表中存放的地址是按照你数值的大小来排列的,也就是说你把case 1写后面,它还是在大表的第一个位置存储的,编译器会先帮你排序,然后再把地址放到大表里面存储。

因此对于上面大表里地址的内容如下:

9f18a0:printf("111")

9f1882:printf("222")

9f1891:printf("333")

9f1873:printf("444")

还是按照从小到达的顺序跳转的

由于我们论证了switch当中case的顺序不会影响大表的顺序,所有以后都使用默认排序。

4、当case的数值很大的时候还会生成大表吗?

00DE1845  mov         eax,dword ptr [n]  
00DE1848  mov         dword ptr [ebp-0C4h],eax  
00DE184E  mov         ecx,dword ptr [ebp-0C4h]  
00DE1854  sub         ecx,65h  
00DE1857  mov         dword ptr [ebp-0C4h],ecx  
00DE185D  cmp         dword ptr [ebp-0C4h],3  
00DE1864  ja          $LN7+0Dh (0DE18ADh)  
00DE1866  mov         edx,dword ptr [ebp-0C4h]  
00DE186C  jmp         dword ptr [edx*4+0DE18C4h]
00DE1873  push        offset string "111\n" (0DE7B30h)  
00DE1878  call        _printf (0DE10CDh)  
00DE187D  add         esp,4  
00DE1880  jmp         $LN7+0Dh (0DE18ADh)  
00DE1882  push        offset string "222\n" (0DE7B38h)  
00DE1887  call        _printf (0DE10CDh)  
00DE188C  add         esp,4  
00DE188F  jmp         $LN7+0Dh (0DE18ADh)  
00DE1891  push        offset string "333\n" (0DE7B40h)  
00DE1896  call        _printf (0DE10CDh)  
00DE189B  add         esp,4  
00DE189E  jmp         $LN7+0Dh (0DE18ADh)  
00DE18A0  push        offset string "111\n" (0DE7B48h)  
00DE18A5  call        _printf (0DE10CDh)  
00DE18AA  add         esp,4  
00DE18AD  pop         edi  
00DE18AE  pop         esi  
00DE18AF  pop         ebx  
00DE18B0  add         esp,0C4h  
00DE18B6  cmp         ebp,esp  
00DE18B8  call        __RTC_CheckEsp (0DE1244h)  
00DE18BD  mov         esp,ebp  
00DE18BF  pop         ebp  
00DE18C0  ret  

switch语句详细逆向分析_第6张图片 

可以看到仍然可以生成大表,只需要ecx-65就可以定位到元素在大表当中的位置了

由此可以论证只要是连续的/比较接近的数值,可以生成大表

5、那么离散的情况呢?

我们测试101 104 105 106的情况,即中间缺少了2个连续的项

00811845  mov         eax,dword ptr [n]  
00811848  mov         dword ptr [ebp-0C4h],eax  
0081184E  mov         ecx,dword ptr [ebp-0C4h]  
00811854  sub         ecx,65h  
00811857  mov         dword ptr [ebp-0C4h],ecx  
0081185D  cmp         dword ptr [ebp-0C4h],5  
00811864  ja          $LN7+0Dh (08118ADh)  
00811866  mov         edx,dword ptr [ebp-0C4h]  
0081186C  jmp         dword ptr [edx*4+8118C4h]

 switch语句详细逆向分析_第7张图片

 我们发现它依然会生成大表,而且空缺的那两个不连续的项,编译器不是填0,而是填上default的跳转地址。

6、如果只保留头和尾,把中间项都删掉呢?

即:401 409,中间都删除了:

00411845  mov         eax,dword ptr [n]  
00411848  mov         dword ptr [ebp-0C4h],eax  
0041184E  mov         ecx,dword ptr [ebp-0C4h]  
00411854  sub         ecx,65h  
00411857  mov         dword ptr [ebp-0C4h],ecx  
0041185D  cmp         dword ptr [ebp-0C4h],9  
00411864  ja          $LN7+0Dh (04118B4h)  
00411866  mov         edx,dword ptr [ebp-0C4h]  
0041186C  movzx       eax,byte ptr [edx+4118DCh]  
00411873  jmp         dword ptr [eax*4+4118C8h]

我们需要注意这一行代码:

00411866  mov         edx,dword ptr [ebp-0C4h]  
0041186C  movzx       eax,byte ptr [edx+4118DCh]  
00411873  jmp         dword ptr [eax*4+4118C8h]

switch语句详细逆向分析_第8张图片 

大表下面的这个按照字节进行检索的就是小表。

当删除到一定程度的时候,就把大表精简了,把default部分删除掉,使用小表来辅助:

小表就是紧挨着大表下面存储的,可以节省空间,因为它是按照字节来存储跳转的default地址的,可以看到它存的都是04,用来跳转到default,节省一些空间。

7、毫不连续的情况:

120 45 310 88

肯定就是按照if..else的方式来生成汇编代码,因为你不管是大表小表都对性能提升没有上面意义,所以直接转化成if..else语句即可。

总结:什么情况下使用switch?

》越连续连续;越相近越好;常量最好是连续挨在一起!

至此,我们完成了今天的逆向任务!喜欢的话多多点赞关注吧!

你可能感兴趣的:(逆向学习,汇编,windows,c++)