裸函数

Posted reverse-xiaoyu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了裸函数相关的知识,希望对你有一定的参考价值。

概述

  _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:Projectxiaoyu1xiaoyu1.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 }

 

 
 未完,待续补充~~~~~~~~
 

以上是关于裸函数的主要内容,如果未能解决你的问题,请参考以下文章

VC/C++ 裸属性有啥作用?

VSCode自定义代码片段——声明函数

VSCode自定义代码片段8——声明函数

函数参数中裸星号的目的是啥? [复制]

Python函数定义中的裸斜杠? [复制]

在裸函数内部 - 如何进行简单的赋值