代码还原反汇编之特殊除法还原
系列文章
反汇编技术之熟悉IDA工具
反汇编逆向技术之寻找Main入口点
反汇编代码还原之优化方式
反汇编代码还原之加减乘
反汇编代码还原之除法为2的幂
反汇编代码还原之除法为非2的幂
一丶了解数学知识
1.1 简介
在下面会有大量的数学知识来进行讲解. 当然如果你奔着如何还原.直接按照定式还原就行.不用纠结如何计算出来的
但是你了解数学知识.从数学角度来看待优化.那么会可以了解其真正原理 本人数学也不好.但还是查阅很多资料.把基础数学
罗列出来.一来是便与复习.二来是能看到基本的数学公式即可.
1.2 数学知识
1.2.1 数学知识之代数与解方程
方程: 有一个未知数.我们来解这个未知数那么叫做解方程
例如:
解方程我们可以代入一个数进行去解.也可以直接做平衡解.
意思就是 如果 等式的左边+ 那么我们就利用减法.两边都减去这个值. 如果是x 那么做相反运算也就是/ 反之亦然
解:
1 2 3 4 5 6 7 8 9 | x + 3 = 6
x + 3 - 3 = 6 - 3
x = 3
4x + 5 = 17
4x + 5 - 5 = 17 - 5
4x = 12
4x / 4 = 12 / 4
x = 3
|
1.2.2 简化表达式去括号
简化表达式分为 移除括号 交换结合定律 合并同类项 等
移除括号. 看公式:
1 2 3 4 5 6 | 3 ( 5 + 2 ) 展开的时候计算括号的值变成 3 * 5 + 3 * 2 = 15 + 6
a(b + c) = ab + ac
3 (x + 6 ) = 3x + 3 * 6
负数乘法去括号遵循 负正得负 负负得正的规律
- 3 (a + - 6 ) = - 3a + - 3 * - 6 = - 3a + 18
- 3 (a + 6 ) = - 3a + - 3 * 6 = - 3a + - 18
|
关于去括号的另一个特性
1 2 | 3 * ( 2 + 4 ) = 3 * 6
3 * ( 2 + 4 ) = 3 * 2 + 3 * 4
|
两种方式都是可以得出结果的.一般第一种就是加这个数的和.第二种就是拆分为乘数来.计算之后在相机啊.
第二种用途用于不好算的数来用的
例如:
1 2 | 2 * 204 直接算算不出可以简化为
2 * 200 + 2 * 4 = 408
|
1.2.3 简化表达式之交叉相乘
交叉相乘用于分数.可以帮助我们进行简化
解决的是把一个分数变为表达式
原理就是分子与分母相乘
可以看到分子变了.而分母都变成了(12×3) 所以都是除同一个数
所以可以去掉了.变成 8 × 3 = 12 × 2
公式记为
1.2.4 简化表达式之合并同类项
如果看官方简介会看到一大堆名词解释.那么这里说一下自我的理解吧.
同类项 就是这一类属于一项.优先把他们组合起来.
例如:
在这里 有xy就是同类项.所以可以优先组合起来.
组合的时候我们可以再根据加法减法符号来组合同符合类别的
1 2 | ( - xy + 5xy ) + ( - 2xy - 4xy ) + ( 3 - 7 ) = = - 2xy - 4
也可以变成
|
1.2.5 简化表达式之分数的加法简化
分数加法
分数的加法是有一条简单的规矩的.就是去分母.
如何去分母之前也有说.就是让分母一致.然后直接计算分子
我们可以看一下上面的倒数相乘去分母.就是一个很好的例子
公式为如上,也就是交叉相乘的结果
分数减法同加法一样.之不管变成相减了
1.2.7 简化表达式之分数乘法
分数乘法
分数乘法简化还是按照上乘上下乘下原则
1.2.8分数除法
分数除法要转变为分数乘法.具体原则就是 *分数的倒数来进行相乘
其余按照分数乘法来做
二丶除法特殊汇编
2.1 特殊定式汇编
2.1.1高级代码与汇编对应
高级代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | int main( int argc, char * argv[])
{
/ *
除法
* /
unsigned int NumberOne = 0 ;
unsigned int NumberTwo = 0 ;
scanf( "%u" ,&NumberOne);
scanf( "%u" ,&NumberTwo);
unsigned int Count1 = NumberOne / - 6 ;
unsigned int Count2 = NumberTwo / 7 ;
printf( "%d%d" ,Count2,Count1);
system( "pause" );
return 0 ;
}
|
一个是无符号/-6 一个是/正数7
看下汇编
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | .text: 00401000
.text: 00401000
.text: 00401000 ; int __cdecl main( int argc, const char * * argv, const char * * envp)
.text: 00401000 _main proc near ; CODE XREF: start + AF↓p
.text: 00401000
.text: 00401000 var_8 = dword ptr - 8
.text: 00401000 var_4 = dword ptr - 4
.text: 00401000 argc = dword ptr 4
.text: 00401000 argv = dword ptr 8
.text: 00401000 envp = dword ptr 0Ch
.text: 00401000
.text: 00401000 sub esp, 8
.text: 00401003 xor eax, eax
.text: 00401005 mov [esp + 8 + var_8], eax
.text: 00401009 mov [esp + 8 + var_4], eax
.text: 0040100D lea eax, [esp + 8 + var_8]
.text: 00401011 push eax
.text: 00401012 push offset aU ; "%u"
.text: 00401017 call _scanf
.text: 0040101C lea ecx, [esp + 10h + var_4]
.text: 00401020 push ecx
.text: 00401021 push offset aU ; "%u"
.text: 00401026 call _scanf
.text: 0040102B mov ecx, [esp + 18h + var_8]
.text: 0040102F mov eax, 7
.text: 00401034 mul ecx
.text: 00401036 sub ecx, edx
.text: 00401038 mov eax, 24924925h
.text: 0040103D shr ecx, 1
.text: 0040103F add ecx, edx
.text: 00401041 shr ecx, 1Fh
.text: 00401044 push ecx
.text: 00401045 mov ecx, [esp + 1Ch + var_4]
.text: 00401049 mul ecx
.text: 0040104B sub ecx, edx
.text: 0040104D shr ecx, 1
.text: 0040104F add ecx, edx
.text: 00401051 shr ecx, 2
.text: 00401054 push ecx
.text: 00401055 push offset aDD ; "%d%d"
.text: 0040105A call _printf
.text: 0040105F push offset aPause ; "pause"
.text: 00401064 call _system
.text: 00401069 xor eax, eax
.text: 0040106B add esp, 28h
.text: 0040106E retn
.text: 0040
|
2.1.2 核心代码反汇编还原
我们去掉流水线优化后的核心反汇编如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | .text: 00401000
.text: 00401000
.text: 00401000 ; int __cdecl main( int argc, const char * * argv, const char * * envp)
.text: 00401000 _main proc near ; CODE XREF: start + AF↓p
.text: 00401000
.text: 00401000 var_8 = dword ptr - 8
.text: 00401000 var_4 = dword ptr - 4
.text: 00401000 argc = dword ptr 4
.text: 00401000 argv = dword ptr 8
.text: 00401000 envp = dword ptr 0Ch
.text: 00401000
.text: 00401003 xor eax, eax
.text: 00401005 mov [esp + 8 + var_8], eax
.text: 00401009 mov [esp + 8 + var_4], eax
核心位置 / - 6
.text: 0040102B mov ecx, [esp + 18h + var_8]
.text: 0040102F mov eax, 7
.text: 00401034 mul ecx
.text: 00401036 sub ecx, edx
.text: 0040103D shr ecx, 1
.text: 0040103F add ecx, edx
.text: 00401041 shr ecx, 1Fh
.text: 00401044 push ecx
核心位置 / 7
.text: 00401038 mov eax, 24924925h
.text: 00401045 mov ecx, [esp + 1Ch + var_4]
.text: 00401049 mul ecx
.text: 0040104B sub ecx, edx
.text: 0040104D shr ecx, 1
.text: 0040104F add ecx, edx
.text: 00401051 shr ecx, 2
.text: 00401054 push ecx
|
观看代码定式.我们发现了一个特点. 核心汇编代码都是 乘 减 移 加 移的指令
1 2 3 4 5 6 7 8 | .text: 00401038 mov eax, 24924925h
.text: 00401045 mov ecx, [esp + 1Ch + var_4]
.text: 00401049 mul ecx
.text: 0040104B sub ecx, edx
.text: 0040104D shr ecx, 1
.text: 0040104F add ecx, edx
.text: 00401051 shr ecx, 2
.text: 00401054 push ecx
|
如果你想要还原.记住代码定式
1 2 3 4 5 6 7 8 | .text: 00401038 mov eax, M
.text: 00401045 mov ecx, 被除数
.text: 00401049 mul ecx
.text: 0040104B sub ecx, edx
.text: 0040104D shr ecx, n
.text: 0040104F add ecx, edx
.text: 00401051 shr ecx, n
.text: 00401054 push ecx
|
利用除法转变为乘法的特性.我们首先统计 n值 然后使用2的幂加上n值. 一般是2^(32 + n)
注意这里是幂值想加
如下
然后统计M值
这里的代码还原的公式为
比如我们要还原/7 我们可以代入公式
设M = 24924925h 10进制 = 613566757
设n值 = 3 进行幂想加后得出 2^35
代入公式之后计算的结果向上取整
得出结果为7 这个就是我们求的被除数. 所以这一整段代码我们可以还原为
如果是负数一样代入公式.比如这里是/-6
M = 7
n = 1F + 1 = 32
代入公式得
很明显这是一个很大的数.这个数放到计算器中可以看到是一个负数
我们看16进制就可以看出这个是个负数,我们对其取反.然后转变为DWORD即可.
2.1.3 特殊汇编的除法原理
还记得我们上一讲的除法转变为乘法的例子吧
简单例子如下
那么这里其实本质还是用这个除法转变为乘法的公式.只不过有些许不同
不同点在于C计算位置. 也就是计算M数的时候. 如果n的取值大于32. 那么其结果会超过 4个字节整数的表达范围 所以要进行调整.
调整为我减去2^32次方 然后最后的时候再加上
比如下
那么我们的除法就会随之改变.剩下的就是求出M怎么得出的
在这里我们看下汇编表达形式.并且列出与之对应表达式 但是我们先看一下乘法的特性
2.1.4 x86乘法特性与x64乘法特性
被乘数 |
乘数 |
乘积结果存放 |
AL(Byte) |
reg/mem8 |
AX |
AX(WORD) |
reg/mem16 |
DX:AX |
EAX |
reg/mem32 |
EDX:EAX |
举例
1 2 3 | mov al, 5h
mov bl, 10h
mul bl / / ax = = 0050 ,CF = 0
|
与之同理
1 2 3 4 5 6 | .data
val1 WORD 2000h
val2 WORD 0l00h
.code
mov ax, val1 ; AX = 2000h
mul val2 ; DX:AX = 00200000h , CF = 1
|
4字节计算被乘数是4个字节
1 2 3 | mov eax, 12345h
mov ebx, 1000h
mul ebx ; EDX:EAX = 0000000012345000h , CF = 0
|
2.1.5 汇编等式还原
了解了乘法原理我们来看等式.根据我们的汇编产生的等式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | .text: 00401038 mov eax, 24924925h
.text: 00401045 mov ecx, [esp + 1Ch + var_4]
.text: 00401049 mul ecx
eax = M
ecx = 被除数
M * ecx 结果放在 edx:eax中
.text: 0040104B sub ecx, edx
此条代码是让被除数 - M * ecx的高 32 位乘积.
等价于 ecx - (M * ecx) / 2 ^ 32
.text: 0040104D shr ecx, 1
然后整体又 / 2 的一次方
(ecx - (M * ecx) / 2 ^ 32 ) / 2 ^ 1
.text: 0040104F add ecx, edx
最后又加上乘积的高位
((ecx - (M * ecx) / 2 ^ 32 ) / 2 ) + (M * ecx) / 2 ^ 32
.text: 00401051 shr ecx, 2
最后整体又 / 2 的 2 次方
(((ecx - (M * ecx) / 2 ^ 32 ) / 2 ) + (M * ecx) / 2 ^ 32 ) / 2 ^ 2
.text: 00401054 push ecx
最后使用乘积高位
|
最终我们以图示的方式来列出公式
然后我们化简
首先是第一段化简 也可以称作是简化 如果不明白看下上面的数学知识补充
最后得出的公式 我们直接求解即可.
2^35 / (2^32 + M) 就得出了最终结果
比如我们的 /7 我们代入公式
1 | 2 ^ 35 / ( 2 ^ 32 + 24924925h ) = = = 6.99999 向上取整 = 7
|
2.2 特殊汇编M大于0x80000000的加调整
2.2.1 高级代码与反汇编
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | int main( int argc, char * argv[])
{
/ *
除法
* /
int NumberOne = 0 ;
int NumberTwo = 0 ;
scanf( "%u" ,&NumberOne);
scanf( "%u" ,&NumberTwo);
int Count1 = NumberOne / 7 ;
printf( "%d%d%d" ,Count1);
system( "pause" );
return 0 ;
}
|
汇编对应代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | .text: 00401000 ; int __cdecl main( int argc, const char * * argv, const char * * envp)
.text: 00401000 _main proc near ; CODE XREF: start + AF↓p
.text: 00401000
.text: 00401000 var_8 = dword ptr - 8
.text: 00401000 var_4 = dword ptr - 4
.text: 00401000 argc = dword ptr 4
.text: 00401000 argv = dword ptr 8
.text: 00401000 envp = dword ptr 0Ch
.text: 00401000
.text: 00401000 sub esp, 8
.text: 00401003 xor eax, eax
.text: 00401005 mov [esp + 8 + var_8], eax
.text: 00401009 mov [esp + 8 + var_4], eax
.text: 0040100D lea eax, [esp + 8 + var_8]
.text: 00401011 push eax
.text: 00401012 push offset aU ; "%u"
.text: 00401017 call _scanf
.text: 0040101C lea ecx, [esp + 10h + var_4]
.text: 00401020 push ecx
.text: 00401021 push offset aU ; "%u"
.text: 00401026 call _scanf
.text: 0040102B mov ecx, [esp + 18h + var_8]
.text: 0040102F mov eax, 92492493h
.text: 00401034 imul ecx
.text: 00401036 add edx, ecx
.text: 00401038 sar edx, 2
.text: 0040103B mov eax, edx
.text: 0040103D shr eax, 1Fh
.text: 00401040 add edx, eax
.text: 00401042 push edx
.text: 00401043 push offset aDDD ; "%d%d%d"
.text: 00401048 call _printf
.text: 0040104D push offset aPause ; "pause"
.text: 00401052 call _system
.text: 00401057 xor eax, eax
.text: 00401059 add esp, 24h
.text: 0040105C retn
.text: 0040105C _main endp
|
提取出核心汇编
1 2 3 4 5 6 7 8 9 | .text: 0040102B mov ecx, [esp + 18h + var_8]
.text: 0040102F mov eax, 92492493h
.text: 00401034 imul ecx
.text: 00401036 add edx, ecx
.text: 00401038 sar edx, 2
.text: 0040103B mov eax, edx
.text: 0040103D shr eax, 1Fh
.text: 00401040 add edx, eax
.text: 00401042 push edx
|
2.2.2 代码定式还原
观看上面代码.发现跟我们除法转化为乘法的代码定式很像 唯一不同的就是在使用 imul 指令之后.后面不是移位而是紧接着是一个add指令
其实这里的代码跟我们的特殊汇编第一种很相似. 这里的M数也很大. 原因是除法转换为乘法的时候做了调整.加了2^32次方
这个定式等价于除法转化为乘法的定式
直接使用这个定式进行还原即可.
1 | 2 ^ 34 / 2454267027 = 6.999 ... = 7
|
除法转化为乘法的代码定式为
解方程得
2.2.3代码优化原理
编译器再计算M数的时候(2^n/b)是以无符号数来进行计算的.而代入除法转变为乘法的代码中.是以有符号进行处理的.有符号的最高位是代表符号位
而无符号的最高位是数据位.所以如果你以无符号来进行计算.那么结果就会出错. 所以我们计算机中.如果(2^n/b)计算出的M数大于0x80000000
最高位为1也就是负数的表现形式.那么实际参与除法转变为乘法的过程是以补码来计算的. 结果是以
来进行计算的. 所以我们的除法转变为乘法的公式又变了.
变成了
这里的括号是求补码的意思 计算机中 2^n / b - 2^32次方是可以计算出来的
所以根据我们的代码定式列出方程式
1 2 3 4 5 6 7 8 9 | .text: 0040102B mov ecx, [esp + 18h + var_8]
.text: 0040102F mov eax, 92492493h
.text: 00401034 imul ecx
.text: 00401036 add edx, ecx
.text: 00401038 sar edx, 2
.text: 0040103B mov eax, edx
.text: 0040103D shr eax, 1Fh
.text: 00401040 add edx, eax
.text: 00401042 push edx
|
在加法这里.直接使用edx想加. 而EXE是M与被除数计算出来的.是乘积的高位.所以这里的edx等价于是
我们直接列出公式
直接进行代码公式优化即可.
这个公式等价于除法转变为乘法的公式 所以直接使用公式还原即可.
2.3 特殊汇编大于0x80000000无调整
当除数为负数且无调整的时候会出现这样的问题新的除法调整
2.3.1 高级代码与反汇编
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | int main( int argc, char * argv[])
{
/ *
除法
* /
int NumberOne = 0 ;
int NumberTwo = 0 ;
scanf( "%u" ,&NumberOne);
scanf( "%u" ,&NumberTwo);
int Count1 = NumberOne / - 5 ;
printf( "%d" ,Count1);
system( "pause" );
return 0 ;
}
|
核心汇编
1 2 3 4 5 6 7 8 | .text: 0040102B mov ecx, [esp + 18h + var_8]
.text: 0040102F mov eax, 99999999h 大于 0x8 ..没有进行调整
.text: 00401034 imul ecx
.text: 00401036 sar edx, 1
.text: 00401038 mov eax, edx
.text: 0040103A shr eax, 1Fh
.text: 0040103D add edx, eax
.text: 0040103F push edx
|
2.3.2 代码定式还原
遇到上述指令.直接使用代码定式还原
这里我们已知M 跟n值 直接代入公式即可.
结果向上取整.但是我们结果要判别为负.
2.3.3 除法原理还原
首先我们先看一下除法转变为乘法的公式
如果我们b为正数的时候.那么公式就是使用上面的公式. 如果为负数那么除法公式就变化了.变成了负数的方式求结果了
如下:
求 -C
那么最终如果我们要求b(除数) 就是 2^n /(2^32 - M) 即可.
2.4 M小于0x80000000 的减调整
减调整对于我们特殊的定式汇编我们算的是加调整. M值是小于0x80000000 而且有add调整.说明是一个正数
如果小于还是进行减调整.那么 我们要还原的除数还是为负数
2.4.1高级代码与反汇编
看下高级代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | int main( int argc, char * argv[])
{
/ *
除法
* /
int NumberOne = 0 ;
int NumberTwo = 0 ;
scanf( "%u" ,&NumberOne);
scanf( "%u" ,&NumberTwo);
int Count1 = NumberOne / - 7 ;
printf( "%d" ,Count1);
system( "pause" );
return 0 ;
}
|
核心反汇编
1 2 3 4 5 6 7 8 9 | .text: 0040102B mov ecx, [esp + 18h + var_8]
.text: 0040102F mov eax, 6DB6DB6Dh
.text: 00401034 imul ecx
.text: 00401036 sub edx, ecx 减调整
.text: 00401038 sar edx, 2
.text: 0040103B mov eax, edx
.text: 0040103D shr eax, 1Fh
.text: 00401040 add edx, eax
.text: 00401042 push edx
|
2.4.2 代码公式还原
如果想要计算出上方的定式.那么我们还是使用
进行还原即可.
代入公式得
1 | 2 ^ 34 / ( 2 ^ 32 - 6DB6DB6Dh ) = 6.99999 ...
|
结果向上取整.得出7 但是是负数所以得出是-7
2.4.3 除法优化原理
跟我们除数为 +7的代码公式相似.(2.2小结,M大于0x8000000) 只不过除数变成负数了.所以要对M数进行取负计算.
公式如下:
上面的公式是有符号为正数的公式.此时我们对我们的M取负数即可.
设C为如下公式
最终求解即可.
使用
进行还原即可.
三丶总结
M小于0x80000000
如果M大于0x8... 且有加调整 那么除数为正数 使用 b = 2^n / b 还原即可
如果M大于0x8 且没有调整 那么除数为负数 使用 b = 2^n /(2^32 - M) 还原即可.
M大于0x80000000
如果有减调整.那么除数为负数. 使用 b = 2^n/(2^32-M) 即可.
如果加调整,且满足 乘 减 移 加 移 使用 b = 2^n/(2^32+M) 即可.
除法的优化与还原资料. 参考自恩师 钱林松 出版的 <<C++反汇编与逆向分析技术揭秘>> 在此前提上加了自己的一些理解.以及定式还原的方式.
最后感谢一下 编程技术版主KevinsBobo 本书的公式资料在我写的时候有些许不理解.最后请教编程技术版主.然后熬夜做公式做还原得出的.
还是那句话 高手复习. 新手学习
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2020-9-28 09:05
被TkBinary编辑
,原因: 修改MD中乘法直接误认为是字符