在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
}