逆向 API AuxKlibQueryModuleInformation 枚举内核驱动列表的实现 - 嘶吼 RoarTalk – 回归最本质的信息安全,互联网安全新媒体,4hou.com

逆向 API AuxKlibQueryModuleInformation 枚举内核驱动列表的实现

luochicun 逆向破解 2020-03-05 09:09:29
收藏

导语:逆向 API AuxKlibQueryModuleInformation 枚举内核驱动列表的实现

本文,我将介绍对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中实现的,如下所示:

4.PNG

这个函数是Zw函数,你可以点击这里读取更多关于Zw函数的信息。

Zw函数调用Nt函数,并将PreviousMode更改为KernelMode。因此,jmp KiServiceInternal在后台调用NtQuerySystemInformation。 ntdll中存在相同的名称,但ntoskrnl.exe包含系统调用的实际实现。如果你查看ntdll,会看到相同的ID是在执行“syscall”。

5.PNG

这是因为内核Zw函数和用户模式Nt函数都经过SSDT来提取系统调用处理程序指针,在本文的示例中,将在内核模式中执行相同的函数(NtQuerySystemInformation)。

现在,让我们重复一下当前的任务:我们需要找到SystemInformationClass 0xb指向的位置。检查NtQuerySystemInformation后,我们看到了通过ExpQuerySystemInformation或返回错误状态的所有方法。SystemInformationClass在rcx中传递,请注意,我没有开始从上到下阅读反汇编,我只想知道当SystemInformationClass = 0xb时会发生什么。

6.PNG

跟踪rcx,我们看到在调用ExpQuerySystemInformation之前它没有改变。这意味着ExpQuerySystemInformation的第一个参数是SystemInformationClass参数。

查看ExpQuerySystemInformation可以开始跟踪rcx中的值,在IDA中,我们可以突出显示rcx寄存器并查看其使用位置。

7.PNG

你可以看到rcx值被移动到rdi,然后它被覆盖,现在我们需要查看rdi,这是rdi寄存器的下一个用法:

8.png

我们知道0xb小于0x49,所以让我们观察一下loc_14069F19C:

10.png

这是switch语句的示例。该表包含switch语句中不同情况的处理程序。 IDA已经为你找到了案例!要轻松找到案例,你可以使用“Search Text” (Alt-T)并搜索以下文本:“case 11”。所以,在搜索这篇文章后,我们发现了以下内容:

11.png

好的!所以我们找到了查询模块列表的实际代码。从它的名称中,我们可以理解PsLoadedModuleList列表包含一个已加载模块的列表。

ExpQueryModuleInformation函数如下所示:

12.PNG

我们在这个函数中看到一个大循环,看看函数的开头,我们看到:

13.png

现在,我们假设这个函数枚举这个列表并返回这个列表中的映像,我们将在需要的时候验证这个假设。最后,我们完成了第一个任务,查找什么是SystemInformationClass 0xb?

第一个需要回答的问题是,这个API (AuxKlibQueryModuleInformation)是否保证返回的模块列表总是最新的?

要回答这个问题,我们需要了解返回的值是否总是最新的。“最新的”的一般定义可以有不同的含义。但是一般的答案是:不能保证在调用AuxKlibQueryModuleInformation之后,列表是最新的。看起来PsLoadedModuleList受ERESOURCE同步对象(读写锁)PsLoadedModuleResource保护。在调用ExpQueryModuleInformation之前,我们会获取锁。但在那之后,我们调用ExReleaseResourceLite,列表可以再次更新,我们可以假定仅在获得锁时才更改PsLoadedModuleList。

如果AuxKlibQueryModuleInformation被设计为获取PsLoadedModuleList的更新版本,那么它将允许调用者自己获取或释放锁。事实上,PsLoadedModuleResource是在windows 10的ntoskrnl中导出的:

14.png

因为可以从同一线程两次获取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为循环变量的过程如下:

16.png

是的,看来估算是正确的。为了大致了解此循环的作用,让我们看一下循环对象内r14的用法:

17.png

看起来来自ListItem结构(代表已加载模块)的值已复制到rsi指向的某些输出结构,通过查看rsi的来源,来验证rsi是否包含调用者的输出缓冲区:

18.png

好的,答案是rsi = (SecondParam + 8),接下来,让我们来看看第二个参数(rdx)是怎么来的:

(在ExpQuerySystemInformation内部)

19.png

我们来跟踪rbx,可以使用Alt-Up来查看rbx的第一次分配发生在什么位置:

20.png

rbx包含了ExpQuerySystemInformation的第四个参数,让我们追踪一下调用者,看看它来自哪里:

(在NtQuerySystemInformation内部)

21.png

它是NtQuerySystemInformation的第二个参数,让我们看看这个函数的原型:

22.png

好极了!我们是正确的,因为第二个参数是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并查看返回值的代码,但是有一种更简单的方法,就是用循环对象本身。

24.png

我们可以假设这是在将映像放入目标缓冲区之前对映像名称的转换,让我们在调用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/如若转载,请注明原文地址
  • 分享至
取消

感谢您的支持,我会继续努力的!

扫码支持

打开微信扫一扫后点击右上角即可分享哟

发表评论