linux内存写保护,[原创]不用CR0或MDL修改内核非分页写保护内存的一种思路(x64)
開(kāi)門(mén)見(jiàn)山,本文的核心思路就是通過(guò)填充頁(yè)表項(xiàng),將一塊連續(xù)的虛擬地址映射到新的地址,同時(shí)將需要修改的只讀內(nèi)存對(duì)應(yīng)頁(yè)表項(xiàng)的Dirty位置位。在Windows操作系統(tǒng)下,寫(xiě)保護(hù)是通過(guò)保護(hù)特定虛擬地址實(shí)現(xiàn)的,若不建立新映射,則即使將Dirty位置位,嘗試寫(xiě)只讀內(nèi)存照樣會(huì)觸發(fā)BugCheck,若建立了新映射但不置位Dirty則觸發(fā)PAGE_FAULT的BugCheck,兩個(gè)步驟缺一不可。
填充頁(yè)表項(xiàng)首先需要?jiǎng)討B(tài)定位PTEBase,國(guó)際慣例是采用大表哥的頁(yè)表自映射法,代碼如下: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;
}
這里我順便也給出另一種獲取方案,基本思路是通過(guò)NT導(dǎo)出函數(shù)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大小的數(shù)據(jù),里面就存放有MmPfnDataBase,MmPfnDataBase是一個(gè)以物理地址頁(yè)幀號(hào)為索引的內(nèi)存信息數(shù)組,其中就有物理頁(yè)對(duì)應(yīng)的PTE項(xiàng)的地址PteAddress,我們傳一個(gè)有效的物理地址進(jìn)去(如當(dāng)前CR3: __readcr3() & 0xFFFFFFFFF000)取出其中的PteAddress,由于PTEBase必定是0x8000000000的倍數(shù),因此可由PteAddress直接算出PTEBase。另外,從Win10 RS1周年預(yù)覽版開(kāi)始,KeCapturePersistentThreadState開(kāi)始受全局變量ForceDumpDisabled的控制,若注冊(cè)表“HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\CrashControl”中有關(guān)子項(xiàng)滿(mǎn)足條件,此變量會(huì)在開(kāi)機(jī)啟動(dòng)時(shí)置為1,導(dǎo)致KeCapturePersistentThreadState調(diào)用失敗。綜上所述我們得到第二種獲取PTEBase的代碼如下://若在Win10上測(cè)試,需要在這里加上"#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后,現(xiàn)在進(jìn)入正題,下面代碼的主要思路是:首先從某個(gè)有效的按512G對(duì)齊的內(nèi)核虛擬地址開(kāi)始,一直到0xFFFFFFFFFFFFFFFF,查找未被占用的PML4T(下文統(tǒng)稱(chēng)PXE)子項(xiàng),即PXE的Valid位為0的子項(xiàng)。
對(duì)于有效起始內(nèi)核虛擬地址,一開(kāi)始筆者選用的是MmSystemRangeStart,虛擬機(jī)測(cè)試發(fā)現(xiàn)對(duì)于8.1/10映射成功了,而Vista/7/8下CPU并沒(méi)有承認(rèn)映射地址的有效性觸發(fā)了BugCheck,調(diào)試器發(fā)現(xiàn)Vista/7/8下MmSystemRangeStart=0xFFFF080000000000,而8.1/10下MmSystemRangeStart=0xFFFF800000000000,并且Vista/7/8下映射地址范圍為[0xFFFF080000000000,?0xFFFF800000000000)時(shí),調(diào)試器的CrashDump提示為Noncanonical Virtual Address。查閱了Intel手冊(cè)后,筆者發(fā)現(xiàn)當(dāng)前的Intel CPU支持的虛擬地址尋址最大位數(shù)限制為48位,對(duì)于64位Windows來(lái)說(shuō),第47位被用來(lái)區(qū)分用戶(hù)層虛擬地址和內(nèi)核層虛擬地址,即內(nèi)核層地址實(shí)際上只有47位的有效位,于是得出有效起始內(nèi)核虛擬地址為0xFFFF800000000000。當(dāng)然嚴(yán)謹(jǐn)起見(jiàn),可以使用CPUID的0x80000008號(hào)功能,此時(shí)eax寄存器的ah即為處理器支持的虛擬地址最大有效位數(shù),設(shè)其為x,則對(duì)64位地址,只要將最高的(65-x)位全部置1,剩余的(x-1)位全部置0,即得有效起始內(nèi)核虛擬地址。
找到未使用的PXE子項(xiàng)后,申請(qǐng)一段連續(xù)的物理內(nèi)存,初始大小為PXE子項(xiàng)以及PXE子項(xiàng)的512個(gè)PPE項(xiàng)所描述的頁(yè)面大小,即(1 + 0x200) * PAGE_SIZE,若申請(qǐng)失敗,則將申請(qǐng)的PPE項(xiàng)減半,以此類(lèi)推……由于是連續(xù)物理內(nèi)存,因此最好的方案是通過(guò)MmAllocateContiguousMemory申請(qǐng),若使用ExAllocatePool,則當(dāng)申請(qǐng)的頁(yè)面不是2M大頁(yè)面時(shí),其虛擬地址對(duì)應(yīng)的物理地址很可能不是連續(xù)的,這會(huì)給我們后續(xù)填充512個(gè)PPE項(xiàng)的物理頁(yè)幀號(hào)徒增不少麻煩。申請(qǐng)到連續(xù)物理地址后,第一個(gè)頁(yè)面填充至目標(biāo)PXE子項(xiàng),第2到513個(gè)頁(yè)面的物理頁(yè)幀號(hào)按順序填充到PXE子項(xiàng)頁(yè)面描述的512個(gè)PPE項(xiàng)中。隨后給定任一個(gè)需要映射的虛擬地址,我們先將它按0x8000000000(512G)進(jìn)行對(duì)齊,再依次檢索其PXE、PPE、PDE項(xiàng),若PXE項(xiàng)Valid為0或LargePage為1則不映射,否則開(kāi)始依次檢索PPE和PDE。若PPE項(xiàng)Valid為0,則把映射地址對(duì)應(yīng)的PPE頁(yè)面清零。若Valid為1則分別處理是否LargePage的情形,若為1G大頁(yè)面,則將其等分成512個(gè)2M大頁(yè)面,再把對(duì)應(yīng)的物理頁(yè)幀號(hào)按順序填充到PPE描述的512個(gè)PDE項(xiàng)中;若不是大頁(yè)面,則將被映射地址的PPE項(xiàng)對(duì)應(yīng)的一頁(yè)全部復(fù)制到映射地址對(duì)應(yīng)的PPE頁(yè)面中。從這里開(kāi)始,基本的地址映射已經(jīng)完成,接下來(lái)對(duì)欲修改的頁(yè)面只要把其對(duì)應(yīng)的PTE或大頁(yè)面PDE或大頁(yè)面PPE項(xiàng)的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;?//有效起始內(nèi)核虛擬地址0xFFFF800000000000對(duì)應(yīng)的PXE子項(xiàng)
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超大頁(yè)面
{
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超大頁(yè)面
{
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上的測(cè)試代碼與結(jié)果: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;
總結(jié)一下,以上實(shí)現(xiàn)的代碼大概類(lèi)似弟中弟中弟版MmBuildMdlForNonPagedPool和MmMapLockedPagesSpecifyCache,不同的是本文的代碼更多地相當(dāng)于一份512G的虛擬地址空間物理內(nèi)存的分布快照,這其中可能存在部分分頁(yè)內(nèi)存在快照過(guò)程中被放入物理內(nèi)存而快照后再次被分回硬盤(pán)的情況,因此在映射地址空間使用MmIsAddressValid遠(yuǎn)遠(yuǎn)不如在原地址空間使用靠譜。
10-7更新:感謝@syser老鐵的關(guān)于刷新TLB的建議,測(cè)試發(fā)現(xiàn)刷TLB的確不需要KeFlushTb,只要對(duì)要訪問(wèn)或修改的映射頁(yè)面invlpg刷新單個(gè)PTE對(duì)應(yīng)的TLB項(xiàng)即可。以上代碼在Vista SP0至Win10 18363均測(cè)試有效。
最后于 2020-10-7 23:22
被hhkqqs編輯
,原因:
總結(jié)
以上是生活随笔為你收集整理的linux内存写保护,[原创]不用CR0或MDL修改内核非分页写保护内存的一种思路(x64)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 搭建个人博客详细教程
- 下一篇: linux系统(Centos 7)部署环