逆向知识第九讲,switch case语句在汇编中表达的方式

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

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

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

1.高级代码:

#include "stdafx.h"

int main(int argc, char* argv[])
{
    switch(argc)
    {
    case 0:
        printf("case 0\n");

        break;
    case 1:
        printf("case 1\n");

        break;
    case 2:
        printf("case 2\n");

        break;
    }
    printf("HelloWorld\n");
    return 0;
}

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.扩展知识(Defaultcase上面,或者中间)

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 0:
        printf("case 0\n");
        break;
    case 1:
        printf("case 1\n");
        break;
   
    case 2:
        printf("case 2\n");
        break;
    case 3:
        printf("case 3\n");
    case 5:
        printf("case 5\n");    
        break;
    case 6:
        printf("case 6\n");
        break;
    case 7:
        printf("case 7\n");
        break;
    default:
        printf("default \n");
        break;
   
    }
    printf("HelloWorld\n");
    return 0;
}

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 0:
        printf("case 0\n");
        break;
    case 100:
        printf("case 100\n");
        break;
    case 200:
        printf("case 200\n");
        break;
    case 35:
        printf("case 35\n");
    case 45:
        printf("case 45\n");    
        break;
    case 60:
        printf("case 60\n");
        break;
    case 70:
        printf("case 70\n");
        break;
    default:
        printf("default \n");
        break;
   
    }
    printf("HelloWorld\n");
    return 0;
}

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 0:
        printf("case 0\n");
        break;
    case 256:
        printf("case 256\n");
        break;
    case 34:
        printf("case 34\n");
        break;
    case 500:
        printf("case 500\n");    
        break;
    case 510:
        printf("case 510\n");
        break;
    default:
        printf("default \n");
        break;
   
    }
    printf("HelloWorld\n");
    return 0;
}

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

因为对于编译器来说,不知道你命中率高的那个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/

 

posted @ 2019-07-20 23:46  gd_沐辰  阅读(828)  评论(0编辑  收藏  举报