亚洲精品久久久中文字幕-亚洲精品久久片久久-亚洲精品久久青草-亚洲精品久久婷婷爱久久婷婷-亚洲精品久久午夜香蕉

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

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

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

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

翻譯: Kendiv( [email protected] )

更新: Thursday, February 24, 2005

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

匯編語(yǔ)言的救援行動(dòng)

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

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

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

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

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 等)由寄存器對(duì) EDX : EAX 返回。寄存器 EAX 包含 0 到 31 位, EDX 保存 32 到 63 位。

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

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

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

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

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

Hook 分派程序(Hook Dispatcher)

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

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

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

2. SpyHookInitializeEx() 的內(nèi)部部分包括位于兩塊 C 代碼段之間的所有代碼。這部分在一開(kāi)始大量使用了 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 的實(shí)現(xiàn)方式

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

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

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

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

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

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. 擴(kuò)充 SpyHook 宏調(diào)用

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

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

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

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

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

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

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 版本的符號(hào)表

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

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

表 5-2. 可識(shí)別的格式控制 ID

ID

稱(chēng)

%+

句柄(登記)

將句柄和對(duì)象名寫(xiě)入日志,并將其加入句柄表。

%!

句柄(檢索)

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

%-

句柄(撤銷(xiāo)登記)

將句柄和對(duì)象名寫(xiě)入日志,并將其從句柄表移除

%a

ANSI 字符串

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

%b

BOOLEAN

將一個(gè) 8 位的邏輯值寫(xiě)入日志

%c

CLIENT_ID*

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

%d

DWORD *

將該 DWORD 所指變量的值寫(xiě)入日志

%i

IO_STATUS_BLOCK *

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

%l

LARGE_INTEGER *

將一個(gè) LARGE_INTEGER 的值寫(xiě)入日志

%n

數(shù)值 (DWORD)

將一個(gè) 32 位無(wú)符號(hào)數(shù)寫(xiě)入日志

%o

OBJECT_ATTRIBUTES *

將對(duì)象的 ObjectName 寫(xiě)入日志

%p

指針

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

%s

狀態(tài) (NTSTATUS)

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

%u

UNICODE_STRING *

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

%w

寬字符串

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

%%

百分號(hào)轉(zhuǎn)義符

將一個(gè)“ % ”號(hào)寫(xiě)入日志

標(biāo)簽: Windows系統(tǒng)
主站蜘蛛池模板: 成人美女黄网站视频大全 | www一级黄色片 | 欧美一区二区三区不卡视频 | 国产成人一区二区三区视频免费蜜 | 国产一区在线视频观看 | 国产1级片 | 免费视频观看在线www日本 | 国产女人体一区二区三区 | 国产成人a毛片 | 成年人免费黄色片 | 日韩亚洲欧美一区二区三区 | 久久久久久久国产精品 | 日韩欧美亚洲综合久久99e | 国产啪精品视频网给免丝袜 | 天天拍夜夜添久久精品中文 | chinese国产在线视频 | 91久久亚洲国产成人精品性色 | 国产精品91视频 | 大陆1区二区三区 | 色天天天综合色天天碰 | 三级大黄| 欧美精品国产综合久久 | 欧美黑人在线视频 | 特黄免费 | 久久午夜羞羞影院免费观看 | 亚洲一区二区中文字5566 | 欧美日韩亚洲区久久综合 | 一级特级欧美aa毛片免费 | 国产欧美成人xxx视频 | 午夜影院在线观看视频 | 上海麻豆文化传媒网站入口 | 国产毛片一区二区三区精品 | 午夜久久久久久网站 | 免费看三级黄色片 | 日韩欧美一区二区三区不卡在线 | 一级毛片在线播放免费 | 午夜免费福利不卡网址92 | 亚洲视频一二三 | 免费观看一级成人毛片 | 思99re久久这里只有精品首页 | 国产美女福利视频 |