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

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

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

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

[2] 以逆向的角度来看流程控制语句——switch的更多相关文章

  1. 2017.10.14 Java的流程控制语句switch&&随机点名器

    今日内容介绍 1.流程控制语句switch 2.数组 3.随机点名器案例 ###01switch语句解构     * A:switch语句解构       * a:switch只能针对某个表达式的值作 ...

  2. 00018_流程控制语句switch

    1.选择结构switch switch 条件语句也是一种很常用的选择语句,它和if条件语句不同,它只能针对某个表达式的值作出判断,从而决定程序执行哪一段代码. 2.switch语句的语法格式 swit ...

  3. java流程控制语句switch

    switch 条件语句也是一种很常用的选择语句,它和if条件语句不同,它只能针对某个表达 式的值作出判断,从而决定程序执行哪一段代码. 格式: switch (表达式){ case 目标值1: 执行语 ...

  4. Java运算符、引用数据类型、流程控制语句

    1运算符 1.1算术运算符 运算符是用来计算数据的符号. 数据可以是常量,也可以是变量. 被运算符操作的数我们称为操作数. 算术运算符最常见的操作就是将操作数参与数学计算: 运算符 运算规则 范例 结 ...

  5. JAVA 1.6 流程控制语句

    1. 条件运算符(三元表达式),其形式为:type d = a ? b : c; 具体化形式为:int d = 2 < 1 ? 3 : 4;2. 轻量级的文本编辑器:UltraEdit.Edit ...

  6. 二、JavaScript语言--JS基础--JavaScript进阶篇--流程控制语句

    1.if语句--做判断 if语句是基于条件成立才执行相应代码时使用的语句. 语法: if(条件) { 条件成立时执行代码} 注意:if小写,大写字母(IF)会出错! 假设你应聘web前端技术开发岗位, ...

  7. C#之流程控制语句

    通过一系列的学习,我们知道尽管计算机可以完成工作,但实质上这些工作都是按照我们事先编好的程序执行的,所以,程序是计算机的灵魂,计算机程序执行的控制流程由三种基本的控制结构控制,即顺序结构,选择结构,循 ...

  8. java-04流程控制语句

    这里先简单介绍几种流程控制语句 包括if/if-else.switch语句 1.三大流程控制结构 所谓流程控制,就是说要控制程序的执行方式,根据不同的情况执行不同的代码,从而得到不同情况下的不同结果. ...

  9. JavaScript的流程控制语句

    JS的核心ECMAScript规定的流程控制语句和其他的程序设计语言还是蛮相似的.我们选择一些实用的例子来看一下这些语句.顺序结构我们在这里就不再提到,直接说条件和循环以及其他语句.一.条件选择结构  ...

  10. JavaScript进阶 - 第4章 跟着我的节奏走(流程控制语句)

    第4章 跟着我的节奏走(流程控制语句) 4-1 做判断(if语句) if语句是基于条件成立才执行相应代码时使用的语句. 语法: if(条件) { 条件成立时执行代码} 注意:if小写,大写字母(IF) ...

随机推荐

  1. Solon2 开发之IoC,七、切面与函数环绕拦截

    想要环绕拦截一个 Bean 的函数.需要三个前置条件: 通过注解做为"切点",进行拦截(不能无缘无故给拦了吧?费性能) Bean 的 method 是被代理的 在 Bean 被扫描 ...

  2. Solon 开发进阶

    Solon 开发进阶 一.插件扩展机制 二.体外扩展机制 三.常用配置说明 四.启动参数说明 五.全局异常订阅 本系列在内核知识的基础上做进一步延申.主要涉及: 插件扩展体系 体外扩展体系 常用配置 ...

  3. CDS 重命名失败

    当创建CDS视图,名称命名错误,后将视图名称更改后,激活报错(例如,第一次创建的视图名称为ZVWM014,后改为ZVMM014) SQL view ZVWM014 cannot be renamed ...

  4. A*(A star)搜索总结

    定义 先复制一则定义 A*算法在人工智能中是一种典型的启发式搜索算法 启发中的估价是用估价函数表示的: h(n)=f(n)+g(n) 其中f(n)是节点n的估价函数 g(n)表示实际状态空间中从初始节 ...

  5. 最全!即学即会 Serverless Devs 基础入门(上)

    作者 | 刘宇(花名:江昱) 在上篇<即学即会 Serverless | 如何解决 Serverless 应用开发部署的难题>中,我们阐述了工具链的重要性,那么本文将带领各位快速实现 Se ...

  6. Java 21新特性-虚拟线程 审核中

    本文翻译自国外论坛 medium,原文地址:https://medium.com/@benweidig/looking-at-java-21-virtual-threads-0ddda4ac1be1 ...

  7. java基础(15)--多态

    一.多态的含义 1.多种形态.多种状态,指的是编译与运行有不同的状态 2.编译时->静态绑定 3.执行时->动态绑定 4.多类典型场景:父类的引用指向了子类型的对象   二.向下转型与向上 ...

  8. ApplicationContextAware 的理解和应用

    当我们在项目中获取某一个spring bean时,可以定义一个类,实现ApplicationContextAware  该接口,该接口可以加载获取到所有的 spring bean. package c ...

  9. 基于python的视频点播网站(python+django+vue开发的视频点播网站-视频管理系统)

    演示地址 前台地址: http://video.gitapp.cn 后台地址:http://video.gitapp.cn/admin 后台管理帐号: 用户名:admin123 密码:admin123 ...

  10. Oracle索引&约束

    Oracle索引&约束 1索引的原理 索引是一种允许直接访问数据表某一数据行的树形结构,为了提高查询效率而引入,是独立于表的对象,可以存放在与表不同的表空间(TABLESPACE)中 索引记录 ...