反汇编分析C语言
环境:VC6.0
为什么不使用Visual Studio?
Visual Studio的反汇编代码更复杂一些,如下为VS2019的空函数反汇编代码
可以看到有CheckForDebuggerJustMyCode
等一些额外的函数
为了便于理解和学习,便采用VC6.0来进行学习
空函数反汇编
#include "stdafx.h"
//空函数
void function(){
}
int main(int argc, char* argv[])
{
//调用空函数
function();
return 0;
}
我们通过反汇编来分析这段空函数
函数外部
12: function();
00401048 call @ILT+5(function) (0040100a)
13: return 0;
0040104D xor eax,eax
14: }
0040104F pop edi
00401050 pop esi
00401051 pop ebx
00401052 add esp,40h
00401055 cmp ebp,esp
00401057 call __chkesp (004010e0)
0040105C mov esp,ebp
0040105E pop ebp
0040105F ret
函数内部
6: void function(){
00401010 push ebp
00401011 mov ebp,esp
00401013 sub esp,40h
00401016 push ebx
00401017 push esi
00401018 push edi
00401019 lea edi,[ebp-40h]
0040101C mov ecx,10h
00401021 mov eax,0CCCCCCCCh
00401026 rep stos dword ptr [edi]
7:
8: }
00401028 pop edi
00401029 pop esi
0040102A pop ebx
0040102B mov esp,ebp
0040102D pop ebp
0040102E ret
分析函数
函数调用
00401048 call @ILT+5(function) (0040100a)
首先就是通过call来调用我们的function函数
函数内部
接着进到函数的内部
有了之前画堆栈图的经验,我们不难看出,尽管我们的函数是个空函数,但其汇编代码依然完成了以下流程:
- 提升堆栈
- 保护现场
- 初始化提升的堆栈
- 恢复现场
- 返回
提升堆栈
00401010 push ebp
00401011 mov ebp,esp
00401013 sub esp,40h
保护现场
00401016 push ebx
00401017 push esi
00401018 push edi
PS:前面的push ebp也是保护现场
初始化提升的堆栈
00401019 lea edi,[ebp-40h]
0040101C mov ecx,10h
00401021 mov eax,0CCCCCCCCh
00401026 rep stos dword ptr [edi]
恢复现场
00401028 pop edi
00401029 pop esi
0040102A pop ebx
0040102B mov esp,ebp
0040102D pop ebp
PS:这里的mov esp,ebp就是降低堆栈,与前面的提升堆栈相对应,所以也属于恢复现场的一部分
返回
0040102E ret
函数返回后
函数返回后不出意料地返回到了调用CALL地下一行语句,我们接着看
0040104D xor eax,eax
这里是将eax清零,注意到我们的语句为return 0 这里就是将eax作为返回值来传递
一般来说eax都是作为函数的返回值,但不绝对,有的函数返回值是存在内存里或其它情况,要具体情况具体分析
接着看下面的代码:
0040104F pop edi
00401050 pop esi
00401051 pop ebx
很明显,这里是在还原现场,别忘了我们的主程序main本身也是个函数,这是在还原调用main前保护的现场
接着往下走
00401052 add esp,40h
00401055 cmp ebp,esp
00401057 call __chkesp (004010e0)
这里首先是将esp减少了40h,然后比较ebp和esp,最后再调用一个chkesp函数
从名称就不难看出chkesp = check esp ,检查esp,这个函数就是用来检查堆栈是否平衡的
继续
0040105C mov esp,ebp
0040105E pop ebp
依旧是恢复现场
最后就是返回
0040105F ret
总结空函数分析
我们可以看到,即便一个空函数什么都没有做,但调用一个空函数所产生的汇编代码却不少
保护现场、恢复现场以及堆栈平衡的检查等等都没少,可谓麻雀虽小五脏俱全
简单加法函数反汇编
有了前面分析空函数的经验,我们再来分析分析一个简单的加法函数
#include "stdafx.h"
int Plus(int x,int y){
return x+y;
}
int main(int argc, char* argv[])
{
//调用加法函数
Plus(1,2);
return 0;
}
函数外部
16: Plus(1,2);
004010A8 push 2
004010AA push 1
004010AC call @ILT+0(Plus) (00401005)
004010B1 add esp,8
17: return 0;
004010B4 xor eax,eax
18: }
004010B6 pop edi
004010B7 pop esi
004010B8 pop ebx
004010B9 add esp,40h
004010BC cmp ebp,esp
004010BE call __chkesp (004010e0)
004010C3 mov esp,ebp
004010C5 pop ebp
004010C6 ret
函数内部
10: int Plus(int x,int y){
00401060 push ebp
00401061 mov ebp,esp
00401063 sub esp,40h
00401066 push ebx
00401067 push esi
00401068 push edi
00401069 lea edi,[ebp-40h]
0040106C mov ecx,10h
00401071 mov eax,0CCCCCCCCh
00401076 rep stos dword ptr [edi]
11: return x+y;
00401078 mov eax,dword ptr [ebp+8]
0040107B add eax,dword ptr [ebp+0Ch]
12: }
0040107E pop edi
0040107F pop esi
00401080 pop ebx
00401081 mov esp,ebp
00401083 pop ebp
00401084 ret
分析函数
函数调用
004010A8 push 2
004010AA push 1
004010AC call @ILT+0(Plus) (00401005)
结合前面的空函数分析,我们可以明显发现这里的函数调用环节,多了两个push
就是将函数所需的参数压入堆栈,这里的参数为 2 和 1,注意压入的顺序是反着的(由调用协定决定,下篇笔记会详细说明)
函数内部
提升堆栈保护现场初始化
提升堆栈、保护现场、初始化部分和空函数如出一辙,这里就不再赘述
00401060 push ebp
00401061 mov ebp,esp
00401063 sub esp,40h
00401066 push ebx
00401067 push esi
00401068 push edi
00401069 lea edi,[ebp-40h]
0040106C mov ecx,10h
00401071 mov eax,0CCCCCCCCh
00401076 rep stos dword ptr [edi]
实际执行
00401078 mov eax,dword ptr [ebp+8]
0040107B add eax,dword ptr [ebp+0Ch]
这里的[ebp+8]就是我们前面压入的参数1,[ebp+c]就是前面压入的参数2
于是这两条语句其实就是
00401078 mov eax,1
0040107B add eax,2
将1+2的结果保存到eax中(此时eax又作为函数返回值的载体)
恢复现场和返回
接下来的内容就和空函数一样了,恢复现场和返回,也不再赘述
0040107E pop edi
0040107F pop esi
00401080 pop ebx
00401081 mov esp,ebp
00401083 pop ebp
00401084 ret
函数返回后
004010B1 add esp,8
17: return 0;
004010B4 xor eax,eax
18: }
004010B6 pop edi
004010B7 pop esi
004010B8 pop ebx
004010B9 add esp,40h
004010BC cmp ebp,esp
004010BE call __chkesp (004010e0)
004010C3 mov esp,ebp
004010C5 pop ebp
004010C6 ret
函数返回后我们会发现与先前的空函数相比多了这一行代码:
004010B1 add esp,8
这里是对应我们前面压入的两个参数1和2,压入参数后esp减少了8,这里我们函数调用结束后,就不再需要之前压入的两个参数了,于是将esp恢复到压入参数前,这其实也算在恢复现场里,用来平衡堆栈
我们可以发现,这条语句是在我们call调用完毕返回后执行的平衡堆栈操作,所以这种操作也被称为堆栈外平衡
与之相对就是堆栈内平衡:即在call里面就把堆栈平衡好了
之后的代码就和空函数无异了,也不再赘述,简单的加法函数分析到这也就告一段落了