一丶Switch Case语句在汇编中的第一种表达方式 (引导性跳转表)

第一种表达方式生成条件:

case 个数偏少,那么汇编中将会生成引导性的跳转表,会做出 if else的情况(类似,但还是能分辨出来的)

1.高级代码:

#include "stdafx.h"

int main(int argc, char* argv[])
{
switch(argc)
{
case :
printf("case 0\n"); break;
case :
printf("case 1\n"); break;
case :
printf("case 2\n"); break;
}
printf("HelloWorld\n");
return ;
}

2.汇编代码在Debug版本下:

可以看出,生成的跳转表

比较和跳转在一起,而且跳转的时候是一个跳转表. (注意,这里可能不是比较,只要影响标志位即可,也可能是 Dec inc add ....等等但是没有实质性的代码)

注意最后一个跳转 JMP,当JMP的位置,或者代码中JMP的位置(代码中也就是 跳转地址过去后的地方)都是同一个地址,那么则是跳转到SWITCH_END(也就是switch case 语句块结束)

注意这里是代码逻辑分开的.

先判断逻辑,然后进行跳转执行.而在跳转过后的里面判断是否有JMP,有JMP的话,则代表是break

如果内部没有JMP则代表没有Break,那么语法是支持这样写法的.

3.汇编中Release版本

Release版本其实是一样的.只不过需要注意的是,它有代码外提的情况下.

因为我们每一个case里面都有打印输出函数,而且其参数都是一样的,所以跳转过来之后,里面只需要PUSH即可.Switch case完毕之后则会调用printf打印.

4.扩展知识(Default在case上面,或者中间)

4.1直接伪代码:

  case 0 ... break;

default ....  break

case 2 ....break

4.2汇编代码 Debug

debug版本不用说,直接JMP位置到default位置.

4.3.Release版本下.

对于Release版本,有的时候可能直接变为Default语句

有的时候会变成JNZ执行.这个时候JNZ下面要还原成原来的CASE语句

第一变化的还原手法

第一变化还原挺简单的

1.遇到cmp比较的, 和谁比较,那么跳转的地址变为对应的Case即可.注意,你点击地址过去之后则是Case语句的代码,但是要注意是否代码里面有JMP(没有则没有break)

2.有的时候不是CMP, 有的时候是 dec Reg32,这个时候就要算一下的,比如  dec Reg32结果是多少,则对应还原成多少,比如dec Reg32结果是0,那么其还原成Case0,但是还原第二个的时候要注意

第一个已经Dec了一下,第二个又还原的Dec 所以要和第一个相关联才可以.

二丶Switch Case语句在汇编中的第二种表达方式(简介寻址表)

case 语句块有多个,且其中间隔不算多, 间隔的地方填写Default

2.1高级代码:

#include "stdafx.h"

int main(int argc, char* argv[])
{
switch(argc)
{
case :
printf("case 0\n");
break;
case :
printf("case 1\n");
break; case :
printf("case 2\n");
break;
case :
printf("case 3\n");
case :
printf("case 5\n");
break;
case :
printf("case 6\n");
break;
case :
printf("case 7\n");
break;
default:
printf("default \n");
break; }
printf("HelloWorld\n");
return ;
}

2.2 Debug版本下的汇编代码表现形式

首先这里有两个问题

1. 为什么是JA  (A是无符号的高于比较)

2. 为什么出现了数组寻址公式.

解答第一个问题:

  是这样的,第二种优化方式它会生成跳转表,而跳转表就是在第二种里面出现的寻址.而做表的前提下就是要保证Case语句是排序好的.

所以在这里 case语句首先排序好,而后坐标平移到0位置

什么是坐标平移到0位置?

比如我们有代码

case -1

case 2

case 3

那么此时坐表的条件就是

case -1 变为case 0

case 2  变为 2

这样坐表之后则是从0开始,而里面保存的则是case的地址.

这个时候只会和case最大值作比较.

我们看下动态调试中是什么表现形式把.

坐标平移之后,则可以建立一个表格,用的时候查表即可.所以我们查表的时候可以看到很多case语句的地址

从0开始. 没有的则填写Default即可. 一般JA后面的地址就是Default或者Switch End地址,从表中我们也看出来了.第五项填写的是Switch End的地址.

正好我们的代码中缺少的第五项没有

Debug还原手法:

Debug还原很简单,首先是比较case最大值,(看下是否调整了,也就是坐标平移了,如果平移了,比如以前有个-1,而case 最大值是3,那么会比较 4,因为-1 要+1其余各个的case语句也要加1)

4.3Release版本下的汇编代码

Release版本下也是一样,此时它也是会用 case值和最大值比较,然后去数组中寻址查找表去跳转

注意: 这里数组寻址*4的意思是,地址是4个字节对其

比如我们的表格中存放的是case地址

00401000     case0 Address

00401004     case1 Address

00401008    case 2 Address

首先会和最大值比较,没有超过,那么直接用case的值 *4去表中找地址即可.

比如我们的case值是2, 比较是否大于7,

然后 jA指令判断是否高于7,高于7跳转到 Default或者Switch End,不高于,则 根据case值去表中查找跳转

比如现在是2,那么 2* 4 = 8 + 数组首地址(00401000) = 401008 然后取出内容跳转,此时内容也是case2的地址.

扩展思路:

上面讲的是Switch原型知识,我们要学会扩展.比如如果我case中有个-1怎么办

那么此时-1 +1 坐标平移到0的位置,然后其余各个的case  +1

那么比较的时候和case最大的值相比,现在最大的值也+1了. 所以还原的时候,最大值要-1,其余各个case值都要-1则可以.

代码还原方式.

第二种代码还原方式

1.判断是否调整,调整了可以得出最小值,比如 add eax,2 那么得出最小值是-2,因为坐表要从0开始,这里调整则得出-2了,

2.得出case最大值比较,这里也要看是否调整了,没有调整那么最大值是多少就是多少,调整的那么case最大值则是 当前值 - 调整的值

3.得出最小值和最大值之后,去地址表中寻址即可.因为是排好序的,如果没有调整,那么就是从0开始,还原的时候就是case0

调整过后,那么减去调整的值即可,比如调整了2,那么第0项则是 0 - 2 则是case - 2 比较.

三丶Switch Case语句在汇编中的第三种表达方式(多表联合查询)

这种表达式相比前两种有点难理解,但是我们说过,编译器做的越多,那么我们还原的越快.

第三种表达式形成的条件:

当最大case值 - 最小case值的值在255直接,则使用第三种优化方式.

3.1高级代码

#include "stdafx.h"

int main(int argc, char* argv[])
{
switch(argc)
{
case :
printf("case 0\n");
break;
case :
printf("case 100\n");
break;
case :
printf("case 200\n");
break;
case :
printf("case 35\n");
case :
printf("case 45\n");
break;
case :
printf("case 60\n");
break;
case :
printf("case 70\n");
break;
default:
printf("default \n");
break; }
printf("HelloWorld\n");
return ;
}

3.2 Debug版本下的汇编代码

此时我们可以看出有两次查表的动作

下标表:

这里我们说下第一个表,第一个表我们成为下标表

下标表,里面存放的是Case排好序的值的位置,比如我们的02 在下表中找一下,则是对应45的位置.  那么此时从里面取出来做第二个case,去地址表中寻到case 45 的地址.

下标表的作用,这里的下标表主要是一种优化,一种空间换时间的做法,这里的优化主要是,比如我们屏幕的颜色,白色很多,那么我就优化为,当有白色的时候,去下标表中寻找白色

就和我们的下标表中的07一样,只要知道这个,那么都是default

地址表:

地址表则和第二种优化一样,存放的case的真实地址.

debug还原手法:

1.首先判断是否调整,调整了,可以得出最小case的值, (0 - 调整的值)  没调整则默认从0开始

2.比较的时候会和最大case值比较(没有调整则最大case值就是本身)调整了(最大case值 - 调整后的值 = 真实的最大case值)

3.遇到ja则后边现修改为 default或者switchend

4.从下标表中寻得case当前的位置,(比如存放的是3,其位置在下表表中是35)那么此时代表的就是 case 35 (属于第三个case语句)

5.从下标表中得出case语句的位置,(第几个case)以及case的值是多少(case 35)那么去地址表中寻找其case35的地址即可.

3.3 Release版本下的汇编

和Debug版本下相差无异,只不过有代码外提

此时还原很好还原

1.展开下表表格

2.展开地址表格

展开后则是上面的模样,第一个是地址表,第二个是下标表格.

注意展开的时候可能会遇到问题

1.不是我这种显示,没有这么整齐, 此时 按键盘上的  * 键,可以设置按照数组显示, 设置数组大小,间隔.等等..

2.没有显示 offset addr...  可能显示的直接是一个地址, 此时可以选择快捷键 ctrl + 0 显示成当前段偏移

3.数据类型不对,我的是dd 而你们的可能解释为 db dw 等等.. 此时按键盘快捷键 d即可.可以调整数据类型

还原方法;

和Debug一样, (因为我没有调整所以不用算了,而且从0开始)此时先去下标表中寻找0, 这个0找的的是case语句的位置 也就是第一条case语句是0

然后在寻找在数组的那个位置, 这个位置指的是 case xxx  xxx的值是多少

此时我能找到0,那么去数组表中寻找0地址,改为case 0,  按n键修改

修改1的值,也就是找case 第2条语句是什么

在35位置寻找到1,那么此时 第二条语句代表的case则是case 35

利用里面的下边去地址表中寻址,则找到第二个地址则是case 35

我们修改过后则上面会显示了.此时我们正常的还原即可.

四丶Switch Case语句在汇编中的第四种表达方式(二分优化查表,四种变化混合)

第四种表达方式,生成条件,其case值之间的间隔大于255则会使用第四种表达方式.

4.1高级代码:

#include "stdafx.h"

int main(int argc, char* argv[])
{
switch(argc)
{
case :
printf("case 0\n");
break;
case :
printf("case 256\n");
break;
case :
printf("case 34\n");
break;
case :
printf("case 500\n");
break;
case :
printf("case 510\n");
break;
default:
printf("default \n");
break; }
printf("HelloWorld\n");
return ;
}

首先说下如果遇到这种情况,编译器会怎么做.

因为对于编译器来说,不知道你命中率高的那个case语句会执行,所以只能从中间切开

会生成汇编代码  jg 或者 jle  (大于或者小于代码 )还有中间判断相等.

比如我们有一组case值

0

200

300

310

320

此时汇编代码会先判断是否 jz(等于,可能不是jz反正判断相等的跳转即可) 找到中间的case,所以此时还原这个值即可.

不用管大于小于,最后的时候管.

4.2Debug下的汇编代码:

可以看出一大堆的判断 相等的代码.

还原手法:

看到JE比较,那么看上方条件和谁比较(没有调整的情况下)那么还原当前地址为这个case即可.

4.3Release版本下的汇编.

这个是还原过后的,只需要判断 相等即可,根据它的条件来判断,只不过Release版本有代码外提

JNZ先修改为 Default ,此时如果进去每个case语句块中,按到的JMP的跳转地址和JNZ跳转的地址是一样的话,那么就是SWITCH_END

只需要还原相等即可.

注意:

  当我还原case 510的时候,有没有发现其上面还原500的时候都是减掉了,所以510的时候又减掉了一次所以此时判断是510

大体的规模已经出来了,下面就是还原具体代码了,代码很简单,不还原了,看下和高级代码对比即可.

看看case是否一样.

转载于:

作者:IBinary
出处:http://www.cnblogs.com/iBinary/

逆向知识第九讲,switch case语句在汇编中表达的方式的更多相关文章

  1. 逆向知识第八讲,if语句在汇编中表达的方式

    逆向知识第八讲,if语句在汇编中表达的方式 一丶if else的最简单情况还原(无分支情况) 高级代码: #include "stdafx.h" int main(int argc ...

  2. JavaScript基础知识(if、if else、else if、while、switch...case语句)

    13.语句 概念:就是分号(:) 代表一条语句的结束 习惯:一行只编写一条语句:一行编写多条语句(代码可读性较差) 语句块:可以包含多条语句     "{ }"将多条语句包裹 u ...

  3. 为什么说在使用多条件判断时switch case语句比if语句效率高?

    在学习JavaScript中的if控制语句和switch控制语句的时候,提到了使用多条件判断时switch case语句比if语句效率高,但是身为小白的我并没有在代码中看出有什么不同.去度娘找了半个小 ...

  4. java中的Switch case语句

    java中的Switch case 语句 在Switch语句中有4个关键字:switch,case break,default. 在switch(变量),变量只能是整型或者字符型,程序先读出这个变量的 ...

  5. switch… case 语句的用法

    switch… case 语句的用法   public class Test7 { public static void main(String[] args) { int i=5; switch(i ...

  6. if语句,if...else if语句和switch...case语句的区别和分析

    前段时间在工作中遇到了一个关于条件判断语句的问题,在if语句,if else if语句和switch case语句这三者之间分析,使用其中最有效率的一种方法. 所以就将这个问题作为自己第一篇博客的主要 ...

  7. Python | 基础系列 · Python为什么没有switch/case语句?

    与我之前使用的所有语言都不同,Python没有switch/case语句.为了达到这种分支语句的效果,一般方法是使用字典映射: def numbers_to_strings(argument): sw ...

  8. 为什么switch...case语句比if...else执行效率高

    在C语言中,教科书告诉我们switch...case...语句比if...else if...else执行效率要高,但这到底是为什么呢?本文尝试从汇编的角度予以分析并揭晓其中的奥秘. 第一步,写一个d ...

  9. 逆向知识第一讲,IDA的熟悉使用,以及TEB,PEB结构

    逆向知识第一讲,IDA的熟悉使用,以及TEB,PEB结构 一丶熟悉IDA,以及手工制作sig文件. IDA,静态分析工具,网上随便找一个即可下载. 首先,我们写一个可执行EXE,最简单的 使用IDA打 ...

随机推荐

  1. csp-s模拟95

    近两场开始文件评测,感觉和平时真不太一样. 考试时间是多了$10min$,但处理文件.输入输出什么的杂事也是忙来忙去.这可能也最像真实的$csps$了,稍有疏忽可能都是致命伤. T1.T2挂的对拍全打 ...

  2. 《你不知道的JavaScript(上)》笔记——动态作用域

    动态作用域让作用域作为一个在运行时就被动态确定的形式, 而不是在写代码时进行静态确定的形式.动态作用域并不关心函数和作用域是如何声明以及在何处声明的, 只关心它们从何处调用. 换句话说, 作用域链是基 ...

  3. Win10安装多个MySQL实例

    Win10安装MySQL-8.0.15 1.下载mysql-8.0.15-winx64.zip安装包,地址如下 https://cdn.mysql.com//Downloads/MySQL-8.0/m ...

  4. SpringMVC接收集合页面参数

    SpringMVC接收集合页面参数 Spring MVC在接收集合请求参数时,需要在Controller方法的集合参数里前添加@RequestBody,而@RequestBody默认接收的enctyp ...

  5. PAT 甲级 1013 Battle Over Cities (25 分)(图的遍历,统计强连通分量个数,bfs,一遍就ac啦)

    1013 Battle Over Cities (25 分)   It is vitally important to have all the cities connected by highway ...

  6. MapReduce编程实例

    MapReduce常见编程实例集锦. WordCount单词统计 数据去重 倒排索引 1. WordCount单词统计 (1) 输入输出 输入数据: file1.csv内容 hellod world ...

  7. Salt之CentOS7.5使用RPM包安装MySQL5.7.22

    手动安装参考CentOS7.5使用RPM包安装MySQL5.7.22 目录结构为 install.sls文件 #按顺序分别安装commin libs client server #避免冲突安装comm ...

  8. 《精通并发与Netty》学习笔记(10 - 详解NIO (一) Channel、Buffer )

    一.Java NIO 概述 Java NIO 由以下几个核心部分组成:ChannelsBuffersSelectors虽然Java NIO 中除此之外还有很多类和组件,但在我看来,Channel,Bu ...

  9. linux支持并发的服务器回射程序实例

    实例一:不支持并发,单服务器---单客户端 /************************************************************************* > ...

  10. Reactor系列(六)Exception异常系列(六)Exception异常

    #java##reactor##flux##error##exception# 视频解说: https://www.bilibili.com/video/av79468713/ FluxMonoTes ...