反汇编技术之熟悉IDA工具
反汇编逆向技术之寻找Main入口点
反汇编代码还原之优化方式
反汇编代码还原之加减乘
学习除法优化能使我们认识反汇编中的除法表达式. 进而更好的 进行代码还原.
除法指令 对应的指令是 Div以及IDIV 而这两个指令周期是特别长的.
比如一个 DIV 是100. 而 sar指令就10. 那么编译器肯定会选择使用 Sar指令来进行优化
除法是优化的除数 而我们反汇编的时候就是得出除数是多少. 进而还原为C代码
除数是常量才有优化的余地.如果是一个未知除数.(变量) 那么就会使用原生 DIV 或者IDIV来进行操作
除法优化是根据数学模型来进行优化的. 除非数学上有突破.否则在底层的除法反汇编表现形式不会有多大的变化. 学习除法要具备简单的数学知识.
原因是在我们计算机中,除法是整数除法. 也就是向零取整的.
比如现实中
7/3 = 2..1 而计算机中就如下 7 / 3 = 2
何为向下取整 向上取整 向零取整
有一坐标线. 在这条坐标线上有一些列数字. 包含正数 负数 以及0
如下:
0 往右 属于正数
0 往左属于负数
向下取整:
比如我们说 对 a 向下取整. 那么意思就是取得不大于a的最大整数
比如 a = 4.5 那么 向下取整就是 4
比如 a = -4.5 那么向下取整就是 -5
向下取整也可以理解为在坐标线上. 是向左走.取得不大于这个数的最大整数
向上取整:
向上取整则相反.在坐标线上往右走. 意思是 取得 + 最接近a的整数值.另一个意思就是
取得不小于a的最小整数值
比如a = 4.5 向上取整则是 5
比如 a = -4.5 向上取整就是 -4
向零取整:
向零取整就简单的. 都是往0方向取整的.
对于正数 a 的向零取整,等价于是对a的向下取整.
对于负数 a的向零取整,等价于是对 -a的向上取整
a = 4.5 向零取整则为4
a = --4.5 向零取整 则为 -4
a = 被除数
b = 除数
当商为正数 > 0 与商为负数分别有两种表现形式
正数除数计算 则使用的公式是向下取整
负数除数计算 则使用的公式是向上取整
看下图
如上图,分别对 被除数为正负数 向上取整与向下取整做了公式转换
这里有四个公式分别为公式1 公式2 公式3 公式4
首先我们先看一下除法是2的幂的时候 代码表现形式
高级代码:
核心代码反汇编 去掉无用汇编
看下高级代码
对应反汇编
这里使用了 cdq汇编指令 cdq是符号扩展的意思. 是将 eax扩展为edx.eax 什么意思. 如果eax符号位为1.那么edx就用1填充.也就是FFFFFFFF,如果符号位为0.那么edx 结果就为 0
这里使用cdq 意思就是我们是个有符号数. 直接使用 idiv 来进行除法
遇到这种形式.我们直接根据汇编进行还原即可.
除数为 2 等价于是 2的一次方 设幂 = n 则n = 1 sar n 等价于 x / 2^n
首先如果除数为2. 且为正数 那么我们的思路 是可以用 sar算术右移 进行优化的
在汇编中 sar为算术右移 shr为逻辑右移
两者区别如下:
正因为我们是用有符号被除数来进行除法的 所以需要判断符号位.如果判断符号位.那么就可能需要cmp 或者影响标志位的指令.以及对应的jxx指令来判断商是为正数的计算结果.以及为负数的计算结果
而显然如果这样 CPU指令周期会更长.根本没有优化的余地.所以就要进行无分支优化.
观看高级代码与对应汇编
乍一看 这几句代码很晕. 为什么/2汇编代码表现为这样.
其实可以分为两部分看. 因为这里面带有 无分支优化(也就是判断商为负还是为正数)
我们拆分开看.
算术右移的优化
这两句应该能看明白吧. 获得被除数 被除数算术右移1位. 等价于 被除数/2. 这也是sar指令的作用
sar 1就等于 /2 那么 sar2 就是4 以此类推 sar n 是否就是等价于 /2^n
如果单看这两句应该就明白了. 反汇编代码还原的时候 直接 还原为 eax /2. 除数还原出来就是2
无分支优化
无分支优化我们说过,就是判断商为正还是为负数
着重看一下cdq 以及 sub eax,edx
cdq 如果被除数是负数. 那么扩展符号位之后 edx = -1 (也就是FFFFFFFF)
cdq 如果被除数为正数 那么扩展符号位之后 edx = 0
所以在这里 edx要么是-1 要么是0
sub eax,edx 指令的含义就是 调整被除数. 如果为负数那么被除数+1. 如果是正数不做调整
负数的情况下 sub eax,edx === (-被除数) - (-1) 根据数学定理 负负得正.等价于 (-被除数) + 1
正数的请开情况下 sub eax,edx == 被除数- 0 == 被除数。 所以如果是正数的情况下。 cdq + sub eax,edx
这两个指令是没有用的。
无分支优化的调整疑问
很多人看完之后可能会想。为什么要做调整。 比如 4 /2 = 2 那么-4 /2 = -2 结果不一样吗. +1调整之后我们的
被除数就变为了 -5 / 2了. 再计算 -5/2 =-2.5了吗. 原因也是我说过的. 在计算机中只会计算整数除法.
而我们的整数除法本质就是向零取整 所以对于负数来说.其结果是向上取整来计算的. 对于正数来说其结果是向下取整来计算了. 所以我们总结起来就是向零取整 就是让计算负数的时候满足向上取整. 对于计算正数的时候满足向下取整. 所以这里优化如果是负数的情况下.应该要满足向上调整.所以+1调整
反汇编代码还原
遇到这种反汇编不要慌.稳一下. 其实就是再算除法. 直接根据 sar n来进行还原除数即可.
上述汇编进行高级代码还原为如下
eax = 被除数
eax / 2^1 === 被除数 / 2
只不过这里有无分支判断.所以被除数可以看做是有符号数
数学原理
被除数为正数且除数为>0
如果想知道原理.那么可以看这个小主题.如果想知道如何还原不用看这个主题了
我们说过对于被除数为正数的除法计算.且其除数为 2^n值的时候. 那么商就按照 向下取整来计算
汇编中表现形式就是 sar n即可. 被除数 >> n的形式
那么设 x = 被除数. 那么 x / 2n 等价于 x >>n
x = 4
2^n = 2
代入公式得出 4 / 2 = 2 n值取为1 那么 2^1 = 2
被除数为负数
被除数为负数的除法计算.且除数大于0. 那么商就按照向上取整来计算
而如果被除数为负数. 那么使用公式2可以将向上取整优化为向下取整.
设x = 被除数 x < 0 设置b = 除数 并且值 = 2^n 并且是大于0
那么 x / b 是向下取整来计算的
如果转化为向上取整 则等于 (x + b - 1) / b
设x = -4
b = 2
代入公式得
(-4 + 2 - 1) / 2 ===> (-3) /2 商向下取整 = -2
所以我们的负数在汇编中的表现形式其实是定式 (x + (b - 1)) >>n
所以才有了我们的代码
(-被除数 - 1) / n 商向下取整
所以这里sar是向下取整. 对于正数来说没问题.向下取整就是直接计算即可. x >>n 但是对于负数来说.直接向下
取整那么结果就会有问题. 所以要做+1调整才可以. 中间产生的变化就是 将向上取整变为了向下取整转化后如果除数大于0 且被除数 是负数. 所以就产生了 +1 调整
4 = 2的2次方
汇编代码表现形式
观看汇编
还是分为两部分看.
被除数为正数. 除数为正数 向下取整计算 x >> n
被除数为负数 除数为正数
还是带有无分支指令
如果被除数为负数 那么除法需要按照向上取整原则来进行运算 但是我们因为要去掉分支.
所以还是要将 向上取整 转化为向下取整 使用公式2
正数:
(x + b -1) / b
and edx,3 这里的3 可以看做是b-1 也就是2^n-1值 其实是已经计算出来了.
add eax,edx 这里可以看做是 -被除数 + (2^n-1)
简化一下
(-被除数 + (2^n-1) /b
n的取值我们看到是2 那么2^n = 4
继续简化
(-被除数 + (4 - 1) / 4
继续简化
(-被除数 + 3) / 4
所以上面的and edx,3 其实就是让edx变为3. 如果是正数计算.edx = 0; 那么我们说正数不用看这两条指令. 如果是负数. edx就是-1 -1%3 = 3 其实就是设置edx为3. 最后add 再去想加.就满足了我们的公式.
sar 2 等价于 /4
代码定式反汇编
遇到这种定式.直接看下n值. 这里的n值为2. 所以得出 2^n = 4. 也就是得出了除数为4
这里的3可以看做是负数做优化的时候.需要将向上取整代码转化为向下取整 所产生的额外代码.
比如这里是3. 可以看做是 2^n - 1. 如果符合这个形式. 那么直接得出 除数为2 即可.
这一块代码反汇编为高级代码为
除数8 = 2 ^3 次方. 所以还是按照公式还原即可. 如果被除数为负数 除数为正数. 去看公式2.公式2将向上取整转为向下取整
正数: x >> n
负数(x + (b - 1) / b
代码同 2^1 次方 2^2次方 还原方式一样
高级代码
核心汇编
可以看到如果我们无符号 / 2的幂 直接使用 shr逻辑右移来进行优化了.
反汇编的时候 取n值进行还原即可
汇编
观看汇编代码.可以看到其代码定式跟除数为2的幂一样.唯一不同的就是 商的结果进行求补
neg 指令的作用就是 0 -xxx 比如eax = 1 neg(eax) 等于是 0 - 1 = FFFFFFFF
这里加这条指令的含义就是.如果商为负数 那么进行求补.商的结果就为正数了.
还原方式还是 取n值 唯一不同的是因为有neg. 所以前边加负数
其实这个主题主要做一个对比. 目的就是告诉大家 VC6.0 与 Visual Studio 2019下代码优化方式一样
汇编代码
可以发现,同vc6.0的代码并没有什么变化. 因为优化是根据数学定理优化的.除非数学变了.否则不会发生改变.
高级代码
汇编代码
跟VC6.0一样没有多大变化
高级代码
核心汇编代码
可以看到.除了 cdq 换成了 cqo之外. 还是使用数学定理.
那么同理.除数为正数2的幂一样.大家自己建立工程出查看即可.
还是那句话.高手复习.新手学习.
int
main(
int
argc, char
*
argv[])
{
/
*
除法
*
/
int
NumberOne
=
0
;
int
NumberTwo
=
0
;
scanf(
"%d"
,&NumberOne);
scanf(
"%d"
,&NumberTwo);
int
Count1
=
NumberOne
/
NumberTwo;
int
Count2
=
NumberOne
/
2
;
int
Count3
=
NumberTwo
/
4
;
int
Count5
=
NumberTwo
/
8
;
printf(
"%d%d%d%d%d"
,Count5,Count3,Count2,Count1);
system(
"pause"
);
return
0
;
}
int
main(
int
argc, char
*
argv[])
{
/
*
除法
*
/
int
NumberOne
=
0
;
int
NumberTwo
=
0
;
scanf(
"%d"
,&NumberOne);
scanf(
"%d"
,&NumberTwo);
int
Count1
=
NumberOne
/
NumberTwo;
int
Count2
=
NumberOne
/
2
;
int
Count3
=
NumberTwo
/
4
;
int
Count5
=
NumberTwo
/
8
;
printf(
"%d%d%d%d%d"
,Count5,Count3,Count2,Count1);
system(
"pause"
);
return
0
;
}
mov ecx, [esp
+
1Ch
+
var_8]
mov esi, [esp
+
1Ch
+
var_4]
mov eax, ecx
cdq
idiv esi
mov eax, ecx
cdq
sub eax, edx
sar eax,
1
mov eax, esi
cdq
and
edx,
3
add eax, edx
sar eax,
2
cdq
and
edx,
7
add eax, edx
sar eax,
3
push eax
mov ecx, [esp
+
1Ch
+
var_8]
mov esi, [esp
+
1Ch
+
var_4]
mov eax, ecx
cdq
idiv esi
mov eax, ecx
cdq
sub eax, edx
sar eax,
1
mov eax, esi
cdq
and
edx,
3
add eax, edx
sar eax,
2
cdq
and
edx,
7
add eax, edx
sar eax,
3
push eax
int
Count1
=
NumberOne
/
NumberTwo;
int
Count1
=
NumberOne
/
NumberTwo;
mov ecx, [esp
+
1Ch
+
var_8]
mov esi, [esp
+
1Ch
+
var_4]
mov eax, ecx
cdq
idiv esi
mov ecx, [esp
+
1Ch
+
var_8]
mov esi, [esp
+
1Ch
+
var_4]
mov eax, ecx
cdq
idiv esi
ecx
=
var_8
esi
=
var_4
ecx
/
esi
=
=
var_8
/
var_4
ecx
=
var_8
esi
=
var_4
ecx
/
esi
=
=
var_8
/
var_4
int
Count2
=
NumberOne
/
2
;
int
Count2
=
NumberOne
/
2
;
mov eax, ecx 获得被除数
cdq 被除数进行符号扩展
sub eax, edx 被除数减去符号扩展位
sar eax,
1
算术右移
mov eax, ecx 获得被除数
cdq 被除数进行符号扩展
sub eax, edx 被除数减去符号扩展位
sar eax,
1
算术右移
mov eax, ecx 获得被除数
sar eax,
1
mov eax, ecx 获得被除数
sar eax,
1
mov eax, ecx 获得被除数
cdq 被除数进行符号扩展
sub eax, edx 被除数减去符号扩展位
sar eax,
1
算术右移
mov eax, ecx 获得被除数
cdq 被除数进行符号扩展
sub eax, edx 被除数减去符号扩展位
sar eax,
1
算术右移
mov eax, ecx 获得被除数
cdq 被除数进行符号扩展
sub eax, edx 被除数减去符号扩展位
sar eax,
1
算术右移
mov eax, ecx 获得被除数
cdq 被除数进行符号扩展
sub eax, edx 被除数减去符号扩展位
sar eax,
1
算术右移
mov eax, ecx
cdq 被除数为负数 edx
=
-
1
sub eax, edx
-
被除数
-
1
=
=
(被除数
+
1
)
sar eax,
1
/
n
mov eax, ecx
cdq 被除数为负数 edx
=
-
1
sub eax, edx
-
被除数
-
1
=
=
(被除数
+
1
)
sar eax,
1
/
n
mov eax, esi 获得被除数
cdq 符号扩展.edx 要么为
1
要么为
0
and
edx,
3
被除数 &
2
^n
-
1
add eax, edx 被除数
+
符号扩展
sar eax,
2
向下取整完成除法
mov eax, esi 获得被除数
cdq 符号扩展.edx 要么为
1
要么为
0
and
edx,
3
被除数 &
2
^n
-
1
add eax, edx 被除数
+
符号扩展
sar eax,
2
向下取整完成除法
mov eax, esi 获得被除数
sar eax,
2
向下取整完成除法
mov eax, esi 获得被除数
sar eax,
2
向下取整完成除法
mov eax, esi 获得被除数
cdq 符号扩展.edx 要么为
1
要么为
0
and
edx,
3
被除数 &
2
^n
-
1
add eax, edx 被除数
+
符号扩展
sar eax,
2
向下取整完成除法
mov eax, esi 获得被除数
cdq 符号扩展.edx 要么为
1
要么为
0
and
edx,
3
被除数 &
2
^n
-
1
add eax, edx 被除数
+
符号扩展
sar eax,
2
向下取整完成除法
mov eax, esi
cdq
and
edx,
3
add eax, edx
sar eax,
2
mov eax, esi
cdq
and
edx,
3
add eax, edx
sar eax,
2
eax
=
var_xxx
eax
=
eax
/
4
=
=
var_xxx
/
4
eax
=
var_xxx
eax
=
eax
/
4
=
=
var_xxx
/
4
mov eax, esi
cdq
and
edx,
7
add eax, edx
sar eax,
3
mov eax, esi
cdq
and
edx,
7
add eax, edx
sar eax,
3
int
main(
int
argc, char
*
argv[])
{
/
*
除法
*
/
unsigned
int
NumberOne
=
0
;
unsigned
int
NumberTwo
=
0
;
scanf(
"%u"
,&NumberOne);
scanf(
"%u"
,&NumberTwo);
unsigned Count1
=
NumberOne
/
NumberTwo;
unsigned Count2
=
NumberOne
/
2
;
unsigned Count3
=
NumberTwo
/
4
;
unsigned Count5
=
NumberTwo
/
8
;
printf(
"%d%d%d%d%d"
,Count5,Count3,Count2,Count1);
system(
"pause"
);
return
0
;
}
int
main(
int
argc, char
*
argv[])
{
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2020-9-22 13:36
被TkBinary编辑
,原因: 修改语义错误