C语言逆向——switch语句中的大表和小表,本质上是内在存储空间降低

发布时间 2023-04-03 15:58:58作者: bonelee

连续值中抹去多项

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] ==>没有看懂。。。todo
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 + eax
4] 在这个地址中得到一个偏移 作为查询大表的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个字节。