作业 : 内存加载的PE文件 & 解析导入表 & IATHook

要读取已经执行的进程的 PE 结构,可以使用以下步骤:

  1. 获取目标进程的句柄(使用 OpenProcess 函数)。
  2. 获取目标进程的基址(即模块句柄)(使用 GetModuleHandleExEnumProcessModules 函数)。
  3. 读取目标进程内存中的 DOS 头(使用 ReadProcessMemory 函数)。
  4. 从 DOS 头中获取 NT 头的偏移位置(即 DOS 头中的 e_lfanew 字段)。
  5. 读取目标进程内存中的 NT 头(使用 ReadProcessMemory 函数)。
  6. 从 NT 头中获取可选头的偏移位置。
  7. 读取目标进程内存中的可选头(使用 ReadProcessMemory 函数)。
  8. 根据可选头中的数据目录表中的导入表项,获取导入表的位置。
  9. 读取目标进程内存中的导入表(使用 ReadProcessMemory 函数)。
void OpenProcessAnalyzeImportTable(DWORD dwProcessId)
{
    // 13056
    HANDLE hProcess = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, FALSE, dwProcessId);
    if (hProcess != NULL)
    {
        // 获取目标进程模块句柄
        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 字段中的偏移是相对于可执行文件的起始位置的偏移
        // 读取目标进程的 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);
        
        //标准文件头
        IMAGE_FILE_HEADER fileHeader;
        DWORD fileHeaderOffset = DosHeader.e_lfanew + sizeof(DWORD);
        if (!ReadProcessMemory(hProcess, (LPBYTE)hModules[0] + fileHeaderOffset, &fileHeader, sizeof(fileHeader), NULL))
        {
            printf("读取标准文件头失败\n");
            CloseHandle(hProcess);
            return;
        }
        printf("Section number : %08X\n", fileHeader.NumberOfSections);

        //扩展文件头
        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("读取扩展头失败\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);
        }

         // 获取导入表的位置
        DWORD importTableRva = optionHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
        // 读取目标进程的导入表
        IMAGE_IMPORT_DESCRIPTOR importTable;
        if (!ReadProcessMemory(hProcess, (LPBYTE)hModules[0] + importTableRva, &importTable, sizeof(IMAGE_IMPORT_DESCRIPTOR), NULL))
        {
            printf("读取导入表失败\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日期时间标志 : %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("进程打开失败,错误代码 %d\n", dwError);
    }
}

OpenProcess 函数

OpenProcess 是一个 Windows API 函数,用于打开一个已存在的进程并返回其句柄。

语法

HANDLE OpenProcess(
  DWORD dwDesiredAccess, // 指定对进程对象的访问权限
  BOOL bInheritHandle, // 指定新句柄是否可被子进程继承
  DWORD dwProcessId // 要打开的进程的标识符。可以使用进程标识符(PID)来指定目标进程
);

示例

DWORD dwProcessId = 1234;

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

  // 对进程进行操作...

  CloseHandle(hProcess);
} else {
  DWORD dwError = GetLastError();
  printf("进程句柄获取失败,错误代码:%d\n", dwError);
}

GetModuleHandleEx 函数

GetModuleHandleEx 函数用于获取指定进程中模块的句柄(基址)。

函数原型

BOOL GetModuleHandleEx(
  DWORD   dwFlags, // 标志位,用于指定搜索模块的方式
  LPCTSTR lpModuleName, // 要获取句柄的模块名称。可以为 NULL,表示获取当前进程中的模块句柄
  HMODULE *phModule // 用于接收获取到的模块句柄的指针
);

GetModuleHandleEx 函数获取当前进程的模块句柄

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

GetModuleHandleEx 函数获取指定进程的模块句柄

// 获取指定进程的句柄
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)

进程句柄 & 内存基址 & 模块句柄 & 模块基址

  • 进程的句柄与加载到内存的基址是相关的。
  • 进程句柄只是一个用于标识和引用进程的标识符,它本身并不直接提供进程的内存位置信息。
  • 但是,通过使用进程句柄可以获取进程加载到内存中的模块句柄(Module Handle)或模块基址(Module Base Address)
  • 模块句柄或模块基址是指表示进程中加载的某个模块(如动态链接库或可执行文件)在内存中的起始地址。通过获取进程的模块句柄或模块基址,可以进一步定位和操作模块内的数据、函数和资源。
  • 对于一个已加载的模块,模块句柄和模块基址通常是相同的,它们都指向模块在内存中的起始位置。因此,在大多数情况下,可以将模块句柄和模块基址视为相同的概念,用于引用和访问模块。

EnumProcessModules 函数

EnumProcessModules 函数是Windows API中的一个函数,用于获取指定进程中加载的所有模块(DLL)的句柄。

BOOL EnumProcessModules(
  HANDLE  hProcess, // 要枚举模块的进程的句柄
  HMODULE *lphModule, // 指向模块句柄数组的指针,用于接收模块句柄
  DWORD   cb, // 指定模块句柄数组的大小(以字节为单位)
  LPDWORD lpcbNeeded // 指向接收实际返回的模块句柄数的变量的指针
);

该函数的作用是枚举指定进程中加载的模块,并将每个模块的句柄存储在提供的数组中。通过指定的句柄数组大小,可以控制要获取的模块句柄的数量。

示例代码 获取指定进程中加载的模块(DLL)的文件名

tips

  • 获取所有模块,定义接收模块数组 HMODULE hModules[1024];,数组中的第一个元素(hModules[0])对应的是主模块(也就是进程自身的模块)的句柄
DWORD processId = 1234; // 替换为目标进程的 ID
// 打开进程获取句柄
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processId);

HMODULE hModules[1024];
DWORD cbNeeded;

// 获取进程中加载的模块句柄
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 函数

ReadProcessMemory 是一个 Windows API 函数,用于从一个指定进程的内存中读取数据。

语法

BOOL ReadProcessMemory(
  HANDLE  hProcess, // 要读取内存的目标进程的句柄
  LPCVOID lpBaseAddress, // 要读取的内存地址在目标进程中的起始位置
  LPVOID  lpBuffer, // 用于存储读取的数据的缓冲区
  SIZE_T  nSize, // 要读取的字节数
  SIZE_T  *lpNumberOfBytesRead  // 指向存储实际读取字节数的变量的指针
);

示例

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("读取成功!读取到的字节数:%d\n", nBytesRead);

        // 处理读取到的数据...
    } else {
        DWORD dwError = GetLastError();
        printf("读取失败,错误代码:%d\n", dwError);
    }

    CloseHandle(hProcess);
} else {
    DWORD dwError = GetLastError();
    printf("进程句柄获取失败,错误代码:%d\n", dwError);
}

你可能感兴趣的:(作业 : 内存加载的PE文件 & 解析导入表 & IATHook)