概述
_declspec(naked)修饰可以生成一个“裸”函数, 使用后C编译器将生成不含函数框架的纯汇编代码,裸函数中什么都没有,所以也不能使用局部变量,只能全部用内嵌汇编实现。
裸函数的定义
1 void __declspec(naked) Function() 2 3 { 4 ... 5 }
_declspec(naked) 的介绍:
_declspec(naked),就是告诉编译器,在编译的时候,不要优化代码,通俗的说就是,没代码,完全要自己写
比如:
1 #define NAKED __declspec(naked) 2 3 void NAKED code(void) 4 { 5 __asm 6 { 7 ret 8 } 9 }
使用__declspec(naked)关键字定义函数:
1,使用 naked 关键字必须自己构建 EBP 指针 (如果用到了的话);
2,必须自己使用 RET 或 RET n 指令返回 (除非你不返回);
_delcspec(naked)用在驱动编写,C语言内嵌汇编完成一些特定功能。
实例
我们先通过一个C语言中最简单函数,然后观察反汇编代码,看看编译器为我们做了些什么
编译环境:VmWare Workstation 15 Pro、windows 7、VC++ 6.0 英文版
1 // xiaoyu1.cpp : Defines the entry point for the console application. 2 // 3 4 #include "stdafx.h" 5 6 void Plus1() 7 { 8 9 } 10 11 int main(int argc, char* argv[]) 12 { 13 Plus1(); 14 return 0; 15 }
Plus1()很显然是我们C语言中最简单的一个函数,但是它真的有这么简单吗?我们来看看它的反汇编代码,看看编译器都为这个函数做了些什么。我们在第13行代码处按下F9下一个断点。
按下F7,F5,进入如下窗口:
在这个窗口处,右键选择Go To Disassembly,进入我们的反汇编窗口
点击之后进入如下界面
看到最左边那一列有个向右的小黄色箭头了吗?这标志我们的程序停在这,黄色箭头处的代码:
1 00401068 call @ILT+0(Plus1) (00401005)
还记得我在前面博文中讲过call吗?https://www.cnblogs.com/Reverse-xiaoyu/p/11470633.html(JMP、CALL、RET指令),call做了两件事情,将下一行地址压栈,并修改EIP的值,我们按下F11,跟进去。
第一次按下F11我们会看到这行代码
1 00401005 jmp Plus1 (00401020)
再按下F11,如下所示
1 00401020 push ebp 2 00401021 mov ebp,esp 3 00401023 sub esp,40h 4 00401026 push ebx 5 00401027 push esi 6 00401028 push edi 7 00401029 lea edi,[ebp-40h] 8 0040102C mov ecx,10h 9 00401031 mov eax,0CCCCCCCCh 10 00401036 rep stos dword ptr [edi] 11 00401038 pop edi 12 00401039 pop esi 13 0040103A pop ebx 14 0040103B mov esp,ebp 15 0040103D pop ebp 16 0040103E ret
在最底层,发现事情都不是明面上看到的那么简单,C语言中一个什么都不写的最简单的一个函数,编译器居然为我们生成了这么多的汇编代码,如果你不懂这段,可以移步我的这一篇博文https://www.cnblogs.com/Reverse-xiaoyu/p/11489082.html
看完了正常的函数之后,我们再去看看裸函数,看看最底层,编译器为它做了些什么,在概要里也说过,编译器什么都不会为它做,自己自生自灭去。
可能你在反汇编窗口不知道如何退回到编辑界面,按下shift + F5就退回去了
1 // xiaoyu1.cpp : Defines the entry point for the console application. 2 // 3 4 #include "stdafx.h" 5 6 void __declspec(naked) plus() 7 { 8 9 } 10 11 void Plus1() 12 { 13 14 } 15 16 int main(int argc, char* argv[]) 17 { 18 plus(); 19 return 0; 20 }
我们在程序调用处,第18行按下F9下一个断点,然后进入反汇编窗口。
1 --- D:\Project\xiaoyu1\xiaoyu1.cpp --------------------------------------------------------------------------------------------------------------------------- 2 15: 3 16: int main(int argc, char* argv[]) 4 17: { 5 00401020 push ebp 6 00401021 mov ebp,esp 7 00401023 sub esp,40h 8 00401026 push ebx 9 00401027 push esi 10 00401028 push edi 11 00401029 lea edi,[ebp-40h] 12 0040102C mov ecx,10h 13 00401031 mov eax,0CCCCCCCCh 14 00401036 rep stos dword ptr [edi] 15 18: plus(); 16 00401038 call @ILT+10(plus) (0040100f) 17 19: return 0; 18 0040103D xor eax,eax 19 20: } 20 0040103F pop edi 21 00401040 pop esi 22 00401041 pop ebx 23 00401042 add esp,40h 24 00401045 cmp ebp,esp 25 00401047 call __chkesp (0040d430) 26 0040104C mov esp,ebp 27 0040104E pop ebp 28 0040104F ret
黄色的小箭头,此时指向第16行 00401038 call @ILT+10(plus) (0040100f),两步F11进去,程序会直接跑没了,跳到一堆int 3的地方去,因此概述中的2,必须自己使用 RET 或 RET n 指令返回 (除非你不返回);这句话就告诉我们该怎么做了
我们可以自己写一个内敛汇编,加一个ret进去
1 // xiaoyu1.cpp : Defines the entry point for the console application. 2 // 3 4 #include "stdafx.h" 5 6 void __declspec(naked) plus() 7 { 8 __asm 9 { 10 ret 11 } 12 } 13 14 void Plus1() 15 { 16 17 } 18 19 int main(int argc, char* argv[]) 20 { 21 plus(); 22 return 0; 23 }
在C程序中,一个程序有call就必然有ret,否则它就乱了,找不到它回家的路了,加上这一步之后,调用裸函数的时候它就会正常返回了。
无参数无返回值的函数框架
1 void __declspec(naked) plus() 2 { 3 __asm 4 { 5 //提升堆栈 6 push ebp 7 mov ebp,esp 8 sub ebp,0x40 9 //保护现场 10 push ebx 11 push esi 12 push edi 13 //向缓冲区填充数据 14 lea edi,dword ptr ds:[ebp-0x40] 15 mov eax,0xCCCCCCCC 16 mov ecx,0x10 17 rep stosd ;rep stos dword ptr es:[edi] 18 //恢复现场 19 pop edi 20 pop esi 21 pop ebx 22 //降低堆栈 23 mov esp,ebp 24 pop ebp 25 //返回函数调用前的下一行地址 26 ret 27 } 28 }
有参数有返回值的函数框架
1 int __declspec(naked) plus(int x, int y) 2 { 3 __asm 4 { 5 //提升堆栈 6 push ebp 7 mov ebp,esp 8 sub esp,0x40 9 //保护现场 10 push ebx 11 push esi 12 push edi 13 //向缓冲区填充数据 14 lea edi,dword ptr ds:[ebp-0x40] 15 mov eax,0xCCCCCCCC 16 mov ecx,0x10 17 rep stos dword ptr es:[edi] 18 19 //函数核心功能块 20 mov eax,dword ptr ds:[ebp+0x8] 21 add eax,dword ptr ds:[ebp+0xC] 22 23 //恢复现场 24 pop edi 25 pop esi 26 pop ebx 27 28 //降低堆栈 29 mov esp,ebp 30 pop ebp 31 //返回函数调用前的下一行地址 32 ret 33 } 34 }
带局部变量的函数框架
1 int __declspec(naked) plus(int x, int y) 2 { 3 __asm 4 { 5 //提升堆栈 6 push ebp 7 mov ebp,esp 8 sub esp,0x40 9 //保护现场 10 push ebx 11 push esi 12 push edi 13 //向缓冲区填充数据 14 lea edi,dword ptr ds:[ebp-0x40] 15 mov eax,0xCCCCCCCC 16 mov ecx,0x10 17 rep stos dword ptr es:[edi] 18 19 //局部变量入栈 20 mov dword ptr ds:[ebp-0x4] 21 mov dword ptr ds:[ebp-0x8] 22 23 //函数核心功能块 24 mov eax,dword ptr ds:[ebp+0x8] 25 add eax,dword ptr ds:[ebp+0xC] 26 27 //恢复现场 28 pop edi 29 pop esi 30 pop ebx 31 //降低堆栈 32 mov esp,ebp 33 pop ebp 34 //返回函数调用前的下一行地址 35 ret 36 } 37 }
未完,待续补充~~~~~~~~