⑴ HOOK NtCreateThread 怎麼獲得 創建進程的命令行參數
線程注入,說到底還是創建線程,只不過創建的線程在別的進程中運行而已,那麼,我們需要下手的地方跟監控普通線程創建需要下手的地方應當是一樣的——NtCreateThread(不過這不是一個太好的選擇,因為NtCreateThread的定位稍顯麻煩。這兒只是一個為了說明如何監控而做一個例子而已,所以盡可能選擇簡單的方法來掛鉤,修改ssdt可以掛這個函數,所以就選擇了它。^-^更好的選擇見後文)。
我認為我有必要先列出NtCreateThread的原型:
NTSTATUS
NtCreateThread(
__out PHANDLE ThreadHandle,
__in ACCESS_MASK DesiredAccess,
__in_opt POBJECT_ATTRIBUTES ObjectAttributes,
__in HANDLE ProcessHandle,
__out PCLIENT_ID ClientId,
__in PCONTEXT ThreadContext,
__in PINITIAL_TEB InitialTeb,
__in BOOLEAN CreateSuspended
)
__out的那些就都不解釋了,主要解釋需要用到的。
access_mask是訪問許可權,不解釋;ObjectAttr是對象屬性,_opt的,不解釋;ProcessHandle就是將要被創建線程的進程(新線程在這個進程中運行)的句柄了;剩下幾個也跟本文無關,也不解釋,需要了解詳細信息可以查MSDN。
需要關注的參數我想我已經說的很明確了——ProcessHandle。我們可以使用ObReferenceObjectByHandle來獲取這個Handle所指向的進程的EPROCESS的地址(PEPROCESS)。
剩下的就是判斷這個線程是正常創建還是遠程創建的問題了,這個判斷將會變得很容易,因為我們可以用獲得到PEPROCESS和IoGetCurrentProcess的結果相比較——NtCreateThread總是應當在創建者的進程上下文中被執行。
不過當我們編譯運行之後就會發現,還有一個問題是我們不得不關注的——運行程序的時候父進程會「幫助」子進程創建子進程的主要線程(因為這個時候子進程還沒有線程,所以不可能自己創建),所以這就引出了另外一個問題——如何判斷這個遠程線程創建是注入還是正常的程序運行呢?
簡單分析下我們不難發現,二者的區別在於將被創建線程的進程是否還存在其他線程——因為我們的代碼是在NtCreateThread之前執行的,所以如果是正常運行的程序的話,這個時候它不應當有任何的線程,而線程注入則不同,線程注入的話目標進程應當已經有了至少一個線程(一個主線程和若干個附屬線程(或者沒有附屬線程))。
那麼如何判斷目標進程是否已經存在線程呢?在EPROCESS結構中:
...
+0x190 ThreadListHead : _LIST_ENTRY
...
我想這個ThreadListHead這是很容易理解的。。。。
實現方法(因為只是為了演示,所以所有的地址、偏移等都是硬編碼):
ThreadListHead = 0x190;
//==========================================
BOOLEAN
ProcessNoThread( PEPROCESS Process)
{
PLIST_ENTRY Entry;
PLIST_ENTRY ThreadListEntry;
PLIST_ENTRY ListHead;
ThreadListEntry = (PLIST_ENTRY)((ULONG)Process + ThreadListHead);
Entry = ThreadListEntry->Flink;
return (Entry==ThreadListEntry);
}
//==========================================
NTSTATUS new_NtCreateThread(
OUT PHANDLE ThreadHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
IN HANDLE ProcessHandle,
OUT PCLIENT_ID ClientId,
IN PCONTEXT ThreadContext,
IN PVOID InitialTeb,
IN BOOLEAN CreateSuspended )
{
NTSTATUS st;
PVOID pepCurrentProcess=0;
st=ObReferenceObjectByHandle(ProcessHandle,(ACCESS_MASK)PROCESS_ALL_ACCESS,NULL,KernelMode,&pepCurrentProcess,NULL);
if (NT_SUCCESS(st))
{
if (IoGetCurrentProcess()!=pepCurrentProcess)
{
if (!ProcessNoThread((PEPROCESS)pepCurrentProcess)) DbgPrint("PROCESS 0x%X have created a thread into PROCESS 0x%X, NtCreateThread return value = 0x%x",IoGetCurrentProcess(),pepCurrentProcess,st);
}
ObDereferenceObject((PVOID)pepCurrentProcess);
}
st=old_NtCreateThread(ThreadHandle,DesiredAccess,ObjectAttributes,ProcessHandle,ClientId,ThreadContext,InitialTeb,CreateSuspended);
return st;
}
//==========================================
我想這段代碼也不至於太難理解。。
是時候說說其他的一些問題了:
1、其實hook PspCreateThread要比hook NtCreateThread相對容易一些(兩個函數同樣都沒有被導出,而PspCreateThread可以很容易的在PsCreateSystemThread中被定位,但NtCreateThread的定位就需要分析PE文件了(改SSDT另當別論,不過改SSDT的強度太差))。
2、遍歷進程的線程是使用硬編碼的,這使得通用性變得很差,而通過遍歷PspCidTable枚舉系統中的線程則成為一種不錯的方法(從PspCreateThread中的代碼來看,是由PspCreateThread在PspCidTable中ExCreateHandle的,但是我沒有做測試)
就這樣把,我自負的認為我的語言表達能力還算是不錯的。
---EOF---