c++ 动态解析PE导出表
阅读原文时间:2023年07月11日阅读:1

测试环境是x86

#include <iostream>
#include <Windows.h>
#include <TlHelp32.h>
#include <string.h>

using namespace std;

const string processName = "game2.exe";
const string dllName = "kernel32.dll";
const string exportFunName = "LoadLibraryA";

// 获取PID
DWORD getPID(string name)
{
    DWORD pid = 0;
    HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (hSnap != INVALID_HANDLE_VALUE)
    {
        PROCESSENTRY32 pe;
        pe.dwSize = sizeof(pe);
        if (Process32First(hSnap, &pe))
        {
            do {
                if (!_wcsicmp(pe.szExeFile, wstring(name.begin(), name.end()).c_str())) {
                    pid = pe.th32ProcessID;
                    break;
                }
            } while (Process32Next(hSnap, &pe));
        }
    }
    CloseHandle(hSnap);
    return pid;
}

// 获取模块基址
uintptr_t getModuleBaseAddress(DWORD pid, const wchar_t* modName)
{
    uintptr_t modBaseAddr = 0;
    HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, pid);

    if (hSnap != INVALID_HANDLE_VALUE)
    {
        MODULEENTRY32 me;
        me.dwSize = sizeof(me);
        if (Module32First(hSnap, &me))
        {
            do {
                if (!_wcsicmp(me.szModule, modName)) {
                    modBaseAddr = (uintptr_t)me.modBaseAddr;
                    break;
                }
            } while (Module32Next(hSnap, &me));
        }
    }
    CloseHandle(hSnap);
    return modBaseAddr;
}

// 读取字节集中的ASCII
void ReadASCII(BYTE* addr, size_t offset, char r[])
{
    size_t i = 0;
    char c;
    while (true)
    {
        c = *(addr + offset + i);
        r[i] = c; // 需要把最后一个0写进去
        if (!c) break;
        i++;
    }
}

void* MyGetProcAddress(string gameProcessName, string modName, string exportFunName)
{
    DWORD pid = getPID(processName);
    if (!pid) return nullptr;

    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
    if (!hProcess) return nullptr;

    uintptr_t kernel32BaseAddr = getModuleBaseAddress(pid,
        wstring(modName.begin(), modName.end()).c_str());

    printf("kernel32BaseAddr: %x\n", kernel32BaseAddr);

    auto RVA2VA = [&](DWORD rva) -> DWORD
    {
        return kernel32BaseAddr + rva;
    };

    // 储存headers+节表 对齐后
    BYTE peHeader[0x1000];
    ReadProcessMemory(hProcess, (LPVOID)kernel32BaseAddr, peHeader, sizeof(peHeader), 0);

    IMAGE_DOS_HEADER* dosHeader = (IMAGE_DOS_HEADER*)peHeader;

    // nt头可以分为PE头,标准头,可选头
    IMAGE_NT_HEADERS* ntHeader = (IMAGE_NT_HEADERS*)(kernel32BaseAddr + dosHeader->e_lfanew);

    IMAGE_DATA_DIRECTORY* exportEntry = (IMAGE_DATA_DIRECTORY*)&ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
    if (!exportEntry->Size)
    {
        // 没有导出表
        return nullptr;
    }

    BYTE* exportDirData = new BYTE[exportEntry->Size];
    ReadProcessMemory(hProcess, (LPVOID)RVA2VA(exportEntry->VirtualAddress),
        exportDirData, exportEntry->Size, 0);

    IMAGE_EXPORT_DIRECTORY* exportDir = (IMAGE_EXPORT_DIRECTORY*)exportDirData;

    // 函数数量
    printf("NumberOfFunctions: %d\n", exportDir->NumberOfFunctions);

    // 以函数名导出的数量
    printf("NumberOfNames:     %d\n", exportDir->NumberOfNames);

    // 获取fun name表的VA地址
    // 每个大小是DWORD,一共有NumberOfNames个,并且表里面存的都是RVA值
    DWORD* AddressOfNamesVA = (DWORD*)RVA2VA(exportDir->AddressOfNames);

    DWORD itRVA = 0;
    uintptr_t itVA = 0;
    char funName[1024];
    size_t funNameIndex = 0;
    for (; funNameIndex < exportDir->NumberOfNames; funNameIndex++)
    {
        itRVA = *(AddressOfNamesVA + funNameIndex);
        itVA = kernel32BaseAddr + itRVA;
        ReadASCII((BYTE*)itVA, 0, funName);
        // printf("%s\n", funName);
        if (!strcmp(funName, exportFunName.c_str()))
        {
            break;
        }
    }
    printf("funName: %s\n", funName);
    printf("funNameIndex: %d\n", funNameIndex);

    // 拿到索引后去AddressOfNameOrdinals表,找到对应的索引中的值,取出来的值是
    // AddressOfFunctions的索引,里面存的就是地址

    // 获取AddressOfNameOrdinals表的VA地址,每个大小是WORD
    WORD* AddressOfNameOrdinalsVA = (WORD*)RVA2VA(exportDir->AddressOfNameOrdinals);
    WORD AddressOfFunctionsIndex = *(AddressOfNameOrdinalsVA + funNameIndex);

    // 获取AddressOfFunctions表的VA地址,每个大小是DWORD
    DWORD* AddressOfFunctionsVA = (DWORD*)RVA2VA(exportDir->AddressOfFunctions);

    // 每个函数地址存的是RVA地址,需要转为VA使用
    DWORD funAddrVA = *(AddressOfFunctionsVA + AddressOfFunctionsIndex);
    printf("funName Addr RVA: %x\n", RVA2VA(funAddrVA));

    delete[] exportDirData;
    CloseHandle(hProcess);

    return (VOID*)RVA2VA(funAddrVA);
}

int main()
{
    void* loadlibrary_a = MyGetProcAddress(processName, dllName, exportFunName);
    printf("loadlibrary_a: %p\n", loadlibrary_a);
    return 0;
}


kernel32BaseAddr: 77a80000
NumberOfFunctions: 1603
NumberOfNames:     1603
funName: LoadLibraryA
funNameIndex: 961
funName Addr RVA: 77aa2990
loadlibrary_a: 77AA2990