C++逆向:除法的非常规优化及其识别与还原
编辑本文涉及到的是除法的f非常规优化方法,包括:MagicNum
溢出的情况和有符号数除以非2的幂的两种特殊情况。测试环境与编译选项均与上一篇常C/C++除法的常规优化及其识别与还原的情况相同。
…
具体的知识点包括:
- 无符号数除以非2的幂的特殊情况
- 有符号数除以非2的幂的两种特殊情况
- 除数是正数,但是
MagicNum
为负 - 除数是负数,但是
MagicNum
为正
- 除数是正数,但是
无符号数除以非2的幂的特殊情况
测试代码:
int main(int argc, char* argv[])
{
printf("%d", (unsigned int)argc / 7);
return 0;
}
整理后对应的反汇编代码:
.text:00401000 ; int __cdecl main(int argc, const char **argv, const char **envp)
.text:00401000 _main proc near ; CODE XREF: start+AFp
.text:00401000
.text:00401000 arg_0 = dword ptr 4
.text:00401000
.text:00401000 mov ecx, [esp+arg_0]
.text:00401004 mov eax, 24924925h
.text:00401009 mul ecx
.text:0040100B sub ecx, edx
.text:0040100D shr ecx, 1
.text:0040100F add ecx, edx
.text:00401011 shr ecx, 2
.text:00401014 push ecx
.text:00401015 push offset s->D ; "%d"
.text:0040101A call printf
.text:0040101A
.text:0040101F add esp, 8
.text:00401022 xor eax, eax
.text:00401024 retn
.text:00401024
.text:00401024 _main endp
在常规优化中,其代码定式应该是:
mov eax, MagicNum
mul x
shr edx, n
而在上述代码中,操作却不一致,这是为什么?上述操作又是在做什么?可以根据上述代码写出数学表达式进行化简来得到其结果,设ecx = arg0
, C = 24924925h
,有:
化简可得到:
于是可以发现,其实就是编译器在计算MagicNum
的时候,发现得到的结果用4个字节存放不下,多了一个进位出来,也就是 。所以在后序计算结果的时候,由于MagicNum
是低4字节的结果,所以需要进行调整,在计算的时候需要先加上这个进位才能正确的得到结果。而它之所以拆分为上述情况,是因为要将除法转为周期更短的移位运算,进行了等价变换。
还原时需要将MagicNum
加上 再进行还原即可。
有符号数除以非2的幂的两种特殊情况
出现这两种特殊情况的原因是,由于编译器在计算MagicNum
的时候,是按照无符号数来进行运算的,但是在有符号数除法中需要使用有符号命令,此时得到的MagicNum
可能会为负数,因此需要做调整;同理,其MagicNum
本应该是负数,但是在计算过程中却被视为正数,因此也需要做调整,故有以下两种情况。
除数是正数,但是MagicNum
为负
测试代码:
int main(int argc, char* argv[])
{
printf("%d", argc / 7);
return 0;
}
对应的反汇编结果为:
.text:00401000 ; int __cdecl main(int argc, const char **argv, const char **envp)
.text:00401000 _main proc near ; CODE XREF: start+AFp
.text:00401000
.text:00401000 arg_0 = dword ptr 4
.text:00401000
.text:00401000 mov ecx, [esp+arg_0]
.text:00401004 mov eax, 92492493h ; MagicNum为负数,因为大于0x7fff, ffff
.text:00401009 imul ecx
.text:0040100B add edx, ecx ; 对比之前的有符号数除以非2的幂多了这个操作
.text:0040100D sar edx, 2
.text:00401010 mov eax, edx
.text:00401012 shr eax, 1Fh
.text:00401015 add edx, eax
.text:00401017 push edx
.text:00401018 push offset s->D ; "%d"
.text:0040101D call printf
.text:0040101D
.text:00401022 add esp, 8
.text:00401025 xor eax, eax
.text:00401027 retn
.text:00401027
.text:00401027 _main endp
将上述操作所描写的数学公式写出来并化简,可以得到以下式子:
其中,即:M
是对C取补之后的结果,真是的MagicNum
应该为正值。
其多添加的这一句add edx, ecx
其实就是对无符号整数乘以有符号数做调整,而还原方法是直接按照正数的方法进行还原即可。
除数是负数,但是MagicNum
为正
测试代码为:
int main(int argc, char* argv[])
{
printf("%d", argc / -7);
return 0;
}
反汇编结果为:
.text:00401000 ; int __cdecl main(int argc, const char **argv, const char **envp)
.text:00401000 _main proc near ; CODE XREF: start+AFp
.text:00401000
.text:00401000 arg_0 = dword ptr 4
.text:00401000
.text:00401000 mov ecx, [esp+arg_0]
.text:00401004 mov eax, 6DB6DB6Dh ; MagicNum为正数
.text:00401009 imul ecx
.text:0040100B sub edx, ecx ; 多出了一个减法操作
.text:0040100D sar edx, 2
.text:00401010 mov eax, edx
.text:00401012 shr eax, 1Fh
.text:00401015 add edx, eax
.text:00401017 push edx
.text:00401018 push offset s->D ; "%d"
.text:0040101D call printf
.text:0040101D
.text:00401022 add esp, 8
.text:00401025 xor eax, eax
.text:00401027 retn
.text:00401027
.text:00401027 _main endp
同样,将上述运算写为数学表达式:
其中,即:真正的M
是一个负值。
其还原方法还是先求补,再还原。
整体总结
总结一下识别方法:
- 在识别时,先观察是否有加1做调整的操作,如果有且运算指令使用的是有符号运算指令,则被除数是有符号数。
- 查看
MagicNum
是否是负数(大于等于0x8000,0000
即为负数)。如果是负数,且未见加调整,则除数是负数;如果是正数,且有减调整,则除数是负数,需要先求补再进行还原;同理,如果是负数,且有加调整,除数是正数;如果是正数,且未有减调整,则除数是正数
对于还原方法,首先判断除数是正数还是负数来进行还原:
- 正数还原除数
- 负数还原除数
- 溢出还原除数
- 0
-
分享