[2] 以逆向的角度来看流程控制语句——switch
[2] 以逆向的角度来看流程控制语句——switch
1. switch分支数小于4
汇编标识:
00401021 mov [ebp-4], ecx
00401024 cmp dword ptr [ebp-4], 1
00401028 jz short loc_401038 ;如果n==1,跳转到case1语句代码块
0040102A cmp dword ptr [ebp-4], 3
0040102E jz short loc_401047 ;如果n==3,跳转到case3语句代码块
00401030 cmp dword ptr [ebp-4], 64h
00401034 jz short loc_401056 ;如果n==100,跳转到case100语句代码块
00401036 jmp short loc_401063 ;跳转switch结束代码块
{
00401038 push offset aN1
0040103D call sub_4010F0
00401042 add esp, 4 ;case1语句代码块
}
00401045 jmp short loc_401063 ;跳转switch结束代码块
{
00401047 push offset aN3
0040104C call sub_4010F0
00401051 add esp, 4 ;case3语句代码块
}
00401054 jmp short loc_401063 ;跳转switch结束代码块
{
00401056 push offset aN100
0040105B call sub_4010F0
00401060 add esp, 4 ;case100语句代码块
}
逆向总结:
mov reg, mem ; 取出switch中考察的变量
;影响标志位的指令
jxx xxxx ; 跳转到对应case语句块的首地址处
; 影响标志位的指令
jxx xxxx
; 影响标志位的指令
jxx xxxx
jmp END ; 跳转到switch的结尾地址处
...... ; case语句块的首地址
jmp END ; case语句块结束,有break则产生这个jmp
...... ; case语句块的首地址
jmp END ; case语句块的结束,有break则产生这个jmp
...... ; case语句块的首地址
jmp END ; case语句块结束,有break则产生这个jmp
END: ; switch结尾
......
if…else if结构会在条件跳转后紧跟语句块;而switch结构则将所有的条件跳转都放置在一起,通过条件跳转指令,跳转到相应case语句块中。所有case语句块连在一起,在case语句块中没有break语句时,可以顺序执行后续case语句块
2. switch分支数大于3,且case的判定值为有序线性
#include <stdio.h>
int main(int argc, char* argv[]) {
int n = 1;
scanf("%d", &n);
switch(n){
case 1:
printf("n == 1");
break;
case 2:
printf("n == 2");
break;
case 3:
printf("n == 3");
break;
case 5:
printf("n == 5");
break;
case 6:
printf("n == 6");
break;
case 7:
printf("n == 7");
break;
}
return 0;
}
汇编标识:
00401000 push ebp
00401001 mov ebp, esp
00401003 sub esp, 8
00401006 mov dword ptr [ebp-8], 1
0040100D lea eax, [ebp-8]
00401010 push eax
00401011 push offset unk_417160
00401016 call sub_401180
0040101B add esp, 8
0040101E mov ecx, [ebp-8]
00401021 mov [ebp-4], ecx
00401024 mov edx, [ebp-4] ;edx=n
00401027 sub edx, 1 ;edx=n-1
0040102A mov [ebp-4], edx
0040102D cmp dword ptr [ebp-4], 6
00401031 ja short loc_401095 ;如果n>6,跳转到switch结束代码块
00401033 mov eax, [ebp-4]
00401036 jmp ds:off_40109C[eax*4] ;n当作数组下标,查表获取地址跳转
{
0040103D push offset aN1
00401042 call sub_401140
00401047 add esp, 4 ;case1语句代码块
}
0040104A jmp short loc_401095 ;跳转到switch结束代码块
{
0040104C push offset aN2
00401051 call sub_401140
00401056 add esp, 4 ;case2语句代码块
}
00401059 jmp short loc_401095 ;跳转到switch结束代码块
{
0040105B push offset aN3
00401060 call sub_401140
00401065 add esp, 4 ;case3语句代码块
}
00401068 jmp short loc_401095 ;跳转到switch结束代码块
{
0040106A push offset aN5
0040106F call sub_401140
00401074 add esp, 4 ;case5语句代码块
}
00401077 jmp short loc_401095 ;跳转到switch结束代码块
{
00401079 push offset aN6
0040107E call sub_401140
00401083 add esp, 4 ;case6语句代码块
}
00401086 jmp short loc_401095 ;跳转到switch结束代码块
{
00401088 push offset aN7
0040108D call sub_401140
00401092 add esp, 4 ;case7语句代码块
}
00401095 xor eax, eax ;switch结束代码块
逆向总结:
mov reg, mem ; 取变量
; 对变量进行运算,对齐case地址表的0下标,非必要
; 上例中的eax也可用其他寄存器替换,这里也可以是其他类型的运算
lea eax, [reg+xxxx]
; 影响标志位的指令,进行范围检查
jxx DEFAULT_ADDR
jmp dword ptr [eax*4+xxxx] ; 地址xxxx为case地址表的首地址
1)case最小值为0,edx不需要-1,不需要对齐数组下标
2)case最小值为1,edx需要-1,需要对齐数组下标
进入switch后先进行一次比较,检查输入的数值是否大于case最大值,由于使用了无符号比较(ja指令是无符号比较,大于则跳转),当输入的数值为0或一个负数时,同样会大于6,直接跳转到switch的末尾。如果有default分支,就直接跳至default语句块的首地址
注意:
为了达到线性有序,对于没有case对应的数值,编译器以switch的结束地址或者default语句块的首地址填充对应的表格项
寻址方式:4*index+首地址
3. 非线性switch结构(索引表 最大case值与最小case值差值<256)
#include <stdio.h>
int main(int argc, char* argv[]) {
int n = 0;
scanf("%d", &n);
switch(n) {
case 1:
printf("n == 1");
break;
case 2:
printf("n == 2");
break;
case 3:
printf("n == 3");
break;
case 5:
printf("n == 5");
break;
case 6:
printf("n == 6");
break;
case 255:
printf("n == 255");
break;
}
return 0;
}
两张表:case语句块地址表和case语句块索引表
地址表中的每一项保存一个case语句块的首地址,有几个case语句块就有几项。default语句块也在其中,如果没有则保存一个
switch结束地址。这个结束地址在地址表中只会保存一份。
索引表中会保存地址表的编号,索引表的大小等于最大case值和最小case值的差
总内存大小:
(MAX-MIN)* 1字节 = 索引表大小
SUM * 4字节 = 地址表大小
占用总字节数 =((MAX-MIN)* 1字节)+(SUM * 4字节)
汇编标识:
00401006 mov dword ptr [ebp-8], 0
0040100D lea eax, [ebp-8]
00401010 push eax
00401011 push offset unk_417160
00401016 call sub_401290
0040101B add esp, 8
0040101E mov ecx, [ebp-8]
00401021 mov [ebp-4], ecx
00401024 mov edx, [ebp-4]
00401027 sub edx, 1
0040102A mov [ebp-4], edx
0040102D cmp dword ptr [ebp-4], 0FEh ; switch 255 cases
00401034 ja short loc_40109F ; jumptable 00401040 default case
00401036 mov eax, [ebp-4]
00401039 movzx ecx, ds:byte_4010C4[eax]
00401040 jmp ds:off_4010A8[ecx*4] ; switch jump
00401047 push offset aN1 ; jumptable 00401040 case 0
0040104C call sub_401250
00401051 add esp, 4
00401054 jmp short loc_40109F ; jumptable 00401040 default case
00401056 push offset aN2 ; jumptable 00401040 case 1
0040105B call sub_401250
00401060 add esp, 4
00401063 jmp short loc_40109F ; jumptable 00401040 default case
00401065 push offset aN3 ; jumptable 00401040 case 2
0040106A call sub_401250
0040106F add esp, 4
00401072 jmp short loc_40109F ; jumptable 00401040 default case
00401074 push offset aN5 ; jumptable 00401040 case 4
00401079 call sub_401250
0040107E add esp, 4
00401081 jmp short loc_40109F ; jumptable 00401040 default case
00401083 push offset aN6 ; jumptable 00401040 case 5
00401088 call sub_401250
0040108D add esp, 4
00401090 jmp short loc_40109F ; jumptable 00401040 default case
00401092 push offset aN255 ; jumptable 00401040 case 254
00401097 call sub_401250
0040109C add esp, 4
0040109F xor eax, eax ; jumptable 00401040 default case
逆向总结:
mov reg, mem ; 取出switch变量
sub reg,1 ; 调整对齐到索引表的下标0
mov mem, reg
; 影响标记位的指令
jxx xxxx ; 超出范围跳转到switch结尾或 default
mov reg, [mem] ; 取出switch变量
; eax不是必须使用的,但之后的数组查询用到的寄存器一定是此处使用到的寄存器
xor eax,eax
mov al,byte ptr (xxxx)[reg] ; 查询索引表,得到地址表的下标
jmp dword ptr [eax*4+xxxx] ; 查询地址表,得到对应的case块的首地址
有两次查找地址表的过程,先分析第一次查表代码,byte ptr指明了表中的元素类型为byte。然后分析是否使用在第一次查表中获取的单字节数据作为下标,从而决定是否使用相对比例因子的寻址方式进行第二次查表。最后检查基址是否指向了地址表
4. 非线性switch结构(判定树 最大case值与最小case值差值>255)
将每个case值作为一个节点,找到这些节点的中间值作为根节点,以此形成一棵二叉平衡树,以每个节点为判定值,大于和小于关系分别对应左子树和右子树
#include <stdio.h>
int main(int argc, char* argv[]) {
int n = 0;
scanf("%d", &n);
switch(n){
case 2:
printf("n == 2\n");
break;
case 3:
printf("n == 3\n");
break;
case 8:
printf("n == 8\n");
break;
case 10:
printf("n == 10\n");
break; case 35:
printf("n == 35\n");
break;
case 37:
printf("n == 37\n");
break;
case 666:
printf("n == 666\n");
break;
default:
printf("default\n");
break;
}
return 0;
}
Debug汇编标识:
00401006 mov dword ptr [ebp-8], 0
0040100D lea eax, [ebp-8]
00401010 push eax
00401011 push offset unk_417160
00401016 call sub_4011A0
0040101B add esp, 8
0040101E mov ecx, [ebp-8]
00401021 mov [ebp-4], ecx
00401024 cmp dword ptr [ebp-4], 0Ah
00401028 jg short loc_401047 ;如果n>10,则跳转到判断n>10代码块
0040102A cmp dword ptr [ebp-4], 0Ah
0040102E jz short loc_40108B ;如果n==10,则跳转case10语句代码块
00401030 cmp dword ptr [ebp-4], 2
00401034 jz short loc_40105E ;如果n==2,则跳转case2语句代码块
00401036 cmp dword ptr [ebp-4], 3
0040103A jz short loc_40106D ;如果n==3,则跳转case3语句代码块
0040103C cmp dword ptr [ebp-4], 8
00401040 jz short loc_40107C ;如果n==8,则跳转case8语句代码块
00401042 jmp loc_4010C7 ;跳转default代码块
00401047 cmp dword ptr [ebp-4], 23h
0040104B jz short loc_40109A ;如果n==35,则跳转case35语句代码块
0040104D cmp dword ptr [ebp-4], 25h
00401051 jz short loc_4010A9 ;如果n==37,则跳转case35语句代码块
00401053 cmp dword ptr [ebp-4], 29Ah
0040105A jz short loc_4010B8 ;如果n==666,则跳转case666语句代码块
0040105C jmp short loc_4010C7 ;跳转default代码块
Release汇编标识:
00401020 push ecx
00401021 lea eax, [esp]
00401024 mov dword ptr [esp], 0
0040102B push eax
0040102C push offset unk_417160
00401031 call sub_401150
00401036 mov eax, [esp+8]
0040103A add esp, 8
0040103D cmp eax, 35
00401040 jg short loc_4010A8 ;如果n>35,则跳转到4010A8判断
00401042 jz short loc_401097 ;如果n==35,则跳转到case35语句块代码
00401044 add eax, 0FFFFFFFEh ;eax=n-2,数组下标从0开始
00401047 cmp eax, 8
0040104A ja short loc_4010BB ;如果n>10,则跳转到 default语句块代码
0040104C jmp ds:off_4010F0[eax*4] ;查表
004010A8 cmp eax, 25h
004010AB jz short loc_4010EE ;如果n==37,则跳转到case37语句块代码
004010AD cmp eax, 29Ah
004010B2 jz short loc_4010DD ;如果n==666,则跳转到case666语句块代码
004010B4 cmp eax, 2710h
004010B9 jz short loc_4010CC ;如果n==10000,则跳转到case10000语句块代码
逆向总结:
优化过程中,检测树的左子树或右子树能否满足if…else…优化、有序线性优化、非线性索引优化,利用这3种优化来降低树的高度。选择效率最高,又满足匹配条件的。如果以上3种优化都无法匹配,选择使用判定树进行优化