[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. .NET Core 在其上下文中,该请求的地址无效。

    .NET Core 在其上下文中,该请求的地址无效. 看了端口,发现没被占用,后来发现是IP地址变了 改成正确的IP就可以了.

  2. Intellij 查找排除JAR包的依赖关系(Maven Helper)

    Intellij 查找排除JAR包的依赖关系(Maven Helper) 安装插件 Windows 类似

  3. 聚合查询 分组查询 F与Q查询 添加新字段

    目录 聚合查询 aggregate 聚合函数 起别名 分组查询 annotate 注释函数 起别名 分组查询报错 分组查询练习 总结 添加新字段 F与Q查询 F查询 字符串拼接 concat方法 Q查 ...

  4. MB01 BAPI_GOODSMVT_CREATE退货

    "-----------------------------------------@斌将军--------------------------------------------DATA: ...

  5. Three.js 入门

    Demo代码地址: https://gitee.com/s0611163/three.js-demo Three.js Three.js下载 从GitHub上下载一个Release版本,https:/ ...

  6. Windows | 安装 Docker 遇到的 WSL 2 installation is incomplete 报错的解决方案

    控制面板中打开 Windows功能,在其中勾选 适用于 Linux 的 Windows 子系统 下载 WSL 更新包(非最新版本的也会报错) 更新包下载链接:https://wslstorestora ...

  7. 命令行状态下切换盘符 cd 跨盘

    这个经常忘记,在这记下来 cd /d d: 就是加上/d参数

  8. vivo 商城前端架构升级—前后端分离篇

    本文主要以 vivo 商城项目的前后端分离经验,总结前后端分离思路,整理前后端分离方案,以及分离过程中遇到的问题及解决方案. 一.前言 vivo官方商城在2015年创建网上商城,开辟网络销售渠道,几年 ...

  9. 2022 开源之夏 | Serverless Devs 陪你“变得更强”

    Serverless 是近年来云计算领域热门话题,凭借极致弹性.按量付费.降本提效等众多优势受到很多人的追捧,各云厂商也在不断地布局 Serverless 领域.但是随着时间的发展,Serverles ...

  10. docker目录迁移流程

    概述 在安装测试最新版本的HOMER7的过程中,docker作为基础工具碰到一些问题,针对问题进行总结. docker的默认工作目录在/var目录,而在我们的环境中,/var目录空间预留不足,随着do ...