PE文件结构与函数导出表——详解与实例

PE文件结构与函数导出表——详解与实例

随着windows系统从Xp升级到Win7、Win8, 从32位升级到64位,PE文件结构在整体未变的情况下发生了一些小的变动,一方面是推荐的程序装载地址未采用,另一方面,导出函数序号不再是简单的升序,而是一定程度上的进行了乱序。本文首先对PE文件结构进行了详尽的解说,接着介绍了如何得出函数导出表,整个过程采用SysWoW64目录下的wininet.dll实例进行说明。在介绍过程中,明确指出了Win7、Win8等新系统相对Xp带来的区别。

文章链接:http://blog.csdn.net/typ2004/article/details/44227597

第一部分

1、DOS头

DOS头共64字节,最后一个双字代表PE头的文件地址。

2、PE头

WinNT.h 中 PE 头由三部分构成

typedef struct _IMAGE_NT_HEADERS {
    DWORD Signature;                         //PE ASCII
    IMAGE_FILE_HEADER FileHeader;            //标准头
    IMAGE_OPTIONAL_HEADER32 OptionalHeader;  //可选头
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

第一部分PE标识

是DOWRD大小的PE标识。

第二部分标准头

是20个字节的PE标准头。

具体结构为

typedef struct _IMAGE_FILE_HEADER {
    WORD    Machine;
    WORD    NumberOfSections;                  //PE中节的数量
    DWORD   TimeDateStamp;
    DWORD   PointerToSymbolTable;
    DWORD   NumberOfSymbols;
    WORD    SizeOfOptionalHeader;              //PE可选头的长度
    WORD    Characteristics;
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

第三部分可选头

第三部分是PE可选头

其中 0x12ch 的DWORD长的 ImageBase(0x63000000)为程序的建议装载地址,在XP系统中可能会使用此地址,但在Win7、Win8等系统中,此地址废弃。

其中 0x16ch 的DWORD长的NumberOfRvaAndSizes(0x00000010,通常为此值)为下面数据目录结构的项目数量。

注:对于64位系统来说,文件中NumberOfRvaAndSizes的位置比32位的系统靠后16个字节(前面SizeOfStackReserver等4个值在64位系统中为8字节,32位中为4字节)。

之后的8*IMAGE_NUMBEROF_DIRECTORY_ENTRIES(8*16)大小的字段为数据目录字段,定义了导出表、导入表、资源表、异常表等各类地址和大小(共16项地址和大小对)。其中,可以看到0x170h处的地址0x3D44即是导出表的虚拟地址

typedef struct _IMAGE_DATA_DIRECTORY {
    DWORD   VirtualAddress;                       //地址
    DWORD   Size;                                 //大小
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

PE可选头的具体结构为

typedef struct _IMAGE_OPTIONAL_HEADER {
    //
    // Standard fields.
    //

    WORD    Magic;
    BYTE    MajorLinkerVersion;
    BYTE    MinorLinkerVersion;
    DWORD   SizeOfCode;
    DWORD   SizeOfInitializedData;
    DWORD   SizeOfUninitializedData;
    DWORD   AddressOfEntryPoint;
    DWORD   BaseOfCode;
    DWORD   BaseOfData;

    //
    // NT additional fields.
    //

    DWORD   ImageBase;
    DWORD   SectionAlignment;               //内存中对齐粒度
    DWORD   FileAlignment;                  //文件中对齐粒度
    WORD    MajorOperatingSystemVersion;
    WORD    MinorOperatingSystemVersion;
    WORD    MajorImageVersion;
    WORD    MinorImageVersion;
    WORD    MajorSubsystemVersion;
    WORD    MinorSubsystemVersion;
    DWORD   Win32VersionValue;
    DWORD   SizeOfImage;
    DWORD   SizeOfHeaders;
    DWORD   CheckSum;
    WORD    Subsystem;
    WORD    DllCharacteristics;
    DWORD   SizeOfStackReserve;
    DWORD   SizeOfStackCommit;
    DWORD   SizeOfHeapReserve;
    DWORD   SizeOfHeapCommit;
    DWORD   LoaderFlags;
    DWORD   NumberOfRvaAndSizes;
    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

3、节表

PE头后紧跟着节目录表。

typedef struct _IMAGE_SECTION_HEADER {
    BYTE    Name[IMAGE_SIZEOF_SHORT_NAME];
    union {
            DWORD   PhysicalAddress;
            DWORD   VirtualSize;
    } Misc;
    DWORD   VirtualAddress;
    DWORD   SizeOfRawData;
    DWORD   PointerToRawData;
    DWORD   PointerToRelocations;
    DWORD   PointerToLinenumbers;
    WORD    NumberOfRelocations;
    WORD    NumberOfLinenumbers;
    DWORD   Characteristics;
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

此表中包含的的信息如下:

节点名:.text

虚拟大小:00180454

虚拟偏移:00001000

实际大小:00180600

实际偏移:00000400

文件特征:60000020

节点名:.orpc

虚拟大小:0000009E

虚拟偏移:00182000

实际大小:00000200

实际偏移:00180A00

文件特征:60000020

节点名:.wpp_sf

虚拟大小:00009E3F

虚拟偏移:00183000

实际大小:0000A000

实际偏移:00180C00

文件特征:60000020

节点名:.data

虚拟大小:0000755C

虚拟偏移:0018D000

实际大小:00003E00

实际偏移:0018AC00

文件特征:C0000040

节点名:.idata

虚拟大小:00002444

虚拟偏移:00195000

实际大小:00002600

实际偏移:0018EA00

文件特征:40000040

节点名:.didat

虚拟大小:0000057C

虚拟偏移:00198000

实际大小:00000600

实际偏移:00191000

文件特征:C0000040

节点名:.rsrc

虚拟大小:0002BE40

虚拟偏移:00199000

实际大小:0002C000

实际偏移:00191600

文件特征:40000040

节点名:.reloc

虚拟大小:0000F8BC

虚拟偏移:001C5000

实际大小:0000FA00

实际偏移:001BD600

文件特征:42000040

4、各节

节表之后便是各节,需要注意的是,从节表到第一个节直接用0填充,其他节之间同样用0填充,各节只需要对齐到PE可选头的文件对齐粒度即可(本例中为0x0200)。

第二部分

函数导出表

1、导出目录

从上面已经知道0x3D44即是导出表的虚拟地址,其大小为0x255F。

依次查看各节的【虚拟偏移,虚拟大小),发现导出表位于.text 节中,.text节的 (虚拟偏移-实际偏移)=0x0C00,所以导出表的实际偏移为 0x3D44-0x0C00=0x3144。

导出目录的具体结构为

typedef struct _IMAGE_EXPORT_DIRECTORY {
    DWORD   Characteristics;
    DWORD   TimeDateStamp;
    WORD    MajorVersion;
    WORD    MinorVersion;
    DWORD   Name;                   //指向该导出表的文件名字符串
    DWORD   Base;
    DWORD   NumberOfFunctions;      //所有的导出函数数目
    DWORD   NumberOfNames;          //以函数名导出的函数个数
    DWORD   AddressOfFunctions;     // 导出函数地址表 RVA from base of image
    DWORD   AddressOfNames;         // 导出名称地址表 RVA from base of image
    DWORD   AddressOfNameOrdinals;  // 导出序号地址表 RVA from base of image
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

从此结构可以看出,导出函数数目为0x0143,以函数名导出的函数个数为0x0123,导出函数地址表的地址为0x3d6c-0x0c00=0x316c,导出函数名称地址表的地址为0x4278-0x0c00=0x3678,导出序号表地址为0x4704-0x0c00=0x3b04。

2、导出函数地址表

由地址0x316c开始遍历,0x0FC6A0, 0x154D40, 0x154E30, 0x11C180, 0x11C600, 0x0FEF30, 0x081290等等,即为地址列表。

3、导出函数名称地址表

由地址0x3678开始遍历,得到0x4956, 0x496c, 0x4980等等虚拟偏移,对应的实际偏移为0x3d56, 0x3d6c, 0x3d80等等,查找这些位置的名称字符串如下。

4、导出序号地址表

导出序号 AddressOfNameOrdinals 指向的也是到处序号列表地址,其中的每个序号,与导出函数名称一一对应,代表了这个函数名称对应的函数在导出函数列表里序号。

本例中地址为0x3b04的导出序数表的第一项为0x0006,代表AppCacheCheckManifest 对应函数地址的下标为6(从0开始),即地址0x081290。

5、导出函数在内存中的地址

如果在Xp系统中,导出函数AppCacheCheckManifest 在内存的地址只需加上推荐的程序装在地址 ImageBase(1.2PE头的可选头里的字段)即可。

但是在Win7、Win8等系统中,导出函数在内存中的地址应加上 真正的程序装载地址。这个地址可以用 (DWORD) LoadLibrary(DllFilePath)求得。

至此完成~~

时间: 2024-10-03 01:41:03

PE文件结构与函数导出表——详解与实例的相关文章

python 中join()函数strip() 函数和 split() 函数的详解及实例

1.join()函数 Python中有join()和os.path.join()两个函数,具体作用如下: join():                连接字符串数组.将字符串.元组.列表中的元素以指定的字符(分隔符)连接生成一个新的字符串 语法:  'sep'.join(seq) 参数说明sep:分隔符.可以为空seq:要连接的元素序列.字符串.元组.字典上面的语法即:以sep作为分隔符,将seq所有的元素合并成一个新的字符串 返回值:返回一个以分隔符sep连接各个元素后生成的字符串 os.p

Python学习入门教程,字符串函数扩充详解

因有用户反映,在基础文章对字符串函数的讲解太过少,故写一篇文章详细讲解一下常用字符串函数.本文章是对:程序员带你十天快速入门Python,玩转电脑软件开发(三)中字符串函数的详解与扩充. 如果您想学习并参与本教程的完善与写作.请在下方讨论区,回复相关问题.一起完善本文章教程的书写. Python字符串常用函数. 声明字符串变量: str = ‘关注做全栈攻城狮,写代码也要读书,爱全栈,更爱生活.’ 下面所有字符串函数函数,是对变量str进行操作: 求字符串长度: 函数使用: 运行结果: 值得注意

自写函数VB6 STUFF函数 和 VB.net 2010 STUFF函数 详解

'*************************************************************************'**模 块 名:自写函数VB6 STUFF函数 和 VB.net 2010 STUFF函数 详解'**说    明:蓝凤凰设计商城 浴火凤凰-郭卫 | 蓝凤凰-魔灵 | 郭卫-icecept'**创 建 人:浴火凤凰-郭卫'**日    期:2015年10月10日  23:13:55'**修 改 人:浴火凤凰-郭卫'**日    期:'**描   

ThinkPHP源码阅读2-----C函数配置文件详解

ThinkPHP的配置非常灵活,可自定义加载.大概看了一下,一共有这几个地方会加载配置文件,方便以后的读取 /** * 获取和设置配置参数 支持批量定义 * * @param string|array $name * 配置变量 * @param mixed $value * 配置值 * @return mixed */ function C($name = null, $value = null) { static $_config = array (); // 无参数时获取所有 if (emp

wp_list_categories函数用法详解

本以为写完新手教程之后,可以不写新手应用方面的文章了的,可今天又有朋友在群里问如何显示每个分类下文章数量这个基础性问题,看来Wordpress中文化还有很长的一段路要走,我们任重而道远啊!好,解决你的问题先:正如标题所说,Wordpress是用wp_list_categories这个函数来显示分类的,其用法是:< ?php wp_list_categories('arguments'); ?>arguments即参数,默认参数设置为: $defaults = array('show_optio

setInterval()函数用法详解

setInterval()函数用法详解:此函数用途相当广泛,在滚动代码或者焦点图片等等效果中都有应用,下面就通过实例简单介绍一下此函数的用法.setInterval()函数可以规定在按照指定的周期来执行一段函数,也就是说每隔一定事件就开始执行一次指定的函数.语法如下: setInterval(code,interval) 此函数具有两个参数,第一个参数规定要执行的函数,第二个参数规定函数两次执行之间的间隔,单位是毫秒(1秒=1000毫秒).代码实例如下: <!DOCTYPE HTML> <

eval()函数用法详解

eval()函数用法详解:此函数可能使用的频率并不是太高,但是在某些情况下具有很大的作用,下面就介绍一下eval()函数的用法.语法结构: eval(str) 此函数可以接受一个字符串str作为参数,并把此str当做一段javascript代码去执行,如果str执行结果是一个值则返回此值,否则返回undefined.如果参数不是一个字符串,则直接返回该参数,实例如下: eval("var a=1");//声明一个变量a并赋值1. eval("2+3");//执行加运

C语言中的system函数参数详解

http://blog.csdn.net/pipisorry/article/details/33024727 函数名: system 功   能: 发出一个DOS命令 用   法: int system(char *command); system函数已经被收录在标准c库中,可以直接调用 system()函数用于向操作系统传递控制台命令行,以WINDOWS系统为例,通过system()函数执行命令和在DOS窗口中执行命令的效果是一样的,所以只要在运行窗口中可以使用的命令都可以用SYSTEM()

LayoutInflater的inflate函数用法详解

LayoutInflater的inflate函数用法详解 LayoutInflater作用是将layout的xml布局文件实例化为View类对象. 获取LayoutInflater的方法有如下三种: ? LayoutInflater inflater=(LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); View layout = inflater.inflate(R.layout.main, nul