网络软件 系统工具 应用软件 图形图像 多媒体类 免费游戏 安全相关 免费音乐 网页素材 电子书籍 考试考题 建站源码
教育教学 多媒体类 编程开发 操作系统 游戏天地 娱乐天地 简历求职 站长专区 网页设计 安全技术 图形图像 文学驿站
业界资讯 | 图形图像 | 操作系统 | 网络冲浪 | 工具软件 | 办公软件 | 媒体动画 | 精文荟萃 | 认证考试 | 网页设计 | 技术开发 | 专栏
当前位置:热点网络学院编程开发其他开发语言《Undocumented Windows 2000 Secrets》翻译 --- 第五章(2)
精品推荐
热点TOP10
·用PHP判断oicq是否在线的小程序
·VB程序员眼中的C# 4
·Python编程入门(5)
·WIN32汇编: 6.处理键盘消息
·Effective C++ 2e Item3
·Effective C++ 2e Item6
·实例讲解 C 语言的分支结构
·Win32ASM经验点滴
·C语言程序书写规范
·Guru of the Week 条款15:类之间的关系(下篇)
·PHP4用户手册:流程控制-while
·深度探索C++对象模型第五章构造、解构、拷贝语义学
·程序员考试补课笔记-第三天
·CppUnit Cookbook中文版
·用汇编访问COM对象
·C++ Gotchas 条款62:替换Global New和Global Delete
·C++ Builder几个应用技巧
·闲谈BCB(二)
·异常处理(二)
·洗牌的一个C++类!
《Undocumented Windows 2000 Secrets》翻译 --- 第五章(2)
日期:2005年11月13日 作者: 查看:[大字体 中字体 小字体]

第五章  监控Native API调用

翻译:Kendiv( fcczj@263.net )

更新:Thursday, February 24, 2005

 

声明:转载请注明出处,并保证文章的完整性,本人保留译文的所有权利。

 

汇编语言的救援行动

通用解决方案的主要障碍是C语言的典型参数传递机制。就像你知道的,C通常在调用函数的入口点之前会将函数参数传递到CPU堆栈中。根据函数需要的参数数量,参数堆栈的大小将有很大的差别。Windows 2000248Native API函数需要的参数堆栈的大小位于068字节。这使得编写一个唯一的hook函数变得非常困难。微软的Visual C/C++提供了一个完整的汇编(ASM)编译器,该编译器可处理复杂度适中的代码。具有讽刺意味的是,在我的解决方案中所使用的汇编语言的优点正是通常被认为是其最大缺点的特性:汇编语言不提供严格的类型检查机制。只要字节数正确就一切OK了,你可以在任何寄存器中存储几乎所有的东西,而且你可以调用任何地址,而不需要关心当前堆栈的内容是什么。尽管这在应用程序开发中是一种很危险的特性,但这确实最容易获取的:在汇编语言中,很容易以不同的参数堆栈调用同一个普通的入口点,稍后将介绍的API hook Dispatcher将采用这一特性。

 

通过将汇编代码放入以关键字__asm标记的分隔块中就可调用Microsoft Visual C/C++嵌入式汇编程序。嵌入式汇编缺少宏定义以及Microsoft’s big Macro AssemblerMASM)的评估能力,但这些并没有严重的限制它的可用性。嵌入式汇编的最佳特性是:它可以访问所有的C变量和类型定义,因此很容易混合CASM代码。不过,当在C函数中包含有ASM代码时,就必须遵守C编译器的某些重要的基本约定,以避免和C代码的冲突:

l         C函数调用者假定CPU寄存器EBPEBXESIEDI已经被保存了。

 

l         如果在单一函数中,将ASM代码和C代码混合在一起,则需要小心的保存C代码可能保存在寄存器中的中间值。总是保存和恢复在__asm语句中使用的所有寄存器。

 

l         8位的函数结果(CHARBYTE等)由寄存器AL返回。

 

l         16位的函数结果(SHORTWORD等)由寄存器AX返回。

 

l         32位的函数结果(INTLONGDWORD等)由寄存器EAX返回。

 

l         64位的函数结果(__int64LONGLONGDWORDLONG等)由寄存器对EDXEAX返回。寄存器EAX包含031位,EDX保存3263位。

 

l         有确定参数的函数通常按照__stdcall约定进行参数的传递。从调用者的角度来看,这意味着在函数调用之前参数必须以相反的顺序压入堆栈中,被调用的函数负责在返回前从堆栈中移除它们。从被调用的函数的角度来看,这意味着堆栈指针ESP指向调用者的返回地址,该地址紧随最后一个参数(按照原始顺序)。(译注:这意味着,最先被压入堆栈的是函数的返回地址)参数的原始顺序被保留下来,因为堆栈是向下增长的,从高位线性地址到低位线性地址。因此,调用者压入堆栈的最后一个参数(即,参数#1)将是由ESP指向的数组中的第一个参数。

 

l         某些有确定参数的API函数,如著名的C运行时库函数(由ntdll.dllntoskrnl.exe导出),通常使用__cdecl调用约定,该约定采用与__stdcall相同的参数顺序,但强制调用者清理参数堆栈。

 

l         __fastcall修饰的函数声明,则希望前两个参数位于CPU寄存器ECXEDX中。如果还需要更多的参数,它们将按照相反的顺序传入堆栈,最后由被调用者清理堆栈,这和__stdcall相同。

 

; this is the function's prologue

push   ebp                    ; save current value ebp

mov   ebp, esp                ; set stack frame base address

sub    esp, SizeOfLocalStorage   ; create local storage area

 

; this is the function's epilogue

mov   esp, ebp                ; destroy local storage area

pop    ebp                    ; restore value of ebp

ret

列表5-2.  堆栈帧,序言和尾声

 

l         很多C编译器在进入函数后,会立即针对函数参数构建一个堆栈帧,这需要使用CPU的基地址指针寄存器EBP列表5-2给出了此代码,这通常被称为函数的“序言”和“尾声”。有些编译器采用更简洁的i386ENTERLEAVE操作符,在“序言被执行后,堆栈将如5-3所示。EBP寄存器作为一分割点将函数的参数堆栈划分为两部分:(1)局部存储区域,该区域中包含所有定义于函数范围内的局部变量(2)调用者堆栈,其中保存有EBP的备份和返回地址。注意,微软的Visual C/C++的最新版中默认不使用堆栈帧。替代的是,代码通过ESP寄存器访问堆栈中的值,不过这需要指定变量相对于当前栈顶的偏移量。这种类型的代码非常难以阅读,因为每个PUSHPOP指令都会影响ESP的值和所有参数的偏移量。在此种情况下不再需要EBP,它将作为一个附加的通用寄存器。

 

l         在访问C变量时必须非常小心。经常出现在嵌入式ASM中的bug是:你将一个变量的地址而不是它的值加载到了寄存器中。使用ptroffset地址操作符存在潜在的二义性。例如,指令:mov eaxdword ptr SomeVariable将加载DWORD类型的SomeVariable变量的值到EAX寄存器,但是,mov eaxoffset SomeVariable将加载它的线性地址到EAX中。

 

5-3.  堆栈帧的典型布局

 

 

Hook分派程序(Hook Dispatcher)

这部分的代码将较难理解。编写它们花费了我很多时间,而且在这一过程中我还欣赏了无数的蓝屏。我最初的方法是提供一个完全用汇编语言编写的模块。不过,这个方法在链接阶时带来了很大的麻烦,因此,我改为在C模块中使用嵌入式汇编。为了避免创建另一个内核模式的驱动程序,我决定将hook代码整合到Spy设备驱动程序中。还记得在4-2底部列出的形如SPY_IO_HOOK_*IOCTL函数吗?现在我们将和它们来一次亲密接触。后面的示列代码来自w2k_spy.cw2k_spy.h,可以在随书CD\src\w2k_spy中找到它们。

 

列表5-3的核心部分是Native API Hook机制的实现代码。该列表开始处是一对常量和结构体定义,后面的aSpyHooks[]需要它们。紧随这个数组的是一个宏,该宏实际上是三行嵌入式汇编语句,这三行汇编语句非常重要,稍后我将介绍它们。列表5-3的最后一部分用来建立SpyHookInitializeEx()函数。猛地一看,这个函数的功能似乎很难理解。该函数组合了一下两个功能:

1.         SpyHookInitializeEx()的表面部分包括一段用来设置aSpyHooks[]数组的C代码,这部分代码用Spy设备的Hook函数指针以及与之相关联的字符串格式协议来初始化aSpyHooks[]数组。SpyHookInitializeEx()函数可被分割为两部分:第一部分到第一个__asm语句后的jmp SpyHook9指令。第二部分显然是从ASM标签----SpyHook9开始,该部分位于第二个__asm语句块的最后。

 

2.         SpyHookInitializeEx()的内部部分包括位于两块C代码段之间的所有代码。这部分在一开始大量使用了SpyHook宏,紧随其后的是一大块复杂的汇编代码。可能你已经猜到了,这些汇编代码就是前面提到的通用Hook例程。

 

#define SPY_CALLS             0x00000100 // max api call nesting level

#define SDT_SYMBOLS_NT4     0xD3

#define SDT_SYMBOLS_NT5     0xF8

#define SDT_SYMBOLS_MAX    SDT_SYMBOLS_NT5

 

// -----------------------------------------------------------------

 

typedef struct _SPY_HOOK_ENTRY

    {

    NTPROC Handler;

    PBYTE  pbFormat;

    }

    SPY_HOOK_ENTRY, *PSPY_HOOK_ENTRY, **PPSPY_HOOK_ENTRY;

 

#define SPY_HOOK_ENTRY_ sizeof (SPY_HOOK_ENTRY)

 

// -----------------------------------------------------------------

 

typedef struct _SPY_CALL

    {

    BOOL            fInUse;               // set if used entry

    HANDLE          hThread;              // id of calling thread

    PSPY_HOOK_ENTRY pshe;                 // associated hook entry

    PVOID           pCaller;              // caller's return address

    DWORD           dParameters;          // number of parameters

    DWORD           adParameters [1+256]; // result and parameters

    }

    SPY_CALL, *PSPY_CALL, **PPSPY_CALL;

 

#define SPY_CALL_ sizeof (SPY_CALL)

 

// -----------------------------------------------------------------

 

SPY_HOOK_ENTRY aSpyHooks [SDT_SYMBOLS_MAX];

 

// -----------------------------------------------------------------

// The SpyHook macro defines a hook entry point in inline assembly

// language. The common entry point SpyHook2 is entered by a call

// instruction, allowing the hook to be identified by its return

// address on the stack. The call is executed through a register to

// remove any degrees of freedom from the encoding of the call.

 

#define SpyHook                              \

        __asm   push    eax                  \

        __asm   mov     eax, offset SpyHook2 \

        __asm   call    eax

 

// -----------------------------------------------------------------

// The SpyHookInitializeEx() function initializes the aSpyHooks[]

// array with the hook entry points and format strings. It also

// hosts the hook entry points and the hook dispatcher.

 

 

 

// -----------------------------------------------------------------

// The SpyHookInitializeEx() function initializes the aSpyHooks[]

// array with the hook entry points and format strings. It also

// hosts the hook entry points and the hook dispatcher.

 

void SpyHookInitializeEx (PPBYTE ppbSymbols,

                          PPBYTE ppbFormats)

    {

    DWORD dHooks1, dHooks2, i, j, n;

 

    __asm

        {

        jmp     SpyHook9

        ALIGN   8

SpyHook1:       ; start of hook entry point section

        }

 

// the number of entry points defined in this section

// must be equal to SDT_SYMBOLS_MAX (i.e. 0xF8)

 

SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook //08

SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook //10

SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook //18

SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook //20

SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook //28

SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook //30

SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook //38

SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook //40

SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook //48

SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook //50

SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook //58

SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook //60

SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook //68

SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook //70

SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook //78

SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook //80

SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook //88

SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook //90

SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook //98

SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook //A0

SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook //A8

SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook //B0

SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook //B8

SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook //C0

SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook //C8

SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook //D0

SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook //D8

SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook //E0

SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook //E8

SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook //F0

SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook //F8

 

    __asm

        {

SpyHook2:       ; end of hook entry point section

        pop     eax                     ; get stub return address

        pushfd

        push    ebx

        push    ecx

        push    edx

        push    ebp

        push    esi

        push    edi

        sub     eax, offset SpyHook1    ; compute entry point index

        mov     ecx, SDT_SYMBOLS_MAX

        mul     ecx

        mov     ecx, offset SpyHook2

        sub     ecx, offset SpyHook1

        div     ecx

        dec     eax

        mov     ecx, gfSpyHookPause     ; test pause flag

        add     ecx, -1

        sbb     ecx, ecx

        not     ecx

        lea     edx, [aSpyHooks + eax * SIZE SPY_HOOK_ENTRY]

        test    ecx, [edx.pbFormat]     ; format string == NULL?

        jz      SpyHook5

        push    eax

        push    edx

        call    PsGetCurrentThreadId    ; get thread id

        mov     ebx, eax

        pop     edx

        pop     eax

        cmp     ebx, ghSpyHookThread    ; ignore hook installer

        jz      SpyHook5

        mov     edi, gpDeviceContext

        lea     edi, [edi.SpyCalls]     ; get call context array

        mov     esi, SPY_CALLS          ; get number of entries

SpyHook3:

        mov     ecx, 1                  ; set in-use flag

        xchg    ecx, [edi.fInUse]

        jecxz   SpyHook4                ; unused entry found

        add     edi, SIZE SPY_CALL      ; try next entry

        dec     esi

        jnz     SpyHook3

        mov     edi, gpDeviceContext

        inc     [edi.dMisses]           ; count misses

        jmp     SpyHook5                ; array overflow

SpyHook4:

        mov     esi, gpDeviceContext

        inc     [esi.dLevel]            ; set nesting level

        mov     [edi.hThread], ebx      ; save thread id

        mov     [edi.pshe], ed