也谈SSDT Hook(一)
一、原理篇
1.??????????? 關于系統服務。
系統服務是由操作系統提供一組函數,使得開發者能夠通過APIs直接或間接的調用。一個API可以對應一個系統服務,也可以一個API依賴多個系統服務。比如,WriteFile API對應的系統服務是ntoskrnl.exe中的NtWriteFile。系統服務分發屬于陷阱分發的范疇,更詳細的資料可參考’Windows Internal(4th edition)’相關章節。從APIs到系統服務的分發過程可簡化為圖1:
?
?
?
圖1
圖1只表現了ntdll.dll分發系統服務陷阱的過程,對于GDI/USER過程,它是負責管理圖形界面的,暫不作考慮。要鉤住系統服務當然要修改服務分發表了(要搞系統服務當然不只值一個方法,但是本文只考慮怎樣通過SSDT來做),所以,關鍵是要找到服務分發列表的索引號(0,1,2,…,n),就可以找到相應的系統服務內存入口地址。系統服務分發表的結構可以直觀的簡化為圖2:
圖2
?
Windows系統服務是Nt*系列的Native APIs,他們在內存中的入口地址保存在SSDT中。另外,還應該注意Zw*系列的Native APIs,這是以Nt開頭的系統服務入口點的鏡像,它把原先的訪問模式設置為內核模式,從而消除了參數的有效性檢查過程,因為Nt系統服務只有當原來的訪問模式為ring 3時才進行參數檢查。多說幾句,除了在ring 0的ntoskrnl.exe有導出中,在ring 3的ntdll.dll中也有這個兩系列的函數。這四者的關系怎樣呢?以NtQuerySystemInformation系統服務為例:
?
Ring 3
lkd>? u ntdll!ZwQuerySystemInformation L4
ntdll!ZwQuerySystemInformation:
7c92e1aa b8ad000000????? mov???? eax,0ADh
7c92e1af ba0003fe7f????? mov???? edx,offset SharedUserData!SystemCallStub (7ffe0300)
7c92e1b4 ff12??????????? call??? dword ptr [edx]
7c92e1b6 c21000????????? ret???? 10h
?
lkd> u ntdll!NtQuerySystemInformation L4
ntdll!ZwQuerySystemInformation:
7c92e1aa b8ad000000????? mov???? eax,0ADh
7c92e1af ba0003fe7f????? mov???? edx,offset SharedUserData!SystemCallStub (7ffe0300)
7c92e1b4 ff12??????????? call??? dword ptr [edx]
7c92e1b6 c21000????????? ret???? 10h
?
由此可見,在Ring 3下ntdll.dll中,這兩個函數是完全一樣的。
Ring 0
lkd> u nt!ZwQuerySystemInformation L6
nt!ZwQuerySystemInformation:
804de440 b8ad000000????? mov???? eax,0ADh
804de445 8d542404??????? lea???? edx,[esp+4]
804de449 9c????????????? pushfd
804de44a6a08??????????? push??? 8
804de44ce8e0110000????? call??? nt!KiSystemService (804df631)
804de451 c21000????????? ret???? 10h
?
lkd> u nt!NtQuerySystemInformation
nt!NtQuerySystemInformation:
8057e786 6810020000????? push??? 210h
8057e78b 6830ab4e80????? push??? offset nt!ExTraceAllTables+0x1eb (804eab30)
8057e790 e8a64cf6ff????? call??? nt!_SEH_prolog (804e343b)
8057e795 33c0??????????? xor???? eax,eax
8057e797 8945e4????????? mov???? dword ptr [ebp-1Ch],eax
8057e79a8945dc????????? mov???? dword ptr [ebp-24h],eax
8057e79d 8945fc????????? mov???? dword ptr [ebp-4],eax
8057e7a0 64a124010000??? mov???? eax,dword ptr fs:[00000124h]
?
在Ring 0下,ZwQuerySystemInformation實現了對KiSystemService(系統服務分發器)的調用,并在阿函數開始的時候將索引號放入eax寄存器(mov eax,0ADh),這是我們需要的,通過0ADh可以找到系統服務NtQuerySystemInformation,下節詳細討論。
在’ Undocumented Windows 2000 Secrets’中有所闡述,這里讓大家看到事實了。找幾個其他的APIs嘗試一下,自己去悟吧,沒悟性成不了佛的。
?
1.??????????? 找到Hook入口
系統服務分發表是一個C的數據結構,ntolkrnl.exe導出了該結構的指針(符號為KeServiceDescriptorTable)。其實,內核還維護了一個替代的SDT,其名稱為:KeServiceDescriptorTableShadow,但這個SDT并沒有被ntolkrnl.exe導出。KeServiceDescriptorTable定義如下:
struct _KeServiceDescriptorTableEntry
{
unsigned int *ServiceTableBase;
unsigned int *ServiceCounterTableBase; //Used only in checked build
nsigned int NumberOfServices;
unsigned char *ParamTableBase;
} KeServiceDescriptorTableEntry, *PKeServiceDescriptorTableEntry
其第一個成員ServiceTableBase就是系統服務列表數組的其實地址。
首先,就是要獲取KeServiceDescriptorTableEntry的內存地址。由于KeServiceDescriptorTable已經被導出,所以導入KeServiceDescriptorTable即可:
extern PServiceDescriptorTableEntry KeServiceDescriptorTable;
創建訪問參考:
PServiceDescriptorTableEntry pSDT = KeServiceDescriptorTable;
不過,hoglund是這樣做的
__declspec(dllimport)? ServiceDescriptorTableEntry_t KeServiceDescriptorTable;
異曲同工,都是導入KeServiceDescriptorTable。
現在找到了SDT,有了一個好的開頭,接下來就是要找到關注的系統服務了,才能做一些想做的事情。回到ZwQuerySystemInformation的那段反匯編的代碼:
lkd> u nt!ZwQuerySystemInformation L6
nt!ZwQuerySystemInformation:
804de440 b8ad000000????? mov???? eax,0ADh
804de445 8d542404??????? lea???? edx,[esp+4]
804de449 9c????????????? pushfd
804de44a6a08??????????? push??? 8
804de44ce8e0110000 ?????call??? nt!KiSystemService (804df631)
804de451 c21000????????? ret???? 10h
它索引號0ADh放到了eax寄存器,dd一下:
lkd> dd nt!ZwQuerySystemInformation
804de440? 0000adb8 24548d00 086a9c04 0011e0e8
804de450? 0010c200 0000aeb8 24548d00 086a9c04
804de440是它的入口地址,AD就存放在那段機器碼里。這樣就可以了:
DWORD dwIndex= *(*ULONG)((UCHAR*)ZwQuerySystemInformation+1);
好多小星星(*),慢慢理解吧。轉換成匯編就容易理解了:
mov???? ecx, DWORD PTR [ZwQuerySystemInformation];
mov???? edx, [ecx+1];
最后,有了KeServiceDescriptorTable.ServiceTableBase的地址,又找到了索引號,這樣所關注系統服務就找到了。
KeServiceDescriptorTable.ServiceTableBase+ dwIndex*4
手工試一下,還是以ZwQuerySystemInformation為例子。
通過KeServiceDescriptorTable.ServiceTableBase獲取系統服務數組的起始地址
lkd> dd KeServiceDescriptorTable
8055a680? 804e36a8 00000000 0000011c80513eb8
是這里804e36a8,可以先都為快:
lkd> dd 804e36a8
804e36a8? 80580302 80579b8c8058b7ae 805907e4
804e36b8? 805905fe 806377a0 80639931 8063997a
804e36c8? 8057560b 806481cf 80636f5f8058fb85
804e36d8? 8062f0a4 8057be31 8058cc26 806261bd
804e36e8? 805dcf20 80568f9d 805d9ac1 805a2bb0
804e36f8? 804e3cb4 806481bb 805ca22c804f0e28
804e3708? 80569649 80567d49 8058fff3 8064e1c1
804e3718? 8058f8f5 80581225 8064e42ff584dc90
試一下第一個系統服務80580302是誰呢?
lkd> u 80580302
nt!NtAcceptConnectPort:
80580302 689c000000????? push??? 9Ch
80580307 68d8224f80????? push??? offset nt!_real+0x128 (804f22d8)
8058030c e82a31f6ff????? call??? nt!_SEH_prolog (804e343b)
80580311 64a124010000??? mov? ???eax,dword ptr fs:[00000124h]
80580317 8a8040010000??? mov???? al,byte ptr [eax+140h]
8058031d 884590????????? mov???? byte ptr [ebp-70h],al
80580320 84c0??????????? test??? al,al
80580322 0f84e9080300??? je????? nt!NtAcceptConnectPort+0x1df (805b0c11)
果然是NtAcceptConnectPort!套用算法公式找一下NtQuerySystemInformation:
804e36a8+0xAD*4 = 804E395C
lkd> dd 804E395C
804e395c? 8057e786 80590ad0 80591857 805871f3
804e396c? f7377b46 8056d338 80570e3b 8059068f
804e397c? 804e303a806477af 805710d8 805dae6c
804e398c? 8058f6a6 8057b545 8057dbee 80566809
804e399c? 8058b492 80567272 8065a3d6 8064e029
804e39ac? f58647c0 8057f307 8056ae96 8056a9ae
804e39bc? 80622b92 8062b803 8058aa2cf584d960
804e39cc? 8062b5fc 8059d753 8053c14af5864a50
那就是8057e786的位置了,反匯編:
lkd> u 8057e786
nt!NtQuerySystemInformation:
8057e786 6810020000????? push??? 210h
8057e78b 6830ab4e80????? push??? offset nt!ExTraceAllTables+0x1eb (804eab30)
8057e790 e8a64cf6ff????? call??? nt!_SEH_prolog (804e343b)
8057e795 33c0??????????? xor???? eax,eax
8057e797 8945e4?? ???????mov???? dword ptr [ebp-1Ch],eax
8057e79a8945dc????????? mov???? dword ptr [ebp-24h],eax
8057e79d 8945fc????????? mov???? dword ptr [ebp-4],eax
8057e7a0 64a124010000??? mov???? eax,dword ptr fs:[00000124h]
真的是NtQuerySystemInformation。搞定了!
有些網上流傳的代碼將新的系統服務函數命名為NewZwQuerySystemInformation在語法角度是沒有什么錯誤,但是實際上它并不是替換了ZwQuerySystemInformation而是NtQuerySystemInformation,這種命名讓讀者產生誤解,應該是NewNtQuerySystemInformation更為妥當。我們只是通過ZwQuerySystemInformation來找到NtQuerySystemInformation,最終都是在Nt*系列的函數上做文章的。對于那些“鉤住Zw*”文章的提法,也不敢茍同,壞事都是新的Nt*干的,Zw*只是提供了線索,有點受冤了。
?
2.??????????? 系統服務替換及還原
萬事俱備,是不是可以“動手”了?不妨試一下,Windows 2000及以上必定是BSOD,傷心的藍色海洋。Why?該內存區域寫保護。點解?去掉寫保護,修改標識寄存器CR0。
| 31 | 30 | ... | 18 | 17 | 16 | ... | 5 | 4 | 3 | 2 | 0 | 1 |
| P/G | C/D | ... | A/M |
| W/P | ... | N/E | E/T | T/S | E/M | M/P | P/E |
我們主要注意這個WP這位,其他的請參考IA-32 Volume 3A;
WP——Write Protect,當設置為1時只提供讀頁權限;
PE——Paging,當設置為1時提供分頁;
MP——Protection Enable,當設置為1時進入保護模式;
因此,只要把WP這一位設置為0時,就可以修改SSDT了。
?
去除寫保護標示:
unsigned long _cr0;
_asm
{
cli;
mov eax,cr0
mov _cr0,eax
and eax,0fffeffffh
mov cr0,eax
}
恢復寫保護:
_asm
{
mov eax, _cr0
mov cr0,eax
sti
}
還有更紳士的做法,將整個SSDT的存儲數組映射到一個非分頁MDL(Memory Description List)的內存空間,然后就方便對這塊內存區域修改屬性、改寫內容... Greg Hoglund那個例子的做法。
lkd> dt _mdl
nt!_MDL
?? +0x000 Next???????????? : Ptr32 _MDL
?? +0x004 Size???????????? : Int2B
?? +0x006 MdlFlags???????? : Int2B
?? +0x008 Process????????? : Ptr32 _EPROCESS
?? +0x00cMappedSystemVa?? : Ptr32 Void
?? +0x010 StartVa????????? : Ptr32 Void
?? +0x014 ByteCount??????? : Uint4B
?? +0x018 ByteOffset?????? : Uint4B
最后,服務卸載時當然不能忘了把SSDT修改過來,就是上述操作的逆過程,大同小異。
(待續)
總結
以上是生活随笔為你收集整理的也谈SSDT Hook(一)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: windows调试器设置
- 下一篇: docker 仓库镜像 替换_Docke