1.空函数
void fun()
{
}
反汇编
保护栈底
push ebp
提升堆栈
mov ebp,esp
sub esp,40h
保护现场
push ebx
push esi
push edi
填充缓冲区
lea edi,[ebp-40h]
mov ecx,10h
mov eax,0CCCCCCCCh
rep stos dword ptr [edi]
2.带参函数
两个数相加
int fun(int a,int b)
{
return a + b;
}
反汇编
传参
push 4
push 3
调用函数求和函数
call 0E13C0h
外平栈
add esp,8
保护栈底
push ebp
提升堆栈
mov ebp,esp
sub esp,0C0h
保护现场
push ebx
push esi
push edi
填充缓冲区
mov edi,ebp
xor ecx,ecx
mov eax,0CCCCCCCCh
rep stos dword ptr es:[edi]
mov ecx,offset _EDC018E5_test1@cpp (0EC066h)
call @__CheckForDebuggerJustMyCode@4 (0E1320h)
函数真正的功能在这里实现a+b然后放到cx
mov eax,dword ptr [a]
add eax,dword ptr [b]
恢复现场
pop edi
pop esi
pop ebx
降低栈顶
add esp,0C0h
堆栈平衡错误检查
cmp ebp,esp
call __RTC_CheckEsp (0E1244h)
恢复主调函数堆栈
mov esp,ebp
pop ebp
返回
ret
三个数相加
int fun(int a,int b,int c)
{
return a + b + c;
}
反汇编
传参
push 5
push 4
push 3
调用求和函数
call fun (0F213C5h)
外平栈
add esp,0Ch
保护栈底
push ebp
提升堆栈
mov ebp,esp
sub esp,0C0h
保护现场
push ebx
push esi
push edi
填充缓冲区
mov edi,ebp
xor ecx,ecx
mov eax,0CCCCCCCCh
rep stos dword ptr es:[edi]
mov ecx,offset _EDC018E5_test1@cpp (0F2C066h)
call @__CheckForDebuggerJustMyCode@4 (0F21320h)
函数真正的功能实现a+b+c放到eax
mov eax,dword ptr [a]
add eax,dword ptr [b]
add eax,dword ptr [c]
恢复现场
pop edi
pop esi
pop ebx
降低堆栈
add esp,0C0h
堆栈平衡错误检查
cmp ebp,esp
call __RTC_CheckEsp (0F21244h)
恢复堆栈
mov esp,ebp
pop ebp
返回
ret
3.裸函数
直接执行裸函数会执行出错,因为编译器不会帮你做任何操作例如ret等
void __declspec(naked) fun()
{
}
反汇编
不会自动生成反汇编代码,调用函数后跳到int3 停止函数执行
加上ret可以正常执行,因为可以正确返回
void __declspec(naked) fun()
{
_asm {
ret;
}
}
无参数无返回值的函数框架
void __declspec(naked) fun()
{
_asm {
push ebp
mov ebp,esp
sub esp,0x40
push ebx
push esi
push edi
lea edi,dword ptr ds:[ebp-0x40]
mov eax,0xCCCCCCCC
mov ecx,0x10
rep stosd
pop edi
pop esi
pop ebx
mov esp,ebp
pop ebp
ret
}
}
有参数有返回值的函数框架
int __declspec(naked) fun(int x, int y)
{
_asm {
push ebp
mov ebp,esp
sub esp,0x40
push ebx
push esi
push edi
lea edi,dword ptr ds:[ebp-0x40]
mov eax,0xCCCCCCCC
mov ecx,0x10
rep stosd
mov eax, dword ptr ds : [ebp + 8]
add eax, dword ptr ds : [ebp + 0xC]
pop edi
pop esi
pop ebx
mov esp,ebp
pop ebp
ret
}
}
带局部变量的函数框架
int __declspec(naked) fun(int x, int y)
{
_asm {
push ebp
mov ebp,esp
sub esp,0x40
push ebx
push esi
push edi
lea edi,dword ptr ds:[ebp-0x40]
mov eax,0xCCCCCCCC
mov ecx,0x10
rep stosd
mov dword ptr ds : [ebp - 4] , 2
mov dword ptr ds : [ebp - 8] , 3
mov eax, dword ptr ds : [ebp + 8]
add eax, dword ptr ds : [ebp + 0xC]
pop edi
pop esi
pop ebx
mov esp,ebp
pop ebp
ret
}
}
4.调用约定
调用约定 | 参数压栈顺序 | 平衡堆栈 | |||
__cdecl | 从右至左入栈 | 调用者清理栈 | |||
__stdcall | 从右至左入栈 | 自身清理堆栈 | |||
__fastcall | ECX/EDX传送前两个 | 自身清理堆栈 | |||
剩下:从右至左入栈 |
_cdecl
int __cdecl Plus(int a, int b)
{
return a + b;
}
反汇编
push 5
push 4
call Plus (08813E8h)
add esp,8
如果不指定调用约定默认为_cdecl
不定参数的函数可以使用这种方式
_stdcall
int __stdcall Plus(int a, int b)
{
return a + b;
}
push 5
push 4
call Plus (0C513EDh)
在函数内部平衡堆栈
不定参数的函数无法使用这种方式
ret 8
__fastcall
int __fastcall Plus(int a, int b,int c)
{
return a + b;
}
寄存器方式传参 (前两个参数用寄存器,后面的参数用push入栈)
在函数内部平衡堆栈
mov edx,5
mov ecx,4
call Plus (0BB13F2h)
5.程序的真正入口
main 或WinMain 是“语法规定的用户入口”,而不是“应用程序入口”。应用程序入口通常是启动函数。
main 函数被调用前要调用的函数
GetVersion()
_heap_init()
GetCommandLineA()
_crtGetEnvironmentStringsA()
_setargv()
_setenvp()
_cinit()
main函数的参数
main(__argc, _argv, _get_initial_narrow_environment());
main函数的识别
有3个参数,分别是命令行参数个数、命令行参数信息和环境变量信息,而且main函数是启动函数中唯一具有3个参数的函数。同 理,WinMain也是启动函数中唯一具有4个参数的函数。 main函数返回后需要调用exit函数,结束程序根据main函数调用 的特征,找到入口代码第一次调用exit函数处,离exit最近的且有 3个参数的函数通常就是main函数。