裸函数 __declspec(naked)

蒋岳
2023-12-01

在C语言转化成汇编的过程中,编译器会自动处理堆栈,比如以下代码,即使add函数里面一条语句都没有,但是编译器在编译过程中 还是生成了很多指令(00F11380 - 00F1139C) 用于处理堆栈

void add()
{
	
}
int main()
{
	add();
	return 0;
}

转化成汇编

int main()
{
00F113B0  push        ebp  
00F113B1  mov         ebp,esp  
00F113B3  sub         esp,0C0h  
00F113B9  push        ebx  
00F113BA  push        esi  
00F113BB  push        edi  
00F113BC  lea         edi,[ebp-0C0h]  
00F113C2  mov         ecx,30h  
00F113C7  mov         eax,0CCCCCCCCh  
00F113CC  rep stos    dword ptr es:[edi]  
	add();
00F113CE  call        add (0F110F0h)  
add:
00F110F0  jmp         add (0F11380h)  
void add()                        //{}里面的全是编译器自动生成的
{
00F11380  push        ebp  
00F11381  mov         ebp,esp  
00F11383  sub         esp,0C0h  
00F11389  push        ebx  
00F1138A  push        esi  
00F1138B  push        edi  
00F1138C  lea         edi,[ebp-0C0h]  
00F11392  mov         ecx,30h  
00F11397  mov         eax,0CCCCCCCCh  
00F1139C  rep stos    dword ptr es:[edi]  
	
}
	return 0;
00F113D3  xor         eax,eax  
}

裸函数就是在C语言转化成汇编的过程中,不用编译器帮我们处理堆栈,比如以下这个add就是裸函数  用 __declspec(naked) 声明

void __declspec(naked) add()
{
	
}
int main()
{
	add();
	return 0;
}

转化成汇编

int main()
{
00671390  push        ebp  
00671391  mov         ebp,esp  
00671393  sub         esp,0C0h  
00671399  push        ebx  
0067139A  push        esi  
0067139B  push        edi  
0067139C  lea         edi,[ebp-0C0h]  
006713A2  mov         ecx,30h  
006713A7  mov         eax,0CCCCCCCCh  
006713AC  rep stos    dword ptr es:[edi]  
	add();
006713AE  call        add (6710F0h)  
add:
006710F0  jmp         add (671380h)      
add:
00671380  int         3              //跟上面的函数相比 这里编译器没有生成任何处理堆栈的代码
00671381  int         3  
	return 0;
006713B3  xor         eax,eax  
}

在调用add这个函数时,因为编译器没有处理堆栈  并且也没有返回地址 所以程序运行到00671380再继续往下运行时就会int 3中断停止,这就是没有编译器帮助我们处理的结果,但是,既然是裸函数 ,编译器不帮我们处理堆栈和返回地址,那就需要我们自己写汇编语言  手工处理栈顶和栈底 让程序可以正常运行。

比如如下代码,将汇编语言嵌入到C语言当中,手工处理堆栈,即使是裸函数,程序也可以正常运行。

void __declspec(naked) add()
{
	__asm
	{
		ret        //添加这一段指令 使得汇编程序有了返回地址  可以继续运行
	}
}
int main()
{
	add();
	return 0;
}

转成汇编

int main()
{
00251390  push        ebp  
00251391  mov         ebp,esp  
00251393  sub         esp,0C0h  
00251399  push        ebx  
0025139A  push        esi  
0025139B  push        edi  
0025139C  lea         edi,[ebp-0C0h]  
002513A2  mov         ecx,30h  
002513A7  mov         eax,0CCCCCCCCh  
002513AC  rep stos    dword ptr es:[edi]  
	add();
002513AE  call        add (2510F0h)  
002510F0  jmp         add (251380h)  
void __declspec(naked) add()
{
	__asm
	{
		ret
00251380  ret                  //可以看到 这里有了返回地址 程序可以返回继续运行 而不是向下执行 int 3
00251381  int         3  
00251382  int         3  
00251383  int         3  
	}
}

	return 0;
002513B3  xor         eax,eax  
}

上面的add是个空函数,下面是一个小练习,在裸函数里面手工处理堆栈,使得程序可以正常运行

int __declspec(naked) add(int x,int y)
{
	__asm
	{
		push ebp					//保存栈底
		mov ebp,esp					//提升栈底
		sub esp,0x40				//提升栈顶
		push ebx					//保护现场
		push esi					//保护现场
		push edi					//保护现场
		lea edi,[esp]				//设置填充起始地址
		mov eax,0xCCCCCCCC			//设置填充内容
		mov ecx,0x3					//设置填充次数
		rep stos dword ptr [edi]	//重复填充
		mov eax,dword ptr [ebp+0x8]	 //因为在函数被调用的时候 参数已经压栈 所以这里可以从栈中直接取出参数值				
		add eax,dword ptr [ebp+0xC]  //因为在函数被调用的时候 参数已经压栈 所以这里可以从栈中直接取出参数值				
		pop edi						//恢复现场
		pop esi						//恢复现场
		pop ebx						//恢复现场
		mov esp,ebp
		pop ebp
		ret
	}
}
int main()
{
	int a = add(2,3);
	return 0;
}

汇编代码如下

int main()
{
00C913B0  push        ebp  
00C913B1  mov         ebp,esp  
00C913B3  sub         esp,0CCh  
00C913B9  push        ebx  
00C913BA  push        esi  
00C913BB  push        edi  
00C913BC  lea         edi,[ebp-0CCh]  
00C913C2  mov         ecx,33h  
00C913C7  mov         eax,0CCCCCCCCh  
00C913CC  rep stos    dword ptr es:[edi]  
	int a = add(2,3);
00C913CE  push        3  
00C913D0  push        2  
00C913D2  call        add (0C9108Ch) 
add:
00C9108C  jmp         add (0C91380h)  
int __declspec(naked) add(int x,int y)            //可以看到 这里即使编译器没有帮我们处理堆栈 我们手工处理堆栈 效果是一样的
{
	__asm
	{
		push ebp					//保存栈底
00C91380  push        ebp  
		mov ebp,esp					//提升栈底
00C91381  mov         ebp,esp  
		sub esp,0x40				//提升栈顶
00C91383  sub         esp,40h  
		push ebx					//保护现场
00C91386  push        ebx  
		push esi					//保护现场
00C91387  push        esi  
		push edi					//保护现场
00C91388  push        edi  
		lea edi,[esp]				//设置填充起始地址
00C91389  lea         edi,[esp]  
		mov eax,0xCCCCCCCC			//设置填充内容
00C9138C  mov         eax,0CCCCCCCCh  
		mov ecx,0x3					//设置填充次数
00C91391  mov         ecx,3  
		rep stos dword ptr [edi]	//重复填充
00C91396  rep stos    dword ptr es:[edi]  
		mov eax,dword ptr [ebp+0x8]	 //因为在函数被调用的时候 参数已经压栈 所以这里可以从栈中直接取出参数值				
00C91398  mov         eax,dword ptr [x]  
		add eax,dword ptr [ebp+0xC]  //因为在函数被调用的时候 参数已经压栈 所以这里可以从栈中直接取出参数值				
00C9139B  add         eax,dword ptr [y]  
		pop edi						//恢复现场
00C9139E  pop         edi  
		pop esi						//恢复现场
00C9139F  pop         esi  
		pop ebx						//恢复现场
00C913A0  pop         ebx  
		mov esp,ebp
00C913A1  mov         esp,ebp  
		pop ebp
00C913A3  pop         ebp  
		ret
00C913A4  ret   
00C913D7  add         esp,8  
00C913DA  mov         dword ptr [a],eax  
	return 0;
00C913DD  xor         eax,eax  
}

 类似资料: