64位内核开发第十二讲,内核下的MDL操作
MDL 内存描述符列表
一丶MDL简介
1.1 MDL描述
虚拟地址会跨越一系列连续的虚拟地址,IO缓冲区也可以分布在多个物理页上,并且这些物理页是不连续的。操作系统就会使用 内存描述符表(MDL
)这个结构来描述虚拟内存缓冲区的物理页面布局的。
MDL
其实是一个MDL结构体,他后面跟着一个描述 I/O 缓冲区所在的物理内存的数据数组。 MDL
的大小根据MDL描述的 I/O缓冲区的特征而变化的。 系统例程可以用于计算MDL
所需要的大小,以及分配和释放MDL
。
要说一句的是 MDL是半透明的。我们的驱动程序不应该直接访问 MDL结构体中的成员。而是使用操作系统提供的宏来进行操作。
在MDL
后面 还跟着一个 PFN_NUMBER
(无符号整数)的数组,每个元素描述着缓冲区所覆盖的一个月面。 而整个MDL列表则是对于一个缓冲区页面的描述
。
1.2 MDL的结构与宏操作
先看下MDL
结构体是啥样
typedef struct _MDL {
struct _MDL *Next;
CSHORT Size;
CSHORT MdlFlags;
struct _EPROCESS *Process;
PVOID MappedSystemVa;
PVOID StartVa;
ULONG ByteCount;
ULONG ByteOffset;
} MDL, *PMDL;
参数详解:
参数 | 含义 |
---|---|
Next |
指向下一MDL的指针 |
Size |
整个MDL的长度(包含后面的数组) |
MdlFlags |
MDL的标记值 |
Process |
所属进程的EPROCES |
MappedSystemVa |
该段缓冲区映射在系统空间的地址 |
StartVa |
虚拟地址(对齐4KB) |
ByteCount |
该段缓冲区的长度 |
ByteOffset |
虚拟地址偏移 |
其中要说的是 startVa + byteoffset =该段缓冲在Process进程空间中的虚拟地址
MDLflags 可以判断是否是锁定状态 是的话就进行释放。
其字段有如下:(未公开也是看资料得来的)
-
MDL_MAPPED_TO_SYSTEM_VA MDL_SOURCE_IS_NONPAGED_POOL
如果设置了这两个位 那么MappedSystemVa
才是有效的 -
MDL_WRITE_OPERATION MDL_PAGES_LOCKED
此字段代表MmProbeAndLockPages
函数设置的。代表锁定了页面。
下面代码演示的是 从IRP中释放MDL链
VOID MyFreeMdl(PMDL Mdl)
{
PMDL currentMdl, nextMdl;
for (currentMdl = Mdl; currentMdl != NULL; currentMdl = nextMdl)
{
nextMdl = currentMdl->Next;
if (currentMdl->MdlFlags & MDL_PAGES_LOCKED)
{
MmUnlockPages(currentMdl);
}
IoFreeMdl(currentMdl);
}
}
MDL 宏 是用来操作 MDL的 可以看下如下 宏
名称 | 作用 |
---|---|
MmGetMdlVirtualAddress |
返回 I/O 缓冲区的大小(以字节为单位) |
MmGetMdlByteCount |
返回 I/O 缓冲区的大小(以字节为单位) |
MmGetMdlByteOffset |
返回 I/O 缓冲区开头的物理页内的偏移量 |
MmGetMdlBaseVa |
返回I/O缓冲区物理页的虚拟地址 |
MmGetSystemAddressForMdlSafe |
获取缓冲映射在系统空间的地址 |
MmGetSystemAddressForMdl |
同上,一个安全一个不安全。 |
下面看一下宏的本质实现
#define MmGetMdlVirtualAddress(Mdl) \
((PVOID) ((PCHAR) ((Mdl)->StartVa) + (Mdl)->ByteOffset)
#define MmGetMdlByteCount(Mdl) ((Mdl)->ByteCount)
#define MmGetMdlByteOffset(Mdl) ((Mdl)->ByteOffset)
#define MmGetMdlBaseVa(Mdl) ((Mdl)->StartVa)
#define MmGetSystemAddressForMdl(MDL) \
(((MDL)->MdlFlags & (MDL_MAPPED_TO_SYSTEM_VA | \
MDL_SOURCE_IS_NONPAGED_POOL)) ? \
((MDL)->MappedSystemVa) : \
(MmMapLockedPages((MDL),KernelMode)))
#define MmGetSystemAddressForMdlSafe(MDL, PRIORITY) \
(((MDL)->MdlFlags & (MDL_MAPPED_TO_SYSTEM_VA | \
MDL_SOURCE_IS_NONPAGED_POOL)) ? \
((MDL)->MappedSystemVa) : \
(MmMapLockedPagesSpecifyCache((MDL), \
KernelMode, \
MmCached, \
NULL, \
FALSE, \
(PRIORITY))))
二丶MDL实战操作
2.1 MDL的申请和释放
MDL 申请 与释放是使用的如下API
PMDL IoAllocateMdl(
[in, optional] __drv_aliasesMem PVOID VirtualAddress,
[in] ULONG Length,
[in] BOOLEAN SecondaryBuffer,
[in] BOOLEAN ChargeQuota,
[in, out, optional] PIRP Irp
);
void IoFreeMdl(
[in] PMDL Mdl
);
其中还可以使用 MmCreateMdl
(与IoAllocateMdl
类似,只不过参数少了几个)
但是MmCreateMdl
本质是 申请一个非分页内存。然后初始化为MDL的。 IoAllocateMdl
也可以使用。但是要注意你的内存是什么内存。
释放自然不用多说 主要说下申请的参数意思。
参数 | 含义 |
---|---|
VirtualAddres |
指向MDL要描述的缓冲区的虚拟地址的指针 |
Leng |
表示参数1缓冲的长度 |
SecondaryBuf |
是否是 主/副缓冲区,只是将 此MDL插入到IRP中(Irp->MdlAddress )还是替换此值 TRUE 插入 FALSE 替换。如果没有IRP与MDL关联 则必须为FALSE |
ChargeQ |
保留给系统使用,必须是FALSE |
Irp |
给定的IRP指针,根据参数3来确定是否将MDL插入或者替换到此IRP中。如果IRP为NULL则给FALSE即可。 |
简单使用:
PVOID pBuffer = ExAllocatePoolWithTag(NonPagedPool, 0x1000, 'abcd');
PMDL pMdl = IoAllocateMdl(pBuffer, 0x1000, FALSE, FALSE, NULL);
PVOID pSystemVa = MmGetSystemAddressForMdlSafe(pMdl, NormalPagePriority);
if (pSystemVa != NULL)
{
RtlFillBytes(pSystemVa, 0x1000, 0xAA);
}
if (pMdl)
{
IoFreeMdl(pMdl);
}
if (pBuffer)
{
ExFreePoolWithTag(pBuffer, 'abcd');
}
2.2 描述用户虚拟地址 并且锁定内存
对于可分页内存
(PagedPool
) 虚拟内存和物理内存的对应关系都是临时的,因此如果使用MDL结构的数据数组的时候是仅仅在特定情况下才有效。 我们需要调用如下两个API
MmProbeAndLockPages
MmUnlockPages
来 将 可分页内存锁定
并且为当前MDL初始化此数组数组。 在我使用 MmUnlockPages
的时候是不会将此内存换出到磁盘的。也就是说不会分页此内存。
使用例子如下:
锁定用户虚拟地址
让我们可以进行操作。
PMDL pmdl = IoAllocateMdl(UserVa, userVaSize, FALSE, FALSE, NULL);
ASSERT(pmdl);
__try
{
MmProbeAndLockPages(pmdl, UserMode, IoReadAccess); // IoWriteAccess
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
DbgPrint("error code = %d", GetExceptionCode);
}
PVOID kva = MmGetSystemAddressForMdlSafe(pmdl, NormalPagePriority);
//opt kva
MmUnlockPages(pmdl);
IoFreeMdl(pmdl);
必须使用 try except
包含 上述代码就是锁定用户的虚拟地址,然后保证此内存不会换出。然后使用 MmGetSystemAddressForMdlSafe来获取映射在系统空间的位置(地址) 进行安全的操作。
MmProbeAndLockPages
重点要关注第二个参数还有第三个参数, 第二个参数代表你锁定的地址 用户态的还是内核态的(UserMode/KernelMod
) 第三个参数代表你描述的这块内存是 可读取还是可写入。
重点:
在释放内存的时候请先解锁此内存。然后在进行释放。否则你试试
在上面我们的 有段代码叫做
PVOID kva = MmGetSystemAddressForMdlSafe(pmdl,NormalPagePriority);
它也可以换成更强大的函数,他能突破微软的 CopyOnWrite
机制,也就是内存可读写。MmMapLockedPagesSpecifyCache()
函数如下:
低版本是:MmMapLockedPages()
与之配套的是 MmUnmapLockedPages()
PVOID MmMapLockedPagesSpecifyCache(
[in] PMDL MemoryDescriptorList,
[in] KPROCESSOR_MODE AccessMode,
[in] MEMORY_CACHING_TYPE CacheType,
[in, optional] PVOID RequestedAddress,
[in] ULONG BugCheckOnFailure,
[in] ULONG Priority
);
此函数参数如下:
参数 | 含义 |
---|---|
MemoryDescriptorList |
必须是一个MDL 而且此MDL鄙俗描述了锁定的物理页 |
AccessMode |
MDL的访问模式,内核还是用户。针对内核都要使用kernelMode 用户都要使用UserMode |
CacheType |
指示了MDL的缓存属性,参考MEMORY_CACHING_TYPE 此结构 |
RequestedAddress |
如果AccessMode是用户模式,那么此参数则指定将MDL映射到起始用户虚拟地址,如果为NULL,则系统选择起始地址。系统它会四舍五入来寻找合适地址来适应边界要求。所以我们要检查返回值。 |
BugCheckOnFailure |
系统资源不足无法映射MDL(accmode=kernelMode)的时候(TRUE),那么就会发生错误检查,如果为FALSE 那么此函数返回NULL。驱动程序中必须将此参数设置为FALSE. |
Priority |
一个 MM_PAGEPRIORITY 值。 指示了PTE稀缺是成功的重要性。从WIN8开始,指定的优先级可以使用 MdlMappingNoWrite 或者 MdlMappingNoExecute 优先级的详细信息查看 MmGetSystemAddressForMdlSafe 函数介绍。 |
所以上边的代码 在锁定内存后可以进行如下操作
_try
{
pAddr = MmMapLockedPagesSpecifyCache(pMDL,KernelMode,MmCached,NULL,FALSE,NormalPagePriority);
if (pAddr == NULL)
{
return 0;
}
}
_except(EXCEPTION_EXECUTE_HANDLER)
{
}
2.3 实现对用户内存锁定,进行MDL方式内存读写.
其实主要用的的API 为如下
MmCreateMdl() 用来指定用户的内存(也就是用户的虚拟地址) 根据这个虚拟地址创建一大块MDL
MmBuildMdlForNonPagedPool() 主要作用就是对我们生成的Mdl进行一个更新. 更新对物理内存的描述
MmMapLockedPages() 锁定内存
锁定之后则可以读写进程内存了
//资源释放
IoFreeMdl()
MmUnmapLockedPaged(); 取消锁定
实现函数如下:
BOOLEAN WriteMemoryOfMdl(PVOID user_va, PVOID write_data, SIZE_T write_data_size)
{
PMDL mdl_addr = NULL;
PVOID kernel_lockedaddr = NULL;
// 创建一个Mdl 大小是根据用户的VA(虚拟地址)进行创建的.
mdl_addr = MmCreateMdl(NULL, user_va, write_data_size);
if (NULL == mdl_addr)
{
return FALSE;
}
// 更新MDL对物理内存的描述
MmBuildMdlForNonPagedPool(mdl_addr);
// 内存进行锁定映射.映射到出一个内核地址
kernel_lockedaddr = MmMapLockedPages(mdl_addr, KernelMode);
if (NULL == kernel_lockedaddr)
{
IoFreeMdl(mdl_addr);
}
//进行数据操作 可以读写这块数据
RtlCopyMemory(kernel_lockedaddr, write_data, write_data_size);
//先解除锁定,然后在释放.
MmUnmapLockedPages(kernel_lockedaddr, mdl_addr);
IoFreeMdl(mdl_addr);
return TRUE;
}
使用高版本的 IoAllocateMdl也可以实现,代码如下:
BOOLEAN CRemoteThread::WriteProcessMemoryByUserVa(
PVOID user_vaaddr,
PVOID write_data,
SIZE_T data_size)
{
PMDL mdl_new_user_va = NULL;
PVOID lock_va = NULL;
if (user_vaaddr == NULL)
return FALSE;
if (write_data == NULL || data_size <= 0)
return FALSE;
mdl_new_user_va = IoAllocateMdl(user_vaaddr, (ULONG)data_size, FALSE, FALSE, NULL);
if (mdl_new_user_va == NULL)
return FALSE;
__try
{
MmProbeAndLockPages(mdl_new_user_va, KernelMode, IoReadAccess);
lock_va = MmMapLockedPagesSpecifyCache(mdl_new_user_va, KernelMode, MmCached, NULL, FALSE, NormalPagePriority);
if (lock_va)
{
RtlCopyMemory(lock_va, write_data, data_size);
}
if (lock_va != NULL)
{
MmUnmapLockedPages(lock_va, mdl_new_user_va);
lock_va = NULL;
}
if (mdl_new_user_va != NULL)
{
IoFreeMdl(mdl_new_user_va);
mdl_new_user_va = NULL;
}
return TRUE;
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
if (lock_va != NULL)
{
MmUnmapLockedPages(lock_va, mdl_new_user_va);
lock_va = NULL;
}
if (mdl_new_user_va != NULL)
{
IoFreeMdl(mdl_new_user_va);
mdl_new_user_va = NULL;
}
}
return FALSE;
}
2.4 描述内核内存 与用户态内存共享。
内核内存 与用户共享也很简单。
总共也进行以下几个步骤。
-
申请
NonpagePool
内存 -
申请
Mdl
描述,来描述申请的NonpagePool
-
锁定到系统中
-
使用
MmMapLockedPagesSpecifyCache
映射为 userMode 模式的地址。
伪代码:
PVOID pKernelAddr = ExAllocatePoolWithTag(NonPagedPool, 0x1000, 'ABCD');
PMDL pMdl = IoAllocateMdl(pKernelAddr, 0x1024, FALSE, FALSE, NULL);
__try
{
MmBuildMdlForNonPagedPool(pMdl);
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
}
PVOID pUserAddress = MmMapLockedPagesSpecifyCache(pMdl, UserMode, MmCached, NULL, FALSE, NormalPagePriority);
KdPrint(("UserAddr = %d KernelAddr = %d \r\n", pKernelAddr, pUserAddress));
if (pUserAddress)
MmUnmapLockedPages(pUserAddress, pMdl); //与之配套MmMapLockedPagesSpecifyCache
if (pMdl)
IoFreeMdl(pMdl);
if (pKernelAddr)
ExFreePoolWithTag(pKernelAddr, 'abcd');
2.4 其它 MDL会用到的函数
函数名称 | 作用 |
---|---|
MmInitializeMdl |
初始化MDL结构体但是不会初始化紧跟 MDL 结构的数据数组。也就是说你可以申请一块非分页内存将其格式化为 MDL结构。然后继续进行MDL操作。 |
MmPrepareMdlForReuse |
释放MDL关联的资源,然后重复使用此MDL。配套IoBuildPartialMdl 使用。 可以将IoBuildPartialMdl 参数中的TargetMdl 释放,以便可以重复使用。 |
IoBuildPartialMdl |
使用已经存在的MDL来生成一个新的MDL。用来描述源MDL中描述的缓冲去的子范围。可以理解为将一个大的MDL做了分割。可以将大型传输请求分割为小的传输请求。具体查询下MSDN把。 |
三丶物理内存与其映射
3.1 物理内存映射到系统中
主要是如下函数:
MmGetPhysicalAddress
MmMapIoSpace
PHYSICAL_ADDRESS MmGetPhysicalAddress(
[in] PVOID BaseAddress
);
此函数返回有效的非分页虚拟地址所对应的物理地址
也就是给一个 NonPagePool类型的虚拟地址 它会返回对应的物理地址(对应在内存条的地址)
PVOID MmMapIoSpace(
[in] PHYSICAL_ADDRESS PhysicalAddress, 物理地址
[in] SIZE_T NumberOfBytes, >0 的映射字节
[in] MEMORY_CACHING_TYPE CacheType > 缓存属性
);
此函数 将一个 物理地址 映射到 非分页
的系统空间内
如果失败则返回NULL
void MmUnmapIoSpace(
[in] PVOID BaseAddress,
[in] SIZE_T NumberOfBytes
);
映射之后要取消映射。
函数的使用要使用 ceddk.lib
如果后面实现更改了请查询MSDN文档。
3.2 其它代码
函数 | 用法 |
---|---|
MmAllocateContiguousMemorySpecifyCache |
分配连续的非分页物理内存 然后映射到系统空间中(也可将这块空间映射为用户虚拟地址) |
MmFreeContiguousMemory |
与上面配套,释放内存。 |
举例:
//获取虚拟地址的物理内存 然后将此物理内存映射到系统中 是一个系统的虚拟地址 可以直接操作
PVOID pTestMdl = ExAllocatePoolWithTag(PagedPool, 0x1000, 'ABCD');
PHYSICAL_ADDRESS phyAddr = MmGetPhysicalAddress(pTestMdl);
PVOID pMap = MmMapIoSpace(phyAddr, 0x1000, MmCached);
RtlFillMemory(pMap, 0x1000, 0xaa);
MmUnmapIoSpace(pMap, 0x1000);
//集成了上面几步 直接申请连续的非分页的物理内存 然后映射到地址空间中
//映射的空间属于 NonPagePool
PHYSICAL_ADDRESS lowValue = {0x0000000000800000};
PHYSICAL_ADDRESS highValue = {0x0000000000FFFFFF};
PHYSICAL_ADDRESS N = {0};
PVOID pLockAddr = MmAllocateContiguousMemorySpecifyCache(0x1000, lowValue, highValue, N, MmCached);
RtlFillMemory(pLockAddr, 0x1000, 0XBB);
if (pLockAddr)
MmFreeContiguousMemory(pLockAddr);
坚持两字,简单,轻便,但是真正的执行起来确实需要很长很长时间.当你把坚持两字当做你要走的路,那么你总会成功. 想学习,有问题请加群.群号:725864912(收费)群名称: 逆向学习小分队 群里有大量学习资源. 以及定期直播答疑.有一个良好的学习氛围. 涉及到外挂反外挂病毒 司法取证加解密 驱动过保护 VT 等技术,期待你的进入。
详情请点击链接查看置顶博客 https://www.cnblogs.com/iBinary/p/7572603.html
本文来自博客园,作者:iBinary,未经允许禁止转载 转载前可联系本人.对于爬虫人员来说如果发现保留起诉权力.https://www.cnblogs.com/iBinary/p/13918291.html
欢迎大家关注我的微信公众号.不定期的更新文章.更新技术. 关注公众号后请大家养成 不白嫖的习惯.欢迎大家赞赏. 也希望在看完公众号文章之后 不忘 点击 收藏 转发 以及点击在看功能. QQ群: