switch语句逆向分析

switch语句逆向分析

有序

小于3时

  • 代码:

#include "stdafx.h"

void MySwitch(int x){

switch(x) {

case 1:

printf("num is 1\n");

break;

case 2:

printf("num is 2\n");

break;

case 3:

printf("num is 3\n");

break;

default:

printf("no cases match\n");

break;

}

}

int main(int argc, char* argv[])

{

MySwitch(2);

return 0;

}

  • 反汇编:

10: switch(x) {

0040D7A8 mov eax,dword ptr [ebp+8]

0040D7AB mov dword ptr [ebp-4],eax

0040D7AE cmp dword ptr [ebp-4],1

0040D7B2 je MySwitch+32h (0040d7c2)

0040D7B4 cmp dword ptr [ebp-4],2

0040D7B8 je MySwitch+41h (0040d7d1)

0040D7BA cmp dword ptr [ebp-4],3

0040D7BE je MySwitch+50h (0040d7e0)

0040D7C0 jmp MySwitch+5Fh (0040d7ef)

11: case 1:

12: printf("num is 1\n");

0040D7C2 push offset string "num is 1\n" (00422fc4)

0040D7C7 call printf (00401060)

0040D7CC add esp,4

13: break;

0040D7CF jmp MySwitch+6Ch (0040d7fc)

14: case 2:

15: printf("num is 2\n");

0040D7D1 push offset string "num is 2\n" (00422fb8)

0040D7D6 call printf (00401060)

0040D7DB add esp,4

16: break;

0040D7DE jmp MySwitch+6Ch (0040d7fc)

17: case 3:

18: printf("num is 3\n");

0040D7E0 push offset string "num is 3\n" (00422fac)

0040D7E5 call printf (00401060)

0040D7EA add esp,4

19: break;

0040D7ED jmp MySwitch+6Ch (0040d7fc)

20: default:

21: printf("no cases match\n");

0040D7EF push offset string "Hello World!\n" (0042201c)

0040D7F4 call printf (00401060)

0040D7F9 add esp,4

22: break;

23: }

24: }

  • 反汇编分析:

1.反汇编代码为将参数x的值赋给eax

2.将eax的值放入堆栈中

3.将前面放入堆栈中的eax拿出来和第1个case中的条件进行比较(也就是比较参数x和case)

4.判断是否要跳转,je:jump equal,前面比较的两个数相同则跳转,跳转的地址为case 1对应的地址

5.如果没有跳转则继续将参数和第2个case中的条件进行比较

6.依旧是根据比较的结果判断是否要跳转,跳转的地址为case 2对应的地址

7.如果没有跳转则继续将参数和第3个case中的条件进行比较

8.依旧是根据比较的结果判断是否要跳转,跳转的地址为case 3对应的地址

9.如果没有跳转则绝对跳转到default:

下面的内容就是 case 1,case 2,case 3了

可以注意到,case里面的break都对应为跳出switch,而default里的break因为下面就已经是退出switch所以没有生成对应的汇编代码

case1里的break

case2里的break

case3里的break

通过上面的分析,发现此时(switch 中的case数量≤3时)的反汇编代码和if else并无本质上的区别,都是要依次比较判断条件

大于三时:(同理简化)

ja指令:jump above,大于时跳转(无符号),也就是比较参数x-1和3(case中的最大差值),最大差值就是最大值减最小值,此案例中就是4-1=3

如果x-1>3则跳转,如果前面参数没有减1的话,就变成了直接判断x>3,如果此时x=4也会产生跳转,不符合程序的逻辑(原本x=4应该对应跳转到case 4)

注意到这里采用的是无符号比较,而不采用有符号比较指令jg:jump greater,大于时跳转(有符号),为什么?

这里的比较代码其实就是判断参数是否在(case中的最小值,case中的最大值)这个区间内

当参数小于case中的最小值时,前面的sub ecx,case中的最小值就后就会产生下溢,此时将其看作无符号数就会相当大,一定会大于case中的最大差值,举个简单的例子,假如此时的参数为0,0-1 = -1对应的是十六进制为FFFF FFFF,将其看作无符号数就是4294967295

0040D7BB ja $L539+0Fh (0040d803)

跳转的地址为:0040d803,对应为default的地址

23: default:

24: printf("no cases match\n");

0040D803 push offset string "Hello World!\n" (0042201c)

0040D808 call printf (00401060)

0040D80D add esp,4

25: break;

如果前面没有跳转,这里则又将前面保存的参数-1的值取了出来,并赋值给edx

0040D7BD mov edx,dword ptr [ebp-4]

过上面的分析,发现此时(switch 中的case数量>3时)的反汇编代码和if else的差别就体现出来了,但到达一定条件后一般都会采用到case地址表首地址+偏移的方法,此时是将参数的值减case中的最小值,然后判断这个减完的数值是否大于case中的最大差值,如果大于则直接跳转到default,如果小于或等于则通过jmp [存储case地址表的首地址+偏移×4]的方式直接跳转到对应case的地址,而不再像if else中那样依次比较来判断是否要跳转

无序

  • 代码

switch(x) {

case 2:

printf("num is 2\n");

break;

case 3:

printf("num is 3\n");

break;

case 4:

printf("num is 4\n");

break;

case 1:

printf("num is 1\n");

break;

default:

printf("no cases match\n");

break;

}

简单地调换了一下case语句的顺序,观察其反汇编

  • 反汇编代码

10: 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,1

0040D7B4 mov dword ptr [ebp-4],ecx

0040D7B7 cmp dword ptr [ebp-4],3

0040D7BB ja $L539+0Fh (0040d803)

0040D7BD mov edx,dword ptr [ebp-4]

0040D7C0 jmp dword ptr [edx*4+40D821h]

11: case 2:

12: printf("num is 2\n");

0040D7C7 push offset string "num is 1\n" (00422fd0)

0040D7CC call printf (00401060)

0040D7D1 add esp,4

13: break;

0040D7D4 jmp $L539+1Ch (0040d810)

14: case 3:

15: printf("num is 3\n");

0040D7D6 push offset string "num is 2\n" (00422fc4)

0040D7DB call printf (00401060)

0040D7E0 add esp,4

16: break;

0040D7E3 jmp $L539+1Ch (0040d810)

17: case 4:

18: printf("num is 4\n");

0040D7E5 push offset string "num is 3\n" (00422fb8)

0040D7EA call printf (00401060)

0040D7EF add esp,4

19: break;

0040D7F2 jmp $L539+1Ch (0040d810)

20: case 1:

21: printf("num is 1\n");

0040D7F4 push offset string "num is 271\n" (00422fac)

0040D7F9 call printf (00401060)

0040D7FE add esp,4

22: break;

0040D801 jmp $L539+1Ch (0040d810)

23: default:

24: printf("no cases match\n");

0040D803 push offset string "no cases match\n" (0042201c)

0040D808 call printf (00401060)

0040D80D add esp,4

25: break;

26: }

27: }

EAX = 00000002 EBX = 7FFDB000

ECX = 00000001 EDX = 00000001

ESI = 00000000 EDI = 0012FF28

EIP = 0040D4C0 ESP = 0012FED8

EBP = 0012FF28 EFL = 00000293

内存

0040D521 D6 D4 40 00 衷@.

0040D525 C7 D4 40 00 窃@.

0040D529 F4 D4 40 00 粼@.

0040D52D E5 D4 40 00 逶@.

在值连续的情况下,顺序并不会影响生成大表

空缺地址通过填充default语句块地址解决,但会造成内存浪费

;大表

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递增。

posted @   风花赏秋月  阅读(9)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示