linux内存写保护,[原创]不用CR0或MDL修改内核非分页写保护内存的一种思路(x64)
開門見山,本文的核心思路就是通過填充頁表項,將一塊連續的虛擬地址映射到新的地址,同時將需要修改的只讀內存對應頁表項的Dirty位置位。在Windows操作系統下,寫保護是通過保護特定虛擬地址實現的,若不建立新映射,則即使將Dirty位置位,嘗試寫只讀內存照樣會觸發BugCheck,若建立了新映射但不置位Dirty則觸發PAGE_FAULT的BugCheck,兩個步驟缺一不可。
填充頁表項首先需要動態定位PTEBase,國際慣例是采用大表哥的頁表自映射法,代碼如下:ULONG_PTR?PTEBase?=?0;
BOOLEAN?hzqstGetPTEBase()
{
BOOLEAN?Result?=?FALSE;
ULONG_PTR?PXEPA?=?__readcr3()?&?0xFFFFFFFFF000;
PHYSICAL_ADDRESS?PXEPAParam;
PXEPAParam.QuadPart?=?(LONGLONG)PXEPA;
ULONG_PTR?PXEVA?=?(ULONG_PTR)MmGetVirtualForPhysical(PXEPAParam);
if?(PXEVA)
{
ULONG_PTR?PXEOffset?=?0;
do
{
if?((*(PULONGLONG)(PXEVA?+?PXEOffset)?&?0xFFFFFFFFF000)?==?PXEPA)
{
PTEBase?=?(PXEOffset?+?0xFFFF000)?<
Result?=?TRUE;
break;
}
PXEOffset?+=?8;
}?while?(PXEOffset?
}
return?Result;
}
這里我順便也給出另一種獲取方案,基本思路是通過NT導出函數NTKERNELAPI?ULONG?NTAPI?KeCapturePersistentThreadState(PCONTEXT?Context,?PKTHREAD?Thread,?ULONG?BugCheckCode,?ULONG_PTR?BugCheckParameter1,?ULONG_PTR?BugCheckParameter2,?ULONG_PTR?BugCheckParameter3,?ULONG_PTR?BugCheckParameter4,?PVOID?VirtualAddress);
Dump下0x40000大小的數據,里面就存放有MmPfnDataBase,MmPfnDataBase是一個以物理地址頁幀號為索引的內存信息數組,其中就有物理頁對應的PTE項的地址PteAddress,我們傳一個有效的物理地址進去(如當前CR3: __readcr3() & 0xFFFFFFFFF000)取出其中的PteAddress,由于PTEBase必定是0x8000000000的倍數,因此可由PteAddress直接算出PTEBase。另外,從Win10 RS1周年預覽版開始,KeCapturePersistentThreadState開始受全局變量ForceDumpDisabled的控制,若注冊表“HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\CrashControl”中有關子項滿足條件,此變量會在開機啟動時置為1,導致KeCapturePersistentThreadState調用失敗。綜上所述我們得到第二種獲取PTEBase的代碼如下://若在Win10上測試,需要在這里加上"#define?_WIN10?1"
#ifdef?_WIN10
#define?OFFSET_PTEADDRESS?0x8
#elif
#define?OFFSET_PTEADDRESS?0x10
#endif
ULONG_PTR?PTEBase?=?0;
PBOOLEAN?LocateForceDumpDisabledInRange(ULONG_PTR?StartAddress,?ULONG?MaximumBytesToSearch)
{
PBOOLEAN?Result?=?0;
ULONG_PTR?p?=?StartAddress;
ULONG_PTR?pEnd?=?p?+?MaximumBytesToSearch;
do
{
//cmp?cs:ForceDumpDisabled,?al
//jnz?...
if?((*(PULONGLONG)p?&?0xFFFF00000000FFFF)?==?0x850F000000000538)
{
Result?=?p?+?6?+?*(PLONG)(p?+?2);
break;
}
p++;
}?while?(p?
return?Result;
}
BOOLEAN?GetPTEBase()
{
BOOLEAN?Result?=?FALSE;
CONTEXT?Context?=?{?0?};
Context.Rcx?=?(ULONG64)&Context;
PUCHAR?DumpData?=?ExAllocatePool(NonPagedPool,?0x40000);
if?(DumpData)
{
PBOOLEAN?pForceDumpDisabled?=?LocateForceDumpDisabledInRange((ULONG_PTR)KeCapturePersistentThreadState,?0x300);
if?(pForceDumpDisabled)?*pForceDumpDisabled?=?FALSE;
if?(KeCapturePersistentThreadState(&Context,?0,?0,?0,?0,?0,?0,?DumpData)?==?0x40000)
{
ULONG_PTR?MmPfnDataBase?=?*(PULONG_PTR)(DumpData?+?0x18);
PTEBase?=?*(PULONG_PTR)(MmPfnDataBase?+?OFFSET_PTEADDRESS?+?(((ULONG_PTR)(__readcr3()?&?0xFFFFFFFFF000)?>>?8)?*?3))?&?0xFFFFFF8000000000;
Result?=?TRUE;
}
ExFreePool(DumpData);
}
return?Result;
}
獲取到PTEBase后,現在進入正題,下面代碼的主要思路是:首先從某個有效的按512G對齊的內核虛擬地址開始,一直到0xFFFFFFFFFFFFFFFF,查找未被占用的PML4T(下文統稱PXE)子項,即PXE的Valid位為0的子項。
對于有效起始內核虛擬地址,一開始筆者選用的是MmSystemRangeStart,虛擬機測試發現對于8.1/10映射成功了,而Vista/7/8下CPU并沒有承認映射地址的有效性觸發了BugCheck,調試器發現Vista/7/8下MmSystemRangeStart=0xFFFF080000000000,而8.1/10下MmSystemRangeStart=0xFFFF800000000000,并且Vista/7/8下映射地址范圍為[0xFFFF080000000000,?0xFFFF800000000000)時,調試器的CrashDump提示為Noncanonical Virtual Address。查閱了Intel手冊后,筆者發現當前的Intel CPU支持的虛擬地址尋址最大位數限制為48位,對于64位Windows來說,第47位被用來區分用戶層虛擬地址和內核層虛擬地址,即內核層地址實際上只有47位的有效位,于是得出有效起始內核虛擬地址為0xFFFF800000000000。當然嚴謹起見,可以使用CPUID的0x80000008號功能,此時eax寄存器的ah即為處理器支持的虛擬地址最大有效位數,設其為x,則對64位地址,只要將最高的(65-x)位全部置1,剩余的(x-1)位全部置0,即得有效起始內核虛擬地址。
找到未使用的PXE子項后,申請一段連續的物理內存,初始大小為PXE子項以及PXE子項的512個PPE項所描述的頁面大小,即(1 + 0x200) * PAGE_SIZE,若申請失敗,則將申請的PPE項減半,以此類推……由于是連續物理內存,因此最好的方案是通過MmAllocateContiguousMemory申請,若使用ExAllocatePool,則當申請的頁面不是2M大頁面時,其虛擬地址對應的物理地址很可能不是連續的,這會給我們后續填充512個PPE項的物理頁幀號徒增不少麻煩。申請到連續物理地址后,第一個頁面填充至目標PXE子項,第2到513個頁面的物理頁幀號按順序填充到PXE子項頁面描述的512個PPE項中。隨后給定任一個需要映射的虛擬地址,我們先將它按0x8000000000(512G)進行對齊,再依次檢索其PXE、PPE、PDE項,若PXE項Valid為0或LargePage為1則不映射,否則開始依次檢索PPE和PDE。若PPE項Valid為0,則把映射地址對應的PPE頁面清零。若Valid為1則分別處理是否LargePage的情形,若為1G大頁面,則將其等分成512個2M大頁面,再把對應的物理頁幀號按順序填充到PPE描述的512個PDE項中;若不是大頁面,則將被映射地址的PPE項對應的一頁全部復制到映射地址對應的PPE頁面中。從這里開始,基本的地址映射已經完成,接下來對欲修改的頁面只要把其對應的PTE或大頁面PDE或大頁面PPE項的Dirty位置1即可。ULONG_PTR?AllocateSinglePXEDirectory(OUT?PULONG_PTR?BaseAddress,?OUT?PULONG?SizeOfPPEPages)
{
ULONG_PTR?Result?=?0;
ULONG_PTR?PteBase?=?PTEBase;
ULONG_PTR?OffsetMask?=?0x7FFFFFFFF8;
ULONG_PTR?PXEVA?=?PteBase?+?(((PteBase?+?(((PteBase?+?((PteBase?>>?9)?&?OffsetMask))?>>?9)?&?OffsetMask))?>>?9)?&?OffsetMask)?+?0x800;?//有效起始內核虛擬地址0xFFFF800000000000對應的PXE子項
ULONG_PTR?PXEVAEnd?=?PXEVA?+?0x800;?//0x800?+?0x800?==?0x1000?==?PAGE_SIZE
do
{
if?(!(*(PULONGLONG)PXEVA?&?0xFFFFFFFFF001))
{
PHYSICAL_ADDRESS?Alloc;
Alloc.QuadPart?=?MAXULONG64;
ULONG?TotalSizeOfValidPPEPages?=?0x200?*?PAGE_SIZE;
while?(TotalSizeOfValidPPEPages?>=?PAGE_SIZE?&&?!(Result?=?(ULONG_PTR)MmAllocateContiguousMemory(TotalSizeOfValidPPEPages?+?PAGE_SIZE,?Alloc)))
TotalSizeOfValidPPEPages?>>=?1;
if?(Result)
{
if?(SizeOfPPEPages)?*SizeOfPPEPages?=?TotalSizeOfValidPPEPages;
ULONG64?OringinalIRQL?=?__readcr8();
__writecr8(DISPATCH_LEVEL);
if?(BaseAddress)?*BaseAddress?=?((PXEVA?&?0xFF8)?+?0xFFFF000)?<
ULONG_PTR?PTEVA?=?PteBase?+?((Result?>>?9)?&?OffsetMask);
ULONG_PTR?PDEVA?=?PteBase?+?((PTEVA?>>?9)?&?OffsetMask);
ULONG_PTR?PPEVA?=?PteBase?+?((PDEVA?>>?9)?&?OffsetMask);
ULONGLONG?StartValue?=?*(PULONGLONG)PPEVA;
if?(StartValue?&?0x80)
{
StartValue?&=?~0x80;
StartValue?+=?(Result?&?0x3FFFF000);
}
else
{
StartValue?=?*(PULONGLONG)PDEVA;
if?(StartValue?&?0x80)
{
StartValue?&=?~0x80;
StartValue?+=?(Result?&?0x1FF000);
}
else?StartValue?=?*(PULONGLONG)PTEVA;
}
*(PULONGLONG)PXEVA?=?StartValue;
ULONG?PPEOffset?=?0;
ULONG?PPEOffsetEnd?=?TotalSizeOfValidPPEPages?>>?9;
ULONG_PTR?PPEBase?=?Result;
do
{
*(PULONGLONG)(PPEBase?+?PPEOffset)?=?StartValue?+?((PPEOffset?+?8)?<
PPEOffset?+=?8;
}?while?(PPEOffset?
RtlZeroMemory((PVOID)(PPEBase?+?PPEOffset),?PAGE_SIZE?-?PPEOffset);
__writecr8(OringinalIRQL);
}
break;
}
PXEVA?+=?8;
}?while?(PXEVA?
return?Result;
}
ULONG_PTR?FillPDEArrayForAllValidPPEs(ULONG_PTR?PagePointer,?ULONG_PTR?BaseAddress,?ULONG?SizeOfPPEPages,?ULONG_PTR?VirtualAddress)
{
ULONG_PTR?Result?=?0;
ULONG_PTR?PteBase?=?PTEBase;
ULONG_PTR?OffsetMask?=?0x7FFFFFFFF8;
ULONG_PTR?PDEVA?=?PteBase?+?(((PteBase?+?(((VirtualAddress?&?0xFFFFFF8000000000)?>>?9)?&?OffsetMask))?>>?9)?&?OffsetMask);
ULONG_PTR?PPEVA?=?PteBase?+?((PDEVA?>>?9)?&?OffsetMask);
ULONG_PTR?PXEVA?=?PteBase?+?((PPEVA?>>?9)?&?OffsetMask);
if?((*(PUCHAR)PXEVA?&?0x81)?==?1)?//不支持512G超大頁面
{
if?(SizeOfPPEPages?>=?PAGE_SIZE)
{
ULONG_PTR?NewPDEVA?=?PagePointer?+?PAGE_SIZE;
ULONG_PTR?NewPDEVAEnd?=?NewPDEVA?+?SizeOfPPEPages;
ULONG64?OringinalIRQL?=?__readcr8();
__writecr8(DISPATCH_LEVEL);
do
{
UCHAR?ByteFlag?=?*(PUCHAR)PPEVA;
if?(ByteFlag?&?1)
{
if?((CHAR)ByteFlag?
{
ULONGLONG?PDEStartValue?=?*(PULONGLONG)PPEVA;
ULONG?PDEOffset?=?0;
do
{
*(PULONGLONG)(NewPDEVA?+?PDEOffset)?=?PDEStartValue?+?(PDEOffset?<
PDEOffset?+=?8;
}?while?(PDEOffset?
}
else?memcpy((PVOID)NewPDEVA,?(PVOID)PDEVA,?PAGE_SIZE);
}
else?RtlZeroMemory((PVOID)NewPDEVA,?PAGE_SIZE);
PDEVA?+=?PAGE_SIZE;
PPEVA?+=?8;
NewPDEVA?+=?PAGE_SIZE;
}?while?(NewPDEVA?
__writecr8(OringinalIRQL);
__writecr3(__readcr3());
Result?=?BaseAddress?+?(VirtualAddress?&?0x7FFFFFFFFF);
}
}
return?Result;
}
BOOLEAN?MakeDirtyPage(ULONG_PTR?VirtualAddress)
{
BOOLEAN?Result?=?FALSE;
ULONG_PTR?PteBase?=?PTEBase;
ULONG_PTR?OffsetMask?=?0x7FFFFFFFF8;
ULONG_PTR?PTEVA?=?PteBase?+?((VirtualAddress?>>?9)?&?OffsetMask);
ULONG_PTR?PDEVA?=?PteBase?+?((PTEVA?>>?9)?&?OffsetMask);
ULONG_PTR?PPEVA?=?PteBase?+?((PDEVA?>>?9)?&?OffsetMask);
ULONG_PTR?PXEVA?=?PteBase?+?((PPEVA?>>?9)?&?OffsetMask);
if?((*(PUCHAR)PXEVA?&?0x81)?==?1)?//不支持512G超大頁面
{
UCHAR?ByteFlag?=?*(PUCHAR)PPEVA;
if?(ByteFlag?&?1)
{
if?((CHAR)ByteFlag?
{
*(PUCHAR)PPEVA?|=?0x42;?//Dirty1?&?Dirty
Result?=?TRUE;
}
else
{
ByteFlag?=?*(PUCHAR)PDEVA;
if?(ByteFlag?&?1)
{
if?((CHAR)ByteFlag?
{
*(PUCHAR)PDEVA?|=?0x42;
Result?=?TRUE;
}
else
{
if?(_bittest((PLONG)PTEVA,?0))
{
*(PUCHAR)PTEVA?|=?0x42;
Result?=?TRUE;
}
}
}
}
}
}
__invlpg((PVOID)VirtualAddress);
return?Result;
}
void?FreeSinglePXEDirectory(ULONG_PTR?PagePointer,?ULONG_PTR?BaseAddress)
{
ULONG_PTR?PteBase?=?PTEBase;
ULONG_PTR?OffsetMask?=?0x7FFFFFFFF8;
*(PULONGLONG)(PteBase?+?(((PteBase?+?(((PteBase?+?(((PteBase?+?((BaseAddress?>>?9)?&?OffsetMask))?>>?9)?&?OffsetMask))?>>?9)?&?OffsetMask))?>>?9)?&?OffsetMask))?=?0;
MmFreeContiguousMemory((PVOID)PagePointer);
__writecr3(__readcr3());
}
在Win10 18362.207 x64上的測試代碼與結果:NTKERNELAPI?NTSTATUS?ObReferenceObjectByName(IN?PUNICODE_STRING?ObjectName,?IN?ULONG?Attributes,?IN?PACCESS_STATE?PassedAccessState?OPTIONAL,?IN?ACCESS_MASK?DesiredAccess?OPTIONAL,?IN?POBJECT_TYPE?ObjectType,?IN?KPROCESSOR_MODE?AccessMode,?IN?OUT?PVOID?ParseContext?OPTIONAL,?OUT?PVOID?*Object);
extern?POBJECT_TYPE?*IoDriverObjectType;
NTSTATUS?DriverEntry(PDRIVER_OBJECT?pDriverObj,?PUNICODE_STRING?pRegistryString)
{
ULONG_PTR?PagePointer?=?0;
ULONG_PTR?BaseAddress?=?0;
ULONG?SizeOfValidPPEPages?=?0;
if?(GetPTEBase())
{
UNICODE_STRING?UDrvName;
PDRIVER_OBJECT?DrvObj?=?0;
if?(NT_SUCCESS(RtlInitUnicodeString(&UDrvName,?L"\\Driver\\kbdclass"))?&&?NT_SUCCESS(ObReferenceObjectByName(&UDrvName,?OBJ_CASE_INSENSITIVE,?0,?0,?*IoDriverObjectType,?KernelMode,?0,?(PVOID*)&DrvObj)))
{
ObDereferenceObject(DrvObj);
PagePointer?=?AllocateSinglePXEDirectory(&BaseAddress,?&SizeOfValidPPEPages);
ULONG_PTR?MappedKbdClassBase?=?FillPDEArrayForAllValidPPEs(PagePointer,?BaseAddress,?SizeOfValidPPEPages,?(ULONG_PTR)DrvObj->DriverStart);
if?(MakeDirtyPage(MappedKbdClassBase))?*(PULONG)(MappedKbdClassBase?+?4)?=?0x78563412;
FreeSinglePXEDirectory(PagePointer,?BaseAddress);
}
}
return?STATUS_UNSUCCESSFUL;
}
另外附上Win10的_MMPTE_HARDWARE以供參考typedef?struct?_MMPTE_HARDWARE
{
ULONGLONG?Valid?:?1;
ULONGLONG?Dirty1?:?1;
ULONGLONG?Owner?:?1;
ULONGLONG?WriteThrough?:?1;
ULONGLONG?CacheDisable?:?1;
ULONGLONG?Accessed?:?1;
ULONGLONG?Dirty?:?1;
ULONGLONG?LargePage?:?1;
ULONGLONG?Global?:?1;
ULONGLONG?CopyOnWrite?:?1;
ULONGLONG?Unused?:?1;
ULONGLONG?Write?:?1;
ULONGLONG?PageFrameNumber?:?36;?//Vista?SP0為28,Vista?SP1--10通用36
ULONGLONG?ReservedForHardware?:?4;
ULONGLONG?ReservedForSoftware?:?4;
ULONGLONG?WsleAge?:?4;
ULONGLONG?WsleProtection?:?3;
ULONGLONG?NoExecute?:?1;
}?MMPTE_HARDWARE,?*PMMPTE_HARDWARE;
總結一下,以上實現的代碼大概類似弟中弟中弟版MmBuildMdlForNonPagedPool和MmMapLockedPagesSpecifyCache,不同的是本文的代碼更多地相當于一份512G的虛擬地址空間物理內存的分布快照,這其中可能存在部分分頁內存在快照過程中被放入物理內存而快照后再次被分回硬盤的情況,因此在映射地址空間使用MmIsAddressValid遠遠不如在原地址空間使用靠譜。
10-7更新:感謝@syser老鐵的關于刷新TLB的建議,測試發現刷TLB的確不需要KeFlushTb,只要對要訪問或修改的映射頁面invlpg刷新單個PTE對應的TLB項即可。以上代碼在Vista SP0至Win10 18363均測試有效。
最后于 2020-10-7 23:22
被hhkqqs編輯
,原因:
總結
以上是生活随笔為你收集整理的linux内存写保护,[原创]不用CR0或MDL修改内核非分页写保护内存的一种思路(x64)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 搭建个人博客详细教程
- 下一篇: iOS中使用storyboard进行布局