首页 > 其他分享 >裸函数和调用约定

裸函数和调用约定

时间:2024-06-16 19:21:00浏览次数:11  
标签:调用 函数 int 约定 mov ebp dword ptr

一、裸函数

在正常的函数编译中,即使函数没有定义函数体的内容,编译器也依然会编译出部分汇编指令用来执行函数。

image-20240606124917091

但是如果定义一个裸函数

void _declspec(naked) test()

编译器将不会操作这个函数,不会给其生成汇编指令(但是会在主函数中生成call和jmp指令指向这个裸函数)

可以看到此处全为int 3(断点),编译器没有生成汇编指令,此时函数继续运行将会报错

如果要让这个裸函数正常运行,我们至少要给出一个retn指令

void _declspec(naked) test()
{
	_asm  //在函数中添加汇编指令的格式 _asm __asm均可
	{
		retn
	}
}

image-20240606130644678

几点注意

  • 函数的参数在函数调用之前就已经存在堆栈中了
  • 传参顺序调用约定决定
  • 函数的局部变量存在函数缓冲区
  • 函数返回值存在eax或内存
  • 必须平衡堆栈

二、函数的调用约定

1.常见的函数调用约定

调用约定 参数入栈顺序 平衡堆栈
__cdecl 从右至左 外平栈
__stdcall 从右至左 内平栈
__fastcall 前两个参数存ECX/EDX
剩下的:从右至左入栈
无需平衡堆栈/内平栈

2.__cdecl

c与c++默认使用__cdecl这种调用约定,使用堆栈传递参数,从最后一个参数开始入栈,平衡堆栈为在函数外平衡,即add ebp,X的形式。

例子:

int __cdecl test(int x,int y)
{
	return x+y;
}
int main(int argc, char* argv[])
{
	test(1,2);
	return 0;

image-20240606135043182

3.__stdcall

win32操作系统的API函数使用的是__stdcall这种调用约定,传参规则和__cdecl一样,但是此调用约定使用内平栈,即函数返回时使用ret x指令,表示修改eip的值为返回地址并且将esp加立即数。

例子:

int __stdcall Plus2(int x,int y){
	return x + y;
}

void main(int argc, char* argv[]){
	Plus2();
}

image-20240616182701503

image-20240616182743783

因为有两个参数,所以ret 8完成平栈操作

4.__fastcall

​ 使用此调用约定时,前两个参数直接使用寄存器传参,并且这两个参数不需要平衡堆栈,所以当参数少时,使用此约定速度快。

​ 优势:如果一个函数需要不停调用且参数数量少,那么使用__fastcall效率会高很多,多余2的参数依然会从后到前存入堆栈,并且也是使用内平栈。

例子1:

int __fastcall test(int x,int y)
{
	return x+y;
}
int main(int argc, char* argv[])
{
	test(1,2);
	return 0;
}

image-20240616184149240

image-20240616184440008

可以看到函数没有执行平衡堆栈操作,但需要注意的是:寄存器中的值在参与运算时会先存入缓冲区中,然后再进行运算

例子2:

int __fastcall test(int x,int y,int z,int a)
{
	return x+y+z+a;
}
int main(int argc, char* argv[])
{
	test(1,2,3,4);
	return 0;
}

image-20240616184722410

image-20240616184808141

依然是在call之前将参数存入寄存器,然后多余参数依然放入堆栈,最后使用内平栈(只需要平衡多余参数)

三、判断一个函数参数个数方法

1.错误方法

​ 如果函数使用内平栈,不能单纯根据ret后面的数判断。因为参数可能用push存入堆栈,还可能用mov存入寄存区。平衡堆栈只需要平衡内存中的那一部分,寄存器部分不需要平衡。

​ 也不能根据call指令前面的push和mov指令数量判断参数。可能push的参数不是相邻call指令需要用到的参数。

2.正确方法

​ 一般情况:把push入栈指令或mov指令,和add指令或ret指令结合起来看。

​ 稳妥办法:进入call指令中分析汇编

00401050   push        ebp			    
00401051   mov         ebp,esp				
00401053   sub         esp,48h				
00401056   push        ebx				
00401057   push        esi				
00401058   push        edi				
00401059   push        ecx				
0040105A   lea         edi,[ebp-48h]	
0040105D   mov         ecx,12h				
00401062   mov         eax,0CCCCCCCCh				
00401067   rep stos    dword ptr [edi]				
00401069   pop         ecx				
0040106A   mov         dword ptr [ebp-8],edx  //这里有一个,而且发现函数中没有给edx赋值的语句
0040106D   mov         dword ptr [ebp-4],ecx  //这里有一个,而且发现函数中没有给ecx赋值的语句
00401070   mov         eax,dword ptr [ebp-4]			
00401073   add         eax,dword ptr [ebp-8]			
00401076   add         eax,dword ptr [ebp+8]  //结合下面的ret指令判断
00401079   mov         [g_x (00427958)],eax	  //修改全局变量	
0040107E   pop         edi				
0040107F   pop         esi				
00401080   pop         ebx				
00401081   mov         esp,ebp				
00401083   pop         ebp				
00401084   ret         4			   //上面两个寄存器,再结合这里的4,可以基本确定函数有3个参数	

四、使用裸函数实现功能

int Plus(int x,int y,int z)	
{	
	int a = 2;
	int b = 3;
	int c = 4;
	return x+y+z+a+b+c;
}

int main(int argc, char* argv[]){
	Plus(5,6,7);   
    return 0;
}

裸函数定义:

int __declspec(naked) test(int x,int y,int z)
{
	__asm{
		push ebp
        
        //提升堆栈
        mov ebp,esp
        sub esp,0x40   
        
        //保留现场
        push ebx
        push esi
        push edi
        
        //填充缓冲区
        lea edi,dword ptr es:[ebp-0x40]
        mov ecx,0x10
        mov eax,0xcccccccc
        rep stosd    //rep stos dword ptr ds:[edi]
        
        //局部变量存入缓冲区
        mov dword ptr es:[ebp-0x4],0x2
        mov dword ptr es:[ebp-0x8],0x3
        mov dword ptr es:[ebp-0xC],0x4
        
        //实现函数功能
       	mov eax,dword ptr es:[ebp+0x8]
       	add eax,dword ptr es:[ebp+0xC]
       	add eax,dword ptr es:[ebp+0x10]
       	add eax,dword ptr es:[ebp-0x4]
       	add eax,dword ptr es:[ebp-0x8]
       	add eax,dword ptr es:[ebp-0xC]
       	
       	//恢复现场
       	pop edi
       	pop esi
       	pop ebx
       	
       	//降低堆栈
       	mov esp,ebp
       	pop ebp
       	
       	//函数返回
       	ret
	}
}
int main(int argc, char* argv[])
{
	test(5,6,7);
	return 0;
}

标签:调用,函数,int,约定,mov,ebp,dword,ptr
From: https://www.cnblogs.com/yee-l/p/18251118

相关文章

  • DDMP中的损失函数
    接着扩散模型简述训练扩散模型过程中用到的损失函数形式。完整的观察数据\(x\)的对数似然如下:\[\begin{aligned}\mathrm{log}\p(x)&\geq\mathbb{E}_{q_{\phi}(z_{1:T}|z_0)}\mathrm{log}\frac{p(z_T)\prod_{t=0}^{T-1}p_{\theta}(z_t|z_{t+1})}{\prod_{t=0}^{T-1}q_{\phi}......
  • 简述回调函数的意义和作用
    回调函数是一种在程序中广泛使用的机制,它的意义和作用主要包括以下几个方面:异步操作:在一些需要异步执行的任务中,如网络请求、文件读写等,回调函数可以在任务完成后被调用,以便进行后续的处理。这样可以避免阻塞程序的执行,提高程序的响应性和效率。事件处理:回调函数可以用于处理各......
  • 编写多个函数的ROP链
    我们已经学会了编写单个和两个简单函数的ROP链,在这里我们说一下,编写ROP链多个需要注意的问题之前我们在学习两个函数的ROP时,编写了这样的payload我们当时没有考虑,参数冲突和栈溢出大小,现在我们来说一说举个例子,如果我们上次学习的两个函数的ROP中没有gets函数,而是read函数我们......
  • pytorch动态量化函数
    PyTorch动态量化APIPyTorch提供了丰富的动态量化API,可以帮助开发者轻松地将模型转换为动态量化模型。主要API包括:torch.quantization.quantize_dynamic:将模型转换为动态量化模型。torch.quantization.QuantStub:观察模型层的输入和输出分布。torch.quantization.Observer......
  • Unity的生命周期函数
    在Unity中,各个生命周期函数是在特定的时机被调用的,它们的执行顺序如下:1.Awake:当脚本实例被加载时调用,用于初始化数据。如果物体上有多个脚本,它们的Awake方法会在Start方法之前执行。2.OnEnable:当对象变为活动状态(enabled)或脚本被启用时调用。如果在场景加载后对象已经......
  • 编写单个函数的ROP链
    什么是ROP链在我初识栈溢出那篇博客已经详细的讲了函数的调用过程(基于X86框架),不了解的可以看一下,没有这个理论基础,是学不好ROP的。现在我们说一下什么是ROP。ROP链就是通过返回地址的修改来完成的编程,调用特定的函数的一种编程模式。我们可以联想一下你做的最简单的栈溢出的题,返......
  • Android 使用绑定式调用service中的方法
    在Android中,Service有两种启动方式:startService()和bindService()。startService()启动Service时,Service会被创建并且调用onCreate()和onStartcommand()方法。Service会一直保持运行状态,直到调用stopService()或者stopSelf()方法。bindService()启动Service时,Service会被创建......
  • 要将URL参数转换为JSON对象,可以使用以下函数:
    要将URL参数转换为JSON对象,可以使用以下函数:javascriptfunctiongetQueryParams(url){//使用正则表达式提取URL参数constparamsString=url.split('?')[1];if(!paramsString){return{};}//将参数字符串分割成数组,并解析键值对constparams=......
  • 6、Oracle中的分组函数
    最近项目要用到Oracle,奈何之前没有使用过,所以在B站上面找了一个学习视频,用于记录学习过程以及自己的思考。视频链接:【尚硅谷】Oracle数据库全套教程,oracle从安装到实战应用如果有侵权,请联系删除,谢谢。学习目标:了解组函数。描述组函数的用途。使用GROUPBY子句对数据分......
  • 是否可以从一个static方法内部发出对非static方法的调用
    不可以直接从一个static方法内部发出对非static(即实例)方法的调用。static方法属于类本身,而非static方法则属于类的实例(对象)。由于static方法不依赖于类的任何特定实例,因此它不能直接访问非static方法或实例变量,因为这些方法和变量都需要类的实例来调用或访问。但是,有几种方法......