逆向知识第六讲,取摸优化的几种方式

2018-01-08 13:55:44 浏览数 (1)

        逆向知识第六讲,取摸优化的几种方式

除法讲完之后,直接开始讲 % 运算符在汇编中表现形式

首先C的高级代码贴上来.

高级代码:

代码语言:javascript复制
// Tedy.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"

int main(int argc, char* argv[])
{
    unsigned Number;
    scanf("%d",&Number); //防止常量传播
    printf("%ud rn",Number % 8);//无符号/2的幂
    __asm 
    {
        nop;
        nop;            ;内联汇编,观看的时候知道怎么看
        nop;
    }

    printf("%d",Number % 3);//无符号/非二的幂
    __asm 
    {
        nop;
        nop;
        nop;
    }
    printf("%d" , 8 % Number);//常量/变量
    __asm 
    {
        nop;
        nop;
        nop;
    }
    printf("%d",argc % 8);//有符号/2的幂
    __asm 
    {
        nop;
        nop;
        nop;
    }
    printf("%d",argc %-8);//有符号/2的幂,除数为负数

    __asm 
    {
        nop;
        nop;
        nop;
    }
    printf("%d",argc %3);//有符号/非2的幂
    return 0;
}

一丶无符号% 2的幂在汇编中的表现形式

汇编代码:

高级对应语句:

代码语言:javascript复制
 printf("%ud rn",Number % 8);

可以看出,当无符号%2的幂的时候,直接用and计算. 其值是 2^n - 1的值

比如我们的number %8,那么and的值则是 8-1,而8是2^3次方. 

现在说下7这个数, 0111  它正好是3个指数位,所以还原的时候,直接看占的二进制位数,占了几位就是指数是多少.

需要注意:

  我们一般看%某某个数的时候,我们要知道其结果保存在edx寄存器当中,而eax寄存器保存的是商 

比如:

  8 % 6 = 1 ..... 2 那么其值  1在eax中,其余数2在edx中

 二丶无符号/非二的幂

高级代码:

代码语言:javascript复制
printf("%d",Number % 3);//无符号/非二的幂汇编代码:

可以看出,无符号 / 非2的幂的时候,直接使用 DIV 了,同理有符号 / 非2的幂的时候,就会使用IDIV了.

这个时候还原 除数则看 给的除数是多少了.

比如这里 DIV ecx,而ecx给的是3,则除数是3了.

还可以看到,这个地方 push的edx,那么说明使用的edx,而上面也说了,edx中的值存放的是余数的值.

三丶常量 % 变量

高级代码:

代码语言:javascript复制
printf("%d" , 8 % Number);汇编代码:

可以看出这段汇编代码, eax给的是常量,直接使用DIV了,用的是EDX,由此判定, 除数是一个变量,而这个变量是无符号类型了,因为上面的 XOR EDX,EDX清空了. 看到这一段代码的时候,还原手法:

eax % [ebp var_4] = xxx ... edx

代入公式得:

8 % 变量 = 商...余数.

如果你知道变量是具体的值,比如现在是3

那么8 % 3 = 商...余数 即可.

四丶重点: 有符号 % 2的幂

高级代码:

代码语言:javascript复制
 printf("%d",argc % 8);//有符号/2的幂汇编代码:

有符号的处理,比无符号复杂一些.主要判断一下符号位.

比如上面的高级代码对应的这些汇编代码.看不懂没关系.依次讲解.

我们知道有符号%一个数的时候,需要判断符号位对吧

那么此时分为三部分去看.

第一部分 : 正数的情况下

上面汇编代码表示, 我用有符号变量 % 一个80000007h,得出的结果如果不是负数(jns)那么余数就是正数,直接跳走了

首先我说下为什么是 800...7h

上面也说过了,要保留符号位

那么8则是符号位,也就是1, 而为什么最后是7那?,这个则是保留指数位.

800...7h换算成二进制表达形式为:

10000000000000000000000000000111

高位为符号位,低3位为指数位(当然不是固定的,它的指数位是 2^n-1,比如我们的除数为8,那么指数位则是 2^3 - 1 = 7,而7正好是占3个指数位,比如是16,那么2^4 - 1 = 15,而15的指数位则是占了4个二进制位)

这一段汇编代码,是计算正数的

假设我们的argc有符号变量为9

那么9对应的二进制则为

00000000000000000000000000001001

and (那么与刚才的二进制去 &(与)一下结果还是正数)

10000000000000000000000000000111

 = 

00000000000000000000000000000001  那么其值结果为1,也就是余数为1,而不是负数,此时就可以跳走直接运算了.

第二部分: 负数的情况下

上面说了正数的情况下,你直接and  2^n-1 的值即可.那么得出的结果还是正数.

那么现在是负数额情况下.

我们看到一个16进制的数字 0FFFFFFF8h,那么是什么意思那?

当然对应的二进制表达方式也写出来.

11111111111111111111111111111000

此时看汇编代码 ,注意 dec  inc这些是特殊情况下需要用到的,暂时不管,现在只看 中间的or指令.

我们试想一下,如果我们余数是负数的情况下,

举例子:

  -9 % 8 = 1 ... -1

也就是上面判断为正数的先走一遍.得到的余数二进制为 -1

那么对应二进制也就是

100000000000000000000000000000001 (现在的EDX的值).

然后现在有or了一下0FFFFFFF8h 这个值,那么说下这个怎么得到的.

我们上面说过了,保留了符号位,符号位置为1,还有保留指数位 (2^n - 1)

那么这个时候, 这个值就是 把中间的值变为1,保留(2^n-1的位数)

11111111111111111111111111111000

高位一个符号位,中间的0变为1,最后三个则是指数位,此时or之后

10000000000000000000000000001

or(或 |)

11111111111111111111111111111000

=

1000000000000000000000000001

那么则得出结果是 -1

第三部分: 特殊情况下

特殊情况下,则是 一个 dec,然后最后一个inc回来的时候.

 这个则是当余数为0的情况下才会触发.

比如 8 % 8 = 0;

走第一部分汇编代码的时候,edx里面的值都是0了.

然后-1,继续or, or出来的结果加1还是0.这个主要是余数为0的情况下.

重点: 还原手法

上面只是说的原理.(其实也不算高深点的原理,这里是站在汇编代码的角度下说的,其实真正的都有数学定理和公式)

以后凡是看到这块汇编代码:

我们直接看指数位是多少位即可. 比如上面我们%8,那么指数位是3个,那么还原的时候就是 r = 2^n次方即可.

n = 指数位

n = 3

如果计算上面的余数则

r = 2^ 3 

r = 8即可.

当然我们要看一下最后用的寄存器是不是edx,如果是edx,那么就是 %,如果是用的eax,那么结果就是 /

很显然上面是用的edx,

还原回来的汇编代码为:

[ebp  argc] % 2^n

有符号局部变量  % 8 即可.

五丶有符号 % -2的幂

高级代码:

代码语言:javascript复制
printf("%d",argc %-8);//有符号/2的幂,除数为负数对应汇编代码:

首先在讲解之前,我们要明白一下.

我们举例子:

8 % 6 = 1 ... 2

8 %-6 = -1.....2

但是我们看一下,我们的余数并没有改变其结果, 余数都是2

比如我们列一个公式

a(被除数)  b(除数)  q(商) r (余数)

a % b  = q ... r  这个是基本的.

那么

a % |b| = |q| ... r 摸不摸 b的绝对值,其 r值不变的.影响的只是 q对不起.

但是

|a|  % b = |q| ...|r|  那么这个时候,如果把a变为绝对值,那么绝对会影响r的值.

上面的汇编代码.则是写了一个无分支求绝对值而已.如果数学公式搞懂了,那么看上面的汇编代码则会懂了

第一部分,无分支求绝对值

这个则是无分支求绝对值的代码.

首先esi的值是上面  argc局部变量的值,只不过上下文中没有修改esi,所以在这里直接使用.这里就想象成一个变量

然后CDQ,  edx的值,跟随者eax的符号位填充,如果 eax(也就是现在变量的值是正数,那么eax的高位则是0,那么edx的值全部都是0)

如果是正数的情况下:

正数的情况下,eax是正数,edx因为符号扩展,所以结果是0, xor之后,其结果还是原值.

此时 原值 eax - edx (相当于, - 0 )那么其结果还是原值.

然后

此时把除数变为正数了,那么 直接使用and 7即可.(7是 2^n-1的值)

and之后,其eax的值则是余数(这里不是EDX了,有时候我们要看,这里是eax去弄得,所以放到里面了)

and之后,下方继续几行汇编代码,这些汇编代码都一样得出的结果还是原来的值.

如果是负数的情况下:

汇编代码就是这么一大堆.

然后负数的情况下,执行完求绝对值的代码之后,其结果就变成了正数. 在and eax,7上面弄得.

那么此时如果原来是负数的情况下,那么下方继续再来一遍,变为负数.

那么此时得出的除数是负数. 也就是 b为负数.(除数)

还原手法:

不管怎么做,上面先把绝对值求出来,然后和 (2^n-1)去and,此时得出了除数是  (2^n) ,那么怎么判断正数还是负数.

判断下方是否在取反了即可.

0 人点赞