国产成人精品久久免费动漫-国产成人精品天堂-国产成人精品区在线观看-国产成人精品日本-a级毛片无码免费真人-a级毛片毛片免费观看久潮喷

您的位置:首頁技術(shù)文章
文章詳情頁

《Undocumented Windows 2000 Secrets》翻譯 --- 第五章(2)

瀏覽:104日期:2023-08-27 18:46:02

第五章 監(jiān)控 Native API 調(diào)用

翻譯: Kendiv( fcczj@263.net )

更新: Thursday, February 24, 2005

聲明:轉(zhuǎn)載請注明出處,并保證文章的完整性,本人保留譯文的所有權(quán)利。

匯編語言的救援行動

通用解決方案的主要障礙是 C 語言的典型參數(shù)傳遞機制。就像你知道的, C 通常在調(diào)用函數(shù)的入口點之前會將函數(shù)參數(shù)傳遞到 CPU 堆棧中。根據(jù)函數(shù)需要的參數(shù)數(shù)量,參數(shù)堆棧的大小將有很大的差別。 Windows 2000 的 248 個 Native API 函數(shù)需要的參數(shù)堆棧的大小位于 0 到 68 字節(jié)。這使得編寫一個唯一的 hook 函數(shù)變得非常困難。微軟的 Visual C/C++ 提供了一個完整的匯編( ASM )編譯器,該編譯器可處理復(fù)雜度適中的代碼。具有諷刺意味的是,在我的解決方案中所使用的匯編語言的優(yōu)點正是通常被認(rèn)為是其最大缺點的特性:匯編語言不提供嚴(yán)格的類型檢查機制。只要字節(jié)數(shù)正確就一切 OK 了,你可以在任何寄存器中存儲幾乎所有的東西,而且你可以調(diào)用任何地址,而不需要關(guān)心當(dāng)前堆棧的內(nèi)容是什么。盡管這在應(yīng)用程序開發(fā)中是一種很危險的特性,但這確實最容易獲取的:在匯編語言中,很容易以不同的參數(shù)堆棧調(diào)用同一個普通的入口點,稍后將介紹的 API hook Dispatcher 將采用這一特性。

通過將匯編代碼放入以關(guān)鍵字 __asm 標(biāo)記的分隔塊中就可調(diào)用 Microsoft Visual C/C++ 嵌入式匯編程序。嵌入式匯編缺少宏定義以及 Microsoft's big Macro Assembler ( MASM )的評估能力,但這些并沒有嚴(yán)重的限制它的可用性。嵌入式匯編的最佳特性是:它可以訪問所有的 C 變量和類型定義,因此很容易混合 C 和 ASM 代碼。不過,當(dāng)在 C 函數(shù)中包含有 ASM 代碼時,就必須遵守 C 編譯器的某些重要的基本約定,以避免和 C 代碼的沖突:

l C 函數(shù)調(diào)用者假定 CPU 寄存器 EBP 、 EBX 、 ESI 和 EDI 已經(jīng)被保存了。

l 如果在單一函數(shù)中,將 ASM 代碼和 C 代碼混合在一起,則需要小心的保存 C 代碼可能保存在寄存器中的中間值??偸潜4婧突謴?fù)在 __asm 語句中使用的所有寄存器。

l 8 位的函數(shù)結(jié)果( CHAR , BYTE 等)由寄存器 AL 返回。

l 16 位的函數(shù)結(jié)果( SHORT , Word 等)由寄存器 AX 返回。

l 32 位的函數(shù)結(jié)果( INT , LONG , DWORD 等)由寄存器 EAX 返回。

l 64 位的函數(shù)結(jié)果( __int64 , LONGLONG , DWORDLONG 等)由寄存器對 EDX : EAX 返回。寄存器 EAX 包含 0 到 31 位, EDX 保存 32 到 63 位。

l 有確定參數(shù)的函數(shù)通常按照 __stdcall 約定進(jìn)行參數(shù)的傳遞。從調(diào)用者的角度來看,這意味著在函數(shù)調(diào)用之前參數(shù)必須以相反的順序壓入堆棧中,被調(diào)用的函數(shù)負(fù)責(zé)在返回前從堆棧中移除它們。從被調(diào)用的函數(shù)的角度來看,這意味著堆棧指針 ESP 指向調(diào)用者的返回地址,該地址緊隨最后一個參數(shù)(按照原始順序)。( 譯注 :這意味著,最先被壓入堆棧的是函數(shù)的返回地址 )參數(shù)的原始順序被保留下來,因為堆棧是向下增長的,從高位線性地址到低位線性地址。因此,調(diào)用者壓入堆棧的最后一個參數(shù)(即,參數(shù) #1 )將是由 ESP 指向的數(shù)組中的第一個參數(shù)。

l 某些有確定參數(shù)的 API 函數(shù),如著名的 C 運行時庫函數(shù)(由 ntdll.dll 和 ntoskrnl.exe 導(dǎo)出),通常使用 __cdecl 調(diào)用約定,該約定采用與 __stdcall 相同的參數(shù)順序,但強制調(diào)用者清理參數(shù)堆棧。

l 由 __fastcall 修飾的函數(shù)聲明,則希望前兩個參數(shù)位于 CPU 寄存器 ECX 和 EDX 中。如果還需要更多的參數(shù),它們將按照相反的順序傳入堆棧,最后由被調(diào)用者清理堆棧,這和 __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 編譯器在進(jìn)入函數(shù)后,會立即針對函數(shù)參數(shù)構(gòu)建一個堆棧幀,這需要使用 CPU 的基地址指針寄存器 EBP 。 列表 5-2 給出了此代碼,這通常被稱為函數(shù)的“序言”和“尾聲”。有些編譯器采用更簡潔的 i386 的 ENTER 和 LEAVE 操作符,在“序言被執(zhí)行后,堆棧將如 5-3 所示。 EBP 寄存器作為一分割點將函數(shù)的參數(shù)堆棧劃分為兩部分:( 1 )局部存儲區(qū)域,該區(qū)域中包含所有定義于函數(shù)范圍內(nèi)的局部變量( 2 )調(diào)用者堆棧,其中保存有 EBP 的備份和返回地址。注意,微軟的 Visual C/C++ 的最新版中默認(rèn)不使用堆棧幀。替代的是,代碼通過 ESP 寄存器訪問堆棧中的值,不過這需要指定變量相對于當(dāng)前棧頂?shù)钠屏?。這種類型的代碼非常難以閱讀,因為每個 PUSH 和 POP 指令都會影響 ESP 的值和所有參數(shù)的偏移量。在此種情況下不再需要 EBP ,它將作為一個附加的通用寄存器。

l 在訪問 C 變量時必須非常小心。經(jīng)常出現(xiàn)在嵌入式 ASM 中的 bug 是:你將一個變量的地址而不是它的值加載到了寄存器中。使用 ptr 和 offset 地址操作符存在潛在的二義性。例如,指令: mov eax , dword ptr SomeVariable 將加載 DWORD 類型的 SomeVariable 變量的值到 EAX 寄存器,但是, mov eax , offset SomeVariable 將加載它的線性地址到 EAX 中。

圖 5-3. 堆棧幀的典型布局

Hook 分派程序(Hook Dispatcher)

這部分的代碼將較難理解。編寫它們花費了我很多時間,而且在這一過程中我還欣賞了無數(shù)的藍(lán)屏。我最初的方法是提供一個完全用匯編語言編寫的模塊。不過,這個方法在鏈接階時帶來了很大的麻煩,因此,我改為在 C 模塊中使用嵌入式匯編。為了避免創(chuàng)建另一個內(nèi)核模式的驅(qū)動程序,我決定將 hook 代碼整合到 Spy 設(shè)備驅(qū)動程序中。還記得在 4-2 底部列出的形如 SPY_IO_HOOK_* 的 IOCTL 函數(shù)嗎?現(xiàn)在我們將和它們來一次親密接觸。后面的示列代碼來自 w2k_spy.c 和 w2k_spy.h ,可以在隨書 CD 的 srcw2k_spy 中找到它們。

列表 5-3 的核心部分是 Native API Hook 機制的實現(xiàn)代碼。該列表開始處是一對常量和結(jié)構(gòu)體定義,后面的 aSpyHooks[] 需要它們。緊隨這個數(shù)組的是一個宏,該宏實際上是三行嵌入式匯編語句,這三行匯編語句非常重要,稍后我將介紹它們。 列表 5-3 的最后一部分用來建立 SpyHookInitializeEx() 函數(shù)。猛地一看,這個函數(shù)的功能似乎很難理解。該函數(shù)組合了一下兩個功能:

1. SpyHookInitializeEx() 的表面部分包括一段用來設(shè)置 aSpyHooks[] 數(shù)組的 C 代碼,這部分代碼用 Spy 設(shè)備的 Hook 函數(shù)指針以及與之相關(guān)聯(lián)的字符串格式協(xié)議來初始化 aSpyHooks[] 數(shù)組。 SpyHookInitializeEx() 函數(shù)可被分割為兩部分:第一部分到第一個 __asm 語句后的 jmp SpyHook9 指令。第二部分顯然是從 ASM 標(biāo)簽 ----SpyHook9 開始,該部分位于第二個 __asm 語句塊的最后。

2. SpyHookInitializeEx() 的內(nèi)部部分包括位于兩塊 C 代碼段之間的所有代碼。這部分在一開始大量使用了 SpyHook 宏,緊隨其后的是一大塊復(fù)雜的匯編代碼。可能你已經(jīng)猜到了,這些匯編代碼就是前面提到的通用 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], edx ; save PSPY_HOOK_ENTRY

mov ecx, offset SpyHook6 ; set new return address

xchg ecx, [esp+20h]

mov [edi.pCaller], ecx ; save old return address

mov ecx, KeServiceDescriptorTable

mov ecx, [ecx].ntoskrnl.ArgumentTable

movzx ecx, byte ptr [ecx+eax] ; get argument stack size

shr ecx, 2

inc ecx ; add 1 for result slot

mov [edi.dParameters], ecx ; save number of parameters

lea edi, [edi.adParameters]

xor eax, eax ; initialize result slot

stosd

dec ecx

jz SpyHook5 ; no arguments

lea esi, [esp+24h] ; save argument stack

rep movsd

SpyHook5:

mov eax, [edx.Handler] ; get original handler

pop edi

pop esi

pop ebp

pop edx

pop ecx

pop ebx

popfd

xchg eax, [esp] ; restore eax and...

ret ; ...jump to handler

SpyHook6:

push eax

pushfd

push ebx

push ecx

push edx

push ebp

push esi

push edi

push eax

call PsGetCurrentThreadId ; get thread id

mov ebx, eax

pop eax

mov edi, gpDeviceContext

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

mov esi, SPY_CALLS ; get number of entries

SpyHook7:

cmp ebx, [edi.hThread] ; find matching thread id

jz SpyHook8

add edi, SIZE SPY_CALL ; try next entry

dec esi

jnz SpyHook7

push ebx ; entry not found ?!?

call KeBugCheck

SpyHook8:

push edi ; save SPY_CALL pointer

mov [edi.adParameters], eax ; store NTSTATUS

push edi

call SpyHookProtocol

pop edi ; restore SPY_CALL pointer

mov eax, [edi.pCaller]

mov [edi.hThread], 0 ; clear thread id

mov esi, gpDeviceContext

dec [esi.dLevel] ; reset nesting level

dec [edi.fInUse] ; clear in-use flag

pop edi

pop esi

pop ebp

pop edx

pop ecx

pop ebx

popfd

xchg eax, [esp] ; restore eax and...

ret ; ...return to caller

SpyHook9:

mov dHooks1, offset SpyHook1

mov dHooks2, offset SpyHook2

}

n = (dHooks2 - dHooks1) / SDT_SYMBOLS_MAX;

for (i = j = 0; i < SDT_SYMBOLS_MAX; i++, dHooks1 += n)

{

if ((ppbSymbols != NULL) && (ppbFormats != NULL) &&

(ppbSymbols [j] != NULL))

{

aSpyHooks [i].Handler = (NTPROC) dHooks1;

aSpyHooks [i].pbFormat =

SpySearchFormat (ppbSymbols [j++], ppbFormats);

}

else

{

aSpyHooks [i].Handler = NULL;

aSpyHooks [i].pbFormat = NULL;

}

}

return;

}

列表 5-3. Hook Dispatcher 的實現(xiàn)方式

SpyHook 宏實際是什么呢?在 SpyHookInitializeEx() 函數(shù)中,這個宏被重復(fù)了多大 248 ( 0xF8 )次,這正好是 Windows 2000 Native API 函數(shù)的數(shù)目。在 列表 5-3 的頂部,這個數(shù)目被定義為 SDT_SYMBOLS_MAX 常量,該宏可以使 SDT_SYMBOLS_NT4 或 SDT_SYMBOLS_NT5 。因為我打算支持 Windows NT 4.0 。回到 SpyHook 宏上來:該宏調(diào)用的匯編語句在 列表 5-4 中給出了。每個 SpyHook 都產(chǎn)生同樣的三行代碼:

1. 第一行,將當(dāng)前 EAX 寄存器的內(nèi)容保存到堆棧中。

2. 第二行,將 SpyHook2 的線性地址保存到 EAX 中。

3. 第三行,調(diào)用 EAX 中的地址(即: call eax )。

你可能會驚訝:當(dāng)這個 CALL 返回時會發(fā)生什么。接下來的一組 SpyHook 代碼會被調(diào)用嗎?不 ---- 這個 CALL 并不支持返回,因為在到達(dá) SpyHook2 之后,這個 CALL 的返回地址就會被立即從堆棧中移出, 列表 5-4 最后的 POP EAX 指令可以證明這一點。這種看上去毫無疑義的代碼在古老的匯編程序設(shè)計時代曾被廣泛的討論的一種技巧,就像今天我們討論面向?qū)ο蟮某绦蛟O(shè)計一樣。當(dāng) ASM 老大級人物需要構(gòu)建一個數(shù)組,而此數(shù)組的每一項都有類似的進(jìn)入點,但卻需要被分派到獨立的函數(shù)時,就會采用這種技巧。對所有進(jìn)入點使用幾乎相同的代碼可以保證它們之間有相等的間隔,因此客戶端就可以很容易的通過 CALL 指令的返回地址計算出進(jìn)入點的在數(shù)組中的索引值,數(shù)組的基地址和大小以及數(shù)組中共有多少項

SpyHook1:

push eax

mov eax, offset SpyHook2

call eax

push eax

mov eax, offset SpyHook2

call eax

244 boring repetitions cimitted

push eax

mov eax, offset SpyHook2

call eax

push eax

mov eax, offset SpyHook2

call eax

SpyHook2:

pop eax

列表 5-4. 擴充 SpyHook 宏調(diào)用

例如, 列表 5-4 中第一個 CALL EAX 指令的返回地址是其下一個語句的地址。通常,第 N 個 CALL EAX 指令的返回地址是第 N+1 個語句的地址,但最后一個除外,最后這個將返回 SpyHook2 。因此,從 0 開始的所有進(jìn)入點的索引可以由 5-4 中的通用公式計算出來。這三條規(guī)則中的潛在規(guī)則是: SDT_SYMBOLS_MAX 進(jìn)入點符合內(nèi)存塊 SpyHook2---SpyHook1 。那么有多少個進(jìn)入點符合 ReturnAddress---SpyHook1 呢?因為計算結(jié)果是位于 0 到 SDT_SYMBOLS_MAX 中的某一個數(shù)值,所以,肯定要使用該數(shù)值來獲取一個從 0 開始的索引。

圖 5-4. 通過 Hook 進(jìn)入點的返回地址確定一個 Hook 進(jìn)入點

圖 5-4 所示公式的實現(xiàn)方式可以在 列表 5-3 中找到,在匯編標(biāo)簽 SpyHook2 的右邊。在 5-5 的左下角也給出了該公式的實現(xiàn)代碼,它展示了 Hook Dispatcher 機制的基本原理。注意, i386 的 mul 指令會在 EDX:EAX 寄存器中產(chǎn)生一個 64 位的結(jié)果值,這正是其后的 div 指令所期望的,因此,這里沒有整數(shù)溢出的危險。在 5-5 的左上角,是對 KiServiceTable 的描述,該表將被 SpyHook 宏生成的進(jìn)入點地址修改。在圖的中部展示了展開后的宏代碼(來自 列表 5-4 中)。進(jìn)入點的線性地址位于圖的右手邊。為了完全一致,每個進(jìn)入點的大小都是 8 字節(jié),因此,通過將 KiServiceTable 中每個函數(shù)的索引值乘以 8 ,然后再將乘積加上 SpyHook1 的地址就可得出進(jìn)入點的地址。

事實上,每個進(jìn)入點并不都是純粹的 8 字節(jié)長。我花費了大量的時間來尋找最佳的 hook 函數(shù)的實現(xiàn)方式。盡管按照 32 位邊界對齊代碼并不是必須的,但這從來都不是個壞主意,因為這會提高性能。當(dāng)然,能提升的性能十分有限。你或許會奇怪:為什么我要通過 EAX 寄存器間接的調(diào)用 SpyHook2 ,而不是直接使用 CALL SpyHook2 指令,這不是更高效嗎?是的!不過,問題是 i386 的 CALL (還有 jmp )指令可以有多種實現(xiàn)方式,而且都具有相同的效果,但是產(chǎn)生的指令大小卻不相同。請參考: Intel's Instruction Set Reference of the Pentium CPU family ( Intel 199c )。因為最終的實現(xiàn)方式要由編譯器 / 匯編器來確定,這不能保證所有的進(jìn)入點都會有相同的編碼。換句話說, MOV EAX 和一個 32 位常量操作數(shù)總是以相同的方式編碼,同樣的,這也適用于 CALL EAX 指令。

圖 5-5. Hook Dispatcher 的功能原理

列表 5-3 中還有一點需要澄清。讓我們從 SpyHook9 標(biāo)簽后的最后一快 C 代碼段開始。緊隨 SpyHook9 之后的匯編代碼將 SpyHook1 和 SpyHook2 的線性地址保存在 dHook1 和 dHook2 變量中。接下來,變量 n 被設(shè)為每個進(jìn)入點的大小(由進(jìn)入點數(shù)組的大小除以進(jìn)入點的個數(shù)而得出)。當(dāng)然,這個值將是 8 。 列表 5-3 的剩余部分是一個循環(huán)語句,用來初始化全局?jǐn)?shù)組 aSpyHooks[] 中的所有項。這個數(shù)組所包含的 SPY_HOOK_ENTRY 結(jié)構(gòu)定義于列 5-3 的頂部,該數(shù)組中的每一項都對應(yīng)一個 Native API 函數(shù)。要理解該結(jié)構(gòu)中的 Handler 和 pbFormat 成員是如何被設(shè)置的,就必須進(jìn)一步了解傳遞給 SpyHookInitializeEx() 的 ppbSymbols 和 ppbFormats 參數(shù), 列表 5-5 給出了外包函數(shù) SpyHookInitialize() ,該函數(shù)會選擇適合當(dāng)前 OS 版本的參數(shù)來調(diào)用 SpyHookInitializeEx() 。前面已經(jīng)提示過,我使用的代碼不直接測試 OS 版本或 Build Number ,而是用常量 SPY_SYMBOLS_NT4 、 SPY_SYMBOLS_NT5 和 SDT 中與 ntoskrnl.exe 相關(guān)的 ServiceLimit 成員的值進(jìn)行比較。如果沒有一個匹配, Spy 設(shè)備將把 aSpyHooks[] 數(shù)組內(nèi)容全部初始化為 NULL ,從而有效的禁止 Native API Hook 機制。

BOOL SpyHookInitialize (void)

{

BOOL fOk = TRUE;

switch (KeServiceDescriptorTable->ntoskrnl.ServiceLimit)

{

case SDT_SYMBOLS_NT4:

{

SpyHookInitializeEx (apbSdtSymbolsNT4, apbSdtFormats);

break;

}

case SDT_SYMBOLS_NT5:

{

SpyHookInitializeEx (apbSdtSymbolsNT5, apbSdtFormats);

break;

}

default:

{

SpyHookInitializeEx (NULL, NULL);

fOk = FALSE;

break;

}

}

return fOk;

}

列表 5-5. SpyHookInitialize() 選擇匹配當(dāng)前 OS 版本的符號表

將全局?jǐn)?shù)組: apbSdtSymbolsNT4[] 和 apbSdtSymbolsNT5[] 傳遞給 SpyHookInitializeEx() 函數(shù)作為其第一個參數(shù) ppbSymbols ,這兩個數(shù)組只是簡單的字符串?dāng)?shù)組,包含 Windows NT 4.0 和 Windows 2000 的所有 Native API 函數(shù)的名稱,按照它們在 KiServiceTable 中的索引順序來存儲,最后以 NULL 結(jié)束。 列表 5-6 給出了 apbStdFormats[] 字符串?dāng)?shù)組。這個格式字符串列表也是 hook 機制中很重要的一部分,因為它確定了記錄了那個 Native API 調(diào)用,以及每個記錄項的格式。顯然,這些字符串的結(jié)構(gòu)借鑒了 C 運行時庫中的 printf() 函數(shù),但針對 Native API 經(jīng)常使用的數(shù)據(jù)類型進(jìn)行了修改。 5-2 列出了所有可被 API Logger 識別的格式化 ID 。

PBYTE apbSdtFormats [] =

{

'%s=NtCancelIoFile(%!,%i)',

'%s=NtClose(%-)',

'%s=NtCreateFile(%+,%n,%o,%i,%l,%n,%n,%n,%n,%p,%n)',

'%s=NtCreateKey(%+,%n,%o,%n,%u,%n,%d)',

'%s=NtDeleteFile(%o)',

'%s=NtDeleteKey(%-)',

'%s=NtDeleteValueKey(%!,%u)',

'%s=NtDeviceIoControlFile(%!,%p,%p,%p,%i,%n,%p,%n,%p,%n)',

'%s=NtEnumerateKey(%!,%n,%n,%p,%n,%d)',

'%s=NtEnumerateValueKey(%!,%n,%n,%p,%n,%d)',

'%s=NtFlushBuffersFile(%!,%i)',

'%s=NtFlushKey(%!)',

'%s=NtFsControlFile(%!,%p,%p,%p,%i,%n,%p,%n,%p,%n)',

'%s=NtLoadKey(%o,%o)',

'%s=NtLoadKey2(%o,%o,%n)',

'%s=NtNotifyChangeKey(%!,%p,%p,%p,%i,%n,%b,%p,%n,%b)',

'%s=NtNotifyChangeMultipleKeys(%!,%n,%o,%p,%p,%p,%i,%n,%b,%p,%n,%b)',

'%s=NtOpenFile(%+,%n,%o,%i,%n,%n)',

'%s=NtOpenKey(%+,%n,%o)',

'%s=NtOpenProcess(%+,%n,%o,%c)',

'%s=NtOpenThread(%+,%n,%o,%c)',

'%s=NtQueryDirectoryFile(%!,%p,%p,%p,%i,%p,%n,%n,%b,%u,%b)',

'%s=NtQueryInformationFile(%!,%i,%p,%n,%n)',

'%s=NtQueryInformationProcess(%!,%n,%p,%n,%d)',

'%s=NtQueryInformationThread(%!,%n,%p,%n,%d)',

'%s=NtQueryKey(%!,%n,%p,%n,%d)',

'%s=NtQueryMultipleValueKey(%!,%p,%n,%p,%d,%d)',

'%s=NtQueryOpenSubKeys(%o,%d)',

'%s=NtQuerySystemInformation(%n,%p,%n,%d)',

'%s=NtQuerySystemTime(%l)',

'%s=NtQueryValueKey(%!,%u,%n,%p,%n,%d)',

'%s=NtQueryVolumeInformationFile(%!,%i,%p,%n,%n)',

'%s=NtReadFile(%!,%p,%p,%p,%i,%p,%n,%l,%d)',

'%s=NtReplaceKey(%o,%!,%o)',

'%s=NtSetInformationKey(%!,%n,%p,%n)',

'%s=NtSetInformationFile(%!,%i,%p,%n,%n)',

'%s=NtSetInformationProcess(%!,%n,%p,%n)',

'%s=NtSetInformationThread(%!,%n,%p,%n)',

'%s=NtSetSystemInformation(%n,%p,%n)',

'%s=NtSetSystemTime(%l,%l)',

'%s=NtSetValueKey(%!,%u,%n,%n,%p,%n)',

'%s=NtSetVolumeInformationFile(%!,%i,%p,%n,%n)',

'%s=NtUnloadKey(%o)',

'%s=NtWriteFile(%!,%p,%p,%p,%i,%p,%n,%l,%d)',

NULL

};

列表 5-6. Native API Logger 使用的格式化字符串

這里要特別提出的是:每個格式字符串要求必須提供函數(shù)名的正確拼寫。 SpyHookInitializeEx() 遍歷它接受到的 Native API 符號列表(通過 ppbSymbols 參數(shù)),并試圖從 ppbFormats 列表中找出與函數(shù)名匹配的格式字符串。由幫助函數(shù) SpySearchFormat() 來進(jìn)行比較工作, 列表 5-3 底部的 if 語句中調(diào)用了該函數(shù)。因為要執(zhí)行大量的字符串查找操作,我使用了一個高度優(yōu)化的查找引擎,該引擎基于“ Shift/And ”搜索算法。如果你想更多的學(xué)習(xí)它的實現(xiàn)方式,請察看隨書 CD 的 srcw2k_spyw2k_spy.c 源文件中的 SpySearch*() 函數(shù)。當(dāng) SpyHookInitializeEx() 推出循環(huán)后, aSpyHooks[] 中的所有 Handler 成員都將指向適當(dāng)?shù)?Hook 進(jìn)入點, pbFormat 成員提供與之匹配的格式字符串。對于 Windows NT 4.0 ,所有索引值在 0xD3---0xF8 的數(shù)組成員都將被設(shè)為 NULL ,因為在 NT4 中,它們并沒有被定義。

表 5-2. 可識別的格式控制 ID

ID

%+

句柄(登記)

將句柄和對象名寫入日志,并將其加入句柄表。

%!

句柄(檢索)

將句柄寫入日志,并從句柄表中檢索其對應(yīng)的對象名。

%-

句柄(撤銷登記)

將句柄和對象名寫入日志,并將其從句柄表移除

%a

ANSI 字符串

將一個由 8 位 ANSI 字符構(gòu)成的字符串寫入日志

%b

BOOLEAN

將一個 8 位的邏輯值寫入日志

%c

CLIENT_ID*

將 CLIENT_ID 結(jié)構(gòu)的成員寫入日志

%d

DWORD *

將該 DWORD 所指變量的值寫入日志

%i

IO_STATUS_BLOCK *

將 IO_STATUS_BLOCK 結(jié)構(gòu)的成員寫入日志

%l

LARGE_INTEGER *

將一個 LARGE_INTEGER 的值寫入日志

%n

數(shù)值 (DWORD)

將一個 32 位無符號數(shù)寫入日志

%o

OBJECT_ATTRIBUTES *

將對象的 ObjectName 寫入日志

%p

指針

將指針的目標(biāo)地址寫入日志

%s

狀態(tài) (NTSTATUS)

將 NT 狀態(tài)代碼寫入日志

%u

UNICODE_STRING *

將 UNICOD_STRING 結(jié)構(gòu)的 Buffer 成員寫入日志

%w

寬字符串

將一個由 16 位字符構(gòu)成的字符串寫入日志

%%

百分號轉(zhuǎn)義符

將一個“ % ”號寫入日志

標(biāo)簽: Windows系統(tǒng)
主站蜘蛛池模板: 91久久亚洲最新一本 | 91久久国产综合精品 | 一级做α爱过程免费视频 | 久久精品国产国产精品四凭 | 在线a人片免费观看国产 | 91国内视频在线观看 | 男女乱淫真视频免费一级毛片 | 久久久网站 | 夜色成人免费观看 | 久久爱99re| 成人免费福利网站在线看 | 国产成人亚洲精品久久 | 老司机黄色影院 | 日韩欧美一区二区三区不卡视频 | 午夜三级在线 | 三级全黄的全黄三级三级播放 | 在线视频99 | 欧美一区二区三区激情视频 | 亚洲美女爱爱 | 久久97视频 | 久久精品视频9 | 亚洲精品国产成人一区二区 | 久久骚 | 99久久免费看国产精品 | 久草观看视频 | 特黄特级a级黄毛片免费观看多人 | 久久只有这才是精品99 | 久久视频在线免费观看 | 精品国产日韩亚洲一区二区 | 欧美日韩一区二区视频图片 | 国产日韩视频在线观看 | 欧美另类性视频在线看 | 成人亚州 | 久久精品女人毛片国产 | 国产91一区二这在线播放 | 日韩啪| 久久厕所 | 91精品国产薄丝高跟在线看 | 成人一区二区免费中文字幕 | 欧美日韩国产人成在线观看 | 久久久在线视频精品免费观看 |