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)相关推荐
- 非分页缓冲池内存不足 Httperr.log 文件中记录消息和Connections_refused 网站不能打开
非分页缓冲池内存不足 Httperr.log 文件中记录消息和"Connections_refused" 问题描述: server2003的iis 运行一段时间后莫名其妙的就不能打 ...
- iis6.0站点打不开,找不到服务器或 DNS 错误。非分页缓冲池内存不足故障一例...
windows2003 非分页缓冲池内存不足故障一例 (2011-09-09 15:04:37)iis6.0站点打不开,找不到服务器或 DNS 错误. 问题: iis装好后,运行了一段时间:一个月后有 ...
- 关于 非分页缓冲池 内存占用过高但任务管理器无程序 的特殊原因
本机发生条件: 使用百度网盘下载时,非分页缓冲池占用 以肉眼可见的速度增长,游戏等其他操作无影响. 排查: 尝试升级到教育版系统,关闭Windows Update,无效. 尝试卸载杀手网卡,无效 尝试 ...
- v15.03 鸿蒙内核源码分析(内存映射) | 映射真是个好东西 | 百篇博客分析HarmonyOS源码
子曰:"德不孤,必有邻." <论语>:里仁篇 百篇博客系列篇.本篇为: v15.xx 鸿蒙内核源码分析(内存映射篇) | 映射真是个好东西 内存管理相关篇为: v11. ...
- v11.03 鸿蒙内核源码分析(内存分配) | 内存有哪些分配方式 | 百篇博客分析HarmonyOS源码
子曰:"君子周而不比,小人比而不周."<论语>:为政篇 百篇博客系列篇.本篇为: v11.xx 鸿蒙内核源码分析(内存分配篇) | 内存有哪些分配方式 内存管理相关篇为 ...
- linux 物理内存用完了_调整linux内核尽量用内存,而不用swap
线上一台服务器kswapd0占用大量的cpu资源,导致负载过高,什么是kswapd0? Linux uses kswapd for virtual memory management such tha ...
- Linux 下的进程间通信:管道、消息队列、共享文件、共享内存
Table of Contents 无名管道 命名管道 消息队列 共享文件 示例 1. 生产者程序 示例 2. 消费者程序 共享内存 示例 3. memwriter 进程的源程序 示例 4. memr ...
- Linux内核之浅谈内存寻址
Linux内核之浅谈内存寻址 前言 最近在看内存寻址的内容,略有所得,发此文与大家一起交流.我们知道计算机是由硬件和软件组成,硬件主要包括运算器.控制器.存储器.输入设备和输出设备,软件主要是操作系统 ...
- linux 程序占内存,linux下,一个运行中的程序,究竟占用了多少内存
1. 在linux下,查看一个运行中的程序, 占用了多少内存, 通常的命令有php (1). ps aux:html 其中 VSZ(或VSS)列 表示,程序占用了多少虚拟内存.linux RSS列 ...
最新文章
- ios UI自动化测试
- webservice axis1.4生成客户端
- java轻功游戏,会轻功又可以飞的游戏(3d大型游戏)
- jQuery分别获取选中的复选框值
- 川菜为什么会成为食者最多的地方菜系?
- tensorflow 根据节点获取节点前的整张图
- jQuery最核心的基础设施之一——数据缓存模块进化史
- php socket 超时设置
- Vue+element搭建后台管理系统-二、安装插件
- 《分形艺术,当科学嫁给了艺术》稿件撰写历程
- 【饭谈】软件测试薪资层次和分段(修仙)
- python keyboard hook_[python] PyMouse、PyKeyboard用python操作鼠标和键盘
- 实现广告图片切换效果轮播图效果
- 2022年系统集成项目管理工程师考试知识点:区块链
- 39 项目实战---购物+转账系统
- 华为电脑浏览器主页被劫持
- element ui 日历空控件添加农历 节气和家假日
- STM32CubeIDE:ld.exe: xxx.elf section `.rodata‘ will not fit in region `FLASH‘;`FLASH‘ overflowed
- Postgresql通过docker进行高可用部署 pgpool-II
- Python调用百度AI接口
热门文章
- Beyond Compare 4.2.10 zhuce
- R语言怎么写积分_手记(4):定积分
- 小程序源码:紫色特别舒服的UI趣味测试-多玩法安装简单
- 解析访问www.baidu.com百度的流程?
- CTFHub-SSRF(全部)
- [《命如草贱》偶感小记]2013-2-17
- 斯坦福大学的产学研之路
- 淘宝、京东、天猫商品名称数据集下载最新版本。包括中药、化学器材、摄影、动物、酒水、办公事务。
- cocos2dx 更改精灵图片方法
- pythonocc安装_PythonOCC安装步骤与注意事项