滴水逆向 02-06 switch语句反汇编
之前的笔记都是边听边写的,这次边听边玩塞尔达呢。。。笔记都是课后整理的,可能会有遗落,会尽力回想
switch语句
switch 语句是 if 语句的简写

switch 要求:
- case 后面必须是常量表达式
- case 后面常量表达式的值不能一样
- switch 后面表达式必须为整数
switch语句的反汇编
常见的有四种:
- 分支比较少:if…else
- 分支比较多且值连续:生成大表
- 分支比较多,不连续且差值小:还是大表
- 分支比较多,差值小且空缺多:生成大表+小表
接下来详细看一下每种情况
-
分支比较少:if…else
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void function(int x) {
switch(x) {
case 1:
printf("1");
break;
case 2:
printf("2");
break;
case 3:
printf("3");
break;
default:
printf("5");
break;
}
}
int main(int argc, char* argv[]) {
function(2);
return 0;
}当 case 的数量比较少时,switch 和 if…else 的逻辑类似,都是 cmp 然后 jcc,只是好在把比较全都放在一起了。但是仍然没有改变本质——还是只能一个一个去判断,两者效率差不多

-
分支比较多且值连续:生成大表
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void function(int x) {
switch(x) {
case 1:
printf("1");
break;
case 2:
printf("2");
break;
case 3:
printf("3");
break;
case 4:
printf("4");
break;
default:
printf("5");
break;
}
}
int main(int argc, char* argv[]) {
function(2);
return 0;
}大表是在编译的时候就生成好了的,可以直接在内存里面查到。这时再执行时,会先计算要查询的数值是表中的第几个,如果超出了表的大小就直接跳到 default,在表内的话就用
表的首地址 + 第几个*4去查询,非常之快!
大表内存布局是严格按 case 常量值从小到大连续排列的,case 后面的常量可以是无序的,并不影响大表的生成。
如果源码中写的 case 是乱序的,大表内部存储的目标指令地址就会是乱序的。因为编译后的语句的地址顺序对应着代码执行的顺序,而大表的索引又必须按数值大小来排列。例如这个样子:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void function(int x) {
switch(x) {
case 2:
printf("2");
break;
case 4:
printf("4");
break;
case 3:
printf("3");
break;
case 1:
printf("1");
break;
default:
printf("erro"); //拼错了别管了!
break;
}
}
int main(int argc, char* argv[]) {
function(3);
return 0;
}
-
分支比较多,不连续且差值小:还是大表
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
void function(int x) {
switch(x) {
case 1:
printf("1");
break;
case 2:
printf("2");
break;
case 5:
printf("5");
break;
case 6:
printf("6");
break;
case 7:
printf("7");
break;
default:
printf("error");
break;
}
}
int main(int argc, char* argv[]) {
function(6);
return 0;
}跟之前一样的逻辑,只是大表中空缺的部分会用 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
void function(int x) {
switch(x) {
case 1:
printf("1");
break;
case 2:
printf("2");
break;
case 9:
printf("9");
break;
case 10:
printf("10");
break;
case 12:
printf("12");
break;
default:
printf("error");
break;
}
}
int main(int argc, char* argv[]) {
function(6);
return 0;
}
大表屁股后面紧接着一张小表,大表也与之前的有所不同,会在最后面跟着 default 的地址
而重点就是紫色框内的那句mov dl, byte ptr (004010e0)[eax],这句话的作用就是拿着 eax 中的值,假设是 n 吧,把 004010e0 这个地址 +n*1 的值存入 dl 中(edx 的低 8 位),我的左右括号大小怎么不一样
所以小表里的一字节表示的就是第 n 位对应着大表里的第几个,比如例子中的 2 小表里是 1,那么大表对应的地址就是 0040106E,而 6 在小表里是 5,大表里就是 default 对应的 004010AA
但是这个模式也是有条件的,如果 case 两侧涵盖的数量大于 255 个,就无法用小表了。因为小表中一个字节的最大值为 FF,所以最多只能表示 255 的偏移
do…while语句

总结:
- 根据条件跳转指令所跳转到的地址,可以得到循环语句块的起始地址
- 根据条件跳转指令所在的地址,可以得到循环语句块的结束地址
- 条件跳转的逻辑与源码相同
while语句

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

总结:
- 第一个 jmp 指令之前为赋初值部分
- 第一个 jmp 指令所跳转的地址为循环条件判定部分起始
- 判断条件后面的跳转指令条件成立时跳转的循环体外面
- 条件判断跳转指令所指向的地址上面有一个 jmp,jmp 地址为表达式 3 的起始位置
课后作业
-
写一个 switch 语句,不产生大表也不产生小表,贴出对应的反汇编
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void function(int x) {
switch(x) {
case 1:
printf("1");
break;
case 2:
printf("2");
break;
case 3:
printf("3");
break;
default:
printf("error");
break;
}
}
int main(int argc, char* argv[]) {
function(3);
return 0;
}
-
写一个 switch 语句,只生成大表,贴出对应的反汇编
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
void function(int x) {
switch(x) {
case 1:
printf("1");
break;
case 2:
printf("2");
break;
case 3:
printf("3");
break;
case 4:
printf("4");
break;
case 5:
printf("5");
break;
default:
printf("error");
break;
}
}
int main(int argc, char* argv[]) {
function(3);
return 0;
}
-
写一个 switch 语句,生成大表和小表,贴出对应的反汇编
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
void function(int x) {
switch(x) {
case 1:
printf("1");
break;
case 2:
printf("2");
break;
case 9:
printf("9");
break;
case 15:
printf("15");
break;
case 20:
printf("20");
break;
default:
printf("error");
break;
}
}
int main(int argc, char* argv[]) {
function(3);
return 0;
}
-
为 do…while 语句生成的反汇编填写注释

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

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