首页
社区
课程
招聘
[原创]不用CR0或MDL修改内核非分页写保护内存的一种思路(x64)
发表于: 2020-10-3 21:42 13314

[原创]不用CR0或MDL修改内核非分页写保护内存的一种思路(x64)

2020-10-3 21:42
13314

    开门见山,本文的核心思路就是通过填充页表项,将一块连续的虚拟地址映射到新的地址,同时将需要修改的只读内存对应页表项的Dirty位置位。在Windows操作系统下,写保护是通过保护特定虚拟地址实现的,若不建立新映射,则即使将Dirty位置位,尝试写只读内存照样会触发BugCheck,若建立了新映射但不置位Dirty则触发PAGE_FAULT的BugCheck,两个步骤缺一不可。

    填充页表项首先需要动态定位PTEBase,国际惯例是采用大表哥的页表自映射法,代码如下:

    这里我顺便也给出另一种获取方案,基本思路是通过NT导出函数

    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的代码如下:

    获取到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即可。

    

    在Win10 18362.207 x64上的测试代码与结果:


[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2020-10-7 23:22 被hhkqqs编辑 ,原因:
收藏
免费 13
支持
分享
最新回复 (38)
雪    币: 4709
活跃值: (1605)
能力值: ( LV2,RANK:15 )
在线值:
发帖
回帖
粉丝
2
插个眼
2020-10-3 23:34
0
雪    币: 3681
活跃值: (4856)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3

1.KeCapturePersistentThreadState调用的结构里直接就有PteBase,不需要用MmPfnDataBase计算
2.你这个没处理有Shadow的情况
3.不需要定位KeFlushTb,自己Ipi插过去直接自己刷就行,而且中断拉高因为自己当前用刷自己当前的就够了,不需要完整刷

最后于 2020-10-3 23:42 被syser编辑 ,原因:
2020-10-3 23:41
0
雪    币: 12512
活跃值: (6019)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
4
syser 1.KeCapturePersistentThreadState调用的结构里直接就有PteBase,不需要用MmPfnDataBase计算2.你这个没处理有Shadow的情况3.不需要定位KeFlus ...
PteBase是在某个RS1预览版才被加进去的,但MmPfnDataBase是全版本通用。我只考虑了内核页表的映射,所以就没管Shadow。至于lpi怎么刷倒是要请教一下,我已经在刷TLB这里卡了几天了
2020-10-3 23:44
0
雪    币: 248
活跃值: (3789)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
其实这样跟MDL的实现方式差不多,都是映射到新的地址
只是LZ不直接调用MDL,而是做了跟MDL同样的事
2020-10-4 18:26
0
雪    币: 12512
活跃值: (6019)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
6
yy虫子yy 其实这样跟MDL的实现方式差不多,都是映射到新的地址 只是LZ不直接调用MDL,而是做了跟MDL同样的事
我写这个纯粹吃饱了撑的,之前很好奇MDL的MmMapLockedPages是如何做到把只读内存映射到可写内存的,现在做个弟中弟版MDL才发现原来写保护只是基于虚拟地址而非物理地址
2020-10-4 18:41
0
雪    币: 248
活跃值: (3789)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
hhkqqs 我写这个纯粹吃饱了撑的,之前很好奇MDL的MmMapLockedPages是如何做到把只读内存映射到可写内存的,现在做个弟中弟版MDL才发现原来写保护只是基于虚拟地址而非物理地址
吃饱了撑的也是一种值得发扬的精神
2020-10-4 19:11
0
雪    币: 12512
活跃值: (6019)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
8
自顶一波
2020-10-7 13:47
0
雪    币: 1028
活跃值: (720)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9

mark

最后于 2020-10-8 12:16 被Caim Astraea编辑 ,原因:
2020-10-7 22:47
0
雪    币: 21
活跃值: (87)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
mark
2020-10-7 23:18
0
雪    币: 433
活跃值: (1930)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
11
走一条没有人走过的路..才是最好的...
2020-10-8 01:53
0
雪    币: 6209
活跃值: (5037)
能力值: ( LV10,RANK:160 )
在线值:
发帖
回帖
粉丝
12
mark
2020-10-8 06:33
0
雪    币: 446
活跃值: (600)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
mark
2020-10-8 10:00
0
雪    币: 1042
活跃值: (560)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
14
插眼
2020-10-8 16:19
0
雪    币: 576
活跃值: (2035)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
15
mark,虽然不搞内核
2020-10-11 20:32
0
雪    币: 437
活跃值: (524)
能力值: ( LV3,RANK:27 )
在线值:
发帖
回帖
粉丝
16

操作的其实是R/W位、bit6的Dirty位,其含义并非控制可读可写。

YY(估计是受最后放出的那个结构的影响)

2020-10-15 16:52
0
雪    币: 437
活跃值: (524)
能力值: ( LV3,RANK:27 )
在线值:
发帖
回帖
粉丝
17
“笔者发现当前的Intel CPU支持的虚拟地址寻址最大位数限制为48位,对于64位Windows来说,第47位被用来区分用户层虚拟地址和内核层虚拟地址,即内核层地址实际上只有47位的有效”
说明:
这有点渊源、Intel CPU其实有支持64位的、就是所谓的IA64架构。只不过用的不多、而且也和32位完全不兼容。而当时AMD出的AMD64架构、就是目前被广泛采用的结构、其规定最高的那几位为符号位。由此还引出一个规范性地址的说法。之所以能够流行、主要是因为此结构兼容32位地址结构、而且,即使有几位没用,也已经可以访问足够大的地址空间了。故此,现在所说的x64之类的、几乎都是这种架构。
所以、Intel有些手册上没有的一部分、AMD手册上则都能找到讷
2020-10-15 16:59
0
雪    币: 12512
活跃值: (6019)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
18
GhostValley “笔者发现当前的Intel CPU支持的虚拟地址寻址最大位数限制为48位,对于64位Windows来说,第47位被用来区分用户层虚拟地址和内核层虚拟地址,即内核层地址实际上只有47位的有效” 说明: ...
从报道来看,48位虚拟地址这种设计是Intel跟风AMD的结果,9999-12这种结构也确实完美兼容了32位的299-12,至于Intel有没有后悔这波跟风就是另一回事了。
2020-10-15 17:12
0
雪    币: 12512
活跃值: (6019)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
19
GhostValley 操作的其实是R/W位、bit6的Dirty位,其含义并非控制可读可写。YY(估计是受最后放出的那个结构的影响)
感谢提醒,之前查了些资料,也大概了解这两个位跟可写没什么关系,只是在linux中,新分配的页若没有置位Dirty,尝试写入会触发异常,后来发现在Windows里也是一样,我是挺好奇这个异常是从哪里分发的,请大佬不吝赐教
2020-10-15 18:11
0
雪    币: 437
活跃值: (524)
能力值: ( LV3,RANK:27 )
在线值:
发帖
回帖
粉丝
20
hhkqqs 感谢提醒,之前查了些资料,也大概了解这两个位跟可写没什么关系,只是在linux中,新分配的页若没有置位Dirty,尝试写入会触发异常,后来发现在Windows里也是一样,我是挺好奇这个异常是从哪里分发 ...
“这两个位跟可写没什么关系” 0x42,bit1是R/W位、bit6是Dirty位,bit1就是控制当前物理页是否可读可写的。我意在说明您说的Dirty其实已经是包含了对R/W位的操作的。
2020-10-15 18:25
0
雪    币: 437
活跃值: (524)
能力值: ( LV3,RANK:27 )
在线值:
发帖
回帖
粉丝
21
GhostValley “这两个位跟可写没什么关系” 0x42,bit1是R/W位、bit6是Dirty位,bit1就是控制当前物理页是否可读可写的。我意在说明您说的Dirty其实已经是包含了对R/W位的操作的。
只写入0x2应该也行吧
2020-10-15 18:27
0
雪    币: 437
活跃值: (524)
能力值: ( LV3,RANK:27 )
在线值:
发帖
回帖
粉丝
22
yy虫子yy 其实这样跟MDL的实现方式差不多,都是映射到新的地址 只是LZ不直接调用MDL,而是做了跟MDL同样的事
兄弟说的不大准确:
MDL结构和这个比起来、属于"上层应用"的范畴、MDL最后未文档化的PFN是其本质;
这篇文章则不同、其层面要靠下。
2020-10-15 18:43
0
雪    币: 12512
活跃值: (6019)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
23
GhostValley 只写入0x2应该也行吧
试了一下确实没必要改bit6,关键步骤就两步,改bit1+刷tlb
2020-10-15 19:24
0
雪    币: 665
活跃值: (1051)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
24
插眼
2020-10-19 04:08
0
雪    币: 461
活跃值: (2808)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
25
mark,敬佩楼主。
2020-10-21 14:08
0
游客
登录 | 注册 方可回帖
返回
//