转自:https://write-bug.com/article/1933.html
无论是在 32 位系统内存分布,还是在 64 位系统内存分布中,我们知道高地址空间分配给系统内核使用,低地址空间分配给用户进程使用。
事实上,用户空间和内核空间其实有一块共享区域,大小为 4 KB。它们的内存地址虽然不一样,但是它们都是有同一块物理内存映射出来的。现在,本文就是要实现一个这样的程序,去验证这块共享区域的存在。
实现原理
用户空间和内核空间的共享区域,大小为 4 KB,内核占用其中一小部分,但 Rootkit 应该大约还有 3 KB 空间可使用。这两个虚拟内存地址都映射到同一物理页面,内核程序对这块共享区域有可读、可写的权限,用户程序对这块共享区域只有只读的权限。
其中,对于 32 位系统和 64 位系统来说,这块共享区域对应的内核地址范围以及对应用户空间的地址范围如下表所示:
内核起始地址 | 内核结束地址 | 用户起始地址 | 用户结束地址 | |
---|---|---|---|---|
32 系统 | 0xFFDF0000 | 0xFFDF0FFF | 0x7FFE0000 | 0x7FFE0FFF |
64 系统 | 0xFFFFF780`00000000 | 0xFFFFF780`00000FFF | 0x7FFE0000 | 0x7FFE0FFF |
由上面可以看出,32 位系统和 64 位系统下,该共享区域的内核地址是不同的,而用户空间上的地址都是相同的。
这块共享区域的名称是 KUSER_SHARED_DATA,想要获得关于该共享区域的更过详细解释,可以在 WinDbg 中输入:dt nt!_KUSER_SHARED_DATA 来获取信息。
本文演示的程序,就是在 KUSER_SHARED_DATA 的内核内存中写入数据,然后,由用户称程序读取写入的数据,以此验证 KUSER_SHARED_DATA 区域的存在。
编码实现
用户层程序
int _tmain(int argc, _TCHAR* argv[])
{
// 要偏移 1 KB 大小读取数据, 因为写入的时候是偏移 1 KB 大小写入的
void *pBaseAddress = (void *)(0x7FFE0000 + 0x400);
printf("[Share Data]%s ", pBaseAddress);
system("pause");
return 0;
}
内核层程序
// 向共享区域中写入数据
BOOLEAN WriteShareData(PCHAR pszData, ULONG ulDataSize)
{
PVOID pBaseAddress = NULL;
// 偏移 1 KB 写入数据, 因为系统会占用大约 1 KB 的空间
#ifdef _WIN64
// 64 Bits
pBaseAddress = (PVOID)(0xFFFFF78000000000 + 0x400);
#else
// 32 Bits
pBaseAddress = (PVOID)(0xFFDF0000 + 0x400);
#endif
// 写入
RtlCopyMemory(pBaseAddress, pszData, ulDataSize);
return TRUE;
}
程序测试
在 Windows7 32 位系统下,驱动程序正常执行:
在 Windows10 64 位系统下,驱动程序正常执行:
总结
注意,这块共享区域主要是用来在用户层和内核层之间快速的传递信息的,会占用大约 1 KB 大小的空间。所以,我们通常偏移 0x400 大小处写入我们自己的数据,这样,就不会影响原来的内核代码的正常运行了。