裸函数(无参无返空实现)
void _declspec(naked) Function(){ __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 main(){ Function(); }
函数返回值
函数返回值是通过EAX传出去的(在函数ret之前如果有返回,他会先将要返回的地址的值放在EAX寄存器中,之后EAX保持不变,返回。)
示例代码如下:
;int a=1; ;int b=2; ;int c=a+b; mov dword ptr [ebp-8],1 mov dword ptr [ebp-14h],2 mov eax,dword ptr [ebp-8] add eax,dword ptr [ebp-14h] mov dword ptr [ebp-20h],eax ;eax = result ;此处就是将结果c所在地址的值给到EAX寄存器中返回 mov eax,dword ptr [ebp-20h]
注意:mov dword ptr ds:[ebp-8],1和mov dword ptr [ebp-8],1的区别,第一种指定了段寄存器ds,第二种则使用默认寄存器,默认为ds。
函数传参
函数的第一个参数位置为[ebp+8],第二个参数位置为[ebp+C],因为[ebp+0]位置为原ebp位置,[ebp+4]位置为函数调用后的下一步的地址,所以第一个参数的位置为[ebp+8]。
在调用之前原函数会将每个参数从右往左依次压入堆栈,然后再执行call命令,在call命令执行结束之后需要将原来堆栈加 4×参数数。
调用约定
调用约定 | 参数压栈顺序 | 平衡堆栈 |
---|---|---|
__cdecl | 从右至左入栈 | 调用者清理栈 |
__stdcall | 从右至左入栈 | 自身清理堆栈 |
__fastcall | ECX/EDX传送前两个 剩下:从右至左入栈 | 自身清理堆栈 |
1、add esp,10h
2、ret 10h
3、先把前两参数传给ECX和EDX,然后ECX和EDX再将值放到变量堆栈区,就是自己弄出来到缓冲区。ret 8h
堆栈回溯(EBP)
int Fun1(){ int Addr1=0; int Addr2=0; __asm{ mov eax,dword ptr ds:[ebp+4] mov dword ptr ds:[Addr1],eax mov eax,dword ptr ds:[ebp] mov eax,dword ptr ds:[eax+4] mov dword ptr ds:[Addr2],eax } printf("%x \n",Addr1 - 5); printf("%x \n",Addr2 - 5); }
EBP 回溯过程
-
确定当前栈帧的底部:EBP 寄存器指向当前函数栈帧的底部。当开始回溯时,首先读取当前 EBP 的值。假设这个值为
ebp_value
。 -
获取返回地址:在栈帧中,返回地址通常存储在相对于 EBP 的一个固定偏移量位置。在 32 位系统中,返回地址一般位于
ebp_value + 4
处。通过读取这个位置的值,可以得到当前函数的返回地址,这个返回地址指向调用当前函数的下一条指令的地址,从而知道是从哪里调用到当前函数的。 -
获取上一栈帧的 EBP:上一栈帧的 EBP 值通常存储在当前栈帧底部的位置(即当前 EBP 所指向的位置)。通过读取这个位置的值,可以得到上一栈帧的 EBP,这是回溯到上一个函数栈帧的关键。
-
重复上述步骤:不断重复获取上一栈帧的 EBP、返回地址的操作,就可以沿着栈向上回溯,逐步构建出函数调用链。每次获取新的 EBP 值后,就可以在新的栈帧中找到对应的返回地址和其他相关信息(如函数参数等)。