首页
社区
课程
招聘
[原创]---RootKit 核心技术——利用 NT!_MDL 突破 KiServiceTable 的只读访问限制 PART II ----
发表于: 2018-4-15 23:06 11800

[原创]---RootKit 核心技术——利用 NT!_MDL 突破 KiServiceTable 的只读访问限制 PART II ----

2018-4-15 23:06
11800


nt!_MDL 代表一个 “内存描述符链表” 结构,它描述着用户或内核模式虚拟内存(亦即缓冲区),其对应的那些物理页被锁定住,

无法换出。

因为一个虚拟的,地址上连续的用户或内核缓冲区可能映射到多个不连续的物理页,所以 nt!_MDL 定长(0x1c 字节)的头部后紧

跟数量可变的页框号(Page Frame Numbers),MDL 描述的每个物理页面都有一个页框号,

于是这些页框号引用的物理地址范围就对应了一片特定的用户或内核模式缓冲区。

————————————————————————————————————————————————————————

通常虚拟和物理页的大小为 4 KB,KiServiceTable 中的系统服务数量为 401 个,每函数的入口点占用 4 字节,整张调用表大小

为 1.6 KB,通过 MDL 仅需要一张物理页即可描述这个缓冲区;在这种情况下,该 MDL 后只有一个页框号。


尽管 nt!_MDL 是半透明的结构,不过在内核调试器以及 WRK 源码面前还是被脱的一丝不挂,如下图为 WRK 源码

的 “ntosdef.h” 头文件中的定义,如你所见,称为 “链表” 乃因它的首个字段 “Next” 是一枚指针,指向后一个 nt!_MDL 结构。




对于我们 hook KiServiceTable 的场景而言,无需用到 Next 字段;那什么情况下会用到呢?

Windows 中某些类型的驱动程序,例如网络栈,它们支持 MDL 链,其内的多个 MDL 描述的那些缓冲区实际上是零散的,

假设栈中每个驱动都分配一个 MDL,其后跟着一些物理页框号来描述它们各自用到的虚拟缓冲区,那么这些缓冲区就通过

每个 _MDL 的 Next 字段(指向下一个 MDL)链接起来。

————————————————————————————————————————————————————————————

下面简述 MDL 结构中,各字段的含义及用武之地!

上图还包含了 MdlFlags 字段的所有标志宏定义,这个 2 字节的字段可以是任意宏的组合,用于说明 MDL 的一些状态与属性。

● 对于描述用户模式缓冲区的 MDL,其内的 Process 字段指向所属进程的 EPROCESS 结构,进程中的这块虚拟地址空间被 MDL 锁

住。

● 如果由 MDL 描述的缓冲区映射到内核虚拟地址空间中,_MDL 的 MappedSystemVa 字段指向内核模式缓冲区的基地址。

● 仅当 _MDL 的 MdlFlags 字段内设置了 MDL_MAPPED_TO_SYSTEM_VA 或  MDL_SOURCE_IS_NONPAGED_POOL 比特位,

MappedSystemVa 字段才有效。

● _MDL 的Size字段含有 MDL 头部加上其后的整个 PFN 数组总大小。

● MDL 的 StartVa 字段和 ByteOffset 字段共同定义了由该 MDL 锁定的原始缓冲区的起始地址。

(原始缓冲区可能会映射到其它内核缓冲区或用户缓冲区)

● StartVa 指向虚拟页的起始地址,ByteOffset 包含实际从 StartVa 开始的缓冲区偏移量;

● MDL 的 ByteCount 字段描述由该 MDL 锁定的缓冲区大小(以字节为单位);

对于我们要 hook 的 KiServiceTable 而言, KiServiceTable 这片内核缓冲区所在的虚拟页起点由 StartVa 字段携带;

ByteOffset 字段则携带 KiServiceTable 的页内偏移量,ByteCount 字段携带 KiServiceTable 这片内核缓冲区的大小。

如果你现在看得云里雾里,不用担心,后面我们在调试时会把描述 KiServiceTable 的一个 nt!_MDL 结构实例拿出来分析,

到时候你就会恍然大悟这些字段的设计思想了。

————————————————————————————————————————————————————————————

通过编程方式使用 MDL 绕过 KiServiceTable 的只读属性,需要借助 Windows 执行体组件中的 I/O 管理器以及
内存管理器导出的一些函数,大致流程如下:
IoAllocateMdl() 分配一个 MDL 来描述 KiServiceTable -> MmProbeAndLockPages() 把该 MDL 描述的 KiServiceTable 所
属物理页锁定在内存中,并赋予对这张页面的读写访问权限(实际是将描述该页面的 PTE 内容中的 “R” 标志位修改成 “W”)
-> MmGetSystemAddressForMdlSafe() 将 KiServiceTable 映射到另一片内核虚拟地址区域(一般而言,位于 rootkit 被加载
到的内核地址范围内)。

如此一来,KiServiceTable 的原始虚拟地址与新映射的虚拟地址都转译到相同的物理地址,而且描述新虚拟地址的 PTE 内容标记了

写权限比特位,这样我们就能够通过修改这个新的虚拟地址中的系统服务例程实现安全挂钩 KiServiceTable,不会导致 BugCheck。

如下所示,我把上述涉及的所有操作都封装到一个自定义的函数 MapMdl() 里面。由于整个逻辑比较长,截图分为多张说明:

MapMdl() 在我们的 rootkit 入口点——DriverEntry() 中被调用,而在 DriverEntry() 外部声明几个与 MDL 相关的全局变量,

它们被 MapMdl() 与 DriverEntry() 共享。

注意,os_ki_service_table 存储着 KiServiceTable 的地址(参见前一篇定位 KiServiceTable 的代码),

把它从 DWORD 转换为泛型指针是为了符合 MapMdl() 中的 IoAllocateMdl() 调用时的形参要求;最后一个参数——表达式

0x191 * 4——就是整个 KiServiceTable 缓冲区的大小:假若 MapMdl() 成功返回,则全局变量 mapped_ki_service_table

持有 KiServiceTable 新映射到的内核虚拟地址;这些全局变量都是 “自注释” 的,pfn_array_follow_mdl 持有的地址处内容

就是 MDL 描述的物理页框号:



——————————————————————————————————————————————————————————————

MapMdl() 第一部分逻辑如下图所示,局部变量 mapped_addr 预期存放 KiServiceTable 新映射到的内核虚拟地址,并作为

MapMdl() 的返回值给 DriverEntry(),进一步初始化全局变量 mapped_ki_service_table。

注意,PVOID 可以赋给其它任意类型的指针,这是合法的。

IoAllocateMdl() 返回一枚指针,指向分配好的 MDL,该 MDL 描述 KiServiceTable 的物理内存布局;这枚指针被用来初始化

作为实参传入的全局变量mdl_ptr(mdl_pointer 是形参)。

我添加的第一个软件断点就是为了研究 IoAllocateMdl() 分配的 MDL 其中 MappedSystemVa,StartVa,以及 MdlFlags 这些

字段的内容——事实上,这些字段值会在

IoAllocateMdl() -> MmProbeAndLockPages() ->MmGetSystemAddressForMdlSafe()

调用链的每一阶段发生变化,所以我总共添加了三个断点在相关的检查区域,有助于我们在后面的调试过程中深入理解 nt!_MDL

的设计思想。


我把使用 Windows 执行体组件例程进行的操作放入一个 try-except 块内,以便处理可能出现的异常,except 块内的逻辑如下

图,当违法访问出现时,调用 IoFreeMdl() 释放我们的 MDL 指针,然后 MapMdl() 返回 NULL,从而导致 DriverEntry() 打印出

错信息。

————————————————————————————————————————————————————————————

关于 IoAllocateMdl() 的第二个参数,我们有必要进一步了解,所以我翻译了 MSDN 文档上的相关片段,如下:

IoAllocateMdl() 的第二个参数指定要通过分配的 MDL 描述的缓冲区的大小。如果这个长度小于 4KB,

那么映射它的 MDL 就只描述了一个被锁定的物理页面;

如果长度是 4KB 的整数倍,那么映射它的 MDL 就描述了相应数量的物理页面(通过紧接 MDL 后面的 PFN 数组)

对于 Windows Server 2003,Windows XP,以及 Windows 2000,

此例程支持的最大缓冲区长度(以字节为单位)是:

PAGE_SIZE * (65535 - sizeof(MDL)) / sizeof(ULONG_PTR) (约 67 MB)

对于 Windows Vista 和 Windows Server 2008,能够传入的最大缓冲区大小为:

(2 gigabytes - PAGE_SIZE)

对于 Windows 7 和 Windows Server 2008 R2,能够传入的最大缓冲区大小为:

(4 gigabytes - PAGE_SIZE)

执行此例程的 IRQL 要求为 <= DISPATCH_LEVEL

————————————————————————————————————————————————————————————

MapMdl() 第二部分逻辑如下图所示,它紧跟在第一个软件断点之后。我们检查 MDL 中的 MDL_ALLOCATED_FIXED_SIZE 标志是

否置位,该标志因调用 IoAllocateMdl() 传入第二个参数指示固定大小而置位;MmProbeAndLockPages() 的第三个参数是实现

写访问的关键所在,能否锁定内存倒是其次,因为像 KiServiceTable 这种系统范围的调用表,地位非常重要,如果被换出物理内

存,系统岂不就崩溃了,所以坦白讲我们只是因为需要写权限才调用它的。


[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

最后于 2019-1-11 19:35 被kanxue编辑 ,原因:
收藏
免费 2
支持
分享
最新回复 (19)
雪    币: 81
活跃值: (40)
能力值: ( LV9,RANK:140 )
在线值:
发帖
回帖
粉丝
2
简单来说,MDL是用来描述一段物理内存,将物理内存映射成虚拟内存,然后修改页面属性,从而绕过只读.
2018-4-16 09:22
0
雪    币: 405
活跃值: (2285)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
3
归根到底,物理内存没有任何保护,所有的保护都在页表中实现,基本也就是这里面的猫腻,同样的物理地址指向不同的线性地址,同样的线性地址指向不同的物理地址==。
2018-4-16 13:40
1
雪    币: 368
活跃值: (431)
能力值: ( LV2,RANK:140 )
在线值:
发帖
回帖
粉丝
4
wowocock 归根到底,物理内存没有任何保护,所有的保护都在页表中实现,基本也就是这里面的猫腻,同样的物理地址指向不同的线性地址,同样的线性地址指向不同的物理地址==。
MmProbeAndLockPages
====
这个函数,锁定一个页。
之前一直纠结,是如何办到的,内核中对应着什么数据结构了?
本来睡着了,爬起来看了下reactos的实现,自问自答吧:
如果是用户地址锁住进程的工作集,如果是内核地址使用的pfnLock


又对着wrk看了一遍,竟然没看懂,有人能对着wrk看下,说下怎么实现的吗?
最后于 2018-4-16 14:30 被又出bug了编辑 ,原因:
2018-4-16 13:58
1
雪    币: 1604
活跃值: (640)
能力值: ( LV13,RANK:460 )
在线值:
发帖
回帖
粉丝
5
wowocock 归根到底,物理内存没有任何保护,所有的保护都在页表中实现,基本也就是这里面的猫腻,同样的物理地址指向不同的线性地址,同样的线性地址指向不同的物理地址==。
等第三部分调试章节出来,您就知道其中奥秘了  ^^
2018-4-16 20:38
0
雪    币: 4
活跃值: (170)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
膜拜大佬..
2018-4-17 10:14
0
雪    币: 1535
活跃值: (695)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
666
2018-4-17 17:30
0
雪    币: 6818
活跃值: (153)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
2018-4-17 23:32
0
雪    币: 1604
活跃值: (640)
能力值: ( LV13,RANK:460 )
在线值:
发帖
回帖
粉丝
9
又出bug了 wowocock 归根到底,物理内存没有任何保护,所有的保护都在页表中实现,基本也就是这里面的猫腻,同样的物理地址指向不同的线性地址,同样的线性地址指向不同的 ...
有空帮你看下wrk1.2版源码的实现
2018-4-22 22:46
0
雪    币: 1485
活跃值: (1135)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
请问大神。WIN7以上的系统。R3层还有办法直接访问内核层?比如R3使用调用门,中断门?
2018-4-23 16:53
0
雪    币: 1604
活跃值: (640)
能力值: ( LV13,RANK:460 )
在线值:
发帖
回帖
粉丝
11
冰雄 请问大神。WIN7以上的系统。R3层还有办法直接访问内核层?比如R3使用调用门,中断门?
win7  以上不太清楚,不过我猜底层的用户-内核隔离机制都大同小异:Ring3  无法直接访问  Ring0,要么得通过内联汇编  int  2e  或  
sysenter/syscall  指令;要么得通过  ntdll.dll  中的系统服务前端函数;Ring3  使用调用门或陷阱门,恐怕无法通过段描述符的权限检查;就算在用户模式直接用    int  2e  或  sysenter,也是无法通过段权限检查的吧
最后于 2018-4-23 18:26 被shayi编辑 ,原因:
2018-4-23 18:25
0
雪    币: 1485
活跃值: (1135)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
段权限检查。可以在GDT或  idt中加入一致代码段的调用门或中断门,tss段。达到低权限访问高权限。。最近看了滴水的教程。可以通过tss硬件切换任务。架空系统来绕过权限检查,只是自己测试失败。
2018-4-27 11:57
0
雪    币: 1604
活跃值: (640)
能力值: ( LV13,RANK:460 )
在线值:
发帖
回帖
粉丝
13
冰雄 段权限检查。可以在GDT或 idt中加入一致代码段的调用门或中断门,tss段。达到低权限访问高权限。。最近看了滴水的教程。可以通过tss硬件切换任务。架空系统来绕过权限检查,只是自己测试失败。
这部分课题我涉猎较少,期待您后续的研究心得分享!
2018-4-27 21:47
0
雪    币: 300
活跃值: (2477)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
14
感谢分享
2018-4-28 20:36
0
雪    币: 11716
活跃值: (133)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
15
#寻宝大战#祝看雪19岁快乐!
2019-1-11 20:15
0
雪    币:
活跃值: (421)
能力值: ( LV5,RANK:73 )
在线值:
发帖
回帖
粉丝
16
您好,我这里有个疑问,根据msdn的解释:Because the pages described by the MDL are already nonpageable and are already mapped to the system address space, drivers must not try to lock them by using the MmProbeAndLockPages routine


我的问题有两个:
1.msdn提到不要用MmProbeAndLockPages函数锁定非分页内存或者尝试对描述非分页内存的MDL解锁。SSDT是属于非分页内存,本身就不会被page out。MmProbeAndLockPages函数锁定物理面之后还更新了MDL的FPN,所以后续调用MmGetSystemAddressForMdlSafe能正常映射内核地址。上面的SSDT读写过程是否替换为用 MmBuildMdlForNonPagedPool函数更新MDL之后,再调用MmMapLockedPagesSpecifyCache进行物理内存映射更合理?

2.MmProbeAndLockPages是否没有修改内存读写属性的能力,传入IoReadAccess/IoWriteAccess参数只是为了探测目标内存是否具有相应的权限,能读写内存的原因是MmMapLockedPagesSpecifyCache映射得到的内核虚拟地址是具有读写权限的?因为我在进行inline hook的过程中并没有使用类似调用:MmProbeAndLockPages(addr,IoWriteAccess),直接调用MmMapLockedPagesSpecifyCache映射非分页内存也能进行读写

如有冒犯,请见谅,我是windows内核新手,很多东西都不懂
2020-3-19 00:10
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
17
优秀...............
2020-4-4 20:15
0
雪    币: 3312
活跃值: (3918)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
18
用得着这么麻烦吗?我直接改ssdt表所以物理页对应的pte属性不行吗?
2020-7-7 00:26
0
雪    币: 259
活跃值: (283)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
19
mark
2020-7-7 07:16
0
雪    币: 12363
活跃值: (5884)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
20
三猫 您好,我这里有个疑问,根据msdn的解释:Because the pages described by the MDL are already nonpageable and are already ...
你说的都是对的,楼主表述有误,我看了一下MmProbeAndLockPages的流程,它只是给MDL加上了write_operation的标志位,实际上不论加不加这个标志位,MmMapLockPagesSpecifyCache给物理页新映射的PTE都是RW的。楼主代码里的MmGetSystemAddressForMdlSafe本身也是对MmMapLockPagesSpecifyCache的封装,只要MDL里填充好了PFN,那这函数映射出的新内存就是RW的了,再加上ssdt本身不可换出,楼主用的MmProbeAndLockPages实际上只有给MDL填充PFN的作用,参数填IoWriteAccess不过是自我安慰罢了
2020-9-10 00:11
0
游客
登录 | 注册 方可回帖
返回
//