• 读取PE文件的导出表


        再顺便多写一篇PE文件的文章吧,这一篇文章介绍PE文件的导出表。导出表相对来说应该算最简单的,它的关键结构只有一个。本质上导出表的dir只是指示以下信息,dll的名称地址(ANSI字符串),有多少个导出函数,对导出函数有三个数组,分别是序号数组,函数名称(ANSI字符串)地址数组,函数入口地址(RVA)数组。导入表导出表可以说是直接和动态链接技术相关的 DataDirectory 了。

        首先还是介绍导出表的唯一一个数据结构(仅列出比较关键的成员):

        (1)IMAGE_EXPORT_DIRECTORY:

        (1.1)DWORD   Name; 这个DLL名称(ANSI)字符串的地址(RVA)。

        (1.2)DWORD   Base; 序号数组的计数起始值。(序号数组中的数值加上Base为最终的导出函数序号)

        (1.3)DWORD   NumberOfFunctions; 导出函数个数。

        (1.4)DWORD   NumberOfNames; 函数名个数。这个值通常应该和 NumberOfFunctions 相同(即所有导出函数都应该有一个名称)。

        (1.5)DWORD   AddressOfFunctions; 函数地址RVA的数组。

        (1.6)DWORD   AddressOfNames; 存储函数名称(ANSI)字符串的地址(RVA)的数组的地址(RVA)。

        (1.7)DWORD   AddressOfNameOrdinals; 存储函数序号的数组的地址(RVA)。

        按照惯例,下面是导出表的结构示意图:

        

        下面是读取导入表的代码,由于我的项目属性中使用的是unicode字符串,所以我在使用那些ANSI字符串之前,需要做一次到unicode的转换:

    code_load_exportTable
    //加载导出表
    void CPERcViewDlg::LoadExportTable(LPBYTE lpBaseAddress, PIMAGE_NT_HEADERS pNtHeaders, DWORD rva)
    {
        
    int i;
        TCHAR wcsBuffer[
    256], nodeText[256];

        HTREEITEM hItem_Export 
    = NULL;

        PIMAGE_EXPORT_DIRECTORY pExportTable 
    = (PIMAGE_EXPORT_DIRECTORY)ImageRvaToVa(
            pNtHeaders, 
            lpBaseAddress, 
            rva,
            NULL
            );

        _stprintf(nodeText, _T(
    "ExportTable (FileAddress: %08X)"), (DWORD)pExportTable - (DWORD)lpBaseAddress);
        hItem_Export 
    = m_tree.InsertItem(nodeText, TVI_ROOT, TVI_LAST);

        
    //dll名称(char*)
        LPCSTR szDllName = (LPCSTR)ImageRvaToVa(
            pNtHeaders, lpBaseAddress,
            pExportTable
    ->Name,
            NULL);

        
    //把ANSI的名称转换到unicode
        ::MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, szDllName, -1, nodeText, sizeof(nodeText)/sizeof(TCHAR));

        
    //append dll name node
        HTREEITEM hDllName = m_tree.InsertItem(nodeText, hItem_Export, TVI_LAST);

        
    //现在加载每个节点
        DWORD ImageBase = pNtHeaders->OptionalHeader.ImageBase;

        
    //以下全部是RVA。换算到数组入口处的VA:
        PDWORD pFunctions = (PDWORD)ImageRvaToVa(
            pNtHeaders, lpBaseAddress,
            pExportTable
    ->AddressOfFunctions,
            NULL);

        PWORD pOrdinals 
    = (PWORD)ImageRvaToVa(
            pNtHeaders, lpBaseAddress,
            pExportTable
    ->AddressOfNameOrdinals,
            NULL);

        BOOL hasNames 
    = (pExportTable->AddressOfNames != 0);

        PDWORD pNames 
    = NULL;
        
        
    if(hasNames)
        {
            pNames 
    = (PDWORD)ImageRvaToVa(
                pNtHeaders, lpBaseAddress,
                pExportTable
    ->AddressOfNames,
                NULL);
        }

        
    for(i=0; i<pExportTable->NumberOfFunctions; i++)
        {
            
    //函数名称
            if(hasNames)
            {
                LPCSTR szFuncName 
    = (LPCSTR)ImageRvaToVa(
                    pNtHeaders, lpBaseAddress,
                    pNames[i],
                    NULL);

                
    //转换成unicode
                ::MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, szFuncName, -1, wcsBuffer, sizeof(wcsBuffer)/sizeof(TCHAR));
            }

            _stprintf(nodeText, _T(
    "Ordinal: %ld %s, VA: %08X"),
                pExportTable
    ->Base + pOrdinals[i],
                hasNames
    ? wcsBuffer : _T("(null)"),
                ImageBase 
    + pFunctions[i]
                );
            
            m_tree.InsertItem(nodeText, hDllName, TVI_LAST);
        }
    }

        最后导出函数加载到 TreeCtrl 的效果如下:

        

        【补充内容】关于pragma:

        (1)指定内容在 obj 文件中所在的段,具体用法参考MSDN(不太常用),以下分别是代码(函数),初始化数据,未初始化数据,常量:

        #pragma code_seg( [ [ { push | pop}, ] [ identifier, ] ] [ "segment-name" [, "segment-class" ] )
        #pragma data_seg( [ [ { push | pop }, ] [ identifier, ] ] [ "segment-name" [, "segment-class" ] )
         #pragma bss_seg( [ [ { push | pop }, ] [ identifier, ] ] [ "segment-name" [, "segment-class" ] )
        #pragma const_seg( [ [ { push | pop}, ] [ identifier, ] ] [ "segment-name" [, "segment-class" ] )
     

        (2)在某个obj文件中指定一个在链接时需要搜索的库(比较常用):

        #pragma comment( lib, "emapi" )

        【参考资料】

        (1)看雪论坛精华8;

        (2)winnt.h;

  • 相关阅读:
    leetcode 155. Min Stack 、232. Implement Queue using Stacks 、225. Implement Stack using Queues
    leetcode 557. Reverse Words in a String III 、151. Reverse Words in a String
    leetcode 153. Find Minimum in Rotated Sorted Array 、154. Find Minimum in Rotated Sorted Array II 、33. Search in Rotated Sorted Array 、81. Search in Rotated Sorted Array II 、704. Binary Search
    leetcode 344. Reverse String 、541. Reverse String II 、796. Rotate String
    leetcode 162. Find Peak Element
    leetcode 88. Merge Sorted Array
    leetcode 74. Search a 2D Matrix 、240. Search a 2D Matrix II
    Android的API版本和名称对应关系
    spring 定时任务执行两次解决办法
    解析字符串为泛型的方法
  • 原文地址:https://www.cnblogs.com/hoodlum1980/p/1824133.html
Copyright © 2020-2023  润新知