簡析利用除錯暫存器實現核心函式的HOOK
某些RK,木馬會經常HOOK一些關鍵函式從而達到隱藏等目的,而相應的ARK檢測軟體也會通常會先恢復這些關鍵函式的HOOK(譬如利用硬碟檔案恢復),然後再呼叫來檢測RK,這樣就可以檢測出某些隱藏.下面就介紹利用偵錯程式實現某些核心函式的HOOK.
Intel386以後的系列CPU增加了8個32位的除錯暫存器,從Dr0到Dr7,方便除錯使用.如果設定了相應的除錯資訊,在條件滿足的情況下將會發生 1 號(DB例外)中斷,CPU就會陷入中斷例程,執行中斷程式碼,我們的HOOK目的就可以通過這個實現.
首先看下面百度出來的對暫存器組的使用方法的解釋:
這八個暫存器中由四個用於斷點,兩個用於控制,另兩個保留未用。對這八個暫存器的訪問,只能在0級特權級進行。在其它任何特權級對這八個暫存器中的任意一個暫存器進行讀或寫訪問,都將產生無效操作碼異常。此外,這八個暫存器還可用DR6及DR7中的BD位和GD位進行進一步的保護,使其即使是在0級也不能進行讀出或寫入。
對這些暫存器的訪問使用通常的MOV指令:
MOV reg Dri 該指令將除錯暫存器i中的內容讀至通用暫存器reg中;
MOV Dri reg 該指令將通用暫存器reg中的內容寫至除錯暫存器i中。此處i的取值可以為0至7中的任意值。
這些暫存器的功能如下:
DR0—DR3 暫存器DR0—DR3包含有與四個斷點條件的每一個相聯絡的線性地址(斷點條件則在DR7中)。因為這裡使用的是線性地址,所以,斷點設施的操作,無論分頁機制是否啟用,都是相同的。
DR4—DR5 保留。
DR6是除錯狀態暫存器。當一個除錯異常產生時,處理器設定DR6的相應位,用於指示除錯異常發生的原因,幫助除錯異常處理程式分析、判斷,以及作出相應處理。
DR7是除錯控制暫存器。分別對應四個斷點暫存器的控制位,對斷點的啟用及斷點型別的選擇進行控制。所有斷點暫存器的保護也在此暫存器中規定。
|---------------|----------------|
Dr6 | |BBB BBB B |
| TSD 3 2 1 0 |
| --------------|----------------|
Dr7 |RWE LEN ... RWE LEN | G GLGLGLGLGL |
| 3 3 ... 0 0 | D EE33221100 |
|---------------|----------------|
31 15 0
DR6各位的功能
B0—B3(對應0-3位) 當斷點線性地址暫存器規定的條件被檢測到時,將對應的B0—B3位置1。置位B0—B3與斷點條件是否被啟用無關。即B0—B3的某位被置1,並不表示要進行對應的斷點異常處理。
BD(13位) 如下一條指令要對八個除錯暫存器之一進行讀或寫時,則在指令的邊界BD位置1。在一條指令內,每當即將讀寫除錯暫存器時,也BD位置1。BD位置1與DR7中GD位啟用與否無關。
BS(14位) 如果單步異常發生時,BS位被置1。單步條件由EFLAGS暫存器中的TF位啟用。如果程式由於單步條件進入除錯處理程式,則BS位被置1。與DR6中的其它位不同的是,BS位只在單步陷阱實際發生時才置位,而不是檢測到單步條件就置位。
BT(15位) BT位對任務切換導致TSS中的除錯陷阱位被啟用而造成的除錯異常,指示其原因。對這一條件,在DR7中沒有啟用位。
DR6中的各個標誌位,在處理機的各種清除操作中不受影響,因此,除錯異常處理程式在執行以前,應清除DR6,以避免下一次檢測到異常條件時,受到原來的DR6中狀態位的影響。
DR7各位的功能
LEN LEN為一個兩位的欄位,用以指示斷點的長度。每一斷點暫存器對應一個這樣的欄位,所以共有四個這樣的欄位分別對應四個斷點暫存器。LEN的四種譯碼狀態對應的斷點長度如下
LEN 說明
0 0 斷點為一位元組
0 1 斷點為兩位元組
1 0 保留
1 1 斷點為四位元組
這裡,如果斷點是多位元組長度,則必須按對應多位元組邊界進行對齊。如果對應斷點是一個指令地址,則LEN必須為00
RWE RWE也是兩位的欄位,用以指示引起斷點異常的訪問型別。共有四個RWE欄位分別對應四個斷點暫存器,RWE的四種譯碼狀態對應的訪問型別如下
RWE 說明
0 0 指令
0 1 資料寫
1 0 保留
1 1 資料讀和寫
GE/LE GE/LE為分別指示準確的全域性/區域性資料斷點。如果GE或LE被置位,則處理器將放慢執行速度,使得資料斷點準確地把產生斷點的指令報告出來。如果這些位沒有置位,則處理器在執行資料寫的指令接近執行結束稍前一點報告斷點條件。建議讀者每當啟用資料斷點時,啟用LE或GE。降低處理機執行速度除稍微降低一點效能以外,不會引起別的問題。但是,對速度要求嚴格的程式碼區域除外。這時,必須禁用GE及LE,並且必須容許某些不太精確的除錯異常報告。
L0—L3/G0—G3 L0—L3及G0—G3位
分別為四個斷點暫存器的區域性及全域性啟用訊號。如果有任一個區域性或全域性啟用位被置位,則由對應斷點暫存器DRi規定的斷點被啟用。
GD GD位啟用除錯暫存器保護條件。注意,處理程式在每次轉入除錯異常處理程式入口處清除GD位,從而使處理程式可以不受限制地訪問除錯暫存器。
前述的各個L位(即LE,L0—L3)是有關任務的區域性位,使除錯條件只在特定的任務啟用。而各個G位(即GD,G0—G3)是全域性的,除錯條件對系統中的所有任務皆有效。在每次任務切換時,處理器都要清除L位。
如果你耐心把上面的資訊看完了,基本上也就應該 明白了.其實我們可以利用除錯暫存器做的不只是函式的HOOK,也可以進行I/O的HOOK,下面要說的是在指定的核心函式上下指令執行斷點,然後掛接1號中斷實現HOOK(有很多種方法可以實現,看你自己喜歡哪種了).
首先把DR0暫存器設定為要掛接的核心函式的地址(譬如ZwCreateFile),然後修改 Dr7的L0和G0(第0和第1位)都為1, Dr7的 R/W0(16.17位) 為00, LEN0(18.19位)位為00,這樣當CPU執行到ZwCreateFile地址的時候,就會進入1號中斷例程.
然後我們應該去修改IDT表,將1號中斷指向我們的處理程式。
接著需要考慮在中斷例程我們要做的事情,關於中斷時CPU具體做了什麼,大家可以去搜索.下面只介紹我們所關心的在核心空間發生DB例外時的情況:中斷髮生時,依次將 EFLAGS,CS,EIP壓入堆疊,然後進入中斷程式(由於中斷地址本來就在核心空間,所以不需要切換堆疊).在中斷處理程式中作出相應的處理,然後使用 iretd 指令退出中斷.( iretd 指令: 依次將堆疊彈出到 EIP,CS,EFLAGS),我們可以通過修改堆疊中EIP的值,在中斷返回時跳轉實現HOOK。
上面就是主要的內容,但是還有點問題.Windows在KiFastCall和執行緒切換時會修改Drx的值,為了防止我們的斷點被清除,可以利用Dr7: GD位保護暫存器,這樣任何對除錯暫存器的操作(讀和寫)都會產生DB例外然後進入1號中斷例程.這樣,我們在中斷例程中又需要利用Dr6的標識位處理那些因為操作除錯暫存器產生的例外(關於這個我只是簡單的跳過了那些對DRX操作的程式碼,並沒有詳細分析).
下面可以看詳細的實現程式碼:
簡單實現HOOK下ZwCreateFile,XP下,其他系統慎用
/* drxhook.h Written By yykingking@126.com */ #ifndef _DRX_HOOK #define _DRX_HOOK #include <ntddk.h> typedef unsigned long DWORD; typedef unsigned char BOOL; #pragma pack(push,1) typedef struct _idtr { //定義中斷描述符表的限制,長度兩位元組; shortIDTLimit; //定義中斷描述服表的基址,長度四位元組; unsigned intIDTBase; }IDTR,*PIDTR; typedef struct _IDTENTRY { unsigned short LowOffset; unsigned short selector; unsigned char unused_lo; unsigned char segment_type:4;//0x0E is an interrupt gate unsigned char system_segment_flag:1; unsigned char DPL:2;// descriptor privilege level unsigned char P:1; /* present */ unsigned short HiOffset; } IDTENTRY,*PIDTENTRY; #pragma pack(pop) DWORD GetDBEntry(); void HookDBInt(); void UnHookDBInt(); #endif /* drxhook.cpp Written By yykingking@126.com */ #include "drxhook.h" DWORD g_OldDBEntry; IDTR g_IDTR; DWORD g_OldCreateFile; DWORD g_HookNumber = 0; DWORD g_CR0; BOOL g_bExit; void ReLoadCR0AndSti() { __asm { pusheax moveax, g_CR0 movcr0, eax popeax sti } } void CliAndDisableWPBit() { __asm { cli pusheax moveax, cr0 movg_CR0, eax andeax, 0xFFFEFFFF movcr0, eax popeax } } void PrintHook() { DbgPrint(" Now Get In ZwCreateFile Hook: %d...Pid: %d.../n", g_HookNumber++, (DWORD)PsGetCurrentProcessId()); } __declspec(naked) void NewZwCreateFile() { __asm { pushfd;// 僅僅適合於 XP 作業系統 call PrintHook; popfd; mov eax,0x25; jmp g_OldCreateFile; } } void SetHB()// set hardware breakpoint 設定硬體斷點 { __asm { mov eax, ZwCreateFile;// 想要掛接的函式或者地址 mov dr0, eax; mov eax, dr7; or eax, 0x2703;// 也要修改 dr7:GD 位,以免DrX被作業系統或其他程式修改 and eax, 0xfff0ffff; mov dr7, eax; } } __declspec(naked) void NewDBEntry() { __asm { pushfd; push eax; mov eax, dr6; test eax, 0x2000; jz NOT_EDIT_DRX; // 以下是如果有對DRX的操作的簡單處理,如有需要可以修改 // 我只是簡單的跳過這些指令 and eax, 0xFFFFDFFF; mov dr6, eax;// 清除DR6的標誌 cmp g_bExit, 0; jnz MY_DRV_EXIT;// 驅動 Unload mov eax, [esp+8];// 獲取堆疊中的 EIP add eax, 3;// 由於所有對 DRX 的操作全都是3個位元組的 mov [esp+8], eax;// 修改 EIP ,跳過當前指令,返回時執行下條指令 jmp MY_INT_END; NOT_EDIT_DRX: mov eax, dr6; test eax, 0x1; jz SYS_INT;// 如果不是Dr0 產生的中斷,則跳回原系統中斷 mov eax, [esp+8]; cmp eax, ZwCreateFile;// 判斷一下是不是 ZwCreateFile 的線性地址 jnz SYS_INT; mov eax, NewZwCreateFile; mov [esp+8],eax;// 修改堆疊中的 EIP ,實現返回時跳轉 MY_INT_END: mov eax, dr7; or eax, 0x2000;// 恢復 GD 位 mov dr7, eax; MY_DRV_EXIT:// 整個驅動 UnLoad 時,不恢復 Dr7 pop eax; popfd; iretd; SYS_INT: pop eax; popfd; jmp g_OldDBEntry; } } DWORD GetDBEntry() { PIDTENTRY IdtEntry; DWORD Entry; __asm sidt g_IDTR; IdtEntry = (PIDTENTRY)(g_IDTR.IDTBase + 8); Entry = IdtEntry->HiOffset << 16; Entry |= IdtEntry->LowOffset; return Entry; } void HookDBInt() { DWORD NewEntry; PIDTENTRY IdtEntry; NewEntry = (DWORD)NewDBEntry; g_OldCreateFile = (DWORD)ZwCreateFile + 5;// 新的要跳轉過去的地址 g_OldDBEntry = GetDBEntry(); IdtEntry = (PIDTENTRY)(g_IDTR.IDTBase + 8); CliAndDisableWPBit(); IdtEntry->LowOffset = (USHORT)NewEntry; IdtEntry->HiOffset = (USHORT)( NewEntry >> 16 ); ReLoadCR0AndSti(); SetHB(); g_bExit = FALSE; return; } void UnHookDBInt() { PIDTENTRY IdtEntry; DWORD Entry; __asm sidt g_IDTR; IdtEntry = (PIDTENTRY)(g_IDTR.IDTBase + 8); CliAndDisableWPBit(); g_bExit = TRUE; __asm mov eax, dr7;// 產生一次例外並且清除Dr7:GD if ( g_OldDBEntry != 0 ) { IdtEntry->LowOffset = (USHORT)g_OldDBEntry; IdtEntry->HiOffset = (USHORT)( g_OldDBEntry >> 16 ); } ReLoadCR0AndSti(); DbgPrint(" UnLoad drx hook../n"); return; } NTSTATUS DriverUnload(IN PDRIVER_OBJECT DriverObject) { UnHookDBInt(); return STATUS_SUCCESS; } NTSTATUS DriverEntry( IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath ) { HookDBInt(); DriverObject->DriverUnload = DriverUnload; DbgPrint("Load drxhook Driver Ok.../n"); return STATUS_SUCCESS; }
/***********************/
以上程式碼實現了簡單的ZwCreateFile 函式的HOOK,可以拿DbgView檢視效果.
本人水平有限,程式碼中難免有錯誤出現,希望指正.
同時也希望各位牛人來指點,dddxxxx007@sina.com
/**************下面是羅嗦幾句********************/
1.這個方法呢首先不怎麼實用,因為你用了除錯暫存器後某些殼也想用,因此就衝突了,可能會使某些東西失效,不如傳統的HOOK好用(據說利用缺頁中斷HOOK也比較好用,沒試過)。還有人認為呢這個HOOK雖然是HOOK成功了,但是還得HOOK中斷向量,沒有必要。其實呢,只是多了種思路罷了,多給大家提供一些想法而已。
2.其次呢,這個方法是我在除錯某ARK時想到的,這個ARK的作者說他們會恢復函式的inline hook然後才去呼叫(大面積的恢復,甚至是整個檔案的恢復),於是我就用偵錯程式在該函式上下斷點,結果自然是沒有斷下了,因為下的 (0xcc)斷點被恢復了。於是就索性下了個硬體斷點,這下就斷住了,然後呢就想到了拿這個東西來HOOK。然後就去網上搜資料,發現不少人還是稍微提到過這個方法的。