Watch & Learn

Debugwar Blog

Step in or Step over, this is a problem ...

遍历内核已卸载模块

2023-03-26 @ UTC+0

在恶意软件分析的过程中,我们不应该放过任何一处蛛丝马迹。细节是魔鬼,本文将介绍如何遍历windows内核中已卸载模块列表,让我们直捣恶意软件的最后一处藏身之所,让他们无所遁形。

WinDBG中的Unladed modules

lm命令作为windbg中常用命令,不仅会输出当前已加载模块列表,还会在最后输出一个已卸载模块列表:


在恶意样本分析的场景下,这个已卸载列表有可能会是潜在的突破口,因为恶意软件作者可能只关注常规基础设施的隐藏,但是却忽略了其所使用过已经卸载的模块,有时我们可以以这一点为突破口,找到更多的线索。

Windows对已卸载列表的支持

在泄漏的Windows源码中,有一个名为MmUnloadedDrivers的全局变量,该变量指向已卸载模块列表。

该全局变量对应的数据结构,可以在 base\ntos\inc\mm.h 找到:
  1. typedef struct _UNLOADED_DRIVERS {  
  2.     UNICODE_STRING Name;  
  3.     PVOID StartAddress;  
  4.     PVOID EndAddress;  
  5.     LARGE_INTEGER CurrentTime;  
  6. } UNLOADED_DRIVERS, *PUNLOADED_DRIVERS;  
其对应的遍历代码,可以在 base\ntos\mm\shutdown.c 中找到:
  1. VOID MiReleaseAllMemory (    VOID    )  
  2. {  
  3.     ......  
  4.     if (MmUnloadedDrivers != NULL) {  
  5.         Entry = &MmUnloadedDrivers[0];  
  6.         for (i = 0; i < MI_UNLOADED_DRIVERS; i += 1) {  
  7.             if (Entry->Name.Buffer != NULL) {  
  8.                 RtlFreeUnicodeString (&Entry->Name);  
  9.             }  
  10.             Entry += 1;  
  11.         }  
  12.         ExFreePool (MmUnloadedDrivers);  
  13.     }  
  14.     ......  
  15. }  
而 MI_UNLOADED_DRIVERS 在 base\ntos\mm\mi.h 中定义:
  1. #define MI_UNLOADED_DRIVERS 50  
所以,系统最多保存50个已卸载模块的信息,超过部分会删除当前保存列表中最老的然后将最新的已卸载模块名称(注意,不包括路径)保存在列表最后。

遍历已卸载模块列表

在了解了基本数据结构之后,假设我们现在已经可以通过_internal_get_MmUnloadDriver_address函数获取全局变量MmUnloadedDrivers的地址,那就可以轻易的写出遍历代码,如下:
  1. NTSTATUS enumerate_unloaded_modules()  
  2. {  
  3.     ULONG_PTR Index = 0;  
  4.     PULONG_PTR MmUnloadedDrivers = _internal_get_MmUnloadDriver_address();  
  5.   
  6.     if (NULL == MmUnloadedDrivers) return STATUS_UNSUCCESSFUL;  
  7.   
  8.     for (Index = 0; Index < MI_UNLOADED_DRIVERS; ++Index)  
  9.     {  
  10.         PUNLOADED_DRIVERS each_unloaded_module = (PUNLOADED_DRIVERS)*MmUnloadedDrivers + Index;  
  11.         if (MmIsAddressValid(each_unloaded_module) && MmIsAddressValid(each_unloaded_module->ModuleName.Buffer) && \  
  12.             (each_unloaded_module->ModuleName.Length == each_unloaded_module->ModuleName.MaximumLength || \  
  13.             each_unloaded_module->ModuleName.Length + sizeof(WCHAR) == each_unloaded_module->ModuleName.MaximumLength))  
  14.         {  
  15.             PKE_UNLOADED_MODULE buffer = ExAllocatePoolWithTag(PagedPool, \  
  16.                 sizeof(KE_UNLOADED_MODULE), UTL_ULDMDL_ALLOCATE_TAG);  
  17.             RtlZeroMemory(buffer, sizeof(KE_UNLOADED_MODULE));  
  18.   
  19.             buffer->StartAddress = each_unloaded_module->StartAddress;  
  20.             buffer->EndAddress = each_unloaded_module->EndAddress;  
  21.             buffer->ModuleName.Length = each_unloaded_module->ModuleName.Length;  
  22.             buffer->ModuleName.MaximumLength = each_unloaded_module->ModuleName.MaximumLength;  
  23.             buffer->ModuleName.Buffer = ExAllocatePoolWithTag(PagedPool, \  
  24.                 each_unloaded_module->ModuleName.MaximumLength, UTL_ULDMDL_ALLOCATE_TAG);  
  25.             if (each_unloaded_module->ModuleName.Buffer && MmIsAddressValid(each_unloaded_module->ModuleName.Buffer))  
  26.             {  
  27.                 RtlCopyMemory(buffer->ModuleName.Buffer, each_unloaded_module->ModuleName.Buffer, \  
  28.                     each_unloaded_module->ModuleName.Length > MAX_PATH ? MAX_PATH : each_unloaded_module->ModuleName.Length);  
  29.             }  
  30.             _internal_copy_buffer_to_r3(buffer, sizeof(KE_UNLOADED_MODULE));  
  31.         }  
  32.     }  
  33. }  
验证一下结果:


可见除去本次遍历用驱动utils.sys之外,与本文一开始lm命令输出的列表一致。


目录
WinDBG中的Unladed modules
Windows对已卸载列表的支持
遍历已卸载模块列表

版权所有 (c) 2020 - 2025 Debugwar.com

由 Hacksign 设计