Windows核心新手上路3——掛鉤KeUserModeCallBack

whatday發表於2013-07-20

Windows核心新手上路3——掛鉤KeUserModeCallBack

1.     簡介

在Windows系統中,提供了幾種方式從R0呼叫位於R3的函式,其中一種方式是KeUserModeCallBack,此函式流程如下:

nt!KeUserModeCallback->nt!KiCallUserMode->nt!KiServiceExit->ntdll!KiUserCallbackDispatcher->回撥函式-> int2B->nt!KiCallbackReturn-> nt!KeUserModeCallback(呼叫後)這是一個 ring0->ring3->ring0的過程,在堆疊準備完畢後,借用KiServiceExit的力量回到了ring3,它的著陸點是 KiUserCallbackDispatcher,然後KiUserCallbackDispatcher從PEB中取出 KernelCallbackTable的基址,再以ApiIndex作為索引在這個表中查詢對應的回撥函式並呼叫,呼叫完之後再int2B觸發 nt!KiCallbackReturn再次進入核心,修正堆疊後跳回KeUserModeCallback,完成呼叫。

系統所有的訊息鉤子回撥都是利用KeUserModeCallBack完成的。所以可以通過掛鉤KeUserModeCallback用來過濾對鉤子的呼叫。

1.1 inline hook

KeUserModeCallback沒有對應的R3呼叫介面,所以沒有在SSDT SHADOW中出現,需要用另外的方式來HOOK,可以採用的Inline Hook,即在函式頭部加入一條JMP指令(機器碼E9)跳入代理函式,然後過濾之後決定是否呼叫真實的KeUserModeCallback函式。掛鉤過程如下:

ULONG StartHookKeyUserModeCallBack()

{

         ULONG tmp;

         memset (Ori_Func, 0x90, 100); // nop

         Ori_Func[50] = 0xE9;

         tmp = (ULONG)ProxyFunc - (ULONG)KeUserModeCallback - 5;

         memcpy(jmp_bytes+1, &tmp, 4);

         HeadLen = GetPatchSize (KeUserModeCallback, 5);

         memcpy(Ori_Func, (PVOID)KeUserModeCallback, HeadLen); //原始位元組

         //中間跳

         tmp = (ULONG)KeUserModeCallback + HeadLen - (ULONG)(&Ori_Func[50]) - 5;

         memcpy(&Ori_Func[51], &tmp, 4);

         // 去掉記憶體保護

         __asm

         {

                   cli

                   mov     eax, cr0

                   and     eax, not 10000h

                   mov     cr0, eax

         }

         memcpy(KeUserModeCallback, jmp_bytes, 5);

         // 恢復記憶體保護

         __asm

         {

                   mov     eax, cr0

                   or     eax, 10000h

                   mov     cr0, eax

                   sti

         }

         return TRUE;

}

程式碼1 掛鉤KeUserModeCallback

 

代理函式ProxyFunc程式碼如程式碼2:

__declspec(naked) VOID ProxyFunc(VOID)

{

         __asm

         {

                   MOV   EAX,  ESP

                   PUSHAD

                  

                   PUSH  [EAX+4*5]

                   PUSH  [EAX+4*4]

                   PUSH  [EAX+4*3]

                   PUSH  [EAX+4*2]

                   PUSH  [EAX+4]

                   LEA   EAX, MyKeUserModeCallback

                   CALL  EAX

 

                   TEST  EAX, EAX

                   JZ   continue_exe //return STATUS_SUCCESS

 

                   POPAD

                   RETN  0x14

                  

continue_exe:

                   POPAD

                   LEA   EAX,  Ori_Func //跳回原始函式

                   JMP   EAX

         }

}

程式碼2     代理函式ProxyFunc

首先呼叫自定義的過濾函式MyKeUserModeCallback,然後判斷返回值是否是STATUS_SUCCESS,如果是,則呼叫真實的KeUserModeCallback,否則直接返回,拒絕呼叫R3的回撥用函式。

1.2  MyKeUserModeCallback

下面分部分講解MyKeUserModeCallback對鉤子的過濾操作,其中涉及到很多未公開技術,其程式碼通過逆向工程技術得到。

1.2.1 過濾WH_KEYBOARD_LL型別鉤子

在Windows作業系統中,按鍵訊息到達具體的視窗訊息佇列之前會呼叫WH_KEYBOARD_LL鉤子函式,可以利用此類鉤子擷取鍵盤輸入。虛擬碼如程式碼3所示。

判斷ApiNumber是否是KBD_LL_HOOK_API_NUM

將InputBuffer轉換為PFNHKINLPKBDLLHOOKSTRUCTMSG

填充SYS_EVENT_STRUCT結構,通知R3控制程式作出判斷

如果使用者允許該呼叫則返回STATUS_SUCCESS

否則返回STATUS_UNSUCCESSFUL

虛擬碼3        WH_KEYBOARD_LL過濾

在對WH_KEYBOARD_LL的過濾過程中,還有一點需要做特殊處理。現在有很多的軟體採用WH_KEYBOARD_LL鉤子來做安全輸入控制元件,而不是通過傳統的EDIT控制元件,所以在返回的時候需要將虛擬鍵盤產生的虛假的按鍵修正為真實的按鍵。

1.2.2      過濾WH_KEYBOARD鉤子

在Windows作業系統中,按鍵訊息到達具體的視窗訊息佇列之前會呼叫WH_KEYBOARD鉤子函式,可以利用此類鉤子擷取鍵盤輸入。過濾的虛擬碼如程式碼4所示。

判斷ApiNumber是否是KBD_HOOK_API_NUM

將InputBuffer轉為PFNHKINLPKBDLLHOOKSTRUCTMSG結構

判斷PFNHKINLPKBDLLHOOKSTRUCTMSG結構中GeneralHookHeader成員的nCode成員的值是否為0x20000

如果是則是WH_KEYBOARD鉤子

填充SYS_EVENT_STRUCT結構,通知R3控制程式作出判斷

如果使用者允許該呼叫則返回STATUS_SUCCESS

否則返回STATUS_UNSUCCESSFUL

 

虛擬碼4        WH_KEYBOARD鉤子過濾

WH_KEYBOARD鉤子會同其他一些回撥共用一個ApiNumber,這裡要做進一步的判斷,而且全域性的WH_KEYBOARD鉤子需要在其他程式中LoadLibrary。

同WH_KEYBOARD_LL鉤子,有的程式會利用WH_KEYBOARD鉤子做安全輸入控制元件,在返回時需要將虛擬鍵盤產生的虛假按鍵修正為真實的按鍵。

1.2.3      WH_DEBUG鉤子過濾

在Windows作業系統中,呼叫其他型別的鉤子函式之前,會首先呼叫WH_DEBUG鉤子函式,用於鉤子除錯。惡意程式利用WH_DEBUG型別鉤子可以獲取鍵盤輸入。過濾虛擬碼如程式碼5所示。

判斷ApiNumber是否是DEBUG_HOOK_API_NUM

判斷此DEBUG資訊中的鉤子型別是否包含鍵盤輸入資訊

如果包含,則填充SYS_EVENT_STRUCT結構,通知R3控制程式作出判斷

如果使用者允許該呼叫則返回STATUS_SUCCESS

否則返回STATUS_UNSUCCESSFUL

 

虛擬碼5        WH_DEBUG鉤子過濾

同WH_KEYBOARD_LL WH_KEYBOARD型別的鉤子,有些程式會使用WH_DEBUG鉤子實現安全輸入控制元件,在返回時,需要將虛擬鍵盤產生的虛假按鍵修正為真實的按鍵。

1.2.4      Ke_LoadLibrary過濾

在Windows作業系統中,除了WH_KEYBOARD_LL鉤子外,其他型別的全域性訊息鉤子都需要有DLL被載入進入目標程式(WH_KEYBOARD_LL鉤子只是一次執行緒切換,詳細資訊可以檢視MSDN)。所以過濾Ke_LoadLibrary可以阻止大部分的訊息鉤子(事實上,目前所有的密碼保護程式都通過此方法過濾訊息鉤子,用來保護密碼,但是此方法對WH_KEYBOARD_LL型別的鉤子無效)。過濾虛擬碼如程式碼6所示。

判斷ApiNumber是否LOAD_LIBRARY_API_NUM

如果是,獲取引數資訊(包括DLL路徑)

填充SYS_EVENT_STRUCT結構,通知R3控制程式作出判斷

如果使用者允許該呼叫則返回STATUS_SUCCESS

否則返回STATUS_UNSUCCESSFUL

虛擬碼6        Ke_LoadLibrary過濾

1.2.5      WH_JOURNALRECORD過濾

       在Windows作業系統中,WH_JOURNALRECORD設計的初衷是做訊息的記錄和回放,但是惡意程式利用此型別的鉤子可以獲取鍵盤輸入訊息,從而達到記錄密碼的目的。過濾虛擬碼如程式碼7所示。

判斷ApiNumber是否EVENT_MSG_HOOK_API_NUM

進一步判斷訊息型別是否是按鍵訊息

填充SYS_EVENT_STRUCT結構,通知R3控制程式作出判斷

如果使用者允許該呼叫則返回STATUS_SUCCESS

否則返回STATUS_UNSUCCESSFUL

虛擬碼7        WH_JOURNALRECORD過濾

相關文章