[2] 以逆向的角度来看流程控制语句——switch
[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的更多相关文章
- 2017.10.14 Java的流程控制语句switch&&随机点名器
今日内容介绍 1.流程控制语句switch 2.数组 3.随机点名器案例 ###01switch语句解构 * A:switch语句解构 * a:switch只能针对某个表达式的值作 ...
- 00018_流程控制语句switch
1.选择结构switch switch 条件语句也是一种很常用的选择语句,它和if条件语句不同,它只能针对某个表达式的值作出判断,从而决定程序执行哪一段代码. 2.switch语句的语法格式 swit ...
- java流程控制语句switch
switch 条件语句也是一种很常用的选择语句,它和if条件语句不同,它只能针对某个表达 式的值作出判断,从而决定程序执行哪一段代码. 格式: switch (表达式){ case 目标值1: 执行语 ...
- Java运算符、引用数据类型、流程控制语句
1运算符 1.1算术运算符 运算符是用来计算数据的符号. 数据可以是常量,也可以是变量. 被运算符操作的数我们称为操作数. 算术运算符最常见的操作就是将操作数参与数学计算: 运算符 运算规则 范例 结 ...
- JAVA 1.6 流程控制语句
1. 条件运算符(三元表达式),其形式为:type d = a ? b : c; 具体化形式为:int d = 2 < 1 ? 3 : 4;2. 轻量级的文本编辑器:UltraEdit.Edit ...
- 二、JavaScript语言--JS基础--JavaScript进阶篇--流程控制语句
1.if语句--做判断 if语句是基于条件成立才执行相应代码时使用的语句. 语法: if(条件) { 条件成立时执行代码} 注意:if小写,大写字母(IF)会出错! 假设你应聘web前端技术开发岗位, ...
- C#之流程控制语句
通过一系列的学习,我们知道尽管计算机可以完成工作,但实质上这些工作都是按照我们事先编好的程序执行的,所以,程序是计算机的灵魂,计算机程序执行的控制流程由三种基本的控制结构控制,即顺序结构,选择结构,循 ...
- java-04流程控制语句
这里先简单介绍几种流程控制语句 包括if/if-else.switch语句 1.三大流程控制结构 所谓流程控制,就是说要控制程序的执行方式,根据不同的情况执行不同的代码,从而得到不同情况下的不同结果. ...
- JavaScript的流程控制语句
JS的核心ECMAScript规定的流程控制语句和其他的程序设计语言还是蛮相似的.我们选择一些实用的例子来看一下这些语句.顺序结构我们在这里就不再提到,直接说条件和循环以及其他语句.一.条件选择结构 ...
- JavaScript进阶 - 第4章 跟着我的节奏走(流程控制语句)
第4章 跟着我的节奏走(流程控制语句) 4-1 做判断(if语句) if语句是基于条件成立才执行相应代码时使用的语句. 语法: if(条件) { 条件成立时执行代码} 注意:if小写,大写字母(IF) ...
随机推荐
- .NET Core 在其上下文中,该请求的地址无效。
.NET Core 在其上下文中,该请求的地址无效. 看了端口,发现没被占用,后来发现是IP地址变了 改成正确的IP就可以了.
- Intellij 查找排除JAR包的依赖关系(Maven Helper)
Intellij 查找排除JAR包的依赖关系(Maven Helper) 安装插件 Windows 类似
- 聚合查询 分组查询 F与Q查询 添加新字段
目录 聚合查询 aggregate 聚合函数 起别名 分组查询 annotate 注释函数 起别名 分组查询报错 分组查询练习 总结 添加新字段 F与Q查询 F查询 字符串拼接 concat方法 Q查 ...
- MB01 BAPI_GOODSMVT_CREATE退货
"-----------------------------------------@斌将军--------------------------------------------DATA: ...
- Three.js 入门
Demo代码地址: https://gitee.com/s0611163/three.js-demo Three.js Three.js下载 从GitHub上下载一个Release版本,https:/ ...
- Windows | 安装 Docker 遇到的 WSL 2 installation is incomplete 报错的解决方案
控制面板中打开 Windows功能,在其中勾选 适用于 Linux 的 Windows 子系统 下载 WSL 更新包(非最新版本的也会报错) 更新包下载链接:https://wslstorestora ...
- 命令行状态下切换盘符 cd 跨盘
这个经常忘记,在这记下来 cd /d d: 就是加上/d参数
- vivo 商城前端架构升级—前后端分离篇
本文主要以 vivo 商城项目的前后端分离经验,总结前后端分离思路,整理前后端分离方案,以及分离过程中遇到的问题及解决方案. 一.前言 vivo官方商城在2015年创建网上商城,开辟网络销售渠道,几年 ...
- 2022 开源之夏 | Serverless Devs 陪你“变得更强”
Serverless 是近年来云计算领域热门话题,凭借极致弹性.按量付费.降本提效等众多优势受到很多人的追捧,各云厂商也在不断地布局 Serverless 领域.但是随着时间的发展,Serverles ...
- docker目录迁移流程
概述 在安装测试最新版本的HOMER7的过程中,docker作为基础工具碰到一些问题,针对问题进行总结. docker的默认工作目录在/var目录,而在我们的环境中,/var目录空间预留不足,随着do ...