[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地址表的首地址
image-20220427220236846

​ 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; 
}
image-20220427232424631

两张表: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代码块
image-20220428092148971

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种优化都无法匹配,选择使用判定树进行优化

posted @ 2023-02-03 02:10  修竹Kirakira  阅读(58)  评论(0编辑  收藏  举报