在Windows中枚舉進程中的模塊主要是其中加載的dll,在VC上主要有2種方式,一種是解析PE文件中導入表,從導入表中獲取它將要靜態加載的dll,一種是利用查詢進程地址空間中的模塊,根據模塊的句柄來得到對應的dll,最后再補充一種利用Windows中的NATIVE API獲取進程內核空間中的模塊,下面根據給出這些方式的具體的代碼片段:
解析PE文件來獲取其中的dll
在之前介紹PE文件時說過PE文件中中存在一個導入表,表中記錄了程序中加載的導入dll以及這些dll中函數的信息,這個結構的定義如下:
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics;
DWORD OriginalFirstThunk;
};
DWORD TimeDateStamp;
DWORD ForwarderChain;
DWORD Name;
DWORD FirstThunk;
} IMAGE_IMPORT_DESCRIPTOR;
我只需要獲取這個結構並且根據RVA計算出它在文件中的偏移即可找到對應名稱,利用之前PE解析器中的CPeFileInfo類來解析它即可,下面是具體的代碼:
void EnumModulesByPe(LPCTSTR pszPath)
{
CPeFileInfo peFile;
peFile.strFilePath = pszPath;
peFile.LoadFile();
if (!peFile.IsPeFile())
{
printf("is not a pe file!\n");
return ;
}
peFile.InitDataDirectoryTable();
PIMAGE_IMPORT_DESCRIPTOR pImportTable = peFile.GetImportDescriptor();
while(!peFile.IsEndOfImportTable(pImportTable))
{
printf("%s\n", peFile.RVA2fOffset(pImportTable->Name, (DWORD)peFile.pImageBase));
pImportTable++;
}
}
利用之前的PE解析的類,首先給類中的文件路徑賦值,然后加載到內存,並初始化它的數據目錄表信息,從表中取出導入表的結構,根據結構中的Name字段的值來計算它的真實地址,即可解析出它里面的模塊,這里我們只能解析出PE文件中自身保存的信息,如果dll是在程序運行之時調用LoadLibrary動態加載的,利用這個方法是找不到的。
解析進程地址空間中的模塊
這個方法首先通過OpenProcess函數獲取對應進程的句柄,然后調用EnumProcessModules枚舉進程地址空間中當前存在的模塊,這個函數會返回一個HMODULE句柄的數組,我們遍歷這個數組,對其中的每個句柄調用GetModuleFileNameEx(很多模塊GetModuleFileName獲取不到,具體原因我沒有深入研究)獲取對應的文件路徑。下面是具體的代碼:
HMODULE* phMods = NULL;
HANDLE hProcess = NULL;
DWORD dwNeeded = 0;
DWORD i = 0;
TCHAR szModName[MAX_PATH] = {};
hProcess = OpenProcess( PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, dwProcessId );
if (NULL == hProcess)
{
printf("不能打開進程[ID:0x%x]句柄,錯誤碼:0x%08x\n",dwProcessId);
return;
}
EnumProcessModules(hProcess, NULL, 0, &dwNeeded);
phMods = (HMODULE*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwProcessId);
if( EnumProcessModules(hProcess, phMods, dwNeeded, &dwNeeded))
{
for ( i = 0; i < (dwNeeded / sizeof(HMODULE)); i++ )
{
ZeroMemory(szModName,MAX_PATH*sizeof(TCHAR));
//在這如果使用GetModuleFileName,有的模塊名稱獲取不到,函數返回無法找到該模塊的錯誤
if ( GetModuleFileNameEx(hProcess, phMods[i], szModName,MAX_PATH))
{
printf("%ws\n", szModName);
}
}
}
HeapFree(GetProcessHeap(), 0, phMods);
CloseHandle( hProcess );
由於靜態加載的dll在進程啟動之時就已經被加載到內存中,所以利用這個方法自然可以獲取靜態加載的dll,但是由於它是獲取進程地址空間中加載的dll,所以要求進程要正在運行,畢竟進程如果沒有運行,那么也就不存在地址空間,也就無法獲取其中加載的dll,另外它只能獲取當前進程地址空間中的dll,有的dll這個時候還沒有被加載的話,它自然也獲取不到。所以這個方法也不是能獲取所有加載的dll
獲取內核地址空間中的模塊
不管是解析PE文件還是調用EnumProcessModules都只能獲取用戶層地址空間中的模塊,但是進程不光有用戶空間,還有內核空間,所以在這再提供一種枚舉內核地址空間的模塊的方法。
枚舉內核地址空間主要使用函數ZwQuerySystemInformation(也可以使用NtQuerySystemInformation)在msdn中明確指出,這兩個函數未來可能不在使用,不推薦使用,但是至少現在是仍然支持的,並且可以很好的完成任務。
這兩個函數主要在ntdll.dll中導出,兩個函數的參數用法完全相同,只是一個是比較上層一個比較底層而已。在這主要說明一個,另一個完全一樣:
NTSTATUS WINAPI ZwQuerySystemInformation(
__in SYSTEM_INFORMATION_CLASS SystemInformationClass,
__inout PVOID SystemInformation,
__in ULONG SystemInformationLength,
__out_opt PULONG ReturnLength
);
函數的第一個參數是一個枚舉類型,用來表示我們將要調用此函數來獲取系統哪方面的信息,第二個參數是一個緩沖區,用來存儲該函數輸出的值,第三個參數是緩沖區的長度,第四個參數是實際需要緩沖區的長度,說到這應該很快就可以反應過來,我們可以第一次調用這個函數傳入一個NULL緩沖,緩沖長度給0,讓他返回具體的長度,然后根據這個長度,動態分配一塊內存,再次調用傳入正確的緩沖和長度,獲取數據。
在調用這個函數時需要注意下面幾點:
1. 這個函數是未導出的,所以在微軟的開發環境中是沒有它的定義的,要使用它需要我們自己定義,定義的代碼如下:
//這個NTSTATUS結構在應用層有定義,直接使用即可
typedef NTSTATUS(WINAPI *ZWQUERYSYSTEMINFORMATION)(
__in SYSTEM_INFORMATION_CLASS SystemInformationClass,
__inout PVOID SystemInformation,
__in ULONG SystemInformationLength,
__out_opt PULONG ReturnLength
);
這個函數使用的一些結構是在內核開發環境DDK中定義的,在應用層中可能沒有它的定義,所以在這我們也需要對它們進行定義:
#define NT_SUCCESS(status) ((NTSTATUS)(status)>=0)
typedef enum _SYSTEM_INFORMATION_CLASS {
SystemBasicInformation,
SystemProcessorInformation,
SystemPerformanceInformation,
SystemTimeOfDayInformation,
SystemPathInformation,
SystemProcessInformation,
SystemCallCountInformation,
SystemDeviceInformation,
SystemProcessorPerformanceInformation,
SystemFlagsInformation,
SystemCallTimeInformation,
SystemModuleInformation,
SystemLocksInformation,
SystemStackTraceInformation,
SystemPagedPoolInformation,
SystemNonPagedPoolInformation,
SystemHandleInformation,
SystemObjectInformation,
SystemPageFileInformation,
SystemVdmInstemulInformation,
SystemVdmBopInformation,
SystemFileCacheInformation,
SystemPoolTagInformation,
SystemInterruptInformation,
SystemDpcBehaviorInformation,
SystemFullMemoryInformation,
SystemLoadGdiDriverInformation,
SystemUnloadGdiDriverInformation,
SystemTimeAdjustmentInformation,
SystemSummaryMemoryInformation,
SystemNextEventIdInformation,
SystemEventIdsInformation,
SystemCrashDumpInformation,
SystemExceptionInformation,
SystemCrashDumpStateInformation,
SystemKernelDebuggerInformation,
SystemContextSwitchInformation,
SystemRegistryQuotaInformation,
SystemExtendServiceTableInformation,
SystemPrioritySeperation,
SystemPlugPlayBusInformation,
SystemDockInformation,
SystemProcessorSpeedInformation,
SystemCurrentTimeZoneInformation,
SystemLookasideInformation
} SYSTEM_INFORMATION_CLASS, *PSYSTEM_INFORMATION_CLASS;
- 緩沖區中存儲的數據是一個表示返回數組中元素個數的DWORD類型的數據和一個對應結構體的數組,在MSDN上對這個緩沖進行解釋時說這個緩沖區的頭4個字節存儲了對應數組的元素個數,而后面的存儲的是對應結構的數組,所以在獲取這個結構的數組時需要向后偏移4個字節。這個結構與我們傳入的枚舉值有關,比如我們在這獲取的是進程內核空間中加載的模塊信息,即傳入的枚舉值是SystemModuleInformation,它對應的結構應該是SYSTEM_MODULE_INFORMATION,它們之間的對應關系可以在MSDN中找到。這個結構也需要自己定義,它的定義如下:
typedef struct _SYSTEM_MODULE_INFORMATION // Information Class 11
{
ULONG Reserved[2];
PVOID pBase;
ULONG Size;
ULONG Flags;
USHORT Index;
USHORT Unknown;
USHORT LoadCount;
USHORT ModuleNameOffset;
CHAR ImageName[256];
} SYSTEM_MODULE_INFORMATION, *PSYSTEM_MODULE_INFORMATION;
下面就是這個的代碼:
void EnumKernelModules()
{
HMODULE hNtDll = LoadLibrary(_T("ntdll.dll"));
if (INVALID_HANDLE_VALUE == hNtDll)
{
printf("加載ntdll.dll失敗\n");
return ;
}
ZWQUERYSYSTEMINFORMATION ZwQuerySystemInformation = (ZWQUERYSYSTEMINFORMATION)GetProcAddress(hNtDll, "ZwQuerySystemInformation");
if (NULL == ZwQuerySystemInformation)
{
printf("導出函數失敗\n");
return;
}
PULONG pBuffInfo = NULL;
DWORD dwSize = 0;
ZwQuerySystemInformation(SystemModuleInformation, pBuffInfo, 0, &dwSize);
pBuffInfo = (PULONG)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwSize);
NTSTATUS status = ZwQuerySystemInformation(SystemModuleInformation, pBuffInfo, dwSize, &dwSize);
if (!NT_SUCCESS(status))
{
return;
}
//在這為了驗證之前說的,通過這兩句輸出發現他們的結果相同
printf("%d\n", *pBuffInfo);
printf("%d\n", dwSize / sizeof(SYSTEM_MODULE_INFORMATION));
PSYSTEM_MODULE_INFORMATION pModuleInfo = (PSYSTEM_MODULE_INFORMATION)((ULONG)pBuffInfo + 4);
for (int i = 0; i < *pBuffInfo; i++)
{
printf("%s\n", pModuleInfo[i].ImageName);
}
}