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

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

2018-4-15 23:06
11591


篇开始进入正题,因为涉及 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 结构中,各字段的含义及用武之地!

上图还包含了 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 这种系统范围的调用表,地位非常重要,如果被换出物理内

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

第二个断点紧跟其后,这样就可以在调试器中检查 MmProbeAndLockPages() 是如何修改 MDL 中的标志;也可以使用编程手段

检查,如图中的第二个 if 块逻辑,事实上 MmProbeAndLockPages() 调用会向 MdlFlags 字段内添加 MDL_WRITE_OPERATION

与 MDL_PAGES_LOCKED 标志,这就是我们想要的结果!

最后我们调用 MmGetSystemAddressForMdlSafe() 把该 MDL 描述的原始虚拟地址映射到内核空间的另一处,新地址通常位于

驱动加载到的内核空间某处;局部变量 mapped_addr 持有这个新地址,最终用来返回并初始化全局变量 mapped_ki_service_table。

同理我们可以检查 MmGetSystemAddressForMdlSafe() 修改了哪些 MDL 结构成员,对于理解 MDL 的工作机理非常关键。



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

MapMdl() 第三部分逻辑如下图所示,我们检查 MmGetSystemAddressForMdlSafe() 是否多添加了一个

MDL_MAPPED_TO_SYSTEM_VA 标志,然后以 DBG_TRACE 宏打印信息。

全局变量 backup_mdl_ptr 是我们在调用 IoAllocateMdl() 就做好备份的 MDL 指针,它与 mdl_ptr 指向同一个 nt!_MDL 结构。

接下来的逻辑有助于你理解 MDL 头部后面的 PFN 数组:mdl_ptr 指向 nt!_MDL 结构头部,把它加上 1 ,意味着把它持有的

内存地址加上 1 * sizeof(MDL) 个字节,于是就定位到了 MDL 头部后面的 PFN 数组起始地址——现在全局变量

pfn_array_follow_mdl(一枚 PPFN_NUMBER 型指针)持有这个地址;正如图中倒数第三条 DbgPrint() 调用所言——

MDL 结构后偏移 xx (0x1b)地址处是一个 PFN 数组,用来存储该 MDL 描述的虚拟缓冲区映射到的物理页框号。

最后一条 DbgPrint() 调用通过解引 pfn_array_follow_mdl 来输出该地址处存放的物理页框号。

在 return mapped_addr; 语句的后面,则是 try-except 块的异常捕获逻辑,请参前面截图。



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

现在,程序访问可读写的 mapped_ki_service_table 与只读的 os_ki_service_table 都转译到同一块物理内存,

后者就是实际上存储 KiServiceTable 的地方。接下来,我们用一枚函数指针保存 KiServiceTable 中某个原始的系统服务,

然后用我们的钩子例程地址替换掉该位置处的原始系统服务,而钩子例程内部仅仅是调用原始系统服务,实现安全转发。

为了演示简单起见,我选取 KiServiceTable 中 0x39(57)号例程,因为它的参数只有一个,方便我们的钩子例程仿效同样的

参数声明——内核系统服务调度器(nt!KiFastCallEntry())并不知道它调用的目标系统服务已经被替换成我们的钩子例程,

所以他会以既定方式使用钩子例程的返回值和输出参数,在这种情况下,只要我们的钩子例程原型声明与被挂钩系统服务有

细微差别,都可能导致非预期的内核错误而蓝屏,显然,那些参数既多又复杂的系统服务不适合我用来演示。

此外,某些系统服务接收的参数类型的定义不在 wdm.h / ntddk.h 头文件内,讲明了这些数据类型不是给驱动开发人员使用的,

仅供内核组件使用,为了引入包含该定义的头文件则会碰到复杂的头文件嵌套包含问题,其麻烦程度丝毫不逊于 Linux 平台上

的 “二进制软件包依赖性地狱” 。

57 号系统服务例程亦即 nt!NtCompleteConnectPort(),有且仅有一个文档化的参数,WRK 源码中的相关定义如下图:



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

所以我们的钩子例程只要完全仿效它的返回值类型与形参类型即可,然后在内部调用指向原始例程的函数指针实施重定向。

通过 typedef 定义一个函数指针,其返回值类型与形参类型与 NtCompleteConnectPort() 一致,然后声明一个该函数指针

实例。相关代码如下图:




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

全局变量 ori_sys_service_ptr 持有 NtCompleteConnectPort() 的入口点地址,前者是在我们的 rootkit 入口点

DriverEntry() 中初始化的;保存这枚指针后就可以用钩子例程替换 NtCompleteConnectPort(),如下图所示:




需要指出一点,尽管把指针名称 mapped_ki_service_table 当作数组名称来访问 KiServiceTable 是被 C 语言核心规范允许的,

但是上图那段代码在编译器会产生警告,如下:

 1>warnings in directory d:\kmdsource_use_mdl_mapping_ssdt
 1>d:\kmdsource_use_mdl_mapping_ssdt\usemdlmappingssdt.c(155) : warning C4047: '=' : 'OriginalSystemServicePtr' differs in levels of indirection from 'DWORD'
 1>d:\kmdsource_use_mdl_mapping_ssdt\usemdlmappingssdt.c(157) : warning C4047: '=' : 'DWORD' differs in levels of indirection from 'NTSTATUS (__stdcall *)(HANDLE)'

ori_sys_service_ptr 是一枚 OriginalSystemServicePtr 型函数指针( NTSTATUS (__stdcall *)(HANDLE) ),而

mapped_ki_service_table 是普通指针,它的数组名称表示法结合数组下标,实际上被视为一个存储对应元素的 DWORD 变量,

两者的间接寻址级别不同。

就目前而言我们可以无视这两条警告,因为含有这段代码的 rootkit 源码在编译后确实能够安全地 hook 目标系统服务函数,系统

正常运作不会有问题,类似的警告可以通过指定警告级别的编译选项来过滤掉。

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

讲到这里你一定会嫌我既罗嗦又婆婆妈妈的,那么来看下面这一张简明扼要的全局概览,它解释了 MDL 是如何把一片缓冲区

映射到另一处,并描述两者相同的物理布局,注意,图中的组织结构是执行完 MmGetSystemAddressForMdlSafe() 后才会产生的。




注意,上图中我没有给出 PFN 数组中第一个成员携带的具体 20 位物理页框号,原始和映射到的新内核缓冲区,以及实际 RAM

中的物理页框号,而“byte within page”就是页内特定偏移处开始的字节序列,亦即系统服务例程入口点的实际物理地址!

这些  “占位符”  我会在第三部分的调试单元内给出,毕竟,驱动开发与调试是相辅相成的,只有理论没有实践怎么行,只有源码

没有调试怎知真理,不然,任何人对于内存的需求就真的不会超过 640 K 了。。。。。

最后贴上整个源码,方便各位编译后调试:

#include <ntddk.h>
#include "datatype.h"
#include "dbgmsg.h"
#define ETHREAD_OFFSET_SERVICE_TABLE				0xbc

PMDL  mdl_ptr;
PMDL  backup_mdl_ptr;
PPFN_NUMBER  pfn_array_follow_mdl;
short  mdl_header_length = sizeof(MDL);
DWORD*  mapped_ki_service_table;

void**  os_SSDT_ptr;
DWORD*  os_SSDT;
DWORD  os_ki_service_table;


 typedef NTSTATUS(*OriginalSystemServicePtr)
(
	HANDLE PortHandle
);

 OriginalSystemServicePtr  ori_sys_service_ptr;


 NTSTATUS our_hooking_routine(HANDLE PortHandle) 
 {
		
	 return (ori_sys_service_ptr(PortHandle));
 
 }


PVOID  MapMdl(PMDL  mdl_pointer, PVOID  VirtualAddress, ULONG  Length);
void  UnMapMdl(PMDL  mdl_pointer, PVOID  baseaddr);


//动态卸载后,dps 转储 mapped_ki_service_table 变量的输出应该不是系统服务例程了 

VOID Unload(PDRIVER_OBJECT driver)
{

	DBG_TRACE("OnUnload", "卸载前首先取消 MDL 对 KiServiceTable 的映射");
	UnMapMdl(mdl_ptr, mapped_ki_service_table);
	DBG_TRACE("OnUnload",  "UseMdlMappingSSDT.sys 已卸载");
	return;

}


NTSTATUS DriverEntry(PDRIVER_OBJECT driver, PUNICODE_STRING reg_path)
{
	BYTE*   currentETHREADpointer = NULL;
	

	driver->DriverUnload = Unload;
	currentETHREADpointer = (UCHAR*)PsGetCurrentThread();

	 os_SSDT_ptr = (void**)(currentETHREADpointer + ETHREAD_OFFSET_SERVICE_TABLE);
	 
         os_SSDT = *(DWORD**)os_SSDT_ptr;
	
        os_ki_service_table = *(DWORD*)os_SSDT;

        mapped_ki_service_table = MapMdl(mdl_ptr, (PVOID)os_ki_service_table,  0x191 * 4);

	if (mapped_ki_service_table == NULL) {

		DBG_TRACE("Driver Entry", ".........无法分配 MDL 来描述 OS 的 SSDT,并把它映射到另一个内核地址对其挂钩        和修改.......");
	}

	
	DbgPrint("我们把原始的 OS 系统服务指针表以写权限映射到的新内核空间为:   %p\r\n", mapped_ki_service_table);
	DbgPrint("解引这个新内核地址,应该就是表中的第一个系统服务的地址,或者用调试器命令 !dps 检查两者是否为同一张调用表:   %p\r\n", *mapped_ki_service_table);

		
//0x39 号系统服务为 nt!NtCompleteConnectPort() ,因为它只有一个参数,而且是文档化的,所以较易 hook 并重定向

	ori_sys_service_ptr = mapped_ki_service_table[0x39];

	mapped_ki_service_table[0x39] = our_hooking_routine;

	DbgPrint("我们把 0x39 号系统服务挂钩为:   %p\r\n", mapped_ki_service_table[0x39]);

	return STATUS_SUCCESS;
}



PVOID  MapMdl(PMDL  mdl_pointer,  PVOID  VirtualAddress,  ULONG  Length) 
{

		PVOID  mapped_addr;

		DbgPrint(" _KTHREAD.ServiceTable 自身的地址:   %p\r\n", &os_SSDT_ptr);
		DbgPrint(" ServiceTable 指向:   %p\r\n", os_SSDT_ptr);
		DbgPrint(" ServiceTable 所指处的内容:   %p\r\n", *os_SSDT_ptr);
		DbgPrint(" SSDT,亦即 nt!KeServiceDescriptorTable 地址,与 ServiceTable 所指处内容一致: %p\r\n", os_SSDT);

		DbgPrint(" nt!KeServiceDescriptorTable 所指处的内容:   %X\r\n", *os_SSDT);
		DbgPrint(" KiServiceTable 地址,与上面一致:   %X\r\n", os_ki_service_table);
		DBG_TRACE("MapMdl", ".......表中的系统服务地址可以通过 dps 转储 os_ki_service_table 查看!..........r\n");


		try {

			mdl_pointer = IoAllocateMdl(VirtualAddress, 0x191 * 4, FALSE, FALSE, NULL);

			if (mdl_pointer == NULL) {

				DBG_TRACE("MapMdl", ".........无法分配一个 MDL 来描述原始的 KiServiceTable !..........\r\n");
				return  NULL;
			}

			DbgPrint("分配的 MDL 指针自身的地址:  %p ,可用 dd 转储它持有的地址\r\n", &mdl_pointer);
			DbgPrint("分配的 MDL 指针指向一个 _MDL 的地址:   %p,与 dd %p 的输出一致,它用来描述原始的 KiServiceTable\r\n", mdl_pointer, &mdl_pointer);

			backup_mdl_ptr = mdl_pointer;

			// 这里设置的两个断点是为了观察调用前后的 _MDL.MdlFlags 如何变化
			__asm { 

				int 3;
			}

			if (mdl_pointer->MdlFlags & MDL_ALLOCATED_FIXED_SIZE)
			{
				DBG_TRACE("MapMdl", ".....IoAllocateMdl() 分配的 MDL 结构有固定大小(MDL_ALLOCATED_FIXED_SIZE)........\r\n");
			}

			MmProbeAndLockPages(mdl_pointer, KernelMode, IoWriteAccess);

			__asm {

				int 3;
			}

			if ((mdl_pointer->MdlFlags & MDL_ALLOCATED_FIXED_SIZE) &&
				(mdl_pointer->MdlFlags & MDL_WRITE_OPERATION) &&
				(mdl_pointer->MdlFlags & MDL_PAGES_LOCKED))
			{
				DBG_TRACE("MapMdl", " MmProbeAndLockPages() 以写权限(MDL_WRITE_OPERATION)把 MDL 描述的原始 KiServiceTable 所在页面锁定到物理内存中(MDL_PAGES_LOCKED)\r\n");
			}

			mapped_addr = MmGetSystemAddressForMdlSafe(mdl_pointer, NormalPagePriority);

			// 此处顺便观察 _MDL.MdlFlags 的变化
			__asm {

				int 3;
			}

			if (
				(mdl_pointer->MdlFlags & MDL_ALLOCATED_FIXED_SIZE) &&
				(mdl_pointer->MdlFlags & MDL_WRITE_OPERATION) &&
				(mdl_pointer->MdlFlags & MDL_PAGES_LOCKED) &&
				(mdl_pointer->MdlFlags & MDL_MAPPED_TO_SYSTEM_VA)
				)
			{
				DBG_TRACE("MapMdl", " MmGetSystemAddressForMdlSafe() 把 MDL 结构描述的原始 KiServiceTable 映射到另一个内核虚拟地址(MDL_MAPPED_TO_SYSTEM_VA)\r\n");
			}


			DbgPrint("MmGetSystemAddressForMdlSafe() 调用依然可以通过原始的 MDL 指针访问 _MDL 的地址:   %p\r\n", mdl_pointer);
			DbgPrint("也可以通过备份的 MDL 指针访问 _MDL 的地址:   %p,这都说明 MDL 结构尚未被释放,\r\n", backup_mdl_ptr);

			pfn_array_follow_mdl = (PPFN_NUMBER)(mdl_pointer + 1);

			DbgPrint(" MDL 结构后偏移 %2x 地址处是一个 PFN 数组,用来存储该 MDL 描述的虚拟缓冲区映射到的物理页框号\r\n", mdl_header_length);
			DbgPrint(" 该 PFN 数组的起始地址为:%p\r\n", pfn_array_follow_mdl);
			DbgPrint(" 第一个物理页框号为:%p\r\n", *pfn_array_follow_mdl);
			
			return mapped_addr;
																												
		}

		except (STATUS_ACCESS_VIOLATION) {

			IoFreeMdl(mdl_pointer);
			return NULL;
		}
		
}


void  UnMapMdl(PMDL  mdl_pointer,  PVOID  baseaddr)
 {

	if (mdl_pointer != backup_mdl_ptr) {

		DBG_TRACE("UnMapMdl", ".......先解锁备份 MDL 映射的页面,然后释放备份的 MDL........");

		MmUnlockPages(backup_mdl_ptr);	// 此例程的效果是,无法通过映射的系统地址来访问 KiServiceTable,且 _MDL 结构中各字段已发生变化,
		IoFreeMdl(backup_mdl_ptr);		// 此例程的效果是,MDL 指针不再持有 _MDL 结构的地址


		if (backup_mdl_ptr == NULL) {

			DBG_TRACE("UnMapMdl", ".............解锁页面,释放备份 MDL 完成!................");
		}

		return;
	}


	DBG_TRACE("UnMapMdl", ".........原始 MDL 未被修改,解锁它映射的页面后释放它...........");
		
		// 如果前面使用 MmBuildMdlForNonPagedPool() ,就不能执行下面前2个操作
		//MmUnmapLockedPages(baseaddr,  mdl);
	MmUnlockPages(mdl_pointer);
	IoFreeMdl(mdl_pointer);

	if (mdl_pointer == NULL) {

		DBG_TRACE("UnMapMdl", ".............解锁页面,释放原始 MDL 完成!................");
	}

	return;
}

头文件 dbgmsg.h 内容如下,它仅仅是在预处理阶段替换为 DbgPrint() 的一些可变参数罢了,没啥黑科技可言:

#ifdef LOG_OFF
#define DBG_TRACE(src,msg)
#define DBG_PRINT1(arg1)
#define DBG_PRINT2(fmt,arg1)
#define DBG_PRINT3(fmt,arg1,arg2)
#define DBG_PRINT4(fmt,arg1,arg2,arg3)
#else
#define DBG_TRACE(src,msg)	DbgPrint("[%s]:%s\n",src,msg)
#define DBG_PRINT1(arg1)	DbgPrint("%s",arg1)
#define DBG_PRINT2(fmt,arg1)	DbgPrint(fmt,arg1)
#define DBG_PRINT3(fmt,arg1,arg2)	DbgPrint(fmt,arg1,arg2)
#define DBG_PRINT4(fmt,arg1,arg2,arg3)	DbgPrint(fmt,arg1,arg2,arg3)

另一个包含文件 datatype.h 的所有内容, 请参考第一部分:就是那张 DWORD、WORD、BYTE 类型定义的截图。



[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

最后于 2019-1-11 19:35 被kanxue编辑 ,原因:
收藏
免费 2
支持
分享
最新回复 (19)
雪    币: 81
活跃值: (40)
能力值: ( LV9,RANK:140 )
在线值:
发帖
回帖
粉丝
2
简单来说,MDL是用来描述一段物理内存,将物理内存映射成虚拟内存,然后修改页面属性,从而绕过只读.
2018-4-16 09:22
0
雪    币: 405
活跃值: (2150)
能力值: ( 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
雪    币: 1484
活跃值: (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
雪    币: 1484
活跃值: (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
雪    币: 310
活跃值: (2227)
能力值: ( 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
雪    币: 3102
活跃值: (3623)
能力值: ( 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
雪    币: 11974
活跃值: (5554)
能力值: ( 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
游客
登录 | 注册 方可回帖
返回
//