_declspec(naked) 使用

最近学习驱动开发,在写绕过inline hook的代码时,有个问题困扰了我一天,最后发现原来是在内嵌汇编时,没有使用_declspec(naked)导致的,看来是偶的基础知识掌握的不牢固啊(得补一下了,磨刀不误砍柴功),在此给记录一下,给自己一个警示。

对于jmp类型的hook, 如果自己的过程没有使用_declspec(naked),那么系统会自动给添加一些额外的代码,控制堆栈平衡,但是这些额外的代码会破坏被hook函数的堆栈。

对于call类型的hook,如果 使用_declspec(naked)修饰的话,要注意自己恢复堆栈平衡

下面是网上对_declspec(naked) 的介绍:
_declspec(naked)
就是告诉编译器,在编译的时候,不要优化代码,通俗的说就是
没代码,完全要自己写
比如
#define NAKED __declspec(naked)

void NAKED code(void)
{
__asm
{
ret
}
}
使用__declspec(naked)关键字定义函数:
1,使用 naked 关键字必须自己构建 EBP 指针 (如果用到了的话);
2,必须自己使用 RET 或 RET n 指令返回 (除非你不返回);

_delcspec(naked)用在驱动编写,C语言内嵌汇编完成一些特定功能。

__declspec(naked)是用来告诉编译器函数代码的汇编语言为自己的所写,不需要编译器添加任何汇编代码。详细的作用和意义,可查找相关资料:

这里主要讲一下可能会发生错误的一个地方。

void __declspec(naked) funname()

{

_asm

{

  ...

}

}

 

注意,这是编译器直接拿来用的汇编函数代码,所以一定要记得在开始的时候保存上下文标志位(压栈),在结束的时候要记得恢复上下文(出栈)。并且在结尾要加上ret命令。

 

对于一般的汇编内嵌代码,不必保存上下文了,保存也不会有事,但是不能再加ret命令,因为编译器也会为其加一个,ret命令不能同时执行两次。会导致越界错误




-------------------------------------------------------------------------------------------------------------------------------
对于用 __declspec(naked) 编写干净函数 
  1. VOID __declspec(naked) MyNakedFunction()
  2. {
  3. strcmp(...);
  4. // __cdecl 函数是调用者清除参数堆栈,对于非内联汇编调用这类函数,编译器将自动平衡堆栈,加入 ADD ESP, 8
  5. }
复制代码
  1. VOID __declspec(naked) MyNakedFunction()
  2. {
  3. //...
  4. __asm CALL strcmp;
  5. __asm ADD ESP, 8; // 内联汇编需要自己平衡堆栈
  6. }
复制代码

__declspec(naked) 是且仅是不产生 prolog 和 epilog 代码 {保存并恢复使用过的寄存器和分配局部变量、平衡堆栈、返回值}。

附上刚翻译的一些 MSDN 可能对 naked 函数有点用的资料(我英语很烂,希望没翻译错误)

Microsoft Specific 
All arguments are widened to 32 bits when they are passed. Return values are also widened to 32 bits and returned in the EAX register, except for 8-byte structures, which are returned in the EDX:EAX register pair. Larger structures are returned in the EAX register as pointers to hidden return structures. Parameters are pushed onto the stack from right to left. Structures that are not PODs will not be returned in registers.
The compiler generates prolog and epilog code to save and restore the ESI, EDI, EBX, and EBP registers, if they are used in the function.
Note  
When a struct, union, or class is returned from a function by value, all definitions of the type need to be the same, else the program may fail at runtime.

For information on how to define your own function prolog and epilog code, see Naked Function Calls.
The following calling conventions are supported by the Visual C/C++ compiler.
Keyword  Stack cleanup  Parameter passing  
__cdecl     Caller               Pushes parameters on the stack, in reverse order (right to left)
__clrcall    n/a                   Load parameters onto CLR expression stack in order (left to right).
__stdcall  Callee               Pushes parameters on the stack, in reverse order (right to left)
__fastcall Callee               Stored in registers, then pushed on stack
__thiscall Callee               Pushed on stack; this pointer stored in ECX

微软细节
所有参数在传递时都被扩展为 32 位。除了 8 字节结构体返回在 EDX:EAX 寄存器一对中,其它返回值同样被扩展为 32 位并返回在 EAX 寄存器中。大的结构体返回在 EAX 寄存器中的是指针来间接返回结构体。参数是自右向左压入堆栈。结构体是非 POD(Plain Old Data)类型将不会返回在寄存器中。
如果 ESI、EDI、EBX 和 EBP 寄存器在函数中使用,编译器将产生 prolog 和 epilog 代码保存并恢复它们。
注释
当一个函数用值返回结构体(struct)、联合体(union)或者类(class)时,所有类型的定义必须相同,否则程序可能在运行过程中失败。
关于如何定义你自身函数 prolog 和 epilog 代码的更多信息,请查阅 Naked Function Calls。
Visual C/C++ 编译器支持以下调用转换:
关键字   堆栈清除 参数传递
__cdecl  调用者  将参数倒序压入堆栈(自右向左)
__clrcall 不适用  将参数顺序加载到 CLR expression 堆栈中(自左向右)
__stdcall 被调用者 将参数倒序压入堆栈(自右向左)
__fastcall 被调用者 保存在寄存器然后压入堆栈
__thiscall 被调用者 压入堆栈,指针保存在 ECX 寄存器中

※ __fastcall:两个以内不大于 4 字节(DWORD)的参数将自左向右分别保存在 ECX、EDX 寄存器内,由于没有将参数压入堆栈,函数返回前无需将参数出栈(这就是它FASTCALL由来);其它参数仍按自右向左压入堆栈,函数返回时需要把这些参数出栈。


你可能感兴趣的:(HOOK技术,驱动开发学习,汇编编程学习)