裸函数
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 }
未完,待续补充~~~~~~~~
以上是关于裸函数的主要内容,如果未能解决你的问题,请参考以下文章