本文为看雪论坛精华文章
看雪论坛作者ID:TkBinary
目录
系列文章
一. 了解数学知识
1.1 简介
1.2 数学知识
1.2.1 数学知识之代数与解方程
1.2.2 简化表达式去括号
1.2.3 简化表达式之交叉相乘
1.2.4 简化表达式之合并同类项
1.2.5 简化表达式之分数的加法简化
1.2.7 简化表达式之分数乘法
1.2.6 简化表达式之分数的减法简化
1.2.8 分数除法
二. 除法特殊汇编
2.1 特殊定式汇编
2.1.1高级代码与汇编对应
2.1.2 核心代码反汇编还原
2.1.3 特殊汇编的除法原理
2.1.4 x86乘法特性与x64乘法特性
2.1.5 汇编等式还原
2.2 特殊汇编M大于0x80000000的加调整
2.2.1 高级代码与反汇编
2.2.2 代码定式还原
2.2.3代码优化原理
2.3 特殊汇编大于0x80000000无调整
2.3.1 高级代码与反汇编
2.3.2 代码定式还原
2.3.3 除法原理还原
2.4 M小于0x80000000 的减调整
2.4.1高级代码与反汇编
2.4.2 代码公式还原
2.4.3 除法优化原理
三. 总结
系列文章 反汇编技术之熟悉IDA工具 https://bbs.pediy.com/thread-224499.htm
反汇编逆向技术之寻找Main入口点 https://bbs.pediy.com/thread-224500.htm
反汇编代码还原之优化方式
反汇编代码还原之加减乘
反汇编代码还原之除法为2的幂
反汇编代码还原之除法为非2的幂
一. 了解数学知识
1.1 简介
在下面会有大量的数学知识来进行讲解,当然如果你奔着如何还原,直接按照定式还原就行,不用纠结如何计算出来的。 但是你了解数学知识,从数学角度来看待优化,那么会可以了解其真正原理。本人数学也不好,但还是查阅很多资料,把基础数学 罗列出来,一来是便 于复习,二来是能看到基本的数学公式即可。1.2 数学知识
1.2.1 数学知识之代数与解方程
方程:有一个未知数,我们来解这个未知数那么叫做解方程。 例如:x + 3 = 64x + 5 = 17
解方程我们可以代入一个数进行去解,也可以直接做平衡解。
意思就是如果等式的左边+ 那么我们就利用减法,两边都减去这个值.。如果是x ,那么做相反运算也就是反之亦然。
解:
x + 3 = 6x +3-3= 6 - 3x = 3 4x + 5 = 174x + 5 - 5 = 17 - 54x = 124x / 4 = 12 / 4x = 3
1.2.2 简化表达式去括号
简化表达式分为移除括号,交换结合定律,合并同类项等。 移除括号,看公式:3(5 + 2) 展开的时候计算括号的值变成 3*5 + 3*2 = 15 + 6a(b + c) = ab + ac3(x + 6) = 3x + 3*6负数乘法去括号遵循 负正得负 负负得正的规律-3(a + -6) = -3a + -3*-6 = -3a + 18-3(a + 6) = -3a + -3*6 = -3a + -18
关于去括号的另一个特性:
3 * (2 + 4) = 3 * 63 *(2 + 4) = 3*2 + 3*4
两种方式都是可以得出结果的,一般第一种就是加这个数的和,第二种就是拆分为乘数来,计算之后再相加。
第二种用途用于不好算的数来用的。
例如:
2 * 204 直接算算不出可以简化为2* 200 + 2*4 = 408
1.2.3 简化表达式之交叉相乘
交叉相乘用于分数,可以帮助我们进行简化。 解决的是把一个分数变为表达式:原理就是分子与分母相乘: 可以看到分子变了,而分母都变成了(12×3) ,所以都是除同一个数。 所以可以去掉了,变成 8 × 3 = 12 × 2。 公式记为:
1.2.4 简化表达式之合并同类项
如果看官方简介会看到一大堆名词解释,那么这里说一下自我的理解吧。 同类项 就是这一类属于一项,优先把他们组合起来。 例如: 在这里有xy就是同类项,所以可以优先组合起来。 组合的时候我们可以再根据加法减法符号来组合同符合类别的。(-xy + 5xy) + (-2xy - 4xy)+(3 - 7) == -2xy - 4也可以变成
1.2.5 简化表达式之分数的加法简化
分数加法: 分数的加法是有一条简单的规矩的,就是去分母。 如何去分母之前也有说,就是让分母一致,然后直接计算分子。 我们可以看一下上面的倒数相乘去分母,就是一个很好的例子: 公式为如上,也就是交叉相乘的结果:1.2.6 简化表达式之分数的减法简化
分数减法同加法一样,只不管变成相减了:1.2.7 简化表达式之分数乘法
分数乘法 分数乘法简化还是按照上乘上下乘下原则:1.2.8分数除法
分数除法要转变为分数乘法,具体原则就是分数的倒数来进行相乘。 其余按照分数乘法来做。二. 除法特殊汇编
2.1 特殊定式汇编
2.1.1高级代码与汇编对应
高级代码: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。
看下汇编:
.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 核心代码反汇编还原
我们去掉流水线优化后的核心反汇编如下:.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
观看代码定式,我们发现了一个特点:核心汇编代码都是乘、减、移、加、移的指令。
.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
如果你想要还原,记住代码定式:
.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,这个就是我们求的被除数,所以这一整段代码我们可以还原为:var_4 / 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乘法特性
x86乘法特性 在x86下,乘法的乘积放在edx.eax中,但是这不是绝对。看如下: 举例:mov al,5hmov bl,10hmul bl //ax == 0050,CF = 0
与之同理:
.dataval1 WORD 2000hval2 WORD 0l00h.codemov ax, val1 ; AX = 2000hmul val2 ; DX:AX = 00200000h, CF = 1
4字节计算被乘数是4个字节:
mov eax, 12345hmov ebx, 1000hmul ebx ; EDX:EAX = 0000000012345000h, CF = 0
x64下的乘法特性
64位模式下
,MUL 指令可以使用64位操作数。一个64位寄存器或内存操作数与RAX相乘,产生的128位乘积存放到RDX:RAX寄存器中。
下例中,RAX 乘以 2,就是将 RAX 中的每一位都左移一位。RAX 的最高位溢出到 RDX 寄存器,使得 RDX 的值为 0000 0000 0000 0001h:
.datamultiplier QWORD 10h.codemov rax, OAABBBBCCCCDDDDhmul multiplier ; RDX:RAX = 00000000000000000AABBBBCCCCDDDDOh
2.1.5 汇编等式还原
了解了乘法原理我们来看等式,根据我们的汇编产生的等式:.text:00401038 mov eax, 24924925h.text:00401045 mov ecx, [esp+1Ch+var_4].text:00401049 mul ecxeax = Mecx = 被除数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 我们代入公式:
2^35 / (2^32 + 24924925h) === 6.99999 向上取整 = 7
2.2 特殊汇编M大于0x80000000的加调整
2.2.1 高级代码与反汇编
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;}
汇编对应代码:
.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
提取出核心汇编:
.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次方。 这个定式等价于除法转化为乘法的定式。 直接使用这个定式进行还原即可:2^34 / 2454267027 = 6.999... = 7
除法转化为乘法的代码定式为:
解方程得:
2.2.3代码优化原理
编译器再计算M数的时候(2^n/b)是以无符号数来进行计算的,而代入除法转变为乘法的代码中,是以有符号进行处理的,有符号的最高位是代表符号位。 而无符号的最高位是数据位,所以如果你以无符号来进行计算,那么结果就会出错,所以我们计算机中如果(2^n/b)计算出的M数大于0x80000000。 最高位为1也就是负数的表现形式,那么实际参与除法转变为乘法的过程是以补码来计算的,结果是以 来进行计算的,所以我们的除法转变为乘法的公式又变了。 变成了: 这里的括号是求补码的意思,计算机中 2^n / b - 2^32次方是可以计算出来的。 所以根据我们的代码定式列出方程式:.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 高级代码与反汇编
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;}
核心汇编:
.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高级代码与反汇编
看下高级代码: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;}
核心反汇编:
.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 代码公式还原
如果想要计算出上方的定式,那么我们还是使用 进行还原即可。 代入公式得:2^34 / (2^32 - 6DB6DB6Dh) = 6.99999...
结果向上取整,得出7,但是是负数所以得出是-7。
2.4.3 除法优化原理
跟我们除数为 +7的代码公式相似。(2.2小结,M大于0x8000000) 只不过除数变成负数,.所以要对M数进行取负计算。 公式如下: 上面的公式是有符号为正数的公式,此时我们对我们的M取负数即可。 设C为如下公式: 最终求解即可。 使用以下进行还原即可:三. 总结 1. M小于0x80000000 如果M大于0x8... 且有加调整, 那么除数为正数,使用 b = 2^n / b 还原即可。 如果M大于0x8 且没有调整, 那么除数为负数,使用 b = 2^n /(2^32 - M) 还原即可。 2. M大于0x80000000 如果有减调整,.那么除数为负数,使用 b = 2^n/(2^32-M) 即可。 如果加调整,且满足乘减移加移,使用 b = 2^n/(2^32+M) 即可。 除法的优化与还原资料,参考自恩师钱林松出版的 《C++反汇编与逆向分析技术揭秘C+》 。在此前提上加了自己的一些理解,以及定式还原的方式。 最后感谢一下编程技术版主KevinsBobo本书的公式资料。在我写的时候有些许不理解,最后请教编程技术版,然后熬夜做公式做还原得出的。 还是那句话,高手复习,新手学习。 - End -
看雪ID:TkBinary
https://bbs.pediy.com/user-home-723188.htm
*本文由看雪论坛 TkBinary 原创,转载请注明来自看雪社区。
2.5折优惠票数量有限,先到先得哦!推荐文章++++
* 黑白鉴定入门之一款后门的分析思路
* 双向进攻:分析tcpdump DOS漏洞成因
* PWN:fastbin attack学习
* 反汇编代码还原之除数为非2的幂
* 格式化字符串漏洞解析
公众号ID:ikanxue 官方微博:看雪安全 商务合作:wsc@kanxue.com求分享
求点赞
求在看
戳