本文,我将介绍对AuxKlibQueryModuleInformation进行逆向工程的解决方案。其中,我们会提到驱动程序可以使用记录的API AuxKlibQueryModuleInformation枚举所有加载的模块。这个API是否保证返回的模块列表总是最新的?为了回答这个问题,我们要对Windows 8的AuxKlibQueryModuleInformation进行逆向工程,并解释它是如何工作的。当多个线程请求访问加载的模块列表时,它如何处理这种情况?注意:处理此请求和其他请求的内部函数相当大,因此需要一些耐心。或者,你可以使用调试器来帮助你跟踪感兴趣的代码。
首先,让我们将其划分为我们需要执行的任务:
1.这个API (AuxKlibQueryModuleInformation)是否保证返回的模块列表总是最新的?
2.对Windows 8的AuxKlibQueryModuleInformation进行逆向工程,并解释其工作原理。
3.当多个线程请求访问已加载的模块列表时,它如何处理这种情况?
为了解决这个问题,我们将使用IDA对函数进行静态反向工程。在本文的示例中,我们将不使用反编译器来读取汇编代码。使用反编译器可以节省大量时间,但是学习如何在反汇编窗口中导航对于逆向工程很有价值。
什么是“AuxKlibQueryModuleInformation”?
MSDN: The AuxKlibQueryModuleInformation例程检索有关操作系统已加载的映像模块的信息。
NTSTATUS AuxKlibQueryModuleInformation( PULONG BufferSize, ULONG ElementSize, PVOID QueryInfo );
听起来它应该返回一个映像列表,不过,这些只是猜测,这些映像是加载到内存中的映像还是仅加载到内核中的映像,我们稍后将对此进行检查。
因此,要回答第一个问题,我们的第一个子任务是找到' AuxKlibQueryModuleInformation '是在哪里实现的。
在搜索ntoskrnl.exe时,我们没有找到该函数。这意味着它必须在其他地方声明,通过查看该文档,我们就可以看到这个函数是在aux_klib.h中声明的,所需的库是aux_klib.lib。
LIB文件是静态库,该静态库就是一个包含目标文件的归档文件,链接器可以使用这些目标文件将代码添加到二进制文件中。现在,我们知道这个函数是在aux_klib.lib中定义的。
我们可以用 “dumpbin.exe” 工具来反汇编lib 文件,我们还可以编译一个使用AuxKlibQueryModuleInformation的驱动程序,然后查看编译后的二进制文件,看看结果如何。使用dumpbin.exe快速检查后,可以看到函数调用如下:=
AuxKlibQueryModuleInformation: ..... ..... lea r9,[rsp+20h] ; ReturnLength mov r8d,esi ; SystemInformationLength mov rdx,rbx ; SystemInformation mov ecx,0Bh ; SystemInformationClass call qword ptr [__imp_ZwQuerySystemInformation] ; <------- ..... .....
可以看到,AuxKlibQueryModuleInformation使用ZwQuerySystemInformation来查询模块列表,该函数的定义为:
NTSTATUS WINAPI ZwQuerySystemInformation( _In_ SYSTEM_INFORMATION_CLASS SystemInformationClass, _Inout_ PVOID SystemInformation, _In_ ULONG SystemInformationLength, _Out_opt_ PULONG ReturnLength );
SystemInformationClass是我们想要查询的信息类型,正如你在反汇编中看到的,这个类等于0xb,但是0xb是什么呢?
通常,我会搜索SYSTEM_INFORMATION_CLASS枚举定义,但我们将查看ZwQuerySystemInformation的实际实现以找出SystemInformationClass,以了解如何可以做到。
此函数是在在ntoskrnl.exe中实现的,如下所示:
这个函数是Zw函数,你可以点击这里读取更多关于Zw函数的信息。
Zw函数调用Nt函数,并将PreviousMode更改为KernelMode。因此,jmp KiServiceInternal在后台调用NtQuerySystemInformation。 ntdll中存在相同的名称,但ntoskrnl.exe包含系统调用的实际实现。如果你查看ntdll,会看到相同的ID是在执行“syscall”。
这是因为内核Zw函数和用户模式Nt函数都经过SSDT来提取系统调用处理程序指针,在本文的示例中,将在内核模式中执行相同的函数(NtQuerySystemInformation)。
现在,让我们重复一下当前的任务:我们需要找到SystemInformationClass 0xb指向的位置。检查NtQuerySystemInformation后,我们看到了通过ExpQuerySystemInformation或返回错误状态的所有方法。SystemInformationClass在rcx中传递,请注意,我没有开始从上到下阅读反汇编,我只想知道当SystemInformationClass = 0xb时会发生什么。
跟踪rcx,我们看到在调用ExpQuerySystemInformation之前它没有改变。这意味着ExpQuerySystemInformation的第一个参数是SystemInformationClass参数。
查看ExpQuerySystemInformation可以开始跟踪rcx中的值,在IDA中,我们可以突出显示rcx寄存器并查看其使用位置。
你可以看到rcx值被移动到rdi,然后它被覆盖,现在我们需要查看rdi,这是rdi寄存器的下一个用法:
我们知道0xb小于0x49,所以让我们观察一下loc_14069F19C:
这是switch语句的示例。该表包含switch语句中不同情况的处理程序。 IDA已经为你找到了案例!要轻松找到案例,你可以使用“Search Text” (Alt-T)并搜索以下文本:“case 11”。所以,在搜索这篇文章后,我们发现了以下内容:
好的!所以我们找到了查询模块列表的实际代码。从它的名称中,我们可以理解PsLoadedModuleList列表包含一个已加载模块的列表。
ExpQueryModuleInformation函数如下所示:
我们在这个函数中看到一个大循环,看看函数的开头,我们看到:
现在,我们假设这个函数枚举这个列表并返回这个列表中的映像,我们将在需要的时候验证这个假设。最后,我们完成了第一个任务,查找什么是SystemInformationClass 0xb?
第一个需要回答的问题是,这个API (AuxKlibQueryModuleInformation)是否保证返回的模块列表总是最新的?
要回答这个问题,我们需要了解返回的值是否总是最新的。“最新的”的一般定义可以有不同的含义。但是一般的答案是:不能保证在调用AuxKlibQueryModuleInformation之后,列表是最新的。看起来PsLoadedModuleList受ERESOURCE同步对象(读写锁)PsLoadedModuleResource保护。在调用ExpQueryModuleInformation之前,我们会获取锁。但在那之后,我们调用ExReleaseResourceLite,列表可以再次更新,我们可以假定仅在获得锁时才更改PsLoadedModuleList。
如果AuxKlibQueryModuleInformation被设计为获取PsLoadedModuleList的更新版本,那么它将允许调用者自己获取或释放锁。事实上,PsLoadedModuleResource是在windows 10的ntoskrnl中导出的:
因为可以从同一线程两次获取ERESOURCE锁,所以这意味着调用方实际上可以:
1.获取锁;
2.调用AuxKlibQueryModuleInformation;
3.用列表做一些有趣的事情(列表也已导出;);
4.运行完成后,释放锁;
这将确保在保持锁定状态时无法更新列表,当然,前提是假设PsLoadedModuleList在没有锁定的情况下不会更新。不过这种方法还不够好,因为实际使用会出现以下问题:
1.即使我们检验了我们的假设并假设它是正确的,微软也可以随时更改此行为,例如,微软2.有时会更改锁的类型(将ERESOURCE更改为其他类型的锁);
3.在Windows 7/8中不可用,列表和锁没有导出(这可能是个问题,具体取决于你的运行情况);
出于好奇,让我们使用windows import searcher工具来查找到底是哪些模块导入这些变量:
>python.exe windows_imports_searcher.py search -i index.json -f ntoskrnl.exe!PsLoadedModule* Reading file index.json c:\windows\system32\drivers\ntosext.sys Imports ntoskrnl.exe!PsLoadedModuleResource c:\windows\system32\drivers\ntosext.sys Imports ntoskrnl.exe!PsLoadedModuleList
我们看到导入这个列表的唯一驱动是ntosext.sys,我们可能会错过使用MmGetSystemRoutineAddress导入已加载模块列表的其他模块。此外,有些组件(例如调试器)可以很好地利用此列表。这个列表在windows 10中导出可能是因为ntosext.sys被移出ntoskrnl.exe。
现在回答第2个问题:对AuxKlibQueryModuleInformation进行逆向工程,并解释其工作原理。
我们必须小心处理这个任务。“解释它是如何工作的”很容易被误解为“理解关于AuxKlibQueryModuleInformation实现的每个小细节”。有时候,在逆向工程中,我们想要得到某些东西的概况,这个“东西”可以是一个完整的程序,也可以是一个程序的特定特性。我们必须小心,不能花太多时间来颠倒AuxKlibQueryModuleInformation,因为这里所需要做的就是了解全局。
好了,我们已经从上一个任务的分析中得到了一些映像:
1.AuxKlibQueryModuleInformation在aux_klib.lib静态库中定义;
2.它调用ZwQuerySystemInformation来触发ExpQuerySystemInformation;
3.ExpQuerySystemInformation获取PsLoadedModuleResource并调用ExpQueryModuleInformation
4.我们可以估计ExpQueryModuleInformation会获取列表的快照,并将其保存到输出缓冲区。
5.锁被释放,缓冲区被返回给用户;
好的,实际上我们可以在此时停止分析了,因为我们已经有一个大的框架。但是我们还没有检查ExpQueryModuleInformation,所以让我们验证一下它的作用。
如上所述,这个函数有一个大的循环。我们可以估计此循环枚举PsLoadedModuleList中的条目,让我们验证一下。
我们可以在循环之前看到以下代码,看起来r14是循环变量,并且已与列表的开头进行了比较,如果它指向列表的开头,则循环结束。验证r14为循环变量的过程如下:
是的,看来估算是正确的。为了大致了解此循环的作用,让我们看一下循环对象内r14的用法:
看起来来自ListItem结构(代表已加载模块)的值已复制到rsi指向的某些输出结构,通过查看rsi的来源,来验证rsi是否包含调用者的输出缓冲区:
好的,答案是rsi = (SecondParam + 8),接下来,让我们来看看第二个参数(rdx)是怎么来的:
(在ExpQuerySystemInformation内部)
我们来跟踪rbx,可以使用Alt-Up来查看rbx的第一次分配发生在什么位置:
rbx包含了ExpQuerySystemInformation的第四个参数,让我们追踪一下调用者,看看它来自哪里:
(在NtQuerySystemInformation内部)
它是NtQuerySystemInformation的第二个参数,让我们看看这个函数的原型:
好极了!我们是正确的,因为第二个参数是SystemInformation,是调用者的输出参数。
__kernel_entry NTSTATUS NtQuerySystemInformation( IN SYSTEM_INFORMATION_CLASS SystemInformationClass, OUT PVOID SystemInformation, IN ULONG SystemInformationLength, OUT PULONG ReturnLength );
好,让我们来看最后一个问题
当多个线程请求访问加载的模块列表时,它如何处理这种情况?
我们已经知道答案了!这是使用读写锁来处理的,这种类型的锁允许读取器一起读取列表(这是安全的,因为它们不会更改列表),但是如果编写器希望编辑列表,则只有编写器可以访问列表。奇怪的是,查询函数使用exacquireresourceexclusive velite函数锁定列表,不允许其他人读取列表。这很奇怪,因为这个函数应该读取而不是写入列表。经过验证,我没有在ExpQueryModuleInformation内找到对列表的任何写操作,因此它看起来像是错误的编码,又或许是我没有理解到位。
最后,我们想知道函数是否返回用户模式dll。我们可以尝试找出静态地插入到PsLoadedModuleList中的内容,但是让我们使用动态分析来解决这个问题。我们如何解决这个问题?我们可以编写调用AuxKlibQueryModuleInformation并查看返回值的代码,但是有一种更简单的方法,就是用循环对象本身。
我们可以假设这是在将映像放入目标缓冲区之前对映像名称的转换,让我们在调用RtlUnicodeStringToAnsiString时设置一个断点,然后查看源字符串。为了使这个运行有效,我们必须以某种方式触发ZwQuerySystemInformation,打开process explorer并通过单击View->System Information就可以触发它。
kd> bp fffff80207203c57 "dS /c 100 rdx; g" kd> g ffffa78e`7e605f40 "\SystemRoot\system32\ntoskrnl.exe" ffffa78e`7e606e90 "\SystemRoot\system32\hal.dll" ffffa78e`7e606ef0 "\SystemRoot\system32\kdcom.dll" ffffa78e`7e605f40 "\SystemRoot\system32\ntoskrnl.exe" ffffa78e`7e606e90 "\SystemRoot\system32\hal.dll" ffffa78e`7e606ef0 "\SystemRoot\system32\kdcom.dll" ffffa78e`7e606f50 "\SystemRoot\system32\mcupdate_GenuineIntel.dll" ffffa78e`7e607d60 "\SystemRoot\System32\drivers\msrpc.sys" ffffa78e`7e607dd0 "\SystemRoot\System32\drivers\ksecdd.sys" ffffa78e`7e607e40 "\SystemRoot\System32\drivers\werkernel.sys" ffffa78e`7e607ec0 "\SystemRoot\System32\drivers\CLFS.SYS" ffffa78e`7e607f30 "\SystemRoot\System32\drivers\tm.sys" ffffa78e`7e608010 "\SystemRoot\system32\PSHED.dll" ffffa78e`7e608070 "\SystemRoot\system32\BOOTVID.dll" ffffa78e`7e6080e0 "\SystemRoot\System32\drivers\FLTMGR.SYS" ffffa78e`7e608150 "\SystemRoot\System32\drivers\clipsp.sys" ffffa78e`7e6081c0 "\SystemRoot\System32\drivers\cmimcext.sys" ffffa78e`7e608240 "\SystemRoot\System32\drivers\ntosext.sys" .................. ...(truncated).... ..................
从输出中可以看到,只有内核映像保存在输出列表中。
正如你所看到的,逆向工程的大部分工作是跟踪我们程序中的数据流。这是反编译器可以做得更好的优势所在,因为它们以更高的表示形式显示信息。所有变量在寄存器之间的“临时”移动都不在反编译视图中体现。
本文翻译自:https://repnz.github.io/posts/practical-reverse-engineering/query-module-information/如若转载,请注明原文地址