某些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
[email protected]*/
#ifndef _DRX_HOOK
#define _DRX_HOOK
#include <ntddk.h>
typedef unsigned long DWord;
typedef unsigned char BOOL;
#pragma pack(push,1)
typedef struct _idtr
{
//定義中斷描述符表的限制,長度兩字節;
short IDTLimit;
//定義中斷描述服表的基址,長度四字節;
unsigned int IDTBase;
}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
[email protected]*/
#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
{
push eax
mov eax,
g_CR0
mov cr0, eax
pop eax
sti
}
}
void CliAndDisableWPBit()
{
__asm
{
cli
push eax
mov eax, cr0
mov g_CR0, eax
and eax, 0xFFFEFFFF
mov cr0, eax
pop eax
}
}
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查看效果.
由於本人水平有限,代碼中難免有錯誤出現,希望指正.
同時也希望各位牛人來指點,[email protected]
/**************下面是羅嗦幾句********************/
1.這個方法呢首先不怎麼實用,因為你用了調試寄存器後某些殼也想用,因此就沖突了,可能會使某些東西失效,不如傳統的HOOK好用(據說利用缺頁中斷HOOK也比較好用,沒試過)。還有人認為呢這個HOOK雖然是HOOK成功了,但是還得HOOK中斷向量,沒有必要。其實呢,只是多了種思路罷了,多給大家提供一些想法而已。
2.其次呢,這個方法是我在調試某ARK時想到的,這個ARK的作者說他們會恢復函數的inline hook然後才去調用(大面積的恢復,甚至是整個文件的恢復),於是我就用調試器在該函數上下斷點,結果自然是沒有斷下了,因為下的 (0xcc)斷點被恢復了。於是就索性下了個硬件斷點,這下就斷住了,然後呢就想到了拿這個東西來HOOK。然後就去網上搜資料,發現不少人還是稍微提到過這個方法的,包括 vxk,xikug,都說過。