轉自:https://write-bug.com/article/1933.html
無論是在 32 位系統內存分布,還是在 64 位系統內存分布中,我們知道高地址空間分配給系統內核使用,低地址空間分配給用戶進程使用。
事實上,用戶空間和內核空間其實有一塊共享區域,大小為 4 KB。它們的內存地址雖然不一樣,但是它們都是有同一塊物理內存映射出來的。現在,本文就是要實現一個這樣的程序,去驗證這塊共享區域的存在。
實現原理
用戶空間和內核空間的共享區域,大小為 4 KB,內核占用其中一小部分,但 Rootkit 應該大約還有 3 KB 空間可使用。這兩個虛擬內存地址都映射到同一物理頁面,內核程序對這塊共享區域有可讀、可寫的權限,用戶程序對這塊共享區域只有只讀的權限。
其中,對於 32 位系統和 64 位系統來說,這塊共享區域對應的內核地址范圍以及對應用戶空間的地址范圍如下表所示:
內核起始地址 | 內核結束地址 | 用戶起始地址 | 用戶結束地址 | |
---|---|---|---|---|
32 系統 | 0xFFDF0000 | 0xFFDF0FFF | 0x7FFE0000 | 0x7FFE0FFF |
64 系統 | 0xFFFFF780`00000000 | 0xFFFFF780`00000FFF | 0x7FFE0000 | 0x7FFE0FFF |
由上面可以看出,32 位系統和 64 位系統下,該共享區域的內核地址是不同的,而用戶空間上的地址都是相同的。
這塊共享區域的名稱是 KUSER_SHARED_DATA,想要獲得關於該共享區域的更過詳細解釋,可以在 WinDbg 中輸入:dt nt!_KUSER_SHARED_DATA 來獲取信息。
本文演示的程序,就是在 KUSER_SHARED_DATA 的內核內存中寫入數據,然后,由用戶稱程序讀取寫入的數據,以此驗證 KUSER_SHARED_DATA 區域的存在。
編碼實現
用戶層程序
int _tmain(int argc, _TCHAR* argv[])
{
// 要偏移 1 KB 大小讀取數據, 因為寫入的時候是偏移 1 KB 大小寫入的
void *pBaseAddress = (void *)(0x7FFE0000 + 0x400);
printf("[Share Data]%s\n", pBaseAddress);
system("pause");
return 0;
}
內核層程序
// 向共享區域中寫入數據
BOOLEAN WriteShareData(PCHAR pszData, ULONG ulDataSize)
{
PVOID pBaseAddress = NULL;
// 偏移 1 KB 寫入數據, 因為系統會占用大約 1 KB 的空間
#ifdef _WIN64
// 64 Bits
pBaseAddress = (PVOID)(0xFFFFF78000000000 + 0x400);
#else
// 32 Bits
pBaseAddress = (PVOID)(0xFFDF0000 + 0x400);
#endif
// 寫入
RtlCopyMemory(pBaseAddress, pszData, ulDataSize);
return TRUE;
}
程序測試
在 Windows7 32 位系統下,驅動程序正常執行:
在 Windows10 64 位系統下,驅動程序正常執行:
總結
注意,這塊共享區域主要是用來在用戶層和內核層之間快速的傳遞信息的,會占用大約 1 KB 大小的空間。所以,我們通常偏移 0x400 大小處寫入我們自己的數據,這樣,就不會影響原來的內核代碼的正常運行了。