作業(yè) : 內(nèi)存加載的PE文件 & 解析導(dǎo)入表 & IATHook

要讀取已經(jīng)執(zhí)行的進(jìn)程的 PE 結(jié)構(gòu),可以使用以下步驟:

  1. 獲取目標(biāo)進(jìn)程的句柄(使用 OpenProcess 函數(shù))。
  2. 獲取目標(biāo)進(jìn)程的基址(即模塊句柄)(使用 GetModuleHandleExEnumProcessModules 函數(shù))。
  3. 讀取目標(biāo)進(jìn)程內(nèi)存中的 DOS 頭(使用 ReadProcessMemory 函數(shù))。
  4. 從 DOS 頭中獲取 NT 頭的偏移位置(即 DOS 頭中的 e_lfanew 字段)。
  5. 讀取目標(biāo)進(jìn)程內(nèi)存中的 NT 頭(使用 ReadProcessMemory 函數(shù))。
  6. 從 NT 頭中獲取可選頭的偏移位置。
  7. 讀取目標(biāo)進(jìn)程內(nèi)存中的可選頭(使用 ReadProcessMemory 函數(shù))。
  8. 根據(jù)可選頭中的數(shù)據(jù)目錄表中的導(dǎo)入表項,獲取導(dǎo)入表的位置。
  9. 讀取目標(biāo)進(jìn)程內(nèi)存中的導(dǎo)入表(使用 ReadProcessMemory 函數(shù))。
void OpenProcessAnalyzeImportTable(DWORD dwProcessId)
{
    // 13056
    HANDLE hProcess = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, FALSE, dwProcessId);
    if (hProcess != NULL)
    {
        // 獲取目標(biāo)進(jìn)程模塊句柄
        HMODULE hModules[1];
        DWORD cbNeeded;
        if (!EnumProcessModules(hProcess, hModules, sizeof(hModules), &cbNeeded))
        {
            printf("獲取模塊句柄失敗\n");
            CloseHandle(hProcess);
            return;
        }
        //解析DOS頭
        IMAGE_DOS_HEADER DosHeader;
        ReadProcessMemory(hProcess, (LPVOID)hModules[0], &DosHeader, sizeof(IMAGE_DOS_HEADER), NULL);
        printf("Dos e_magic : %08X\n", DosHeader.e_magic);
        printf("Dos e_lfanew : %08X\n", DosHeader.e_lfanew);

        // 獲取 NT 頭的偏移位置
        DWORD ntHeaderOffset = DosHeader.e_lfanew;
        // e_lfanew 字段中的偏移是相對于可執(zhí)行文件的起始位置的偏移
        // 讀取目標(biāo)進(jìn)程的 NT 頭
        IMAGE_NT_HEADERS ntHeader;
        if (!ReadProcessMemory(hProcess, (LPBYTE)hModules[0] + ntHeaderOffset, &ntHeader, sizeof(ntHeader), NULL))
        {
            printf("讀取 NT 頭失敗\n");
            CloseHandle(hProcess);
            return;
        }
        printf("NT Signature : %08X\n", ntHeader.Signature);
        
        //標(biāo)準(zhǔn)文件頭
        IMAGE_FILE_HEADER fileHeader;
        DWORD fileHeaderOffset = DosHeader.e_lfanew + sizeof(DWORD);
        if (!ReadProcessMemory(hProcess, (LPBYTE)hModules[0] + fileHeaderOffset, &fileHeader, sizeof(fileHeader), NULL))
        {
            printf("讀取標(biāo)準(zhǔn)文件頭失敗\n");
            CloseHandle(hProcess);
            return;
        }
        printf("Section number : %08X\n", fileHeader.NumberOfSections);

        //擴(kuò)展文件頭
        IMAGE_OPTIONAL_HEADER optionHeader;
        DWORD optionHeaderOffset = DosHeader.e_lfanew + sizeof(DWORD) + sizeof(IMAGE_FILE_HEADER);
        if (!ReadProcessMemory(hProcess,(LPBYTE)hModules[0] + optionHeaderOffset, &optionHeader, sizeof(optionHeader), NULL))
        {
            printf("讀取擴(kuò)展頭失敗\n");
            CloseHandle(hProcess);
            return;
        }
        printf("IMAGE_OPTIONAL_HEADER Magic %08X\n", optionHeader.Magic);

        for (size_t i = 0; i < IMAGE_NUMBEROF_DIRECTORY_ENTRIES; i++)
        {
            printf("%s\n", DataDirName[i]);
            printf("\t%08X\n", optionHeader.DataDirectory[i].VirtualAddress);
        }

         // 獲取導(dǎo)入表的位置
        DWORD importTableRva = optionHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
        // 讀取目標(biāo)進(jìn)程的導(dǎo)入表
        IMAGE_IMPORT_DESCRIPTOR importTable;
        if (!ReadProcessMemory(hProcess, (LPBYTE)hModules[0] + importTableRva, &importTable, sizeof(IMAGE_IMPORT_DESCRIPTOR), NULL))
        {
            printf("讀取導(dǎo)入表失敗\n");
            CloseHandle(hProcess);
            return;
        }
        DWORD dwIndex = 1;
        
        while (importTable.OriginalFirstThunk != 0 || importTable.FirstThunk != 0)
        {
            // 讀取模塊名 !!! 
            // importTable.Name RVA 
            char szModuleName[MAX_PATH];
            LPVOID moduleNameAddress = (LPBYTE)hModules[0] + importTable.Name;
            if (!ReadProcessMemory(hProcess, moduleNameAddress, szModuleName, sizeof(szModuleName), NULL))
            {
                printf("讀取模塊名失敗\n");
                CloseHandle(hProcess);
                return;
            }
            printf("模塊名稱 : %s\n", szModuleName);
            printf("\t日期時間標(biāo)志 : %08X\n", importTable.TimeDateStamp);
            printf("\tForwarderChain : %08X\n", importTable.ForwarderChain);
            printf("\t名稱Offset : %08X\n", importTable.Name);
            printf("\tFirstThunk : %08X\n", importTable.FirstThunk);
            printf("\tOriginalFirstThunk : %08X\n", importTable.OriginalFirstThunk);
            ReadProcessMemory(hProcess, (LPBYTE)hModules[0] + importTableRva + sizeof(IMAGE_IMPORT_DESCRIPTOR) * dwIndex, &importTable, sizeof(IMAGE_IMPORT_DESCRIPTOR), NULL);
            dwIndex++;
        }
    }
    else
    {
        DWORD dwError = GetLastError();
        printf("進(jìn)程打開失敗,錯誤代碼 %d\n", dwError);
    }
}

OpenProcess 函數(shù)

OpenProcess 是一個 Windows API 函數(shù),用于打開一個已存在的進(jìn)程并返回其句柄。

語法

HANDLE OpenProcess(
  DWORD dwDesiredAccess, // 指定對進(jìn)程對象的訪問權(quán)限
  BOOL bInheritHandle, // 指定新句柄是否可被子進(jìn)程繼承
  DWORD dwProcessId // 要打開的進(jìn)程的標(biāo)識符??梢允褂眠M(jìn)程標(biāo)識符(PID)來指定目標(biāo)進(jìn)程
);

示例

DWORD dwProcessId = 1234;

HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
if (hProcess != NULL) {
  printf("進(jìn)程句柄獲取成功!\n");

  // 對進(jìn)程進(jìn)行操作...

  CloseHandle(hProcess);
} else {
  DWORD dwError = GetLastError();
  printf("進(jìn)程句柄獲取失敗,錯誤代碼:%d\n", dwError);
}

GetModuleHandleEx 函數(shù)

GetModuleHandleEx 函數(shù)用于獲取指定進(jìn)程中模塊的句柄(基址)。

函數(shù)原型

BOOL GetModuleHandleEx(
  DWORD   dwFlags, // 標(biāo)志位,用于指定搜索模塊的方式
  LPCTSTR lpModuleName, // 要獲取句柄的模塊名稱。可以為 NULL,表示獲取當(dāng)前進(jìn)程中的模塊句柄
  HMODULE *phModule // 用于接收獲取到的模塊句柄的指針
);

GetModuleHandleEx 函數(shù)獲取當(dāng)前進(jìn)程的模塊句柄

HMODULE hModule = NULL;
GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, NULL, &hModule)

GetModuleHandleEx 函數(shù)獲取指定進(jìn)程的模塊句柄

// 獲取指定進(jìn)程的句柄
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processId);
// 獲取模塊的基址
GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,NULL, &hModule)

進(jìn)程句柄 & 內(nèi)存基址 & 模塊句柄 & 模塊基址

  • 進(jìn)程的句柄與加載到內(nèi)存的基址是相關(guān)的。
  • 進(jìn)程句柄只是一個用于標(biāo)識和引用進(jìn)程的標(biāo)識符,它本身并不直接提供進(jìn)程的內(nèi)存位置信息。
  • 但是,通過使用進(jìn)程句柄可以獲取進(jìn)程加載到內(nèi)存中的模塊句柄(Module Handle)或模塊基址(Module Base Address)
  • 模塊句柄或模塊基址是指表示進(jìn)程中加載的某個模塊(如動態(tài)鏈接庫或可執(zhí)行文件)在內(nèi)存中的起始地址。通過獲取進(jìn)程的模塊句柄或模塊基址,可以進(jìn)一步定位和操作模塊內(nèi)的數(shù)據(jù)、函數(shù)和資源。
  • 對于一個已加載的模塊,模塊句柄和模塊基址通常是相同的,它們都指向模塊在內(nèi)存中的起始位置。因此,在大多數(shù)情況下,可以將模塊句柄和模塊基址視為相同的概念,用于引用和訪問模塊。

EnumProcessModules 函數(shù)

EnumProcessModules 函數(shù)是Windows API中的一個函數(shù),用于獲取指定進(jìn)程中加載的所有模塊(DLL)的句柄。

BOOL EnumProcessModules(
  HANDLE  hProcess, // 要枚舉模塊的進(jìn)程的句柄
  HMODULE *lphModule, // 指向模塊句柄數(shù)組的指針,用于接收模塊句柄
  DWORD   cb, // 指定模塊句柄數(shù)組的大?。ㄒ宰止?jié)為單位)
  LPDWORD lpcbNeeded // 指向接收實際返回的模塊句柄數(shù)的變量的指針
);

該函數(shù)的作用是枚舉指定進(jìn)程中加載的模塊,并將每個模塊的句柄存儲在提供的數(shù)組中。通過指定的句柄數(shù)組大小,可以控制要獲取的模塊句柄的數(shù)量。

示例代碼 獲取指定進(jìn)程中加載的模塊(DLL)的文件名

tips

  • 獲取所有模塊,定義接收模塊數(shù)組 HMODULE hModules[1024];,數(shù)組中的第一個元素(hModules[0])對應(yīng)的是主模塊(也就是進(jìn)程自身的模塊)的句柄
DWORD processId = 1234; // 替換為目標(biāo)進(jìn)程的 ID
// 打開進(jìn)程獲取句柄
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processId);

HMODULE hModules[1024];
DWORD cbNeeded;

// 獲取進(jìn)程中加載的模塊句柄
if (EnumProcessModules(hProcess, hModules, sizeof(hModules), &cbNeeded))
{
    int moduleCount = cbNeeded / sizeof(HMODULE);

    for (int i = 0; i < moduleCount; i++)
    {
        TCHAR szModuleName[MAX_PATH];
        // 獲取模塊文件名
        if (GetModuleFileNameEx(hProcess, hModules[i], szModuleName, sizeof(szModuleName) / sizeof(TCHAR)))
        {
            std::cout << "Module " << i + 1 << ": " << szModuleName << std::endl;
        }
    }
}
else
{
    std::cout << "Failed to enumerate modules" << std::endl;
}

ReadProcessMemory 函數(shù)

ReadProcessMemory 是一個 Windows API 函數(shù),用于從一個指定進(jìn)程的內(nèi)存中讀取數(shù)據(jù)。

語法

BOOL ReadProcessMemory(
  HANDLE  hProcess, // 要讀取內(nèi)存的目標(biāo)進(jìn)程的句柄
  LPCVOID lpBaseAddress, // 要讀取的內(nèi)存地址在目標(biāo)進(jìn)程中的起始位置
  LPVOID  lpBuffer, // 用于存儲讀取的數(shù)據(jù)的緩沖區(qū)
  SIZE_T  nSize, // 要讀取的字節(jié)數(shù)
  SIZE_T  *lpNumberOfBytesRead  // 指向存儲實際讀取字節(jié)數(shù)的變量的指針
);

示例

DWORD dwProcessId = 1234;
HANDLE hProcess = OpenProcess(PROCESS_VM_READ, FALSE, dwProcessId);

if (hProcess != NULL) {
    LPVOID lpBaseAddress = (LPVOID)0x12345678;
    BYTE buffer[1024];
    SIZE_T nSize = sizeof(buffer);
    SIZE_T nBytesRead = 0;

    BOOL bSuccess = ReadProcessMemory(hProcess, lpBaseAddress, buffer, nSize, &nBytesRead);

    if (bSuccess) {
        printf("讀取成功!讀取到的字節(jié)數(shù):%d\n", nBytesRead);

        // 處理讀取到的數(shù)據(jù)...
    } else {
        DWORD dwError = GetLastError();
        printf("讀取失敗,錯誤代碼:%d\n", dwError);
    }

    CloseHandle(hProcess);
} else {
    DWORD dwError = GetLastError();
    printf("進(jìn)程句柄獲取失敗,錯誤代碼:%d\n", dwError);
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容