反汇编代码中的优化方式
反汇编代码中的优化方式
一优化方式
优化方式分类
汇编中的加法、减法、乘法、除法 、 取模等等 都是优化方式。
优化方式的分类
- 常量折叠
- 常量传播
- 变量去除
- 归并优化
- Cpu流水线优化
- 数学变换
- 不可达分支优化
- 代码外提优化
优化前提是在Release下且开启o2选项化速度的前提Debug版本也会优化更多的是方便程序员调试。所以在不影响调试的前提下才会进行优化。
常量折叠
int n = 0;
int m = 1;
printf("%d",7+8);
printf("%d",n+6);
printf("%d",n+m);
所谓常量折叠就是在编译前所遇到的常量。是可以就进行计算的。那么就会优化为一个常量值。
例如上面的7+8不会产生add指令 而是在程序编译后直接称为15(0XF)
printf("%d", 7 + 8);
00671873 push 0Fh
00671875 push offset string "%d" (0677B30h)
0067187A call _printf (06710CDh)
0067187F add esp,8
printf("%d", n + 6);
00671882 mov eax,dword ptr [n]
00671885 add eax,6
00671888 push eax
00671889 push offset string "%d" (0677B30h)
0067188E call _printf (06710CDh)
00671893 add esp,8
printf("%d", n + m);
00671896 mov eax,dword ptr [n]
00671899 add eax,dword ptr [m]
0067189C push eax
0067189D push offset string "%d" (0677B30h)
006718A2 call _printf (06710CDh)
006718A7 add esp,8
常量折叠含义就是常量会在编译器给你算出来。
常量传播
常量传播也叫常量扩散 指的就是 变量在写入或者读取的时候没有传递内存地址(&)也没有传指针或者引用来修改值得时候就会发生常量传播。
说白了就是 你没有修改我变量得代码。那么这个变量我就可以认为是常量。
int n = 0;
printf("%d",n+6);
那么进行常量传播之后,n因为没有对其修改。也没有对其进行传地址得操作。所以编译器就会把它变为常量。
int n = 0;
printf("%d",0+6);
而看到这里想大家明白了 又符号常量折叠所以代码继续变化。
int n = 0 ;
printf("%d",6);
这里进行了两次优化,一次是常量传播 一次是常量折叠
复写传播
int c = argc;
int b = argc
变量去除
变量去除是指你程序中定义了变量但是对其没有进行修改。然后进行常量传播,常量折叠一步一步给优化掉
int n = 0;
int m = 1;
printf("%d",n+m);
程序首先发现n m 两个变量相加。但是看了一下上面。发现没有对其进行修改。所以代码就会改变如下:
printf("%d",0+1);
而0+1符号常量折叠。所以最终代码就变为了
printf("%d",1);
对应反汇编
.text:00401018 push 1
.text:0040101A push offset aD ; "%d"
.text:0040101F call _printf
归并优化
归并优化。如果可以一起优化那么我就一起优化。
我们知道printf属性C调用约定。所以需要外平栈而且它是可变参。通过你push参数个数得不同。外平栈的大小也会相应改变
比如:
.text:00401040 sub_401040 proc near ; CODE XREF: start-8D↓p
.text:00401040 push 0Fh
.text:00401042 push offset unk_417A8C
.text:00401047 call sub_401010
.text:0040104C push 6
.text:0040104E push offset unk_417A8C
.text:00401053 call sub_401010
.text:00401058 push 1
.text:0040105A push offset unk_417A8C
.text:0040105F call sub_401010
.text:00401064 add esp, 18h
.text:00401067 xor eax, eax
.text:00401069 retn
.text:00401069 sub_401040 endp
一个参数是4字节。所以累计总共push了6个参数4*6=24个字节。所以平栈也需要24个字节
Cpu流水线优化
Cpu流水线优化其实说白了就是打乱指令执行顺序。而不影响原有功能。这个在我们反汇编的时候需要注意。正常的汇编代码都是平平整整顺顺虚虚 让人一眼看的很舒服,而且反汇编编出高级代码也很快
这里以简单的汇编为例子 因为我们要很多代码才会遇到流水线优化。这里我模拟一下
正常的汇编代码指令顺序
xor eax,eax
xor ebx,ebx
xor ecx,ecx
mov eax,1
add eax,2
mov ebx,eax
mov ecx,3
打乱的流水线
xor eax,eax
mov eax,1
xor ecx,ecx
add eax,2
mov ecx,3
xor ebx,ebx
mov ebx,eax
汇编代码就很简单。
我们着重看一下打乱的汇编
在没打乱之前代码平平整整。打乱之后发现很多汇编进行了穿插
比如
mov eax 1
xor ecx,ecx
在Cpu执行mov eax,1的时候。可以直接执行xor ecx,ecx这样的好处是下行汇编不依赖上一行汇编。之前的指令是下一行指令依赖上一行指令。那么Cpu如果在执行第二行的时候发现你依赖于上一行汇编那么就会等待。
mov eax,1
add eax,2
第一行执行了mov eax,1 那么第二行又使用了eax。那么第二行执行的时候就要等待第一行。
而打断的好处就是 我执行第一行的时候也可以执行第二行而且不会影响你的结果。也可以提升速度。
数学变换优化
数学变化优化:如果操作的数是无意义的。那么就会进行优化.
i=10;
b=11;
i=b+10;
i=b-0;
i=b*3;
i=i/3;
那么以上高级代码直接进行优化 优化为
i=b;
不可达分支优化
不可达分支则是分支永远都不会走。那么也不会产生汇编代码。也没有存在的意义
a=10;
if(a==10)
{
xxxx
}
else
{
xxxx
}
上面已经知道a就是个常量。值就是10.那么会走if块。而else永远不会走。那么就会将else优化掉。当然实际情况中代码肯定不多。不会像我一样简单的写一个a=10去判断。
代码外提优化
所谓代码外提是在循坏的时候进行优化。循环体内部没有产生修改此变量的代码。就会进行优化
int x = xxx;
while(x>y/3)
{
xxxx....
x--;
}
循环体内部并没有操作y/3所以这个值都会放到外面执行则会优化为
t= y/3
while(x>t)
{
xxxx....
x---;
}
而t变量很可能也会经过上面的几种优化变为寄存器变量
去掉优化方式
代码混淆与优化是对立的。所以学习一下优化方便我们更好的人肉优化混淆代码 .
上面所说。都是编译器已经识别到了你的程序没有传地址传地址等。所以我们想办法就是不让他优化。
而