在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使用进程快照获取模块这种方式是先使用 CreateToolhelp32Snapshot 函数来创建一个进程模块的快照，然后通过 Module32First 和 Module32Next 函数来遍历快照中的模块信息Module32First 和Module32Next 函数会通过参数返回一个 MODULEENTRY32结构的模块信息，该结构的定义如下:typedef struct tagMODULEENTRY32 { DWORD dwSize; DWORD th32ModuleID; DWORD th32ProcessID; DWORD GlblcntUsage; DWORD ProccntUsage; BYTE* modBaseAddr; DWORD modBaseSize; HMODULE hModule; TCHAR szModule[MAX_MODULE_NAME32 + 1]; TCHAR szExePath[MAX_PATH]; } MODULEENTRY32, *PMODULEENTRY32;dwSize 参数表示该结构体的大小，在调用Module32First 和Module32Next之前需要先设置这个成员的值th32ProcessID:对应进程的IDmodBaseAddr：模块的起始地址,也就是模块被加载到内存的基地址hModule: 模块的句柄szModule:模块名称szExePath: 模块对应文件所在的路径下面是一个使用该方式获取加载模块的例子BOOL GetModulesTH32(WORD wPID) { HANDLE hSnap; MODULEENTRY32 me; me.dwSize = sizeof(me); hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, wPID); if (hSnap == INVALID_HANDLE_VALUE) { FreeLibrary(hToolhelp); return FALSE; } keepGoing = Module32First(hSnap, &me); while (keepGoing) { printf("%d, %08x, %s, %s\r\n", me.modBaseAddr, me.modBaseSize, me.szModule, me.szExePath); keepGoing = Module32Next(hSnap, &me); } CloseHandle(hSnap); FreeLibrary(hToolhelp); return TRUE; }获取内核地址空间中的模块不管是解析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，让他返回具体的长度，然后根据这个长度，动态分配一块内存，再次调用传入正确的缓冲和长度，获取数据。在调用这个函数时需要注意下面几点：这个函数是未导出的，所以在微软的开发环境中是没有它的定义的，要使用它需要我们自己定义，定义的代码如下：//这个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); } }