久久福利_99r_国产日韩在线视频_直接看av的网站_中文欧美日韩_久久一

您的位置:首頁技術文章
文章詳情頁

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

瀏覽:90日期:2023-08-27 10:31:57

第五章 監控 Native API 調用

翻譯: Kendiv( fcczj@263.net )

更新: Thursday, March 24, 2005

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

本書設計的 hook 機制的最大特色就是它是完全數據驅動的( data-driven )。只需簡單的增加一個新的 API 符號表,該 hook dispatcher 就可適應 Windows 2000 的新版本。而且,通過向 apdSdtFormats[] 數組中加入新的 API 函數的格式化字符串就可在任何時候記錄對這些附加的 API 函數的調用。這并不需要編寫任何附加的代碼 ---API Spy 的動作可完全由一組字符串來確定!不過,在定義新的格式化字符串是必須要小心,因為 w2k_spy.sys 是運行于內核模式的驅動程序。因為在這一系統層次上,系統不能溫和的處理發生錯誤。給 Win32 API 函數提供了一個無效的參數并不是問題 ----- 你會收到一個錯誤提示窗口,同時程序會被系統自動終止。在內核模式下,一個微小的訪問違規都會引發系統藍屏。因此,一定要小心。在需要的地方如果沒有出現一個正確的格式化控制 ID 或缺失了這一 ID 都會使你的系統徹底崩潰。即使一個簡單的字符串有時都是致命的!

現在僅剩 SpyHookInitializeEx() 中的那一大塊 ASM 代碼還未討論,這段代碼由 SpyHook2 和 SpyHook9 標識。這段代碼的一個有趣的特性是:在 SpyHookInitializeEx() 被調用的時候,它們從來都不會被執行。在進入 SpyHookInitializeEx() 后,函數代碼將跳過這一整段代碼,然后在 SpyHook9 標簽處開始恢復執行,此處包含 aSpyHooks[] 數組的初始化代碼。這一大塊 ASM 代碼只有通過 aSpyHooks[] 數組中的 Handler 成員才能進入。稍候,我將展示這些進入點是如何連接到 SDT 的。

在設計這段 ASM 代碼時,我的重要目標之一就是使其是完全非侵入式的。截獲操作系統調用非常危險,因為你從來不會知道被調用的代碼是否會依賴調用上下文( calling context )的某些未知特性。理論上來說,這些 ASM 代碼完全符合 __stdcall 約定,但仍存在出錯的可能性。我不得不選擇將原始的 Native API 處理例程放入幾乎完全相同的環境中,這意味著這些原始函數將使用最初的參數堆棧并且可以訪問所有的 CPU 寄存器,就像它們被正常調用一樣。當然,必須接受由于插入 hook 所帶來的最低限度的危險,否則,監控將不可能實現。在這里,有意義的改動就是維護堆棧中的返回地址。如果你翻回到 5-3 ,你會發現在進入函數時,調用者的返回地址并不位于堆棧的頂部。 SpyHookInitializeEx() 中的 hook dispatcher 占用了此地址,將它自己的 SpyHook6 標簽的地址寫在了這里。因此,原始 Native API 處理例程將被打斷,然后進入 SpyHook6 中,這樣 hook dispatcher 才能檢查原始 Native API 處理例程的參數和它要返回的數據。

在調用原始處理例程之前, dispatcher 將建立一個 SPY_CALL (參見 列表 5-3 )控制塊,該控制塊中包含它稍候將會用到的參數。其中的一些參數在正確記錄 API 調用時會用到,另外一些則提供了有關調用者的信息,因此 dispatcher 可以在寫完 log 后,把控制返回給調用者,就像什么都沒有發生一樣。 Spy 設備在它的全局數據塊 DEVICE_CONTEXT 中維護著一個 SPY_CALL 結構的數組,可通過全局變量 gpDeviceContext 來訪問。 Hook Dispatcher 通過檢查 SPY_CALL 結構中的 InUse 成員來在數組中找到一個空的 SPY_CALL 。 Hook Dispatcher 使用 CPU 的 XCHG 指令來加載和設置該成員的值(譯注: XCHG 指令可以保證此操作為原子操作)。這一點非常重要,因為當代碼運行于多線程環境中時,讀寫全局數據時必須采取保護措施以避免條件競爭。如果在數組中找到了一個空的 SPY_CALL , dispatcher 就會將調用者的線程 ID (通過 PsGetCurrentThreadId() 獲取)、與當前 API 函數相關的 SPY_HOOK_ENTRY 結構的地址以及整個參數堆棧保存到該 SPY_CALL 結構中。需要復制的參數的字節數取自 KiArqumentTable 數組,該數組保存在系統的 SDT 中。如果所有的 SPY_CALL 都被使用了,原始的 API 函數處理例程將被調用而不會產生任何日志記錄。

必須采用 SPY_CALL 數組是因為 Windows 2000 的多線程本性。當 Native API 函數被暫停( suspended )時,這種情況就會經常出現 ---- 此時,另一個線程將獲得控制權,然后在它自己的時間片( time slice )內調用另一個 Native API 函數。這意味著 Spy 設備的 Hook Dispatcher 必須允許在任何時間和任何執行點上的重進入( reenter )。如果 Hook Dispatcher 有單一的全局 SPY_CALL 存儲區域,它就可能在處于等待狀態的線程使用完之前被當前運行的線程覆寫( overwritten )。而這種情況正是藍屏的最佳候選人。為了進一步了解 Native API 的嵌套,我在 Spy 的 DEVICE_CONTEXT 結構中增加了 dLevel 和 dMisses 成員。無論何時只要重進入 hook dispatcher (如,向 SPY_CALL 數組中增加一個新的 SPY_CALL ) dLevel 都不會累加一個 1 。如果超過了最大嵌套層數(如, SPY_CALL 數組已滿), dMisses 就會累加一個 1 ,來標識丟失了一個日志記錄。根據我的觀察,在實際環境下,可以很容易的發現嵌套層達到 4 。這表示即時在高負載( heavy-load )的情況下, Native API 也會被重進入,因此,我將嵌套層數的上限設為 256 。

在調用原始的 API 處理例程之前, Hook Dispatcher 會保存所有的 CPU 寄存器(包括 EFLAGS ),隨后執行路徑將導向函數的進入點。這會在 列表 5-3 中的 SpyHook5 標簽之前立即完成。此時, SpyHook6 將位于棧頂,僅隨其后的是調用者的參數。一旦 API 處理例程推出了,控制將被傳回到 hook dispatcher 的 SpyHook6 標簽。從此處開始執行的代碼也被設計為非入侵的。此時,主要目標是允許調用者可以看到調用上下文,這和原始 API 函數建立的上下文幾乎完全一致。 Dispatcher 的主要問題是要能立即找到保存有當前 API 調用信息的 SPY_CALL 結構。唯一可以依賴的就是調用者的線程 ID ,該 ID 保存在 SPY_CALL 結構的 hThread 成員中。因此, Dispatcher 循環遍歷整個 SPY_CALL 數組以尋找匹配的線程 ID 。注意,代碼不會關心 fmuse 標志的值;這并不是必須的,因為數組中所有未使用的 SPY_CALL 結構的 hThread 都被設為了 0 ,這是系統空閑線程的 ID 。循環會在到達數組結尾時終止。否則的話(譯注:即沒有找到匹配的線程 ID ), Dispatcher 不會將控制返回給調用者,因為這樣做將是致命的。在這種情況下,代碼的選擇余地很小,因此,它會進入 KeBugCheck() ,這樣做的結果當然是使系統以受控的方式終止。不過這種情況應該從來不會發生,但如果它發生了,那表示系統必然出現了很嚴重的錯誤,因此,使系統終止是最佳解決方案。

如果發現了匹配的 SPY_CALL , Hook Dispatcher 將結束它的工作。最后的動作是調用日志記錄函數 SpyHookProtocol() ,需要給該函數傳入一個指向 SPY_CALL 結構的指針。日志記錄所需的信息都保存在該結構中。當 SpyHookProtocol() 返回后, Dispatcher 就釋放它剛才使用的 SPY_CALL ,恢復所有的 CPU 寄存器,然后返回到調用者。

API HOOK 協議

一個好的 API Spy 應該可以在原始函數被調用后還能察看它使用的參數,因為函數可能會通過傳入的緩沖區返回附加的數據。因此,日志函數 SpyHookProtocol() 在 hook 例程結束時將被調用,而此時 API 函數還未返回到調用者。在討論它的實現秘訣之前,請先看看下面給出的兩個示例性的協議( Protocol ),它們會為你提供一個大概的方向。 5-6 是在命令行下執行 dir c: 時產生的日志文件的快照。

請對比 5-6 中列出的日志項和 列表 5-6 給出的協議格式化字符串。在 示列 5-1 中, NtOpenFile() 和 NtClose() 的格式化字符串分別對應 5-6 中的第一行和第四行。它們有著驚人的相似處;每一個格式化控制 ID 都緊隨在一個 % 號后(參考 5-2 ),與其相關的參數項將包含在協議中。不過,協議還包含一些附加的信息,這些信息明顯不屬于格式字符串。稍后我將解釋這種差異的原因。

示例 5-2 給出了一個協議項的一般格式。每一項包含相同個數的域,這些域采用分隔符隔開。這樣分隔可以使程序很容易的解析它。這些域按照如下的一組簡單的基本規則來構建:

l 所有的數字都已十六進制表示,沒有 0 前綴或常見的前綴“ 0x ”

l 函數的多個參數由逗號隔開

l 字符串參數將位于一對雙引號中

l 結構體成員的值由“ . ”符號隔開

圖 5-6. 命令 dir c: 的示列協議

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

18:sO=NtOpenFile(+46C.18,nl00001,o'??C:',i0.1,n3,n4021)lBFEE5AE05B6710,278,2

'%s=NtClose(%-l)'

lB:sO=NtClose(-46C.18='??C:')lBFEE5AE05B6710,278,l

示列 5-1. 比較格式化字符串和協議項

<#> : <status>=<function> (<arguments>) <time> , <thread>, <handles>

示列 5-2. 協議項的一般格式

l 與句柄相關的對象名稱和句柄的值采用“ = ”進行分割。

l 日期 / 時間的 stamp 為 1601-01-01 至今逝去的毫秒數,其格式依賴 Windows 2000 的基本時間格式,精度可達到 1/10 毫秒。

l 線程 ID 是調用 API 函數的線程的唯一數字標識。

l 句柄計數的狀態表示當前注冊到 Spy 設備句柄列表中的句柄的數量。協議函數使用該列表查找與對象名稱相關的句柄。

圖 5-7. 命令 type c:boot.ini 的示列協議

圖 5-7 是在控制臺中執行: type c:boot.ini 命令產生的 API Spy 協議結果。下面給出日志項中的某些列的含義:

l 在 0x31 行,調用了 NtCreateFile() 來打開 ??c:boot.ini 文件。( o”??c:boot.ini” )該函數返回的 NTSTATUS 的值為 0 ( s0 ),即 STATUS_SUCCESS ,并分配了一個新的文件句柄,其值為 0 小 8 ,該句柄屬于進程 0x46c ( +46C.18 )。因此,句柄計數從 1 增加到 2 。

l 在 0x36 行, type 命令將文件 ??c:boot.ini 的前 512 個字節( n200 )讀入位于線性地址 0x0012F5B4 處的緩沖區中,并把從 NtCreateFile() 獲取的句柄解析給 NtReadFile() 函數。系統成功的返回 512 字節( io.200 )。

l 在 0x39 行,將處理另一塊 512 個字節的文件塊。這一次,將到達文件的末尾,因此 NtReadFile() 僅返回了 75 個字節( io.4B )。顯然,我的 boot.ini 文件的大小為: 512+75=587 字節。

l 在 0x3C 行, NtClose() 成功的釋放了指向 ??c:boot.ini 的文件句柄( -46.18=”??c:boot.ini” ),因此,句柄計數將從 2 減少為 1 。

現在,你應該已經明白 Spy 協議的 API 是如何構建的了,這會幫助你掌握協議生成機制的細節,接下來我們將討論這一機制。在前面我曾提及過,用于日志記錄的主要 API 函數是 SpyHookProtocol() 。 列表 5-7 給出了該函數,它將使用 SPY_CALL 結構中的數據來為每個 API 函數生成一個協議記錄并將其寫入一個環形緩沖區中,這里的 SPY_CALL 結構由 Hook Dispatcher 傳入。一個 Spy 設備的客戶端可以通過 IOCTL 調用來讀去這一協議。每個記錄項都是一行文本,每行都由單個行結束符(即 C 語言中的 ”n” )表示行的結束。通過使用內核的 Mutext KMUTEX kmProtcol 來實現串行讀去協議緩沖區, kmProtocol 位于 Spy 設備的全局結構 DEVICE_CONTEXT 中。 列表 5-7 中的 SpyHookWait() 和 SpyHookRelease() 函數用于請求和釋放此 Mutext 對象。所有對協議緩沖區的訪問都必須由 SpyHookWait() 預處理并在結束時由 SpyHookRelease() 處理, SpyHookProtocol() 函數展示了這種行為。

NTSTATUS SpyHookWait (void)

{

return MUTEX_WAIT (gpDeviceContext->kmProtocol);

}

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

LONG SpyHookRelease (void)

{

return MUTEX_RELEASE (gpDeviceContext->kmProtocol);

}

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

// <#>:<status>=<function>(<arguments>)<time>,<thread>,<handles>

void SpyHookProtocol (PSPY_CALL psc)

{

LARGE_INTEGER liTime;

PSPY_PROTOCOL psp = &gpDeviceContext->SpyProtocol;

KeQuerySystemTime (&liTime);

SpyHookWait ();

if (SpyWriteFilter (psp, psc->pshe->pbFormat,

psc->adParameters,

psc->dParameters))

{

SpyWriteNumber (psp, 0, ++(psp->sh.dCalls)); // <#>:

SpyWriteChar (psp, 0, ':');

// <status>=

SpyWriteFormat (psp, psc->pshe->pbFormat, // <function>

psc->adParameters); // (<arguments>)

SpyWriteLarge (psp, 0, &liTime); // <time>,

SpyWriteChar (psp, 0, ',');

SpyWriteNumber (psp, 0, (DWord) psc->hThread); // <thread>,

SpyWriteChar (psp, 0, ',');

SpyWriteNumber (psp, 0, psp->sh.dHandles); // <handles>

SpyWriteChar (psp, 0, 'n');

}

SpyHookRelease ();

return;

}

列表 5-7. 主要的 Hook 協議函數 SpyHookProtocol()

如果你比較一下 列表 5-7 給出的 SpyHookProtocol() 函數的主要部分和 示列 5-2 給出的協議項的一般格式,將很容易找出那個語句生成了協議項中的哪一個域( fIEld )。這樣一來一切就很清楚了為什么 列表 5-6 中的協議字符串沒有說明整個數據項 --- 有些獨立于功能的數據將由 SpyHookProtocol() 添加,而這將不需要格式字符串的幫助。 SpyHookProtocl() 的核心調用是 SpyWriteFormat() ,該函數生成 <status>=<function>[<arguments>] 部分,這依賴于與要記錄的當前 API 函數相關的格式字符串。請參考位于隨書光盤的 srcw2k_spy 目錄下的源文件 w2k_spy.c 和 w2k_spy.h ,以獲取 Spy 設備驅動程序中使用的 SpyWrite*() 函數的更多實現信息。

請注意,這些代碼稍微有些危險。這些代碼編寫與 1997 年是針對 Windows NT 4.0 的。在移植到 Windows 2000 之后,當 hook 工作一段較長時間后會偶爾引發藍屏。更糟糕的是,有些特殊的操作將立即引發藍屏,例如,在 My Favoriter 文本編輯器的 FileOpen 對話框中打開我的電腦時。在分析過多過 crash dump 后,我發現是由于將 NULL 指針傳遞給了某些函數從而導致了系統崩潰。一但 Spy 設備試圖使用這些指針中的某個來記錄該指針引用的數據時,系統就會崩潰。典型的就是,指向 IO_STATUS_BLOCK 結構的指針,在 UNICODE_STRING 和 OBJECT_ATTRIBUTES 結構中存在無效的字符串指針。我還發現某些有 Buffer 成員的 UNICODE_STRING 結構沒有 結束符。因此,我再次強調你不應該假定所有的 UNICODE_STRING 結構都以 結束。在不能確定時,請使用 Length 成員,它總能正確地告訴你在 Buffer 中存放的有效的字節數。

為了修正這一問題,我為所有使用客戶指針的日志函數增加了指針有效性檢查。在結束時,我使用第四章討論過的 SpyMemoryTestAddress() 函數來檢驗一個線性地址指針是否指向一個有效的頁表項( PTE )。更詳細的信息請參考 列表 4-22 列表 4-24 。另一種可能的替代方案是使用結構化異常( __try/__except )。

標簽: Windows系統
主站蜘蛛池模板: 亚洲高清久久 | 欧美一区二区三区免费视频 | 日本精品一区 | 成人免费看 | 91精品国产高清自在线观看 | 最新一级毛片 | 色婷婷在线视频观看 | 欧美日韩高清一区 | 成人黄色一区 | 亚洲三区在线观看 | 欧美成人精品激情在线观看 | 精品国产一区一区二区三亚瑟 | 91.成人天堂一区 | 精品自拍视频 | 国产成人一区二区三区影院在线 | 成人资源在线观看 | 毛片真人毛毛片毛片 | 一区二区三区免费 | 日韩av在线一区 | 色噜噜狠狠狠综合曰曰曰88av | 国产精品美女视频免费观看软件 | 国产午夜精品一区二区三区 | 99国产精品久久久久老师 | www.操操操.com| 亚洲精品一区二区三区在线播放 | 国产精品综合一区二区 | 毛片黄色 | 国产99免费 | 午夜免费电影 | 免费黄色网址在线播放 | www视频在线观看 | 日韩三级电影在线免费观看 | 久久婷婷香蕉 | 久久久久久免费视频 | 中文字幕日韩在线 | 国产三级一区二区 | 福利网站在线观看 | 欧洲一区 | 亚洲精品国产高清 | 永久精品 | 最新的黄色网址 |