icesword?驅動部分分析

信息來源:驅動開發網(www
. zndev . com)
文章作者:wuyanfeng

icesword
. exe?在執行的時候會放出一個驅動程序?ispubdrv . sys? .
icesword . exe?裝載?這個驅動,這個驅動安裝后就不會卸載。直到系統重新啟動。這可能是因為驅動中調用了
PsSetCreateThreadNotifyRoutine?函數
. 下面是這個函數在?ddk?中的介紹。

///////////////////////////////////////////////
PsSetCreateThreadNotifyRoutine?registers?a?driver - supplied?callback?that?is?subsequently?notified?when?a? new? thread?is?created? and? when?such?a?thread?is?deleted .

NTSTATUS
PsSetCreateThreadNotifyRoutine
(
??
IN?PCREATE_THREAD_NOTIFY_ROUTINE?NotifyRoutine
??
);

Any?driver?that?successfully?registers?such?a?callback?must?remain?loaded?until?the?system?itself?is?shut?down .

//////////////////////////////////////////////

雖然ddk?中說成功的調用PsSetCreateThreadNotifyRoutine?函數就要保留驅動直到系統重新啟動,但是還是有辦法做到可以卸載的。

icesword?是如何列出隱藏進程?

icesword?是通過?PspCidTable?這個表來遍厲進程的,?PspCidTable?是一個沒有被?ntoskrnl
. exe?導出的。這就涉及到如何定位
PspCidTable?的問題。icesword?是通過搜索特征串的方式定位?PspCidTalbe
.? PspCidTable?是一個?HANDLE_TALBE?結構 .
PsLookupProcessByProcessId?函數中會引用?PspCidTalbe?變量。icesword?從?PsLookupProcessByProcessId?函數的前幾十個字節
內搜索?PspCidTalbe?變量。那有人就可能會想,那我把?PsLookupProcessByProcessId???這個函數給?patch?了
, 他不就找不到
PspCidTalbe?變量了嗎??對你說的沒錯,是可以這樣。當然我們能想到這點。icesword?的作者也能想到這點。作者為了防止你這么做
也采取了相應的對策。他采取的對策就是運行前效驗恢復的方法。它在執行關鍵的系統函數時會比較函數的頭幾十個字節是否被修改。
如果被修改了它就會把被修改的給恢復成系統原來的內容,那我們可能就會提出一個疑問,如果我在它啟動之前就?patch?了他要效驗
的函數,它怎么能知道系統原來的內容呢?這個問題提的好。現在就讓我們來看看?icesword?的作者是怎么做到這一點的。我們就拿
PsLookupProcessByProcessId???函數來說吧。PsLookupProcessByProcessId???函數是?ntoskrnl
. exe?文件導出的。作者不是用我們通常
的方法來定位?PsLookupProcessByProcessId???函數的,也就是說?ispubdrv
. sys?并沒有導入這個函數。同樣也沒有通過
MmGetSystemRoutineAddress?函數來得PsLookupProcessByProcessId的地址。那他是怎么獲的?PsLookupProcessByProcessId?的地址的呢?
那可能有的人就會想到他是通過?自己打開ntoskrnl
. exe?文件然后分析導出函數做的,對icesword?的作者就是這么做的。當然他這里還是
有技巧在的。作者操作文件也沒有用我們寫驅動程序時常用的操作文件的方式來訪問文件。我們平時在驅動程序里面打開和讀寫文件時
大多是使用?ZwCreateFile
, ZwOpenFile , ZwReadFile , ZwWriteFile , NtCreateFile?等等函數 . 這樣的話作者就可以避免一些文件過濾程序。
作者打開文件用?IoCreateFile?函數。在讀文件的時候作者沒有用正常的?文件相關的?API?函數,而是用的?IofCallDriver?來做的。因為
本人對驅動不熟悉,也不清楚他?IoCallDriver?做什么用,只知道調用完了這個?IoCallDriver?函數后,數據就被讀出來了。?這就防止了
通常的文件讀寫過濾程序。自己分析?pe?文件,找出他要定位的函數的導出地址。然后他會把函數的前?幾十個字節讀出來,當然這里又涉及
到代碼重定位的問題。(熟悉?pe?的人可能都會理解重定位的問題的問題,這里我就不講了。如果有不理解的可以自己參考?PE?文件格式的相關
文檔。)作者自己把讀出來的代碼片段自己做重定為。這樣他就得到了函數開頭部分的原始代碼。作者通過這種方法,就獲得了原始的效驗數據
。這樣他運行系統函數的時候就保證函數沒有被?patch?過。當然了如果你不怕麻煩的話可以把自己的?patch?放到更深的調用路徑上。
這樣即使是用?windbg?
,? softice , syser?調試器下斷點調試,也是斷不住的。當然了你也不能用調試器調試,因為?icesword . exe?會在一個
timer?中不停的重新設置?
int? 1 , int? 3? 的中斷處理函數。設置成?windows?ntoskrnl . exe?中的缺省處理函數。即使你用硬件斷點寄存器也是不
管用的。那有的人就會說既然設置成?windows?ntoskrnl
. exe?中的缺省處理函數就可以使用?windbg?雙機調試 . icesword?也做了處理 ,
icesword?會通過?KdDebuggerEnabled?變量判斷是否允許內核調試。如果允許調試的話 .? icesword?會調用?KdDisableDebugger?函數禁止內核調試。



這里順便在說兩個分析?icesword?中遇到的反調試小陷阱?這里把代碼片段列出來,希望作者原諒

. text : 000xxxF0??? mov? [ ebp + IoControlCode ],? eax
. text : 000xxxF3??? mov?eax ,?[ esp + 5Ch - 6Ch ]?;? 反調試代碼
. text : 000xxxF7??? push?eax
. text : 000xxxF8??? mov?eax ,?[ esp + 60h - 6Ch ]
.
text : 000xxxFC??? pop?ebx
. text : 000xxxFD??? cmp?eax ,? ebx
. text : 000xxxFF??? jz? short? loc_1240B? ;? 如果沒有被調試則會跳轉
. text : 000xxx01??? mov?eax ,? 200EDBh
. text : 000xxx06??? not? eax
. text : 000xxx08??? push?eax
. text : 000xxx09??? pop?edi
. text : 000xxx0A??? stosd

. text : 000xxxF3??? mov?eax ,?[ esp + 5Ch + 6Ch ]? 當單步執行到這條指令或者在這條指令上設置斷點的時候,因為當調試器在這條指令上彈出的時候會
用到被調試程序的堆棧來保存?EFLAGS
, CS , EIP ,? (如果? int? 1 , 或? int? 3? 處理函數用任務門就可以解決這個問題。)例如?當代碼執行到這條指令時
ESP?
=? 805E4320h??? 執行完這條指令是?eax?的值為? [ ESP + 5Ch - 6Ch ]=[ ESP - 10h ]=[ 805E4320h - 10h ]=[ 805E4310h ]? 的值。
當單步執行到?
. text : 000xxxF8??? mov?eax ,?[ esp + 60h - 6Ch ]? 指令的時候?ESP = 805E432Ch? 以為其中入棧了一個?eax?所以?ESP = 805E432Ch ,
執行完? . text : 000xxxF8??? mov?eax ,?[ esp + 60h - 6Ch ]? 條指令的時候?eax? =?[ ESP + 60h - 6Ch ]=[ ESP - Ch ]=[ 805E432Ch - Ch ]=[ 805E4310h ]
如果不調試的情況下?讀的是同一個地址的值,所以兩個值比較應該是相同的?也就是? . text : 000xxxFD??? cmp?eax ,? ebx?這條指令的比較結果
應該是相同的。這個指令?
. text : 000xxxFF??? jz? short? loc_1240B?執行后直接跳轉到。
如果是被調試器調試的情況下?
. text : 000xxxFF??? jz? short? loc_1240B?不會跳轉。?如果不跳轉時下面的代碼?會覆蓋掉系統的當前?ETHREAD
指針。接下來在調用很多系統函數都會導致系統崩潰,并且是崩潰到系統模塊里面,這樣給你定位錯誤帶來誤導。哈哈



. text : 000xxx68??? push? 1? ;? Alignment
. text : 000xxx6A??? push? 40h? ;? Length
. text : 000xxx6C??? push?CurrentEProcessObject? ;? Address
. text : 000xxx72??? call?ds : ProbeForRead

這里是故意做個異常來實現跳轉。如果你在?
. text : 000xxx72??? call?ds : ProbeForRead?指令上單步執行的時候調試器會跑飛了,
也就是說從調試器退出了,沒有繼續跟蹤下去。


接下來說我們的?PspCidTable?我們找到了?PspCidTable?變量后,?PspCidTable?
[ 這個?HANDLE_TABLE?的句柄表中,保存著所有進程和線程對象的指針。
PID(進程ID)和?ThreadID(線程ID)就是在這個句柄表中的索引。這個?HANDLE_TABLE?不屬于任何進程,也沒有鏈在?HANDLE_TABLE?鏈上。全局變量
PspCidTable?中是指向這個?HANDLE_TABLE?的指針。這個?HANDLE_TABLE?還有一點和別的?HANDLE_TABLE?都不同,就是它的?HANDLE_TABLE_ENTRY?中的
第一個
32bit? 放著的是對象體指針(當然需要轉換)而不是對象頭指針(對象指針就是對象體指針)。 ]?( 特別注明?在 [] 的話不是俺寫的是在網上抄來的
這里特別感謝?“JIURL玩玩Win2k進程線程篇?HANDLE_TABLE”?文章的作者:JIURL?
)
我們之要想到辦法遍歷這個?PspCidTable?句柄表就可以遍歷到系統的所有進程。icesword?為了遍歷這個表他使用了系統為公開的?ntoskrnl . exe
的導出函數?ExEnumHandleTable?。

icesword?定位到?ntoskrnl
. exe?導出的?ExEnumHandleTable函數。
這個函數是未公開的函數。
這個函數的函數原形可能是?VOID?STDCALL?ExEnumHandleTable?
( PULONG?HandleTable ,? PVOID?Callback ,? PVOID?Param ,? PHANDLE?Handle?OPTIONAL );

其中的參數?PULONG?HandleTable?就可以用?PspCidTable?做參數 .
PVOID?Callback?的類型為? bool? (* EXENUMHANDLETABLECALLBACK )( HANDLE_TALBE_ENTRY *, DWORD?PID , PVOID?Param )? 函數指針。
PVOID?Param?參數就是傳送給回調函數的參數。
PHANDLE?Handle?OPTIONAL?這個參數俺還沒搞懂什么意思。在說俺也用不到他,所以也不管他了隨他去吧。

當調用?ExEnumHandleTable?函數的時候?函數在每次枚舉到表中的一個句柄時都會調用一次回調函數。

當調用的?Callback?回調函數返回值為?
0? 時繼續枚舉句柄表,如果返回? 1? 時則停止枚舉。