枚舉進程中的模塊


在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;
  1. 緩沖區中存儲的數據是一個表示返回數組中元素個數的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);
    }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM