C语言实现遍历PE文件导入表

近期在学习PE文件的结构,这里整理一下调试好的代码,用于遍历PE文件的导入表信息:

#include <iostream>
#include <stdio.h>
#include <windows.h>
#include <Dbghelp.h> //ImageRvaToVa
#include <cassert>
#include <vector>
#include <string>

#pragma comment(lib,"Dbghelp.lib")

void PrintFunctionInfoByOridinal(const char* moduleName, DWORD dwOrdinal);

int main(int argc, char* argv[])
{
    if (argc != 2 ||
        strlen(argv[1]) <= 1
        ) { printf("Invalid Parameters.\n"); return 0; }
    else
    {
        printf("File binPath:[%s].\n", argv[1]);
    }
    int i, j;
    HANDLE hFile = CreateFileA(
        argv[1], //PE文件名
        GENERIC_READ,
        FILE_SHARE_READ,
        NULL,
        OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL,
        NULL);

    if (hFile == INVALID_HANDLE_VALUE)
    {
        printf("Create File Failed.\n");
        return 0;
    }

    HANDLE hFileMapping = CreateFileMappingA(hFile, NULL, PAGE_READONLY, 0, 0, NULL);

    if (hFileMapping == NULL || hFileMapping == INVALID_HANDLE_VALUE)
    {
        printf("Could not create file mapping object (%d).\n", GetLastError());
        return 0;
    }

    LPBYTE lpBaseAddress = (LPBYTE)MapViewOfFile(hFileMapping,   // handle to map object
        FILE_MAP_READ, 0, 0, 0);

    if (lpBaseAddress == NULL)
    {
        printf("Could not map view of file (%d).\n", GetLastError());
        return 0;
    }

    PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)lpBaseAddress;
    PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)(lpBaseAddress + pDosHeader->e_lfanew);

    //导入表的rva:0x2a000;
    DWORD Rva_import_table = pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;

    if (Rva_import_table == 0)
    {
        printf("NoFound Import Table!");
        //关闭文件,句柄。。
        UnmapViewOfFile(lpBaseAddress);
        CloseHandle(hFileMapping);
        CloseHandle(hFile);
        return 0;
    }

    //这个虽然是内存地址,但是减去文件开头的地址,就是文件地址了
    //这个地址可以直接从里面读取你想要的东西了
    PIMAGE_IMPORT_DESCRIPTOR pImportTable = (PIMAGE_IMPORT_DESCRIPTOR)ImageRvaToVa(
        pNtHeaders,
        lpBaseAddress,
        Rva_import_table,
        NULL
    );

    //减去内存映射的首地址,就是文件地址了。。(很简单吧)
    printf("Address Of ImportTable: 0x%08X\n", ((DWORD)pImportTable - (DWORD)lpBaseAddress));

    //现在来到了导入表的面前:IMAGE_IMPORT_DESCRIPTOR 数组(以0元素为终止)
    //定义表示数组结尾的null元素!
    IMAGE_IMPORT_DESCRIPTOR null_iid;
    IMAGE_THUNK_DATA null_thunk;
    memset(&null_iid, 0, sizeof(null_iid));
    memset(&null_thunk, 0, sizeof(null_thunk));

    //每个元素代表了一个引入的DLL。
    for (i = 0; memcmp(pImportTable + i, &null_iid, sizeof(null_iid)) != 0; i++)
    {
        //LPCSTR: 就是 const char*
        LPCSTR szDllName = (LPCSTR)ImageRvaToVa(
            pNtHeaders, lpBaseAddress,
            pImportTable[i].Name, //DLL名称的RVA
            NULL);

        //拿到了DLL的名字
        printf("-----------------------------------------\n");
        printf("Module [%d]: %s\n", i + 1, szDllName);
        printf("-----------------------------------------\n");
        PIMAGE_THUNK_DATA32 pThunk = NULL;

        //if (pImportTable[i].OriginalFirstThunk) {
            //现在去看看从该DLL中引入了哪些函数
        //我们来到该DLL的 IMAGE_TRUNK_DATA 数组(IAT:导入地址表)前面
            pThunk = (PIMAGE_THUNK_DATA32)ImageRvaToVa(
                pNtHeaders, lpBaseAddress,
                pImportTable[i].OriginalFirstThunk, //【注意】这里使用的是OriginalFirstThunk
                NULL);
        //}

        for (j = 0; memcmp(pThunk + j, &null_thunk, sizeof(null_thunk)) != 0; j++)
        {
            //这里通过RVA的最高位判断函数的导入方式,
            //如果最高位为1,按序号导入,否则按名称导入
            if (pThunk[j].u1.AddressOfData & IMAGE_ORDINAL_FLAG64)
            {
                printf("\t [%d] \t %ld \t 按序号导入\n", j + 1, pThunk[j].u1.AddressOfData & 0xffff);
            }
            else //if(pThunk[j].u1.AddressOfData)
            {
                //按名称导入,我们再次定向到函数序号和名称
                //注意其地址不能直接用,因为仍然是RVA!
                PIMAGE_IMPORT_BY_NAME pFuncName = (PIMAGE_IMPORT_BY_NAME)ImageRvaToVa(
                    pNtHeaders, lpBaseAddress,
                    pThunk[j].u1.AddressOfData,
                    NULL);
                if (pFuncName != NULL)
                {
                    if (pFuncName->Hint == NULL) 
                        printf("\t [%d] \t -- \t %s\n", j + 1, pFuncName->Name);
                    else
                        printf("\t [%d] \t %ld \t %s\n", j + 1, pFuncName->Hint, pFuncName->Name);
                }
                else if((pThunk[j].u1.AddressOfData & 0xffff) != 0) {
                    {
                        printf("\t [%d] \t ", j + 1);
                        PrintFunctionInfoByOridinal(szDllName, pThunk[j].u1.AddressOfData);
                    }
                }

            }
        }
    }


    //关闭文件,句柄。。
    UnmapViewOfFile(lpBaseAddress);
    CloseHandle(hFileMapping);
    CloseHandle(hFile);
    printf("\n操作已经完成。");
    getchar();
    return 0;
}


std::vector<std::vector<std::string>> GetDllFunctionInfo(const char* moduleName)
{
    HMODULE hModule = NULL;
    SetLastError(0);
    hModule = GetModuleHandleA(moduleName);
    if (hModule == nullptr)
    {
        hModule = LoadLibraryA(moduleName);
        if (hModule == nullptr)
            return {};
    }
        //printf("GetModuleHandleA err %d\n", GetLastError());

    const IMAGE_DOS_HEADER* pDosHeader = reinterpret_cast<IMAGE_DOS_HEADER*>(hModule);
    const IMAGE_NT_HEADERS* pNtHeader = reinterpret_cast<IMAGE_NT_HEADERS*>
        (reinterpret_cast<BYTE*>(hModule) + pDosHeader->e_lfanew);

    const IMAGE_EXPORT_DIRECTORY* pExportDirectory = 
        reinterpret_cast<IMAGE_EXPORT_DIRECTORY*>(reinterpret_cast<BYTE*>(
        hModule) + pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);

    const DWORD* pAddressOfFunctions = reinterpret_cast<DWORD*>(
        reinterpret_cast<BYTE*>(hModule) + pExportDirectory->
        AddressOfFunctions);
    const DWORD* pAddressOfNames = reinterpret_cast<DWORD*>(
        reinterpret_cast<BYTE*>(hModule) + pExportDirectory->AddressOfNames);

    const WORD* pAddressOfNameOrdinals = reinterpret_cast<WORD*>(
        reinterpret_cast<BYTE*>(hModule) + pExportDirectory->AddressOfNameOrdinals);

    // 返回格式 [[函数名, 函数地址, 函数序号], ...]
    std::vector<std::vector<std::string>> result;

    for (DWORD i = 0; i < pExportDirectory->NumberOfNames; i++)
    {
        const char* pName = reinterpret_cast<char*>(reinterpret_cast<BYTE*>(hModule) + pAddressOfNames[i]);
        const DWORD dwAddress = pAddressOfFunctions[pAddressOfNameOrdinals[i]];
        const DWORD dwOrdinal = pAddressOfNameOrdinals[i] + pExportDirectory->Base;
        // 根据自身需求,选择是否直接函数内打印
        // std::cout << "Function Name: " << pName << std::endl;
        // std::cout << "Function Address: " << dwAddress << std::endl;
        // std::cout << "Function Ordinal: " << dwOrdinal << std::endl;
        // std::cout << std::endl;
        result.push_back({ pName, std::to_string(dwAddress), std::to_string(dwOrdinal) });
    }

    return result;
}


void PrintFunctionInfoByOridinal(const char* moduleName, DWORD dwOrdinal)
{
    // 获取指定模块中的指定函数信息:
    std::vector<std::vector<std::string>> functionInfo = GetDllFunctionInfo(moduleName);

    std::string funcOrdinal = std::to_string(dwOrdinal);
    std::vector<std::string> result;

    for (const auto& function : functionInfo)
    {
        if (function[2] == funcOrdinal)
        {
            result = function;
            break;
        }
    }

    if (result.empty())
        //std::cout << "Function not found." << std::endl;
        printf("Function not found.\n");
    else
    {
        //std::cout << "Function Name: " << result[0] << std::endl;
        //std::cout << "Function Address: " << result[1] << std::endl;
        //std::cout << "Function Ordinal: " << result[2] << std::endl;
        printf("%d \t %s\n", dwOrdinal, result[0].c_str());
    }
    // 销毁过程
    functionInfo.clear();
    functionInfo.shrink_to_fit();
}

先说一下,上面的结合了其他大佬的部分思路,修改了按名称导入的部分,部分情况下导入表没有函数名称,只有序号时候会出问题,或者这个DLL是延迟加载 DLL则会失败,此时改用我们的 PrintFunctionInfoByOridinal() 加载这个链接库,分析出调用函数的信息。

测试截图:

效果比较满意。

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

涟幽516

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值