0%

12-Switch语句

Switch语句逆向

break加与不加有什么特点?default语句可以省略吗?

答: 不加,每个都会执行,可以省略,不过怕会出错

添加case后面的值,一个一个增加,观察反汇编代码的变化(何时生成大表).

将3中的常量值的顺序打乱,观察反汇编代码(观察顺序是否会影响生成大表).

答: 不会

将case后面的值改成从100开始到109,观察汇编变化(观察值较大时是否生成大表).

答: 会,减的常数不同

将连续的10项中抹去1项或者2项,观察反汇编有无变化(观察大表空缺位置的处理).

答: 无,空缺位置填充了default分支

在10项中连续抹去,不要抹去最大值和最小值(观察何时生成小表).

答: 大表+小表,达到一定数量会生成小表,小表用来查偏移,直接跳,节省内存

将case后面常量表达式改成毫不连续的值,观察反汇编变化.

答:搜索二叉树,骚啊

  1. switch分支少于4个没意思,因为会生成类似if_else的结构
  2. sub ecx,常数,是为了跳转表转向,将其转换成index
  3. 4个分支上会生成大表,直接计算地址,而大表需要连续的数值判断,如果相隔太大,会生成if_else结构

练习

写一个switch语句,不生产大表也不生产小表,贴出对应的反汇编

这个分支少于4个就会生成if_else结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
8:        switch(num)
9: {
0040D708 mov eax,dword ptr [ebp+8]
0040D70B mov dword ptr [ebp-4],eax
0040D70E cmp dword ptr [ebp-4],1
0040D712 je func+32h (0040d722)
0040D714 cmp dword ptr [ebp-4],2
0040D718 je func+41h (0040d731)
0040D71A cmp dword ptr [ebp-4],3
0040D71E je func+50h (0040d740)
0040D720 jmp func+5Dh (0040d74d)
10: case 1:
11: printf("1\n");
0040D722 push offset string "1\n" (00422024)
0040D727 call printf (00401060)
0040D72C add esp,4
12: break;
0040D72F jmp func+5Dh (0040d74d)
13: case 2:
14: printf("2\n");
0040D731 push offset string "2\n" (00422020)
0040D736 call printf (00401060)
0040D73B add esp,4
15: break;
0040D73E jmp func+5Dh (0040d74d)
16: case 3:
17: printf("3\n");
0040D740 push offset string "Hello World!\n" (0042201c)
0040D745 call printf (00401060)
0040D74A add esp,4
18: break;
19: }
20: }

写一个switch语句,只生成大表,贴出对应的反汇编.

这个分支多于4个就可以生成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
8:        switch(num)
9: {
0040D7B8 mov eax,dword ptr [ebp+8]
0040D7BB mov dword ptr [ebp-4],eax
0040D7BE mov ecx,dword ptr [ebp-4]
0040D7C1 sub ecx,1
0040D7C4 mov dword ptr [ebp-4],ecx
0040D7C7 cmp dword ptr [ebp-4],3
0040D7CB ja $L590+0Dh (0040d811)
0040D7CD mov edx,dword ptr [ebp-4]
0040D7D0 jmp dword ptr [edx*4+40D822h]
10: case 1:
11: printf("1\n");
0040D7D7 push offset string "1\n" (00422028)
0040D7DC call printf (00401060)
0040D7E1 add esp,4
12: break;
0040D7E4 jmp $L590+0Dh (0040d811)
13: case 2:
14: printf("2\n");
0040D7E6 push offset string "2\n" (00422024)
0040D7EB call printf (00401060)
0040D7F0 add esp,4
15: break;
0040D7F3 jmp $L590+0Dh (0040d811)
16: case 3:
17: printf("3\n");
0040D7F5 push offset string "3\n" (00422020)
0040D7FA call printf (00401060)
0040D7FF add esp,4
18: break;
0040D802 jmp $L590+0Dh (0040d811)
19: case 4:
20: printf("4\n");
0040D804 push offset string "4\n" (0042201c)
0040D809 call printf (00401060)
0040D80E add esp,4
21: break;
22: }
23: }

还有一种大表,带default的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
8:        switch(num)
9: {
00401038 mov eax,dword ptr [ebp+8]
0040103B mov dword ptr [ebp-4],eax
0040103E mov ecx,dword ptr [ebp-4]
00401041 sub ecx,1
00401044 mov dword ptr [ebp-4],ecx
00401047 cmp dword ptr [ebp-4],9
0040104B ja $L598+0Fh (004010d3)
00401051 mov edx,dword ptr [ebp-4]
00401054 jmp dword ptr [edx*4+4010F1h]
10: case 1:
11: printf("1\n");
0040105B push offset string "1\n" (00422040)
00401060 call printf (004011b0)
00401065 add esp,4
12: break;
00401068 jmp $L598+1Ch (004010e0)
13: /*
14: case 2:
15: printf("2\n");
16: break;
17:
18: case 3:
19: printf("3\n");
20: break;
21: */
22: case 4:
23: printf("4\n");
0040106A push offset string "4\n" (0042203c)
0040106F call printf (004011b0)
00401074 add esp,4
24: break;
00401077 jmp $L598+1Ch (004010e0)
25: case 5:
26: printf("5\n");
00401079 push offset string "5\n" (00422038)
0040107E call printf (004011b0)
00401083 add esp,4
27: break;
00401086 jmp $L598+1Ch (004010e0)
28: case 6:
29: printf("6\n");
00401088 push offset string "6\n" (00422034)
0040108D call printf (004011b0)
00401092 add esp,4
30: break;
00401095 jmp $L598+1Ch (004010e0)
31: case 7:
32: printf("7\n");
00401097 push offset string "7\n" (00422030)
0040109C call printf (004011b0)
004010A1 add esp,4
33: break;
004010A4 jmp $L598+1Ch (004010e0)
34: case 8:
35: printf("8\n");
004010A6 push offset string "8\n" (0042202c)
004010AB call printf (004011b0)
004010B0 add esp,4
36: break;
004010B3 jmp $L598+1Ch (004010e0)
37: case 9:
38: printf("9\n");
004010B5 push offset string "9\n" (00422028)
004010BA call printf (004011b0)
004010BF add esp,4
39: break;
004010C2 jmp $L598+1Ch (004010e0)
40: case 10:
41: printf("10\n");
004010C4 push offset string "10\n" (00422024)
004010C9 call printf (004011b0)
004010CE add esp,4
42: break;
004010D1 jmp $L598+1Ch (004010e0)
43: default:
44: printf("error\n");
004010D3 push offset string "error\n" (0042201c)
004010D8 call printf (004011b0)
004010DD add esp,4
45: break;
46: }
47: }

004010F1 5B 10 40 00 [.@.
004010F5 D3 10 40 00 ..@.
004010F9 D3 10 40 00 ..@.
004010FD 6A 10 40 00 j.@.
00401101 79 10 40 00 y.@.
00401105 88 10 40 00 ..@.
00401109 97 10 40 00 ..@.
0040110D A6 10 40 00 ..@.
00401111 B5 10 40 00 ..@.

这是生成的大表,中间被我注释掉的全部生成了default语句

写一个switch语句,生成大表和小表,贴出对应的反汇编.

11个注释掉6个后终于生成了小表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
8:        switch(num)
9: {
00401038 mov eax,dword ptr [ebp+8]
0040103B mov dword ptr [ebp-4],eax
0040103E mov ecx,dword ptr [ebp-4]
00401041 sub ecx,1
00401044 mov dword ptr [ebp-4],ecx
00401047 cmp dword ptr [ebp-4],9
0040104B ja $L590+0Fh (0040109b)
0040104D mov eax,dword ptr [ebp-4]
00401050 xor edx,edx
00401052 mov dl,byte ptr (004010cd)[eax]
00401058 jmp dword ptr [edx*4+4010B9h]
10: case 1:
11: printf("1\n");
0040105F push offset string "7\n" (00422030)
00401064 call printf (004011b0)
00401069 add esp,4
12: break;
0040106C jmp $L590+1Ch (004010a8)
13: /*
14: case 2:
15: printf("2\n");
16: break;
17:
18: case 3:
19: printf("3\n");
20: break;
21:
22: case 4:
23: printf("4\n");
24: break;
25:
26: case 5:
27: printf("5\n");
28: break;
29:
30: case 6:
31: printf("6\n");
32: break;
33:
34: case 7:
35: printf("7\n");
36: break;
37: */
38: case 8:
39: printf("8\n");
0040106E push offset string "8\n" (0042202c)
00401073 call printf (004011b0)
00401078 add esp,4
40: break;
0040107B jmp $L590+1Ch (004010a8)
41: case 9:
42: printf("9\n");
0040107D push offset string "9\n" (00422028)
00401082 call printf (004011b0)
00401087 add esp,4
43: break;
0040108A jmp $L590+1Ch (004010a8)
44: case 10:
45: printf("10\n");
0040108C push offset string "10\n" (00422024)
00401091 call printf (004011b0)
00401096 add esp,4
46: break;
00401099 jmp $L590+1Ch (004010a8)
47: default:
48: printf("error\n");
0040109B push offset string "error\n" (0042201c)
004010A0 call printf (004011b0)
004010A5 add esp,4
49: break;
50: }
51: }

这就是小表

004010CD 00 04 04 04 ….
004010D1 04 04 04 01 ….

小表查找的语句

1
2
3
4
040104D   mov         eax,dword ptr [ebp-4]
00401050 xor edx,edx
00401052 mov dl,byte ptr (004010cd)[eax]
00401058 jmp dword ptr [edx*4+4010B9h]

小表就是个存了偏移的表,通过比较,取出相对应的偏移位置,然后在jmp

为do..while语句生成的反汇编填写注释.

1
2
3
4
5
6
7
8
9
10
11
12
9:        do{
10: printf("A");
0040103F push offset string "error\n" (0042201c)
00401044 call printf (004011b0)
00401049 add esp,4
11: }while(i++ < 10);
0040104C mov eax,dword ptr [ebp-4]
0040104F mov ecx,dword ptr [ebp-4]
00401052 add ecx,1
00401055 mov dword ptr [ebp-4],ecx
00401058 cmp eax,0Ah
0040105B jl func+1Fh (0040103f)

do while先执行,后面cmp跳转回去,明显特征,只有往上跳,没有往下跳的

总结:

  1. 根据条件跳转指令所跳转到的地址,可以得到循环语句块的起始地址。
  2. 根据条件跳转指令所在的地址,可以得到循环语句块的结束地址。
  3. 条件跳转的逻辑与源码相同。

为while语句生成的反汇编填写注释.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
9:        while(i++ < 10)
0040103F mov eax,dword ptr [ebp-4]
00401042 mov ecx,dword ptr [ebp-4]
00401045 add ecx,1
00401048 mov dword ptr [ebp-4],ecx
0040104B cmp eax,0Ah
0040104E jge func+3Fh (0040105f)
10: {
11: printf("A");
00401050 push offset string "error\n" (0042201c)
00401055 call printf (004011b0)
0040105A add esp,4
12: };
0040105D jmp func+1Fh (0040103f)

while语句特征,先cmp比较,然后在执行,最后往上跳,特征,cmp后才会执行

总结:

  1. 根据条件跳转指令所跳转到的地址,可以得到循环语句块的结束地址;
  2. 根据jmp 指令所跳转到的地址,可以得到循环语句块的起始地址;
  3. 在还原while 比较时,条件跳转的逻辑与源码相反。

为for语句生成的反汇编填写注释.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
9:        for(i=0; i<10; i++)
0040103F mov dword ptr [ebp-4],0
00401046 jmp func+31h (00401051)
00401048 mov eax,dword ptr [ebp-4]
0040104B add eax,1
0040104E mov dword ptr [ebp-4],eax
00401051 cmp dword ptr [ebp-4],0Ah
00401055 jge func+4Ah (0040106a)
10: {
11: printf("%d" , i);
00401057 mov ecx,dword ptr [ebp-4]
0040105A push ecx
0040105B push offset string "1" (0042201c)
00401060 call printf (004011b0)
00401065 add esp,8
12: }
00401068 jmp func+28h (00401048)

for循环的话,开头跳去比较,比较过后执行,最后自增

总结:

  1. 第一个jmp 指令之前为赋初值部分.
  2. 第一个jmp 指令所跳转的地址为循环条件判定部分起始.
  3. 判断条件后面的跳转指令条件成立时跳转的循环体外面
  4. 条件判断跳转指令所指向的地址上面有一个jmp jmp地址为表达式3的起始位置

本文作者:NoOne
本文地址https://noonegroup.xyz/posts/e4f694a1/
版权声明:转载请注明出处!