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递增。
标签:语句,逆向,printf,break,case,switch,num,跳转,ptr From: https://www.cnblogs.com/maqun/p/18473453