C语言逆向——switch语句中的大表和小表,本质上是内在存储空间降低
连续值中抹去多项
CPP代码:
#include "stdafx.h"
void Fun(int x) {
switch (x) {
case 100:
printf("100");
break;
case 101:
printf("101");
break;
case 102:
printf("102");
break;
case 106:
printf("106");
break;
case 108:
printf("108");
break;
case 112:
printf("112");
break;
case 115:
printf("115");
break;
default:
printf("None");
break;
}
}
int main(int argc, char* argv[]) {
Fun(110);
return 0;
}
反汇编:
Fun:
0040D7E0 push ebp
0040D7E1 mov ebp,esp
0040D7E3 sub esp,44h
0040D7E6 push ebx
0040D7E7 push esi
0040D7E8 push edi
0040D7E9 lea edi,[ebp-44h]
0040D7EC mov ecx,11h
0040D7F1 mov eax,0CCCCCCCCh
0040D7F6 rep stos dword ptr [edi]
0040D7F8 mov eax,dword ptr [ebp+8]
0040D7FB mov dword ptr [ebp-4],eax
0040D7FE mov ecx,dword ptr [ebp-4]
0040D801 sub ecx,64h
0040D804 mov dword ptr [ebp-4],ecx
0040D807 cmp dword ptr [ebp-4],0Fh
0040D80B ja $L545+0Fh (0040d888)
0040D80D mov eax,dword ptr [ebp-4]
0040D810 xor edx,edx ;edx清零
0040D812 mov dl,byte ptr (0040d8c6)[eax] ;dl=[小表地址+eax]
0040D818 jmp dword ptr [edx*4+40D8A6h]
$L533:
0040D81F push offset string "102" (00422fb4)
0040D824 call printf (0040d760)
0040D829 add esp,4
0040D82C jmp $L545+1Ch (0040d895)
$L535:
0040D82E push offset string "1003\n" (00422fd8)
0040D833 call printf (0040d760)
0040D838 add esp,4
0040D83B jmp $L545+1Ch (0040d895)
$L537:
0040D83D push offset string "109" (00422fac)
0040D842 call printf (0040d760)
0040D847 add esp,4
0040D84A jmp $L545+1Ch (0040d895)
$L539:
0040D84C push offset string "1009\n" (00422fd0)
0040D851 call printf (0040d760)
0040D856 add esp,4
0040D859 jmp $L545+1Ch (0040d895)
$L541:
0040D85B push offset string "108" (00422fa4)
0040D860 call printf (0040d760)
0040D865 add esp,4
0040D868 jmp $L545+1Ch (0040d895)
$L543:
0040D86A push offset string "109" (00422fc8)
0040D86F call printf (0040d760)
0040D874 add esp,4
0040D877 jmp $L545+1Ch (0040d895)
$L545:
0040D879 push offset string "115" (00422fc0)
0040D87E call printf (0040d760)
0040D883 add esp,4
0040D886 jmp $L545+1Ch (0040d895)
0040D888 push offset string "error\n" (00422f6c)
0040D88D call printf (0040d760)
0040D892 add esp,4
0040D895 pop edi
0040D896 pop esi
0040D897 pop ebx
0040D898 add esp,44h
0040D89B cmp ebp,esp
0040D89D call __chkesp (004010a0)
0040D8A2 mov esp,ebp
0040D8A4 pop ebp
0040D8A5 ret
Memory:
;大表
0040D8A6 1F D8 40 00 .谸.
0040D8AA 2E D8 40 00 .谸.
0040D8AE 3D D8 40 00 =谸.
0040D8B2 4C D8 40 00 L谸.
0040D8B6 5B D8 40 00 [谸.
0040D8BA 6A D8 40 00 j谸.
0040D8BE 79 D8 40 00 y谸.
0040D8C2 88 D8 40 00 堌@.
;小表
0040D8C6 00 01 02 07 ....
0040D8CA 07 07 03 07 ....
0040D8CE 04 07 07 07 ....
0040D8D2 05 07 07 06 ....
小表的解释:
-
当空缺值太多时内存的浪费也会变多,编译器当然知道这样不是办法,所以利用小表来解决这个问题。小表可以看作是一个智能蹦床,对于不同的玩家会给出不同的力,遇到没有付费的玩家(空缺值)直接将他抛出场外(给出参数,使其跳转到
default
的语句块),遇到付费玩家(存在的值)则按照他的等级给出不同的力(给出参数,使其跳转到其对应的语句块) -
可以看出,在小表中所有的空缺值都是
07
(因为在这个样例中,当edx=7
时[edx*4+40D8A6h]
的地址为default
语句块的地址),而存在的值的对应值从0
递增。
断开一定程度的选择
关键点
mov dl,byte ptr (004010d0)[eax]
jmp dword ptr [edx4+4010BCh]
(004010d0)[eax] = [004010d0 + eax4] 在这个地址中得到一个偏移 作为查询大表的edx值。==》内在原理是啥???再度深入思考了下,其实很简单!上面switch case 302~307缺失,如果使用大表查询的话,则需要在大表里填充6个default地址,如果缺失的量比较大,则造成地址空间的浪费,假如100个,则这100个都是default都浪费啊。
如何降低这些浪费的空间呢?很直观的思路就是大表里只需要存一个default,编译器的做法是使用用小表去记录跳转。也就是上面dl(0-255)mov语句,记录了跳转的case清单,小表里可以记录0-255内的跳转,这样就节省了一些空间。
也就是说,小表里记录:
case1 跳转小偏移1
case2 跳转小偏移2
case3 跳转小偏移3
...
对于上面那种6个default的跳转场景:
case302 default跳转小偏移
case302 default跳转小偏移
case303 default跳转小偏移
case304 default跳转小偏移
case305 default跳转小偏移
case306 default跳转小偏移
...
这样最后跳转的时候,就直接先通过小表查询到跳转的小偏移(2个字节),然后大表里就只需要一个default地址(4个字节)即可了。而不再像原来那样,需要大表里6个default地址了。
我们计算下节省的空间,
仅大表的方式存储空间:6x4=24 字节
小表+大表结合的方式:6x2+4=16字节
还是节省了8个字节。
我自己的示例:
void Fun(int x) { switch (x) { case 100: printf("100"); break; case 107: printf("106"); break; case 108: printf("108"); break; case 109: printf("112"); break; case 110: printf("115"); break; default: printf("None"); break; } } int main(int argc, char* argv[]) { Fun(101); return 0; }
73: void Fun(int x) { 0040D790 push ebp 0040D791 mov ebp,esp 0040D793 sub esp,44h 0040D796 push ebx 0040D797 push esi 0040D798 push edi 0040D799 lea edi,[ebp-44h] 0040D79C mov ecx,11h 0040D7A1 mov eax,0CCCCCCCCh 0040D7A6 rep stos dword ptr [edi] 74: switch (x) { 0040D7A8 mov eax,dword ptr [ebp+8] 0040D7AB mov dword ptr [ebp-4],eax 0040D7AE mov ecx,dword ptr [ebp-4] 0040D7B1 sub ecx,64h 0040D7B4 mov dword ptr [ebp-4],ecx 0040D7B7 cmp dword ptr [ebp-4],0Ah 0040D7BB ja $L17058+0Fh (0040d81a) 0040D7BD mov eax,dword ptr [ebp-4] 0040D7C0 xor edx,edx 0040D7C2 mov dl,byte ptr (0040d850)[eax] 0040D7C8 jmp dword ptr [edx*4+40D838h] 75: case 100: 76: printf("100"); 0040D7CF push offset string "102" (00422fb4) 0040D7D4 call printf (0040d710) 0040D7D9 add esp,4 77: break; 0040D7DC jmp $L17058+1Ch (0040d827) 78: case 107: 79: printf("106"); 0040D7DE push offset string "106" (00422fb0) 0040D7E3 call printf (0040d710) 0040D7E8 add esp,4 80: break; 0040D7EB jmp $L17058+1Ch (0040d827) 81: case 108: 82: printf("108"); 0040D7ED push offset string "108" (00422fac) 0040D7F2 call printf (0040d710) 0040D7F7 add esp,4 83: break; 0040D7FA jmp $L17058+1Ch (0040d827)
0040D850 00 05 05 05 05 05 05 01 02 03 04 CC CC CC CC ...........烫烫 0040D85F CC CC CC CC
可以看到小表里,有6个05,可以看到对应的就是101~106的default偏移!
05 05 05 05 05 05