首先需要明确一点switch语句在游戏当中至关重要,而且基本都会使用它来提高效率!
因为我们在找call的时候,如果能够识别出来switch语句,只要找到一个call,后面的就都搞定了
switch:case必须是整数,也必须是常量
拦截服务器发回来的数据包,找到加红,就找到了其他的一连串技能函数
switch当中几个需要注意的细节:
1、如果没有匹配到就直接跳出break
3、不加break就从匹配的case一直往后执行,直到遇到break为止
ebp-4 -8是局部变量,ebp+8 +C是函数的参数
前面的部分都是一样的函数调用流程:
当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
这里传进来的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的位置存的什么呢?
如图所示,他存储的是651873,也就是我们需要跳转的地址
你使用edx*4一次跳4个字节,对应的正好是这个case在大表里的位置
这样,你一次比较都不需要,直接就可以用edx*4+651873查表来跳转
到这里,你应该明白为什么ecx要-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
比如你是第一个case 1,edx就是0,然后跳转到f918a0,执行的正好是printf("111")
可以看到switch语句和顺序没有关系,大表中存放的地址是按照你数值的大小来排列的,也就是说你把case 1写后面,它还是在大表的第一个位置存储的,编译器会先帮你排序,然后再把地址放到大表里面存储。
因此对于上面大表里地址的内容如下:
9f18a0:printf("111")
9f1882:printf("222")
9f1891:printf("333")
9f1873:printf("444")
还是按照从小到达的顺序跳转的
由于我们论证了switch当中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
可以看到仍然可以生成大表,只需要ecx-65就可以定位到元素在大表当中的位置了
由此可以论证只要是连续的/比较接近的数值,可以生成大表
我们测试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]
我们发现它依然会生成大表,而且空缺的那两个不连续的项,编译器不是填0,而是填上default的跳转地址。
即: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]
大表下面的这个按照字节进行检索的就是小表。
当删除到一定程度的时候,就把大表精简了,把default部分删除掉,使用小表来辅助:
小表就是紧挨着大表下面存储的,可以节省空间,因为它是按照字节来存储跳转的default地址的,可以看到它存的都是04,用来跳转到default,节省一些空间。
120 45 310 88
肯定就是按照if..else的方式来生成汇编代码,因为你不管是大表小表都对性能提升没有上面意义,所以直接转化成if..else语句即可。
》越连续连续;越相近越好;常量最好是连续挨在一起!
至此,我们完成了今天的逆向任务!喜欢的话多多点赞关注吧!