逆向 API AuxKlibQueryModuleInformation 枚举内核驱动列表的实现 - 嘶吼 RoarTalk – 网络安全行业综合服务平台,4hou.com

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

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

导语:逆向 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/如若转载,请注明原文地址
  • 分享至
取消

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

扫码支持

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

发表评论

 
本站4hou.com,所使用的字体和图片文字等素材部分来源于原作者或互联网共享平台。如使用任何字体和图片文字有侵犯其版权所有方的,嘶吼将配合联系原作者核实,并做出删除处理。
©2022 北京嘶吼文化传媒有限公司 京ICP备16063439号-1 本站由 提供云计算服务