zoukankan      html  css  js  c++  java
  • 逆向知识第九讲,switch case语句在汇编中表达的方式

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

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

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

    1.高级代码:

    #include "stdafx.h"
    
    int main(int argc, char* argv[])
    {
        switch(argc)
        {
        case 0:
            printf("case 0
    ");
    
            break;
        case 1:
            printf("case 1
    ");
    
            break;
        case 2:
            printf("case 2
    ");
    
            break;
        }
        printf("HelloWorld
    ");
        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
    ");
            break;
        case 1:
            printf("case 1
    ");
            break;
       
        case 2:
            printf("case 2
    ");
            break;
        case 3:
            printf("case 3
    ");
        case 5:
            printf("case 5
    ");    
            break;
        case 6:
            printf("case 6
    ");
            break;
        case 7:
            printf("case 7
    ");
            break;
        default:
            printf("default 
    ");
            break;
       
        }
        printf("HelloWorld
    ");
        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
    ");
            break;
        case 100:
            printf("case 100
    ");
            break;
        case 200:
            printf("case 200
    ");
            break;
        case 35:
            printf("case 35
    ");
        case 45:
            printf("case 45
    ");    
            break;
        case 60:
            printf("case 60
    ");
            break;
        case 70:
            printf("case 70
    ");
            break;
        default:
            printf("default 
    ");
            break;
       
        }
        printf("HelloWorld
    ");
        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
    ");
            break;
        case 256:
            printf("case 256
    ");
            break;
        case 34:
            printf("case 34
    ");
            break;
        case 500:
            printf("case 500
    ");    
            break;
        case 510:
            printf("case 510
    ");
            break;
        default:
            printf("default 
    ");
            break;
       
        }
        printf("HelloWorld
    ");
        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/

  • 相关阅读:
    【Lua】LuaForWindows_v5.1.4-46安装失败解决方案
    【C++】指针引发的bug
    【C++】指针引发的bug
    【C++】位操作(3)-获取某位的值
    bzoj1444
    bzoj1758
    bzoj3091
    poj1741 bzoj2152
    bzoj2125 3047
    bzoj3669
  • 原文地址:https://www.cnblogs.com/gd-luojialin/p/11219833.html
Copyright © 2011-2022 走看看