天天看點

Win64 驅動核心程式設計-3.核心裡使用記憶體

核心裡使用記憶體

記憶體使用,無非就是申請、複制、設定、釋放。在 C 語言裡,它們對應的函數是:malloc、memcpy、memset、free;在核心程式設計裡,他們分别對應 ExAllocatePool、RtlMoveMemory、

RtlFillMemory、ExFreePool。它們的原型分别是:

Win64 驅動核心程式設計-3.核心裡使用記憶體

需要注意的是,RtlFillMemory 和 memset 的原型不同、ExAllocatePool 和 malloc 的原型也不同。前者隻是參數前後調換了一下位置,但是後者則多了一個參數:PoolType。這個PoolType 也是必須了解的。PoolType 在 在 MSDN  的介紹上有 N  種, 其實 常用有 的隻有 2  種 :

PagedPool 和 和 NonPagedPool。PagedPool  是 分頁記憶體,簡單來說就是實體記憶體不夠時,會把這片記憶體移動 到硬碟上,而 NonPagedPool  是 無論實體記憶體如何緊缺,都絕對不把 這片記憶體的内容移動到硬碟上。在往下講之前,先補充一個知識, 就是我們操作的記憶體,都是虛拟記憶體,和實體記憶體是兩碼事。 但虛拟記憶體 的資料是 放在 實體記憶體上的,兩者存在映射關系 , 一般來說,一 片 實體記憶體可以映射為多片虛拟記憶體 , 但一片虛拟記憶體必定隻對應一片實體記憶體。假設虛拟記憶體是 0Xfffff80001234567 在實體記憶體的位址是 0x123456,當實體記憶體不夠用時,實體記憶體 0x123456 的原始内容就挪到硬碟上,然後把另外一片急需要用的内容移到實體記憶體裡。此時,當你再讀取 0Xfffff80001234567 的内容時,就會引發缺頁異常,系統就會把在硬碟上的内容再次放到實體記憶體中(如果這個過程失敗,一般就當機了)。以上說了這麼多廢話,總結兩句:1.NonPagedPool  的 總量 是有限的( 具體 大小和你實體記憶體的大小相) 關) ,而 而 PagedPool  的 總量較多 。申請了 記憶體忘記釋放 都會造成 記憶體洩漏,但是很明顯忘記放 釋放 NonPagedPool  的 後果要嚴重 得多 ;2. 一般來說 ,PagedPool  用來放 資料 (比如 你用ZwQuerySystemInformation  枚舉片 核心子產品,可以申請一大片 PagedPool  存放) 傳回的資料) ,而 而 NonPagedPool  用來放 代碼 (你 寫核心 shellcode  并 需要執行時, 必須 使用 NonPagedPool存放 shellcode) ) 。 以我 的經驗來說,通路到切換出去的記憶體沒事,但是 執行到 切換出去的記憶體必然藍屏 ( 這) 隻是我的經驗,正确性待定)。3. 在 使用者态 ,記憶體 是有 屬性的, 有的 記憶體 片隻 能 讀  不 能  寫 不 能 執行 ( ( PAGE_READ) ) , 有的 内 存  片  可 以  讀  可 以 寫 也 可 以 執 行(PAGE_READ_WRITE_EXECUTE)。在核心裡,PagedPool 和 和 NonPagedPool  都 是 可讀可寫可執行的 , 而且沒有類似 VirtualProtect  之類的函數。示例代碼:

void Test() {
PVOID ptr1 = ExAllocatePool(PagedPool ,0x100);
PVOID ptr2 = ExAllocatePool(NonPagedPool ,0x200);
RtlFillMemory(ptr2 ,0x200 ,0x90);
RtlMoveMemory(ptr1 ,ptr2 ,0x50);
ExFreePool(ptr1);
ExFreePool(ptr2);
DbgPrint("[KrnlHW64]tttttttttt\n");}      

到這裡順便提醒下大家,驅動裡面的SEH很多時候都是在自己騙自己,該藍還得藍。我随便測試了下。

Win64 驅動核心程式設計-3.核心裡使用記憶體
Win64 驅動核心程式設計-3.核心裡使用記憶體

在核心裡想要寫入“别人的”記憶體(一般指 NTOS 等系統子產品的記憶體空間),還有另外的規矩,這裡又涉及到另外兩個概念:IRQL 和記憶體保護。IRQL 稱為中斷請求級别,從 0~31 共32 個級别;記憶體保護可以打開和關閉,如果在記憶體處于保護狀态時寫入,會導緻藍屏。 一般來說 ,要寫入“ 别人 的” 核心記憶體 , 必須關閉記憶體寫保護,并把 IRQL  提升到 2  才行 (絕大多數候 時候 IRQL  都為 為 0 ,當 當 IRQL=2  時,會阻斷大部分線程執行, 防止 執行出錯) )。記憶體是否處于寫保護的狀态記錄在 CR0 寄存器上,是以直接修改 CR0 寄存器的值即可;而提升或降低IRQL 則使用 KeRaiseIrqlToDpcLevel 和 KeLowerIrql 實作(WIN64 的 IRQL 值記錄在 CR8 寄存器上,而 WIN32 的 IRQL 值記錄在 KPCR 上)。代碼如下:

KIRQL WPOFFx64() {
KIRQL irql = KeRaiseIrqlToDpcLevel();
UINT64 cr0 = __readcr0();
cr0 &= 0xfffffffffffeffff;
__writecr0(cr0);
_disable();
return irql;
}
 
 
void WPONx64(KIRQL irql) {
UINT64 cr0 = __readcr0();
cr0 |= 0x10000;
_enable();
__writecr0(cr0);
KeLowerIrql(irql);
}
 
void Test() {
KIRQL irql = WPOFFx64();
PVOID HookCode = ExAllocatePool(NonPagedPool, 0x200);
RtlFillMemory(HookCode, 0x200, 0x90);
RtlMoveMemory(HookCode, NtOpenProcess, 0x3);
RtlMoveMemory(NtOpenProcess ,HookCode , 0x3);
WPONx64(irql);
}      

注意:如果不調用WPOFFx64,WPONx64直接去寫記憶體會藍屏,直接往上面的那個位置寫0也會藍屏。

至于寫入“别人的”記憶體,還有一種微軟推薦的安全方式,就是 MDL 映射記憶體的方式。

這個比較麻煩,大概方法是 申請一個 MDL (類似 句柄的 玩意) ) ,然後 嘗試 鎖定頁面 ,如果 成功,則 讓 系統配置設定一個 “ 安全 ” 的虛拟位址再行 寫入, , 寫入 完畢 後 解鎖頁面 并釋放掉 MDL。以下是某人寫的 SafeCopyMemory

BOOLEAN SafeCopyMemory(PVOID pDestination, PVOID pSourceAddress, SIZE_T SizeOfCopy)
{
PMDL pMdl = NULL;
PVOID pSafeAddress = NULL;
if (!MmIsAddressValid(pDestination) || !MmIsAddressValid(pSourceAddress))
return FALSE;
pMdl = IoAllocateMdl(pDestination, (ULONG)SizeOfCopy, FALSE, FALSE, NULL);
if (!pMdl)
return FALSE;
__try
{
MmProbeAndLockPages(pMdl, KernelMode, IoReadAccess);
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
IoFreeMdl(pMdl);
return FALSE;
}
pSafeAddress = MmGetSystemAddressForMdlSafe(pMdl, NormalPagePriority);
if (!pSafeAddress)
return FALSE;
__try
{
RtlMoveMemory(pSafeAddress, pSourceAddress, SizeOfCopy);
}
__except (EXCEPTION_EXECUTE_HANDLER)
{;}
MmUnlockPages(pMdl);
IoFreeMdl(pMdl);
return TRUE;
}
 
void Test() {
PVOID HookCode = ExAllocatePool(NonPagedPool, 0x200);
RtlFillMemory(HookCode, 0x200, 0x90);
RtlMoveMemory(HookCode, NtOpenProcess, 0x3);
SafeCopyMemory(NtOpenProcess, HookCode, 0x3);
}      

繼續閱讀