写这篇文章得益于我最近钱林松、赵海旭写的新书《C++反汇编与逆向分析》,一直希望学习相关的技术,但是一直没有开始,看到此书后,激发起了我以前的兴趣,最近一直在读这本书,今日将我了解到的知识分享给大家。
首先让我们来看一段最简单的代码
#include<stdio.h>
void func(int first, int second)
{
}
int main()
{
int first = 1,second =2;
func(first,second);
return 0;
}
通过VC6++ 在WIN7 上编译得到的汇编代码如下:
PUBLIC _func
; COMDAT _func
_TEXT SEGMENT
_func PROC NEAR ; COMDAT
; 4 : {
push ebp ;保存调用者的栈底 即main函数的栈底
mov ebp, esp ;更新栈顶指针,将当前的栈顶赋予ebp
sub esp, 64 ; 00000040H //预留 64个字节用于保存临时变量
push ebx ;保存寄存器的值
push esi ;保存寄存器的值
push edi ;保存寄存器的值
lea edi, DWORD PTR [ebp-64] ;取当前临时变量空间的最低地址
mov ecx, 16 ; 00000010H //设置ecx 16
mov eax, -858993460 ; ccccccccH
rep stosd ;初始化局部变量空间
; 5 : }
pop edi ;还原寄存器
pop esi ;还原寄存器
pop ebx ;还原寄存器
mov esp, ebp ;还原 esp
pop ebp ;弹出调用者的栈底 即main函数的栈底
ret 0
_func ENDP
_TEXT ENDS
PUBLIC _main
EXTRN __chkesp:NEAR
; COMDAT _main
_TEXT SEGMENT
_first$ = -4
_second$ = -8
_main PROC NEAR ; COMDAT
; 8 : {
push ebp
mov ebp, esp
sub esp, 72 ; 00000048H
push ebx
push esi
push edi
lea edi, DWORD PTR [ebp-72]
mov ecx, 18 ; 00000012H
mov eax, -858993460 ; ccccccccH
rep stosd
; 9 : int first = 1,second =2;
mov DWORD PTR _first$[ebp], 1 ;初始化 first
mov DWORD PTR _second$[ebp], 2 ;初始化 second
; 10 : func(first,second);
mov eax, DWORD PTR _second$[ebp]
push eax ;将第二个参数入栈
mov ecx, DWORD PTR _first$[ebp]
push ecx ;将第一个参数入栈
call _func ;调用func函数
add esp, 8 ;平衡栈空间 前面有两次push操作
; 11 : return 0;
xor eax, eax
; 12 : }
pop edi ;还原寄存器
pop esi ;还原寄存器
pop ebx ;还原寄存器
add esp, 72 ; 00000048H 平衡栈空间
cmp ebp, esp ;栈平衡检测
call __chkesp ;栈平衡检测函数
mov esp, ebp ;还原 esp
pop ebp ;弹出调用者的栈底 注意main函数不是第一个函数,它也是被其他函数调用的,有兴趣的读者请自行研究
ret 0
_main ENDP
_TEXT ENDS
END
知识补充
(1)ESP:栈指针寄存器(extendedstackpointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的栈顶。
(2)EBP:基址指针寄存器(extendedbasepointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的底部。
上图为本测试实例的栈空间
细心的读者会发现 汇编代码中 add esp, 8 ;修正esp的操作
为什么要做这部操作呢,因为在调用func 函数之前,main 函数中还做了两次push 的入栈操作,将first second 两个参数压入栈中。呵呵可能有些读者已经发现了,这就是所谓的 _cdecl 函数调用,由调用者清栈的操作,我们平时所用的变参的函数必须由这种方式实现,因为被调函数是无法清除到底有多少参数。还有其他的调用方式,如 _stdcall, _fastcall, thiscall 等。这里给大家一个链接http://en.wikipedia.org/wiki/X86_calling_conventions#Caller_clean-up
下面给大家一个_stdcall的例子
#include<stdio.h>
void _stdcall func(int first, int second)
{
}
int main()
{
int first = 1,second =2;
func(first,second);
return 0;
}
; 4 : {
push ebp
mov ebp, esp
sub esp, 64 ; 00000040H
; 5 : }
mov esp, ebp
pop ebp
ret 8
; 10 : func(first,second);
mov eax, DWORD PTR _second$[ebp]
push eax
mov ecx, DWORD PTR _first$[ebp]
push ecx
call _func@8
; 11 : return 0;
xor eax, eax
通过观察上面的汇编代码main函数在调用func后,没有做平衡栈的操作,在func函数中 ret 8 这个指令做了平衡栈的操作。
本例中没有给出读者函数是如何返回返回值的,读者可以自行实验,其实如果是简单的变量,是通过寄存器返回的,如果是数组,结构体呢??
PS:技术拙劣,如有错误请指教