通过内核驱动程序的 IAT Hook

逆向工程 C++ 函数挂钩 内核模式 司机 我在
2021-06-25 09:12:47

我最近写了一个内核驱动程序,它做了很多事情,但我想为某个驱动程序添加一个 IAT 钩子。

如果有意义的话,我想从我的驱动程序中挂钩另一个驱动程序的 IAT。

所以我了解您的典型 IAT 挂钩是通过 DLL 注入完成的,因此您与所述模块位于相同的地址空间内。

我遇到的主要问题是有人可以举一个 IAT 通过内核驱动程序挂接内核驱动程序的例子吗?我想不通。

我一直在使用多个通过 dll 注入进行常规 IAT 挂钩的示例,但将其应用于内核驱动程序。

预期结果: Driver1.sys(执行IAT 挂钩)解析Driver2.sys 的导入表,并用位于Driver1.sys 中的函数(即完成IAT 挂钩)替换函数“DbgPrintEx”导入。

这是我写/引用的内容:

NTSTATUS _HOOKS_::HookFn(PVOID ModuleBase, const char* FunctionName, uintptr_t HookFunc, uintptr_t* OrigFunc) {

IMAGE_DOS_HEADER DosHeader{ 0 };
memcpy(&DosHeader, ModuleBase, sizeof(IMAGE_DOS_HEADER));

IMAGE_NT_HEADERS NtHeaders{ 0 };
memcpy(&NtHeaders, (PVOID)((uintptr_t)ModuleBase + DosHeader.e_lfanew), sizeof(IMAGE_NT_HEADERS));

IMAGE_IMPORT_DESCRIPTOR ImportDescriptor{ 0 };
IMAGE_DATA_DIRECTORY ImportsDirectory = NtHeaders.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];

memcpy(&ImportDescriptor, (PVOID)((uintptr_t)ModuleBase + ImportsDirectory.VirtualAddress), sizeof(ImportDescriptor));

LPCSTR LibraryName = NULL;
PVOID Library = NULL;
IMAGE_IMPORT_BY_NAME FunctionNamee{ 0 };

while (ImportDescriptor.Name != NULL) {
    memcpy(&LibraryName, &ImportDescriptor.Name + (uintptr_t)ModuleBase, sizeof(LibraryName));

    IMAGE_THUNK_DATA OriginalFirstThunk{ 0 }, firstThunk{ 0 };
    memcpy(&OriginalFirstThunk, (PVOID)(ImportDescriptor.OriginalFirstThunk + (uintptr_t)ModuleBase), sizeof(IMAGE_THUNK_DATA));
    memcpy(&firstThunk, (PVOID)(ImportDescriptor.FirstThunk + (uintptr_t)ModuleBase), sizeof(IMAGE_THUNK_DATA));

    while (OriginalFirstThunk.u1.AddressOfData != NULL) {

        memcpy(&FunctionNamee, (PVOID)(OriginalFirstThunk.u1.AddressOfData + (uintptr_t)ModuleBase), sizeof(IMAGE_IMPORT_BY_NAME));

        Log::Debug("First function name -> %s\n", FunctionNamee);

        if (!strcmp(FunctionNamee.Name, FunctionName)) {
            Log::Debug("FOUND FUNCTION!!! Address -> %p\n", firstThunk.u1.Function);
        }
    }
}


return STATUS_SUCCESS;

此代码 BSOD 位于: memcpy(&ImportDescriptor, (PVOID)((uintptr_t)ModuleBase + ImportsDirectory.VirtualAddress), sizeof(ImportDescriptor));

如果我错了,请纠正我,但由于内核驱动程序共享相同的地址空间,我可以简单地使用 memcpy 或 RtlCopyMemory 来获取此挂钩所需的结构,是否有人有任何参考资料或某种我可以阅读的文档?

提前感谢大家!

额外信息:

  • 视窗 10
  • 使用 VMWare 进行测试
  • 我正在我自己的驱动程序上测试钩子

编辑:发布了导致蓝屏死机的错误源代码行。

通过 WinDbg(x64) 进行调试时,驱动程序会正确地将所有地址与内存视图进行比较。除了ImportDescriptor获得 0x6000 的 RVA,在检查ModuleBase + 0x6000RVA 时它显示该内存区域无效,所以我认为 0x6000 RVA 不正确,我不知道为什么

错误检查代码:

  • 蓝屏错误代码:PAGE_FAULT_IN_NONPAGED_AREA
  • BUG校验码:50
3个回答

问题似乎出在源地址计算上:

&ImportDescriptor.Name + (uintptr_t)ModuleBase

这会将局部变量(在堆栈上)地址添加到图像基址,因此结果可能是某个不存在的地址。

您应该将其(即 RVA)添加到图像库中。即这样的事情:

memcpy(&LibraryName, (PVOID)((ULONG_PTR)ModuleBase+ImportDescriptor.Name) , sizeof(LibraryName))

因此,事实证明,如果您在驱动程序上测试 IAT 挂钩,则驱动程序必须在发布模式下编译,而不是在调试模式下编译。

如果驱动程序是在调试模式下编译的,则 RVAImport Descriptor似乎是错误的。但是问题是通过在发行版中编译而不是在调试模式下解决的。

感谢大家的帮助。

不幸的是,这不会成为答案,因为您没有提供足够的信息。如果我最终写了一个答案,那将是一个不同的答案。这么久了,我会将这个非答案作为社区维基,试图提供线索,询问详细信息并指向资源。

如果我错了,请纠正我,但由于内核驱动程序共享相同的地址空间,我可以简单地使用 memcpy 或 RtlCopyMemory [...]

嗯,这个陈述“有点”是真的,但它也不是。它取决于(Windows)内核模式中的许多方面。虽然地址空间(即从最小地址到最大地址)在技术上是相同的,但它很大程度上取决于您的代码执行的上下文(例如 IRQL)以及您尝试访问的内存的属性(例如 NonPagedPool 与 PagedPool) .

但是,这是 KM 驱动程序开发的介绍性材料,因此我建议您选择一本书,例如 Pavel Yosifovich 的“Windows 内核编程”,或者甚至是 Walter Oney 的较旧的著作“Programming the Microsoft Windows Driver Model”。OSR 和 Yosifovich 提供的课程也是学习这些东西的好方法。

除了你忘记提到你得到的错误检验码。这对帮助您非常重要。在没有完整内存转储(或在您的情况下是实时调试会话)的情况下,错误检查中包含的参数也可以提供一个想法。参数是在蓝屏中与错误检查代码一起包含的四项。

  • 在错误检查期间您的代码以什么 IRQL 执行?你不能触摸
  • 您的驱动程序中的代码是如何标记的?(例如PAGED_CODE()或类似的?)
    • 另外,如果您尝试这样做DriverEntry,它总是以PASSIVE_LEVEL任何一种方式运行......
  • 谁是函数的调用者?
  • 假设您没有在 I/O 请求代码路径中执行此操作,您仍然相当浪费堆栈。KM 代码在可用堆栈方面有很多限制。
  • 上述所有问题、评论、评论也应检查您调用的代码(例如Log::Debug())。

也就是说,您的上述陈述PASSIVE_LEVELAPC_LEVEL. 但即使如此,它支付的利用SEH的一些代码,并利用MmIsAddressValidProbeForRead和类似的程序适当。

顺便说一句:也许你想告诉我们你在尝试什么?尽管不可否认,这些都与 RCE 没有密切关系。