除法运算逆向分析

除法运算逆向分析


由于除法指令的指令周期较长,效率低,所以编译器想尽办法用其他指令组合代替除法指令。所以C/C++除法运算的逆向分析较其他运算复杂很多,在此做一下总结


相关数学公式

当b>0时有,

ab=ab+1b


ab=a+b1b

当b<0时,有,
ab=ab1b


ab=a+b+1b


1.除数为2的幂

sar指令相当于向下取整,即 x2n ,而C语言除法结果是向零取整,即 [x2n]
所以有:

[x2n]=x2nx2n=x+2n12nx0x<0

下面看例子:

//.c
printf("nVarOne/8=%d",nVarOne/8);

编译后反汇编:

;.asm
 mov    eax,dword ptr [ebp-4]
 cdq
 and    edx,7
 add    eax,edx
 sar    eax,3
...

and edx,7使得当nVarOne为负数时edx内容为 2n1 ,当nVarOne为正数时edx为0,最后sar eax,3相当于除以 2n


2.除数为负的2的幂

直接给例子:

//.c
printf("nVarOne/-8=%d",nVarOne/-8);

编译后反汇编:

;.asm
 mov    eax, [esp+4]
 cdq
 and    edx,7
 add    eax,edx
 sar    eax,3
 neg    eax
...

除了最后一句其余和上面相同, neg eax相当于对计算结果取负。


接下来讨论难点3和4

3.除数为非2的幂

下面举例由汇编逆向推C代码:


情景1(MagicNumber 0x7FFFFFFF)

易得公式,

xox2no12n

c=2no ,这个值被称为Magic Number。于是就有
xox2no12nxc2n

_main proc near
arg_0= dword ptr 4
    mov     ecx,[esp+arg_0]
    mov     eax,38E38E39h
    imul    ecx
    sar     edx,1
    mov     eax,edx
    shr     eax,1Fh
    add     edx,eax
    push    edx
    push    offset Format   ;"%d
    call    _printf
    add     esp,8
    retn
_main   endp

其中,sar edx,1算术右移1位, shr eax,1Fh逻辑右移31位。
首先,ecx获取参数,eax获取魔数,(edx,eax)=eax*ecx,乘积低4B的eax内容抛弃,只使用乘积高4B的edx内容,这样就相当于乘积右移32位,再加上sar edx,1右移的1位,共右移33位,即n=33,mov eax,edxshr eax,1Fh用来取得乘积结果符号位,乘积为正时eax存放00000000h,乘积位负时存放00000001h。最后,add edx,eax的原因是:
x<0[xo] ,有

[xo]=xc2n=xc2n+1

(这里我认为只要x<0,编译器能够确认 xc2n 这里必定不是整数)
o=2nc=23338E38E39h=8.9999999 ,推导出C代码,

printf("%d",argc/9);

小结:

;x*(2^n/o)
 mov    eax,MagicNumber
 imul   ...
;/2^n
 sar    edx, ...
 mov    reg,edx
 shr    reg,1Fh
;负数调整
 add    edx,reg

当遇到以上指令序列,基本可判定是除法优化后的代码。MagicNumber<=7fffffffh,编译器在imul和sar之间未产生任何调整指令,故认定除数为正数。统计右移总次数确定公式中的n值,使用公式 o=2nc() 得到除数o的近似值。即可恢复除法原型。


情景2(MagicNumber 0x7FFFFFFF)

易得公式,

xox232+232+no232232+n

此式中,魔数 c=232+no232

_main proc near
arg_0= dword ptr 4
 mov     ecx,[esp+arg_0]
 mov     eax,24924925h
 mul    ecx
 sub     ecx,edx
 shr     ecx,1
 add     ecx,edx
 shr     ecx,2
 push   ecx
 push   offset  Format ;"nVarTwo/7=%d\r\n"
 call   _printf
 add     esp,8
 xor     eax,eax
 retn
_main endp

sub ecx,edxshr ecx,1add ecx,edxshr ecx,2这4句可以用一个计算式来表示:

xxc2322+xc23222

化简得:
x232+c235

232+n=235n=3o=232+n232+c=235232+24924925h=6.999997 ,推出C代码:

printf("nVarTwo/7="%d\r\n",argc/7);

小结:

 mov    eax,MagicNumber
 mul    reg
 sub    reg,edx
 shr    reg,1
 add    reg,edx
 (shr   reg,A)

如果遇到以上指令序列,基本可判定是除法优化后的代码。统计右移总次数以确定公式中的n值,使用公式 o=232+n232+c 求解出除数o,即可恢复除法原型。


情景3(MagicNumber 0x80000000)

编译器在计算MagicNumber时是作为无符号处理的,而imul指令是作为有符号处理的。所以当魔数 0x80000000 时,实际参与乘法运算的是个负数,导致魔数与数学公式上的那个“大常数”意义不一致。

y<0:y=232|y|=232+yy=y232xy=xy+x232yyy

易得公式,
xox2no12n(x(2no232)+x232)12nxoxy(y)12n(xy+x232)12n

_main proc near
arg_0= dword ptr 4
    mov     esi,[esp+arg_0]
    mov     eax,92492493h
    imul     esi
    add     edx,esi
    sar     edx,2
    ;...负数调整

上述代码转换成公式:

(esieax+232esi)1234cc=2noo=2nc=23492492493h=6.9999997

反推出C代码:

printf("%d",argc/7);

小结:

 mov    eax,MagicNumber;MagicNumber>7fffffffh
 imul   reg
 add    edx,reg
 sar    edx,...
 mov    reg,edx
 shr    reg,1Fh
 add    edx,reg

当遇到以上指令序列时,基本可判定是除法优化后的代码。当MagicNumber 80000000h ,编译器会在imul和sar之间产生调整作用的add指令,故可认定除数为正。*统计右移的总次数以确定公式中的n值,然后使用公式 o=2nc 求解除数o,即可恢复除法原型。

4.除数为负的非2的幂

易得公式:

xo=xc12n(c<0)c=2n|o|=2n|o|=2322n|o|

情景1(MagicNumber 0x80000000)

_main proc near
arg_0= dword ptr 4
    mov      ecx,[esp+arg_0]
    mov      eax,99999999h
    imul     ecx
    sar      edx,1
    mov      eax,edx
    shr      eax,1Fh
    add      edx,eax
    push     edx
    push     offset Format ;"%d"
    call     _printf
    add      esp,8
    xor      eax,eax
    retn
_main endp

代码体现的表达式:

edx=ecxeax233|o|=2n232c=23323299999999h=4.9999995

于是反推出C代码为:

printf("%d",argc/-5);

小结:

 mov    eax,MagicNumber(>=0x7fffffff)
 imul   reg
 sar    edx,...
 mov    reg,edx
 shr    reg,1Fh
 add    edx,reg

如遇到以上指令序列,则基本可判定是除法优化后的代码。MagicNumber 80000000h ,编译器在imul和sar之间未产生任何调整指令,故可认定除数为负。*统计右移总次数以确定公式中的n值,然后使用公式 |o|=2n232c 求解除数|o|,即可恢复除法原型。

情景2(MagicNumber 0x7FFFFFFF)
当MagicNumber<=7FFFFFFFFh时,除数也有可能是负数。(为什么会有这种情景?这样可以使数学式中 c=2no 表示的范围更大)
为了使 xc2nc=2no(o<0) 表示更小的负数,编译器用类似3中情景3的方法,

p=o,(p>0)xo=xp(x2np12n)((x(2np232)+x232)12n)(x((2np232))x232)12n(x(2322n|o|)x232)12nxc2322n()c=2np232<033yc=(2np232)=2322np=2322n|o|>0

_main proc near
arg_0= dword ptr 4
    mov      ecx,[esp+arg_0]
    mov      eax,6DB6DB6Dh
    imul     ecx
    sub      edx,ecx
    sar      edx,2
    mov      eax,edx
    shr      eax,1Fh
    add      edx,eax
    push     edx
    push     offset Format ;"%d"
    call     _printf
    add      esp,8
    retn
_main endp

上面代码转换成公式:

edx=edxeax232ecx22=ecxeax232ecx234=ecxeax232234ecxo=ecxeax232234|o|=2n232c=2342326DB6DB6Dh=6.9999997

于是反推C代码:

printf("%d",argc/-7);

小结:

 mov    eax,MagicNumber(<=7fffffffh)
 imul   reg
 sub    edx,reg
 sar    edx,...
 mov    reg,edx
 shr    reg,1Fh
 add    edx,reg

当遇到以上指令序列时,基本判定是除法优化后的代码。MagicNumber 7fffffffh,imul和sar之间有sub指令来调整乘积,故认定除数为负数。统计右移总次数以确定公式中的n值,然后使用公式 |o|=2n232c() 求解除数|o|,即可恢复除法原型。

如何从汇编代码中区分正负除数?
当MagicNumber最高位为1时( 80000000h ),对于正除数,MagicNumber为原码形式,编译器会在imul和sar之间产生调整作用的add指令。如果没有,则MagicNumber为补码形式。
当MagicNumber最高位为0时( 7FFFFFFFh ),对于负除数,编译器会在imul和sar之间产生调整作用的sub指令。
这些应作为区分负除数的重要依据。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值