读取PE文件的导出表

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

    再顺便多写一篇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的转换:

 

img_405b18b4b6584ae338e0f6ecaf736533.gif 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;

 

 

目录
相关文章
|
5月前
磁盘文件"test"中保存
【7月更文挑战第9天】磁盘文件"test"中保存。
47 13
|
存储 Windows
2.6 PE结构:导出表详细解析
导出表(Export Table)是Windows可执行文件中的一个结构,记录了可执行文件中某些函数或变量的名称和地址,这些名称和地址可以供其他程序调用或使用。当PE文件执行时Windows装载器将文件装入内存并将导入表中登记的DLL文件一并装入,再根据DLL文件中函数的导出信息对可执行文件的导入表(IAT)进行修正。
244 1
|
存储 算法 编译器
2.7 PE结构:重定位表详细解析
重定位表(Relocation Table)是Windows PE可执行文件中的一部分,主要记录了与地址相关的信息,它在程序加载和运行时被用来修改程序代码中的地址的值,因为程序在不同的内存地址中加载时,程序中使用到的地址也会受到影响,因此需要重定位表这个数据结构来完成这些地址值的修正。当程序需要被加载到不同的内存地址时,相关的地址值需要进行修正,否则程序运行会出现异常。而重定位表就是记录了在程序加载时需要修正的地址值的相关信息,包括修正地址的位置、需要修正的字节数、需要修正的地址的类型等。重定位表中的每个记录都称为一项(entry),每个entry包含了需要修正的地址值的详细信息,通常是以可变
212 0
2.7 PE结构:重定位表详细解析
|
安全 编译器 API
2.5 PE结构:导入表详细解析
导入表(Import Table)是Windows可执行文件中的一部分,它记录了程序所需调用的外部函数(或API)的名称,以及这些函数在哪些动态链接库(DLL)中可以找到。在Win32编程中我们会经常用到导入函数,导入函数就是程序调用其执行代码又不在程序中的函数,这些函数通常是系统提供给我们的API,在调用者程序中只保留一些函数信息,包括函数名机器所在DLL路径。
205 1
|
存储 C++ iOS开发
【C++之文件与文件流】f1.dat 和 f2.dat 文件的读写
【C++之文件与文件流】f1.dat 和 f2.dat 文件的读写
|
存储 安全 数据安全/隐私保护
PE格式:新建节并插入DLL
PE格式是 Windows下最常用的可执行文件格式,理解PE文件格式不仅可以了解操作系统的加载流程,还可以更好的理解操作系统对进程和内存相关的管理知识,而有些技术必须建立在了解PE文件格式的基础上,如文件加密与解密,病毒分析,外挂技术等。
237 0
PE格式:新建节并插入DLL
|
存储 安全 小程序
PE格式:手工给程序插入ShellCode
PE格式是 Windows下最常用的可执行文件格式,理解PE文件格式不仅可以了解操作系统的加载流程,还可以更好的理解操作系统对进程和内存相关的管理知识,而有些技术必须建立在了解PE文件格式的基础上,如文件加密与解密,病毒分析,外挂技术等,本次实验的目标是手工修改或增加节区,并给特定可执行程序插入一段ShellCode代码,实现程序运行自动反弹一个Shell会话。
399 0
PE格式:手工给程序插入ShellCode
|
存储 API 数据安全/隐私保护
PE格式:导入表与IAT内存修正
关于Dump内存原理,我们可以使用调试API启动调试事件,然后再程序的OEP位置写入CC断点让其暂停在OEP位置,此时程序已经在内存解码,同时也可以获取到程序的OEP位置,转储就是将程序原封不动的读取出来并放入临时空间中,然后对空间中的节表和OEP以及内存对齐进行修正,最后将此文件在内存保存出来即可。
322 0
PE格式:导入表与IAT内存修正
|
安全 小程序 Shell
PE格式:新建节并插入代码
PE格式是 Windows下最常用的可执行文件格式,理解PE文件格式不仅可以了解操作系统的加载流程,还可以更好的理解操作系统对进程和内存相关的管理知识,而有些技术必须建立在了解PE文件格式的基础上,如文件加密与解密,病毒分析,外挂技术等。
256 0
PE格式:新建节并插入代码
|
安全 API 索引
PE结构讲解2--导入和导出表
本文为转载文章,整理自小甲鱼老师讲的PE结构课程; 一、导入表的结构 在 PE文件头的 IMAGE_OPTIONAL_HEADER32 结构中的 DataDirectory(数据目录表) 的第二个成员就是指向输入表(导入表)的。
2416 0