在我们学习反汇编的时候.很多人都以为反汇编很难. 其实不然. 学什么都是有技巧的.
而想要锻炼我们的反汇编能力. 第一就是要进行多练. 第二就是熟悉底层原理. 第三就是熟悉套路.
往后几篇 会把C与C++的反汇编形式体现出来. 如果写的不对的话请批评指正.
汇编中的加法 减法 乘法 除法 取模 等等 都是有优化方式.以及有套路的.
优化方式分为以下几种
那么着重介绍一下上面优化方式所代表的意思.
优化的前提是在 Release下切开启O2选项优化速度的前提 Debug版本也会优化但是更多的是方便程序员调试.所以在不影响调试的前提下才会进行优化.
有以下例子:
所谓常量折叠就是 在编译前 所遇到的常量.是可以进行计算的.那么就会优化为一个常量值
例如上面的 7 + 8 不会产生add指令 而是在程序编译后直接成为 15(0xF)
以VC6.0与VS2019分别编译 为什么要两个编译器. 目的就是让大家知道.这个套路不管是几十年前的6.0还是现如今的Vs2019 都是一样的. 可能会有些稍许不同.但是绝不影响你的逆向.以及反汇编.而恰巧这才是真正的核心所在.包括gcc编译也是一样.
Vc6.0 保留核心
可以看到 7+8在反汇编的形势下直接变为了 0xF. 也就是10进制的15
Vs2019
可以看到唯一不同的就是高版本 IDA没有识别出sig库.所以调用的都成了 sub_401010 而低版本函数已经认出来了.
还有就是文件体积变大了. vc6.0 编译出来28kb 2019编译出来98kb. 你品.
上面例子足以说明什么是常量折叠 含义就是常量会在编译器给你计算出来
常量传播也叫做常量扩散 指的就是 变量在写入或者读取的时候没有传递内存地址(&)也没有传指针或者引用来修改值的时候就会发生常量传播
大白话讲就是 你没有修改我变量的代码.那么这个变量我就可以认为是常量了.
以上面高级代码为例子
那么进行常量传播之后. n因为没有对其修改.也没有对其进行传地址的操作. 所以编译器就会把它变为常量了.
那么上面的代码就会产生如下代码
而看到这里想必大家应该明白了. 0 + 6 又符合常量折叠. 所以代码继续变化
以上面汇编为例子
想必大家知道这里为啥是 push 6 了. 这里进行了两次优化 一次是常量传播,一次是常量折叠
变量去除指的就是你程序中定义了变量但是没有对其进行修改. 然后进行常量传播,常量折叠一步一步给优化掉
还是以高级代码为例子
程序首先发现 n m 两个变量相加. 但是看了一下上面. 发现没有对其进行修改.所以代码就会变为如下
而0+1符合常量折叠.所以最终代码就变为了.
对应反汇编
归并优化,如果可以一起优化那么我就一起优化.
我们知道 printf 属于C调用约定. 所以需要外平栈 而且他是可变参.通过你push参数个数的不同.外平栈的大小也会相应改变
比如:
但是我们在上面的汇编代码中.并没有看到 add esp,8 而是直接看到了 add esp,0x18
原因是什么. 在你调用printf的时候.而下面你又调用了相同的几个 printf ,printf 都是C调用约定.
所以我就一起给你平了
所以代码就有如下
一个参数是4个字节. 所以累计总共push了6个参数. 4 * 6 = 24个字节.所以平栈也需要24个字节
24恰巧是 十六进制的 0x18
Cpu流水线优化其实说白了就是打乱指令执行顺序,而不影响原有功能. 这个在我们反汇编的时候需要注意.正常的汇编代码都是平平整整顺顺序序 让人一眼看的很舒服,而且反汇编出高级代码也很快.
这里以简单的汇编为例子 因为我要写很多代码才会遇到流水线优化.这里我模拟一下
正常的汇编代码指令顺序
打乱的流水线
汇编代码就很简单.
我们着重看一下打乱的汇编.
在没打乱之前代码平平整整.打乱之后发现很多汇编进行了穿插
比如
在Cpu执行 mov eax,1的时候.可以直接执行 xor ecx,ecx 这样的好处是下一行汇编不依赖于上一行汇编.
之前的指令是下一行指令依赖于上一行指令. 那么Cpu 如果在执行第二行的时候发现你依赖于上一行汇编.那么就会等待.
第一行执行了 mov eax,1 那么第二行又使用了eax. 那么第二行执行的时候就要等待第一行.
而打断的好处就是 我执行第一行的时候也可以执行第二行而且不会影响你的结果. 也可以提升速度
流水线优化需要你细品.
当你品完之后再看下打乱的汇编
是不是发现很顺眼了. 那么当你还原的时候完全可以按照自己的意愿来恢复汇编进行还原
是不是就很简单了.
数学变换优化: 如果操作的数是无意义的.那么就会进行优化.
那么以上高级代码直接进行优化.优化为
不可达分支则是分支永远都不会走.那么也不会产生汇编代码.也没有存在的意义
上面已经知道a就是个常量.值就是10.那么会走if块. 而 else永远不会走.那么就会将else优化掉. 当然实际情况中代码肯定很多.不会像我一样简单的写一个 a = 10 去判断.
所谓代码外提一般是在循环的时候进行优化.循环体内部没有产生修改此变量的代码.就会进行优化
循环体内部并没有操作y/3. 所以这个值都会放到外面执行
则会优化为
而t变量很可能也会经过上面的几种优化变为了寄存器变量
代码混淆与优化是对立的.所以学习下优化也方便我们更好的"人肉"优化混淆代码
去掉优化的方式说一下
上面所说.都是编译器已经识别到了你的程序没有传地址传指针等. 所以我们想办法就不让他优化.
简单的函数如果你什么也不做也会给你优化掉的.
我们使用了很多scanf 以及printf 并对其变量取地址 那么编译器就不知道我们会不会修改变量的值了.
那么就不会给我们优化了.此时查看汇编代码就能看到真实的最小限度的优化代码了.
上面的几种优化方式,虽然很简单.但是我们也必须要掌握以及了解的. 因为后面的反汇编 代码还原 会有更多的优化. 而那些优化在配合 上面所说的优化就会让你感觉很难. 或者不知道汇编为什么那样做. 而你了解了这些 在单独看各自的优化就会明白.也会豁然开朗.
这些在我博客上已经写过.这里发出来也是为了让新会员重新了解一下反汇编. 高手能复习. 新人能学习.
CPP代码很简单.本次不提交了
int
n
=
0
;
int
m
=
1
;
printf(
"%d"
,
7
+
8
);
printf(
"%d"
,n
+
6
);
printf(
"%d"
,n
+
m);
int
n
=
0
;
int
m
=
1
;
printf(
"%d"
,
7
+
8
);
printf(
"%d"
,n
+
6
);
printf(
"%d"
,n
+
m);
.text:
00401000
push
0Fh
.text:
00401002
push offset aD ;
"%d"
.text:
00401007
call _printf
.text:
0040100C
push
6
.text:
0040100E
push offset aD ;
"%d"
.text:
00401013
call _printf
.text:
00401018
push
1
.text:
0040101A
push offset aD ;
"%d"
.text:
0040101F
call _printf
.text:
00401024
add esp,
18h
.text:
00401000
push
0Fh
.text:
00401002
push offset aD ;
"%d"
.text:
00401007
call _printf
.text:
0040100C
push
6
.text:
0040100E
push offset aD ;
"%d"
.text:
00401013
call _printf
.text:
00401018
push
1
.text:
0040101A
push offset aD ;
"%d"
.text:
0040101F
call _printf
.text:
00401024
add esp,
18h
.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
.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
int
n
=
0
;
printf(
"%d"
,n
+
6
);
int
n
=
0
;
printf(
"%d"
,n
+
6
);
int
n
=
0
;
printf(
"%d"
,
0
+
6
);
int
n
=
0
;
printf(
"%d"
,
0
+
6
);
int
n
=
0
;
printf(
"%d"
,
6
);
int
n
=
0
;
printf(
"%d"
,
6
);
.text:
0040100C
push
6
.text:
0040100E
push offset aD ;
"%d"
.text:
00401013
call _printf
.text:
0040100C
push
6
.text:
0040100E
push offset aD ;
"%d"
.text:
00401013
call _printf
int
n
=
0
;
int
m
=
1
;
printf(
"%d"
,n
+
m);
int
n
=
0
;
int
m
=
1
;
printf(
"%d"
,n
+
m);
printf(
"%d"
,
0
+
1
);
printf(
"%d"
,
1
);
.text:
00401018
push
1
.text:
0040101A
push offset aD ;
"%d"
.text:
0040101F
call _printf
.text:
00401018
push
1
.text:
0040101A
push offset aD ;
"%d"
.text:
0040101F
call _printf
.text:
00401018
push
1
.text:
0040101A
push offset aD ;
"%d"
.text:
0040101F
call _printf
add esp,
8
.text:
00401018
push
1
.text:
0040101A
push offset aD ;
"%d"
.text:
0040101F
call _printf
add esp,
8
.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
.text:
00401040
sub_401040 proc near ; CODE XREF: start
-
8D
↓p
.text:
00401040
push
0Fh
.text:
00401042
push offset unk_417A8C
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
最后于 2020-9-21 11:58
被TkBinary编辑
,原因: 流水线优化位置,操作数手写有误.修改了