首先先要清楚汇编中堆栈的原理 才能了解 stdcall的约束
这里 是我通过ollydbg汇编代码调试去理解
比如
我们看到这个方法的调用位置为 00401378 找到这个地址的调用代码为
00401378 |. E8 BD000000 call 0040143A (这里等价于 call MessageBoxA)
进入 0040143A
0040143A $- FF25 AC314000 jmp 77D507EA
我们看到 无条件跳转到 77D507EA 看这个位置的代码
我们看到进入了user32模块代码如下
77D507EA > 8BFF mov edi,edi
77D507EC /. 55 push ebp
77D507ED |. 8BEC mov ebp,esp
77D507EF |. 833D BC14D777 00 cmp dword ptr ds:[0x77D714BC],0x0
77D507F6 |. 74 24 je XUSER32.77D5081C
77D507F8 |. 64:A1 18000000 mov eax,dword ptr fs:[0x18]
77D507FE |. 6A 00 push 0x0
77D50800 |. FF70 24 push dword ptr ds:[eax+0x24]
77D50803 |. 68 241BD777 push USER32.77D71B24
77D50808 |. FF15 C412D177 call dword ptr ds:[<&KERNEL32.Interlocke>; kernel32.InterlockedCompareExchange
77D5080E |. 85C0 test eax,eax
77D50810 |. 75 0A jnz XUSER32.77D5081C
77D50812 |. C705 201BD777 0100>mov dword ptr ds:[0x77D71B20],0x1
77D5081C |> 6A 00 push 0x0 ; /LanguageID = 0 (LANG_NEUTRAL)
77D5081E |. FF75 14 push [arg.4] ; |Style
77D50821 |. FF75 10 push [arg.3] ; |Title
77D50824 |. FF75 0C push [arg.2] ; |Text
77D50827 |. FF75 08 push [arg.1] ; |hOwner
77D5082A |. E8 2D000000 call USER32.MessageBoxExA ; \MessageBoxExA
77D5082F |. 5D pop ebp
77D50830 \. C2 1000 retn 0x10
在 77D507EA 断点后
我们调用了MessageBoxA方法 看到堆栈中 信息为
0012FE8C 0040137D /CALL 到 MessageBoxA 来自 CRACKME.00401378
0012FE90 000206F6 |hOwner = 000206F6 ('CrackMe v1.0',class='No need to disasm the code!')
0012FE94 00402169 |Text = "No luck there, mate!"
0012FE98 00402160 |Title = "No luck!"
0012FE9C 00000030 \Style = MB_OK|MB_ICONEXCLAMATION|MB_APPLMODAL
第一列是堆栈的地址 也就是 esp的地址
说明从堆栈地址 0012FE9C 到0012FE8C 压入了 5个4字节的数据 通过右边可以看出 这些数据中4个4字节是MessageBoxA 的四个参数
最上面的1个4字节压入的 子程序返回 要执行的代码的地址
第二列存放的地址信息 实际是ds的地址 通过这个地址 找到找到的就是找到的数据
比如00402160 到00402169的9个字节 存放的是No luck!的2进制数据 可以看到 这里可以看到No luck!为8个字节 其实还有最后一字节 为‘\0’表示字符串的结束
我们知道 call了某个地址 需要通过 retn指令结束子程序的运行 retn指令将esp对应的地址 pop出来 从而获得 call完成后的下一个运行代码的地址
我们看到 0012FE8C 0040137D /CALL 到 MessageBoxA 来自 CRACKME.00401378
说明 retn之后 下一个执行的代码是 0040137D 这个地址
但是因为 堆栈中 pop了
0012FE90 000206F6 |hOwner = 000206F6 ('CrackMe v1.0',class='No need to disasm the code!')
0012FE94 00402169 |Text = "No luck there, mate!"
0012FE98 00402160 |Title = "No luck!"
0012FE9C 00000030 \Style = MB_OK|MB_ICONEXCLAMATION|MB_APPLMODAL
这从0012FE9C到0012FE90这 16个字节的数据 都是子程序使用的 所有在retn的同时还要讲该数据清除掉
所以在retn的时候 会pop掉0012FE8C 0040137D /CALL 到 MessageBoxA 来自 CRACKME.00401378 这里
使esp的地址指向0012FE90 也就是0012FE8C+4
我们看到
77D5082F |. 5D pop ebp
77D50830 \. C2 1000 retn 0x10
最后一句是retn 0x10 也就是 10是 10进制的16 也是 retn 16个字节
除了 retn的4个字节 还要加上后面参数的 16个字节 所以 我们可以看到
在retn 0x10 后 esp指向了(0012FE9C 00000030 \Style = MB_OK|MB_ICONEXCLAMATION|MB_APPLMODAL)0012FE9C 的后一个地址
也就是
0012FEA0 0040124A 返回到 CRACKME.0040124A 来自 CRACKME.00401362
这就是参数入栈的原理
__stdcall----参数从右向左入栈
我们看到
0012FE8C 0040137D /CALL 到 MessageBoxA 来自 CRACKME.00401378
0012FE90 000206F6 |hOwner = 000206F6 ('CrackMe v1.0',class='No need to disasm the code!')
0012FE94 00402169 |Text = "No luck there, mate!"
0012FE98 00402160 |Title = "No luck!"
0012FE9C 00000030 \Style = MB_OK|MB_ICONEXCLAMATION|MB_APPLMODAL
MessageBox的四个参数
int WINAPI MessageBox(
_In_opt_ HWND hWnd,
_In_opt_ LPCTSTR lpText,
_In_opt_ LPCTSTR lpCaption,
_In_ UINT uType
);
我们看到栈顶是地址0012FE8C 栈顶也就是最后压入的参数 最先压入的参数也就是栈底的是
0012FE9C 00000030 \Style = MB_OK|MB_ICONEXCLAMATION|MB_APPLMODAL
也就是最先压入的是UINT uType 说明是从右到左压入参数 是stdcall约定
这里是其他blog转载来的 只是说明 除了stdcall还有其他约定
在Win32汇编中,我们经常要和Api打交道,另外也会常常使用自己编制的类似于Api 的带参数的子程序,本文要讲述的是在子程序调用的过程中进行参数传递的概念和分析。一般在程序中,参数的传递是通过堆栈进行的,也就是说,调用者把要传递给子程序(或者被调用者)的参数压入堆栈,子程序在堆栈取出相应的值再使用,比如说,如果你要调用 SubRouting(Var1,Var2,Var3),编译后的最终代码可是
push Var3
push Var2
push Var1
call SubRouting
add esp,12
也就是说,调用者首先把参数压入堆栈,然后调用子程序,在完成后,由于堆栈中先前压入的数不再有用,调用者或者被调用者必须有一方把堆栈指针修正到调用前的状态。参数是最右边的先入堆栈还是最左边的先入堆栈、还有由调用者还是被调用者来修正堆栈都必须有个约定,不然就会产生不正确的结果,而Stdcall就是参数从右到左压入栈,由被调用的子程序来修正堆栈的指针。 __cdecl----参数从右向左入栈,调用者清除栈
__stdcall----参数从右向左入栈,被调用者清除栈 函数func(int a, int b)
补充一点:
__cdecl函数被编译成:_func
__stdcall函数被编译成:_func@8 (8 为参数的字节数) Directive Parameter order Clean-up Passes parameters in registers?
register Left-to-right Routine Yes
pascal Left-to-right Routine No
cdecl Right-to-left Caller No
stdcall Right-to-left Routine No
safecall Right-to-left Routine No
VC里面:PASCAL==CALLBACK==WINAPI==__stdcall _stdcall是Pascal程序的缺省调用方式,通常用于Win32 Api中,函数采用从右到左的压栈方式,自己在退出时清空堆栈。VC将函数编译后会在函数名前面加上下划线前缀,在函数名后加上"@"和参数的字节数。 _cdecl是C和C++程序的缺省调用方式。每一个调用它的函数都包含清空堆栈的代码,所以产生的可执行文件大小会比调用_stdcall函数的大。函数采用从右到左的压栈方式。VC将函数编译后会在函数名前面加上下划线前缀。是MFC缺省调用约定。 关于PASCAL,其实你只要弄明白一点就行了:声明为这种调用约定的函数都是由它本身来清栈,而__cdecl的函数都是由调用者来清栈。