64位内开发第二十四讲,内核中模块的遍历-3种方式
驱动中遍历模块
一丶简介
1.1 基于 PsLoadModuleList 方法
简介:
进入内核了.遍历内核中使用的模块该怎么办. 其实在驱动中.我们的DriverEntry入口位置.
提供了两个参数. 一个是DrIverObject另一个则是注册表路径.
其实遍历模块的技巧就在这个DriverObject中.
众所周知在Ring3下遍历模块可以通过TEB PEB遍历. 我们会接触一个结构体叫做LDR_DATA_TABLE_ENTRY的结构体.
内核中也会使用这个结构体. 看下DriverObject对象所代表的含义.
typedef struct _DRIVER_OBJECT {
CSHORT Type;
CSHORT Size;
PDEVICE_OBJECT DeviceObject; //驱动对象
ULONG Flags; //驱动的标志
PVOID DriverStart; //驱动的起始位置
ULONG DriverSize; //驱动的大小
PVOID DriverSection; //指向驱动程序映像的内存区对象
PDRIVER_EXTENSION DriverExtension; //驱动的扩展空间
UNICODE_STRING DriverName; //驱动名字
PUNICODE_STRING HardwareDatabase;
PFAST_IO_DISPATCH FastIoDispatch;
PDRIVER_INITIALIZE DriverInit;
PDRIVER_STARTIO DriverStartIo;
PDRIVER_UNLOAD DriverUnload;
PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1];
} DRIVER_OBJECT;
typedef struct _DRIVER_OBJECT *PDRIVER_OBJECT;
在这里主要使用 DriverSection这个成员 这个成员则可以解释为LDR_DATA_TABLE_ENTRY结构体.
里面存放着我们的所有模块.
typedef struct _LDR_DATA_TABLE_ENTRY {
LIST_ENTRY InLoadOrderLinks; //链表存储,指向下一个LDR_DATA_TABLE_ENTRY结构
LIST_ENTRY InMemoryOrderLinks;
LIST_ENTRY InInitializationOrderLinks;
PVOID DllBase; //基址
PVOID EntryPoint;
ULONG SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName; //存放着驱动模块名
ULONG Flags;
USHORT LoadCount;
USHORT TlsIndex;
union {
LIST_ENTRY HashLinks;
struct {
PVOID SectionPointer;
ULONG CheckSum;
};
};
union {
struct {
ULONG TimeDateStamp;
};
struct {
PVOID LoadedImports;
};
};
}LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;
所以代码就很简单了.直接遍历自身结构体的 DriverSection成员即可.解释为(LDR_DATA_TABLE_ENTRY)结构.
1.2 代码以及演示.
#include "Driver.h" //这个替换为自己的. 包含Ntddk.h即可.
#include <wdm.h>
//KLDR_DATA_TABLE_ENTRY
typedef struct _LDR_DATA_TABLE_ENTRY {
LIST_ENTRY InLoadOrderLinks;
LIST_ENTRY InMemoryOrderLinks;
LIST_ENTRY InInitializationOrderLinks;
PVOID DllBase;
PVOID EntryPoint;
ULONG SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
ULONG Flags;
USHORT LoadCount;
USHORT TlsIndex;
union {
LIST_ENTRY HashLinks;
struct {
PVOID SectionPointer;
ULONG CheckSum;
};
};
union {
struct {
ULONG TimeDateStamp;
};
struct {
PVOID LoadedImports;
};
};
}LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObj, PUNICODE_STRING pRegPath)
{
ULONG iCount = 0;
NTSTATUS ntStatus;
pDriverObj->DriverUnload = DriverUnLoad;
KdBreakPoint();
/*
主要是遍历DriverObject中的 DriverSection 它可以解释为LDR_DATA_TABLE_ENTRY结构体
*/
PLDR_DATA_TABLE_ENTRY pLdr = NULL;
PLIST_ENTRY pListEntry = NULL;
PLIST_ENTRY pCurrentListEntry = NULL;
PLDR_DATA_TABLE_ENTRY pCurrentModule = NULL;
pLdr = (PLDR_DATA_TABLE_ENTRY)pDriverObj->DriverSection;
pListEntry = pLdr->InLoadOrderLinks.Flink;
pCurrentListEntry = pListEntry->Flink;
while (pCurrentListEntry != pListEntry) //前后不相等
{
//获取LDR_DATA_TABLE_ENTRY结构
pCurrentModule = CONTAINING_RECORD(pCurrentListEntry, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks);
if (pCurrentModule->BaseDllName.Buffer != 0)
{
DbgPrint("ModuleName = %wZ ModuleBase = %p \r\n",
pCurrentModule->BaseDllName,
pCurrentModule->DllBase);
}
pCurrentListEntry = pCurrentListEntry->Flink;
}
return STATUS_SUCCESS;
}
结果.
代码简单改一下.还可以获得指定的模块的基址以及结束地址.
代码如下
VOID GetModuleBaseByName(PDRIVER_OBJECT pDriverObj,UNICODE_STRING ModuleName)
{
PLDR_DATA_TABLE_ENTRY pLdr = NULL;
PLIST_ENTRY pListEntry = NULL;
PLIST_ENTRY pCurrentListEntry = NULL;
PLDR_DATA_TABLE_ENTRY pCurrentModule = NULL;
pLdr = (PLDR_DATA_TABLE_ENTRY)pDriverObj->DriverSection;
pListEntry = pLdr->InLoadOrderLinks.Flink;
pCurrentListEntry = pListEntry->Flink;
while (pCurrentListEntry != pListEntry) //前后不相等
{
//获取LDR_DATA_TABLE_ENTRY结构
pCurrentModule = CONTAINING_RECORD(pCurrentListEntry, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks);
if (pCurrentModule->BaseDllName.Buffer != 0)
{
if (RtlCompareUnicodeString(&pCurrentModule->BaseDllName, &ModuleName, TRUE) == 0)
{
DbgPrint("ModuleName = %wZ ModuleBase = %p ModuleEndBase = %p\r\n",
pCurrentModule->BaseDllName,
pCurrentModule->DllBase,
(LONGLONG)pCurrentModule->DllBase + pCurrentModule->SizeOfImage);
}
}
pCurrentListEntry = pCurrentListEntry->Flink;
}
}
二丶 基于ZwQuerySystemInformation 方法
2.1 简介
在内核中我们还可以使用未公开函数 ZwQuerySystemInformation
来进行模块的遍历。
函数原型如下:
NTSTATUS WINAPI ZwQuerySystemInformation(
_In_ SYSTEM_INFORMATION_CLASS SystemInformationClass,
_Inout_ PVOID SystemInformation,
_In_ ULONG SystemInformationLength,
_Out_opt_ PULONG ReturnLength
);
参数1: 要检索的系统信息的类型。此参数可以是枚举类型SYSTEM_INFORMATION_CLASS下列值之一。
参数2: 根据参数一 你所要提供的一个缓冲区
参数3: 参数2的缓冲区大小
参数4: 返回长度
这种API我们都可以调用两次。第一次来确定所需要的空间。 第二次再来真正的遍历。
所以最主要的就是参数1 我们要填写什么了。
这个API在ring3同样也有。 基本一样。
所以我先引用一下之前写过的文章 然后在写一下实际代码应用。
ZwQuerySystemInfoMation函数使用 - iBinary - 博客园 (cnblogs.com)
其中我们要在内核遍历模块只需要使用 _SYSTEM_INFORMATION_CLASS
结构中的 SystemModuleInformation
号即可。此号就是第 11号功能
如果使用此号 代表我们的Buffer
要传递一个
PSYSTEM_MODULE_INFORMATION
结构 此结构如下:
typedef struct _SYSTEM_MODULE_INFORMATION
{
ULONG ulModuleCount;
SYSTEM_MODULE Modules[1];
} SYSTEM_MODULE_INFORMATION, * PSYSTEM_MODULE_INFORMATION;
参数1 代表当前模块计数或者个数
参数2 是一个数组代表当前模块。
参数2 又是一个新的类型 新类型如下
结构如下
typedef struct _SYSTEM_MODULE
{
ULONG_PTR Reserved[2];
PVOID Base;
ULONG Size;
ULONG Flags;
USHORT Index;
USHORT Unknown;
USHORT LoadCount;
USHORT ModuleNameOffset;
CHAR ImageName[256];
} SYSTEM_MODULE, * PSYSTEM_MODULE;
请注意此结构分为32位和64位。唯一不同的则是第一个成员。
如果是64位下编译使用 请修改第一个成员类型为: ULONG_PTR
或者 __int64
如果是32位下请修改第一个成员为 ULONG
而其实我们只需要将第一个成员修改为 ULONG_PTR
即可。这样如果编译的时候是编译64位的话 ULONG_PTR
则 占八个字节。 否则反之。
2.2 实战代码演示
原理了解了其实代码很简单了
1.首先定义需要的结构
#ifdef __cplusplus
extern "C"
{
#endif
typedef struct _SYSTEM_MODULE
{
ULONG_PTR Reserved[2];
PVOID Base;
ULONG Size;
ULONG Flags;
USHORT Index;
USHORT Unknown;
USHORT LoadCount;
USHORT ModuleNameOffset;
CHAR ImageName[256];
} SYSTEM_MODULE, * PSYSTEM_MODULE;
typedef struct _SYSTEM_MODULE_INFORMATION
{
ULONG ulModuleCount;
SYSTEM_MODULE Modules[1];
} SYSTEM_MODULE_INFORMATION, * PSYSTEM_MODULE_INFORMATION;
NTSTATUS
NTAPI
ZwQuerySystemInformation(
DWORD32 systemInformationClass,
PVOID systemInformation,
ULONG systemInformationLength,
PULONG returnLength);
#ifdef __cplusplus
}
#endif
2.使用代码进行获取
#ifdef _AMD64_
bool getModuleBase(const char *name, unsigned long long &addr, ULONG &size)
#else
bool getModuleBase(const char *name, ULONG &addr, ULONG &size)
#endif // _AMD64_
{
ULONG uNeedSize = 0;
ULONG uIndex = 0;
NTSTATUS ntStatus = STATUS_UNSUCCESSFUL;
bool bRet = false;
PSYSTEM_MODULE_INFORMATION pSysInfo = NULL;
ntStatus = ZwQuerySystemInformation(SystemModuleInformation, &uNeedSize, 0, &uNeedSize);
if (ntStatus != STATUS_INFO_LENGTH_MISMATCH)
{
bRet = false;
goto Done;
}
if (uNeedSize <= 0)
{
bRet = false;
goto Done;
}
pSysInfo = (PSYSTEM_MODULE_INFORMATION)ExAllocatePoolWithTag(NonPagedPool, uNeedSize, 'aaaa');
if (pSysInfo == NULL)
{
bRet = false;
goto Done;
}
ntStatus = ZwQuerySystemInformation(SystemModuleInformation, pSysInfo, uNeedSize, &uNeedSize);
if (!NT_SUCCESS(ntStatus))
{
bRet = false;
goto Done;
}
for (uIndex = 0; uIndex < pSysInfo->ulModuleCount; uIndex++)
{
PSYSTEM_MODULE pModuleInfo = &pSysInfo->Modules[uIndex];
if (MmIsAddressValid(pModuleInfo) && pModuleInfo != NULL)
{
if (MmIsAddressValid(pModuleInfo->ImageName) && pModuleInfo->ImageName != NULL)
{
// if (strstr(pModuleInfo->ImageName, name))
// {
// #ifdef _AMD64_
// addr = (unsigned long long)pModuleInfo->Base;
// size = (unsigned long long)pModuleInfo->Size;
// #else
// addr = (ULONG)pModuleInfo->Base;
// size = (ULONG)pModuleInfo->Size;
// #endif
// bRet = true;
// break;
// }
DbgPrint("[Module]: %s \t\t[Base] %p \t\t[Name]: %s \r\n",
pModuleInfo->ImageName,
pModuleInfo->Base,
pModuleInfo->ImageName + pModuleInfo->ModuleNameOffset);
}
}
}
Done:
if (pSysInfo != NULL)
{
ExFreePoolWithTag(pSysInfo, 'aaaa');
pSysInfo = NULL;
}
return bRet;
}
-
调用示例
extern "C" NTSTATUS DriverEntry( IN PDRIVER_OBJECT pRootDriverObj, IN PUNICODE_STRING pRegPath) { NTSTATUS status = STATUS_UNSUCCESSFUL; pRootDriverObj->DriverUnload = DriverUnload; // KdBreakPoint(); ULONG_PTR addr = 0; ULONG uSize = 0; getModuleBase("win32kfull.sys", addr, uSize); // KdBreakPoint(); return status; }
2.3 效果演示
三丶公开化函数遍历 AuxKlibQueryModuleInformation
3.1 简介
AuxKlibQueryModuleInformation
此函数也可以用于遍历系统内核模块。
但是使用此函数之前我们 首先要包含 aux_klib.h
并且还要包含静态库 Aux_Klib
对于 WDK7600来说 我们包含库直接 采用下列方式即可。
TARGETLIBS = $(DDK_LIB_PATH)/Aux_Klib.lib\
AuxKlibQueryModuleInformation
函数原型如下:
NTSTATUS AuxKlibQueryModuleInformation(
[in, out] PULONG BufferSize,
[in] ULONG ElementSize,
[out, optional] PVOID QueryInfo
);
你可以将其理解为 简化版本的 ZwQuerySystemInformation
参数1: BufferSize 是一个传入传出参数,当我们的参数3要传递的Buffer为NULL的时候 那么调用此函数则会将你所需要的Buffer缓冲的大小写入到参数1里面。这样我们就得到了Buffer的大小了。
参数2: 参数二 必须是 sizeof(AUX_MODULE_BASIC_INFO) 或者 sizeof(AUX_MODULE_EXTENDED_INFO)
参数2的意思是 QueryInfo(参数3)中的数组的每个元素的大小。
参数3: 参数三是一个Buffer,虽然是PVOID 但是它的结构是 AUX_MODULE_BASIC_INFO
或者 AUX_MODULE_EXTENDED_INFO
如果遍历模块我们会使用 AUX_MODULE_EXTENDED_INFO
此结构
此结构如下:
typedef struct _AUX_MODULE_EXTENDED_INFO {
AUX_MODULE_BASIC_INFO BasicInfo;
ULONG ImageSize;
USHORT FileNameOffset;
UCHAR FullPathName[AUX_KLIB_MODULE_PATH_LEN];
} AUX_MODULE_EXTENDED_INFO, *PAUX_MODULE_EXTENDED_INFO;
此结构里面 存储着 FullPathName 也就是文件的路径 也存储着 FileNameOffset 文件名的偏移。 FullPathName+FileNameOffset则可以得到文件名
而参数1的 BasicInfo也是一个结构
typedef struct _AUX_MODULE_BASIC_INFO {
PVOID ImageBase;
} AUX_MODULE_BASIC_INFO, *PAUX_MODULE_BASIC_INFO;
此结构就一个成员 就是 ImageBase
所以说它是对第二种方法的简化。
3.2 使用例子代码
代码很简单
#include "aux_klib.h"
bool EnumModule()
{
ULONG uNeedModuleSize = 0;
ULONG uIndex = 0;
NTSTATUS ntStatus = STATUS_UNSUCCESSFUL;
bool bRet = false;
PAUX_MODULE_EXTENDED_INFO pSysInfo = NULL;
ULONG ulNumberOfModules = 0;
ntStatus = AuxKlibInitialize();
if (!NT_SUCCESS(ntStatus))
{
bRet = false;
goto Done;
}
ntStatus = AuxKlibQueryModuleInformation(&uNeedModuleSize, sizeof(AUX_MODULE_EXTENDED_INFO), NULL);
if (!NT_SUCCESS(ntStatus))
{
bRet = false;
goto Done;
}
// if (ntStatus != STATUS_BUFFER_TOO_SMALL)
// {
// bRet = false;
// goto Done;
// }
if (uNeedModuleSize <= 0)
{
bRet = false;
goto Done;
}
ulNumberOfModules = uNeedModuleSize / sizeof(AUX_MODULE_EXTENDED_INFO);
pSysInfo = (PAUX_MODULE_EXTENDED_INFO)ExAllocatePoolWithTag(NonPagedPool, uNeedModuleSize, 'aaaa');
if (pSysInfo == NULL)
{
bRet = false;
goto Done;
}
RtlZeroMemory(pSysInfo, uNeedModuleSize);
ntStatus = AuxKlibQueryModuleInformation(&uNeedModuleSize, sizeof(AUX_MODULE_EXTENDED_INFO), pSysInfo);
if (!NT_SUCCESS(ntStatus))
{
bRet = false;
goto Done;
}
for (uIndex = 0; uIndex < ulNumberOfModules; uIndex++)
{
DbgPrint("[Module]: %s \t\t[Base] %p \t\t[Name]: %s \r\n",
pSysInfo[uIndex].FullPathName,
pSysInfo[uIndex].BasicInfo.ImageBase,
pSysInfo[uIndex].FullPathName + pSysInfo[uIndex].FileNameOffset);
}
Done:
if (pSysInfo != NULL)
{
ExFreePoolWithTag(pSysInfo, 'aaaa');
pSysInfo = NULL;
}
return bRet;
}
3.3 效果演示
四丶 未公开函数RtlQueryModuleInformation
4.1 简介
在windows系统内核中 还存在一个未公开函数 RtlQueryModuleInformation
此函数也是遍历系统模块的。
其函数结构如下
NTSTATUS
RtlQueryModuleInformation(
PULONG BufferSize,
ULONG UnitSize,
PVOID Buffer);
直接定义出来即可。
使用方式 和第三种 遍历模块的方式一样。
只不过需要把 AuxKlibQueryModuleInformation
换成 RtlQueryModuleInformation
代码参数 参考 AuxKlibQueryModuleInformation
4.2 代码
bool EnumModule2()
{
ULONG uNeedModuleSize = 0;
ULONG uIndex = 0;
NTSTATUS ntStatus = STATUS_UNSUCCESSFUL;
bool bRet = false;
PAUX_MODULE_EXTENDED_INFO pSysInfo = NULL;
ULONG ulNumberOfModules = 0;
ntStatus = RtlQueryModuleInformation(&uNeedModuleSize, sizeof(AUX_MODULE_EXTENDED_INFO), NULL);
if (!NT_SUCCESS(ntStatus))
{
bRet = false;
goto Done;
}
// if (ntStatus != STATUS_BUFFER_TOO_SMALL)
// {
// bRet = false;
// goto Done;
// }
if (uNeedModuleSize <= 0)
{
bRet = false;
goto Done;
}
ulNumberOfModules = uNeedModuleSize / sizeof(AUX_MODULE_EXTENDED_INFO);
pSysInfo = (PAUX_MODULE_EXTENDED_INFO)ExAllocatePoolWithTag(NonPagedPool, uNeedModuleSize, 'aaaa');
if (pSysInfo == NULL)
{
bRet = false;
goto Done;
}
RtlZeroMemory(pSysInfo, uNeedModuleSize);
ntStatus = RtlQueryModuleInformation(&uNeedModuleSize, sizeof(AUX_MODULE_EXTENDED_INFO), pSysInfo);
if (!NT_SUCCESS(ntStatus))
{
bRet = false;
goto Done;
}
for (uIndex = 0; uIndex < ulNumberOfModules; uIndex++)
{
DbgPrint("[Module]: %s \t\t[Base] %p \t\t[Name]: %s \r\n",
pSysInfo[uIndex].FullPathName,
pSysInfo[uIndex].BasicInfo.ImageBase,
pSysInfo[uIndex].FullPathName + pSysInfo[uIndex].FileNameOffset);
}
Done:
if (pSysInfo != NULL)
{
ExFreePoolWithTag(pSysInfo, 'aaaa');
pSysInfo = NULL;
}
return bRet;
}
效果与 第三种方式一样,这里不在列举出来了。
坚持两字,简单,轻便,但是真正的执行起来确实需要很长很长时间.当你把坚持两字当做你要走的路,那么你总会成功. 想学习,有问题请加群.群号:725864912(收费)群名称: 逆向学习小分队 群里有大量学习资源. 以及定期直播答疑.有一个良好的学习氛围. 涉及到外挂反外挂病毒 司法取证加解密 驱动过保护 VT 等技术,期待你的进入。
详情请点击链接查看置顶博客 https://www.cnblogs.com/iBinary/p/7572603.html
本文来自博客园,作者:iBinary,未经允许禁止转载 转载前可联系本人.对于爬虫人员来说如果发现保留起诉权力.https://www.cnblogs.com/iBinary/p/11693606.html
欢迎大家关注我的微信公众号.不定期的更新文章.更新技术. 关注公众号后请大家养成 不白嫖的习惯.欢迎大家赞赏. 也希望在看完公众号文章之后 不忘 点击 收藏 转发 以及点击在看功能. QQ群: