设为首页 加入收藏

TOP

-------- Rootkit 核心技术——利用 nt!_MDL 突破 KiServiceTable 的只读访问限制 Part II --------(一)
2018-10-21 18:08:42 】 浏览:218
Tags:-------- Rootkit 核心 技术 利用 _MDL 突破 KiServiceTable 只读 访问 限制 Part

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

本篇开始进入正题,因为涉及 MDL,所以相关的背景知识是必须的:

 

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,其内的 Process 字段指向所属进程的 EPROCESS 结构,进程中的这块虚拟地址空间被 MDL 锁

住。

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

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

MappedSystemVa 字段才有效。

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

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

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 其中 MappedSyst

首页 上一页 1 2 3 下一页 尾页 1/3/3
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇[C语言] 数据结构-离散存储链表定.. 下一篇-------- ROOTKIT 核心技术——系..

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目