PE 文件结构分析

一:PE整体结构
PE  的意思就是 Portable Executable (可移植的执行体)。
PE 文件的整体大概结构描述:
struct pe
{
DOS MZ header 所有 PE 文件 ( 甚至 32 位的 DLLs)  必须以一个简单的 DOS MZ header  开始。有了它,一旦程序在 DOS 下执行, DOS 就能识别出这是有效的执行体,然后运行紧随 MZ header  之后的 DOS stub
DOS stub           DOS stub 实际上是个有效的 EXE ,在不支持 PE 文件格式的操作系统中,它将简单显示一个错误提示,类似于字符串 "This program requires Windows"  或者程序员可根据自己的意图实现完整的 DOS 代码。大多数情况下它是由汇编器 / 编译器自动生成。通常,它简单调用中断 21h 服务 9 来显示字符串 "This program cannot run in DOS mode"
PE header     PE header  PE 相关结构  IMAGE_NT_HEADERS  的简称,其中包含了许多 PE 装载器用到的重要域。执行体在支持 PE 文件结构的操作系统中执行时, PE 装载器将从 DOS MZ header 中找到 PE header 的起始偏移量(跳过了 DOS stub  直接定位到真正的文件头 PE header )。
Section table 节表。 每个结构包含对应节的属性、文件偏移量、虚拟偏移量等。如果 PE 文件里有 5 个节,那么此结构数组内就有 5 个成员。
Section 1 PE 文件的真正内容划分成块,称之为 sections (节)。每节是一块拥有共同属性的数据,比如代码 / 数据、读 / 写等。当 PE 装载器映射节内容时,它会检查相关节属性并置对应内存块为指定属性。
Section n
}
装载一个 PE 文件的主要步骤 :
1 .当 PE 文件被执行, PE 装载器检查  DOS MZ header  里的  PE header  偏移量。如果找到,则跳转到  PE header
2 PE 装载器检查  PE header  的有效性。如果有效,就跳转到 PE header 的尾部。
3 .紧跟  PE header  的是节表。 PE 装载器读取其中的节信息,并采用文件映射方法将这些节映射到内存,同时付上节表里指定的节属性。
4 PE 文件映射入内存后, PE 装载器将处理 PE 文件中类似  import table (引入表)逻辑部分。
 
二:DOS MZ header解析
DOS MZ header 结构如下( asm 描述,来自汇编头文件 windows.ini ):
IMAGE_DOS_HEADER STRUCT
 e_magic           WORD      ?                           //  魔术数字
 e_cblp            WORD      ?                           //  文件最后页的字节数
 e_cp              WORD      ?                           //  文件页数
 e_crlc            WORD      ?                             //  重定义元素个数
 e_cparhdr         WORD      ?                           //  头部尺寸,以段落为单位
 e_minalloc        WORD      ?                            //  所需的最小附加段
 e_maxalloc        WORD      ?                            //  所需的最大附加段
 e_ss              WORD      ?                           //  初始的 SS 值(相对偏移量)
 e_sp              WORD      ?                //  初始的 SP
 e_csum            WORD      ?                          //  校验和
 e_ip              WORD      ?                            //  初始的 IP
 e_cs              WORD      ?                           //  初始的 CS 值(相对偏移量)
 e_lfarlc          WORD      ?                     //  重分配表文件地址
 e_ovno            WORD      ?                          //  覆盖号
 e_res             WORD   4 dup(?)            //  保留字
 e_oemid           WORD      ?                          // OEM 标识符(相对 e_oeminfo
 e_oeminfo         WORD      ?                           // OEM 信息
 e_res2            WORD 10 dup(?)            //  保留字
 e_lfanew          DWORD      ?               //  exe 头部的文件地址
IMAGE_DOS_HEADER ENDS                                      //sizeof(IMAGE_DOS_HEADER)==0x40
IMAGE_DOS_HEADER 结构体大小为 0x40 bytes
其中e_magic是标记,它应该等于IMAGE_DOS_SIGNATURE(字符为MZ),从而指明当前是DOS头部信息。 windows.ini 中定义的同类各种系统标记如下:
IMAGE_DOS_SIGNATURE equ 5A4Dh     
IMAGE_OS2_SIGNATURE equ 454Eh     
IMAGE_OS2_SIGNATURE_LE equ 454Ch     
IMAGE_VXD_SIGNATURE equ 454Ch     
IMAGE_NT_SIGNATURE equ 00004550h 
其中的 e_lfanew 是指向 PE header 的文件偏移,单位为 byte
 
三:DOS stub解析:
这个部分没有找到详细资料。基本上我们也不需要知道,因为这部分是为了在 MS-DOS 下运行本 exe 文件时显示“ This program cannot be run in DOS mode. ”,也就是说这是一段 MS-DOS 程序,似乎可以称为“ 实模式残余程序 ”。
此部分的起始偏移应该是由 IMAGE_DOS_HEADER .e_lfarlc 指出的,单位为 byte
 
四:PE header解析
PE header  正式命名是  IMAGE_NT_HEADERS ,结构如下
IMAGE_NT_HEADERS STRUCT 
    Signature dd ?               // PE
标记,值为 50h, 45h, 00h, 00h PE/0/0 )。
    FileHeader IMAGE_FILE_HEADER <> // 包含了关于 PE 文件物理分布的一般信息
    OptionalHeader IMAGE_OPTIONAL_HEADER32 <> //
包含了关于 PE 文件逻辑分布的信息
IMAGE_NT_HEADERS ENDS
IMAGE_FILE_HEADER STRUCT
 Machine WORD    ?// 该文件运行所要求的 CPU 。对于 Intel 平台,该值是 IMAGE_FILE_MACHINE_I386 (14Ch)
 NumberOfSections      WORD    ?// 文件的节数目
 TimeDateStamp  DWORD   ?// 文件创建日期和时间,从 1970.1.1 00:00:00 以来的秒数
 PointerToSymbolTable DWORD   ?// 用于调试
 NumberOfSymbols       DWORD   ?// 用于调试
 SizeOfOptionalHeader WORD    ?// 指示紧随本结构之后的  OptionalHeader  结构大小,
必须为有效值
 Characteristics       WORD    ?// 关于文件信息的标记,比如文件是 exe 还是 dll
IMAGE_FILE_HEADER ENDS
Characteristics 详解如下:
Bit 0 (IMAGE_FILE_RELOCS_STRIPPED) :置 1 表示文件中没有重定向信息。每个段都
有它们自己的重定向信息。这个标志在可执行文件中没有使用,在可执行文件中是用一个叫做
基址重定向目录表来表示重定向信息的,这将在下面介绍。
    Bit 1 (IMAGE_FILE_EXECUTABLE_IMAGE)
:置 1 表示该文件是可执行文件(也就是说
不是一个目标文件或库文件)。
    Bit 2 (IMAGE_FILE_LINE_NUMS_STRIPPED)
:置 1 表示没有行数信息;在可执行文件
中没有使用。
    Bit 3 (IMAGE_FILE_LOCAL_SYMS_STRIPPED)
:置 1 表示没有局部符号信息;在可执行
文件中没有使用。
    Bit 4 (IMAGE_FILE_AGGRESIVE_WS_TRIM)

    Bit 7 (IMAGE_FILE_BYTES_REVERSED_LO) 
    Bit 8 (IMAGE_FILE_32BIT_MACHINE)
:表示希望机器为 32 位机。这个值永远为 1
    Bit 9 (IMAGE_FILE_DEBUG_STRIPPED)
:表示没有调试信息,在可执行文件中没有使
用。
    Bit 10 (IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP)
:置 1 表示该程序不能运行于可移
动介质中(如软驱或 CD-ROM )。在这种情况下, OS 必须把文件拷贝到交换文件中执行。
    Bit 11 (IMAGE_FILE_NET_RUN_FROM_SWAP)
:置 1 表示程序不能在网上运行。在这种
情况下, OS 必须把文件拷贝到交换文件中执行。
    Bit 12 (IMAGE_FILE_SYSTEM)
:置 1 表示文件是一个系统文件例如驱动程序。在可执
行文件中没有使用。
    Bit 13 (IMAGE_FILE_DLL)
:置 1 表示文件是一个动态链接库( DLL )。
    Bit 14 (IMAGE_FILE_UP_SYSTEM_ONLY)
:表示文件被设计成不能运行于多处理器系
统中。
    Bit 15 (IMAGE_FILE_BYTES_REVERSED_HI)
:表示文件的字节顺序如果不是机器所期
望的,那么在读出之前要进行交换。在可执行文件中它们是不可信的(操作系统期望按正确的
字节顺序执行程序)。
IMAGE_OPTIONAL_HEADER32 STRUCT
 Magic                         WORD       ?// 标记
 MajorLinkerVersion            BYTE       ?// 链接器的版本号
 MinorLinkerVersion            BYTE       ?//  链接器的版本号
 SizeOfCode                    DWORD      ?//  可执行代码的长度
 SizeOfInitializedData         DWORD      ?//  初始化数据的长度(数据段)
 SizeOfUninitializedData       DWORD      ?//  未初始化数据的长度( bss 段)
 AddressOfEntryPoint   DWORD      ? // 代码的入口 RVA 地址,程序从这儿开始执行。 PE 装载器准备运行的 PE 文件的第一个指令的 RVA 。若您要改变整个执行的流程,可以将该值指定到新的 RVA ,这样新 RVA 处的指令首先被执行。
  BaseOfCode                    DWORD      ?//  可执行代码起始位置
 BaseOfData                    DWORD      ?//  初始化数据起始位置
 ImageBase  DWORD      ?//  载入程序首选的 RVA 地址。 PE 文件的优先装载地址。比如,如果该值是 400000h PE 装载器将尝试把文件装到虚拟地址空间的 400000h 处。字眼 " 优先 " 表示若该地址区域已被其他模块占用,那 PE 装载器会选用其他空闲地址。
 SectionAlignment              DWORD      ?//  段加载后在内存中的对齐方式。内存中节对齐的粒度。例如,如果该值是 4096 (1000h) ,那么每节的起始地址必须是 4096 的倍数。若第一节从 401000h 开始且大小是 10 个字节,则下一节必定从 402000h 开始,即使 401000h 402000h 之间还有很多空间没被使用。
 FileAlignment                 DWORD      ?//  段在文件中的对齐方式。文件中节对齐的粒度。例如,如果该值是 (200h), ,那么每节的起始地址必须是 512 的倍数。若第一节从文件偏移量 200h 开始且大小是 10 个字节,则下一节必定位于偏移量 400h:  即使偏移量 512 1024 之间还有很多空间没被使用 / 定义。
 MajorOperatingSystemVersion   WORD       ?//  操作系统版本
 MinorOperatingSystemVersion   WORD       ? //  操作系统版本
 MajorImageVersion             WORD       ?//  程序版本
 MinorImageVersion             WORD       ? //  程序版本
 MajorSubsystemVersion         WORD       ?//  子系统版本号。 win32 子系统版本。若 PE 文件是专门为 Win32 设计的,该子系统版本必定是 4.0 否则对话框不会有 3 维立体感。
 MinorSubsystemVersion         WORD       ?//  子系统版本号
 Win32VersionValue             DWORD      ?// 一般为 0
 SizeOfImage  DWORD      ?//  程序调入后占用内存大小(字节),等于所有段的长度之和。所有头和节经过节对齐处理后的大小。
 SizeOfHeaders DWORD ?//  所有文件头的长度之和 ( 从文件开始到第一个段之间的大小 ) 。所有头 + 节表的大小,也就等于文件尺寸减去文件中所有节的尺寸。可以以此值作为 PE 文件第一节的文件偏移量
 CheckSum DWORD ?//  校验和。它仅用在驱动程序中,在可执行文件中可能为 0 。它的计算方法 Microsoft 不公开,在 imagehelp.dll 中的 CheckSumMappedFile() 函数可以计算它
 Subsystem WORD ?// NT 子系统,可能是以下的值:
    IMAGE_SUBSYSTEM_NATIVE (1) 
不需要子系统。用在驱动程序中。
    IMAGE_SUBSYSTEM_WINDOWS_GUI(2) WIN32 graphical
程序(它可用 AllocConsole() 来打开一个控制台,但是不能在 一开始自动得到)。
    IMAGE_SUBSYSTEM_WINDOWS_CUI(3) WIN32 console
程序(它可以一开始自动建立)。
    IMAGE_SUBSYSTEM_OS2_CUI(5) OS/2 console
程序(因为程序是 OS/2 格式,所以它很少用在 PE )。
    IMAGE_SUBSYSTEM_POSIX_CUI(7) POSIX console
程序。
    Windows95
程序总是用 WIN32 子系统,所以只有 2 3 是合法的值
 DllCharacteristics            WORD       ?// Dll 状态
 SizeOfStackReserve            DWORD      ?//  保留堆栈大小
 SizeOfStackCommit DWORD ?//  启动后实际申请的堆栈数,可随实际情况变大
 SizeOfHeapReserve             DWORD      ?//  保留堆大小
 SizeOfHeapCommit              DWORD      ?//  实际堆大小
 LoaderFlags                   DWORD      ?// 装载标志?
 NumberOfRvaAndSizes           DWORD      ?//  下面的目录表入口个数
 DataDirectory IMAGE_DATA_DIRECTORY IMAGE_NUMBEROF_DIRECTORY_ENTRIES dup(<>)
IMAGE_OPTIONAL_HEADER32 ENDS
IMAGE_NUMBEROF_DIRECTORY_ENTRIES            equ 16
IMAGE_DATA_DIRECTORY STRUCT
 VirtualAddress    DWORD      ?//  起始 RVA 地址
 isize             DWORD      ?// 长度
IMAGE_DATA_DIRECTORY ENDS
每一个目录表代表以下的值:
 IMAGE_DIRECTORY_ENTRY_EXPORT (0) 输出符号目录用于 DLL    
    IMAGE_DIRECTORY_ENTRY_IMPORT (1)
输入符号目录     
    IMAGE_DIRECTORY_ENTRY_RESOURCE (2)
资源目录     
    IMAGE_DIRECTORY_ENTRY_EXCEPTION (3)
异常目录     
    IMAGE_DIRECTORY_ENTRY_SECURITY (4)
安全目录     
    IMAGE_DIRECTORY_ENTRY_BASERELOC (5)
重定位表     
    IMAGE_DIRECTORY_ENTRY_DEBUG (6)
调试目录
    IMAGE_DIRECTORY_ENTRY_COPYRIGHT (7)
描述版权串     
    IMAGE_DIRECTORY_ENTRY_GLOBALPTR (8)
机器值     
    IMAGE_DIRECTORY_ENTRY_TLS (9)Thread local storage
目录
    IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG (10)Load configuration 
目录     
    IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (11)Bound import directory
目录     
    IMAGE_DIRECTORY_ENTRY_IAT (12)Import Address Table
输入地址表目录
IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT   13   // Delay Load Import Descriptors
IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14   // COM Runtime descriptor
 
RVA 介绍(来自 iczelion pe 教程):
RVA  代表相对虚拟地址。 知道什么是虚拟地址吗?相对那些简单的概念而言, RVA 有些晦涩。简言之, RVA 是虚拟空间中到参考点的一段距离。我打赌您肯定熟悉文件偏移量 : RVA 就是类似文件偏移量的东西。当然它是相对虚拟空间里的一个地址,而不是文件头部。举例说明,如果 PE 文件装入虚拟地址 (VA) 空间的 400000h 处,且进程从虚址 401000h 开始执行,我们可以说进程执行起始地址在 RVA 1000h 。每个 RVA 都是相对于模块的起始 VA 的。
为什么 PE 文件格式要用到 RVA 这是为了减少 PE 装载器的负担。因为每个模块多有可能被重载到任何虚拟地址空间,如果让 PE 装载器修正每个重定位项,这肯定是个梦魇。相反,如果所有重定位项都使用 RVA ,那么 PE 装载器就不必操心那些东西了 它只要将整个模块重定位到新的起始 VA 。这就象相对路径和绝对路径的概念 : RVA 类似相对路径, VA 就象绝对路径。
 
五.Section table解析
Section table 数组成员 的数目由  file header (IMAGE_FILE_HEADER)  结构中  NumberOfSections  域的域值来决定。节表结构又命名为 IMAGE_SECTION_HEADER
IMAGE_SECTION_HEADER STRUCT
    Name1 db IMAGE_SIZEOF_SHORT_NAME dup(?)// 事实上本域的名称是 "name" ,只是 "name" 已被 MASM 用作关键字,所以我们只能用 "Name1" 代替。这儿的节名长不超过 8 字节。记住节名仅仅是个标记而已,我们选择任何名字甚至空着也行,注意这里不用 null 结束。命名不是一个 ASCIIZ 字符串,所以不用 null 结尾。 Vc 下定义为 _IMAGE_SECTION_HEADER.Name
    union Misc// 在目标文件,该地址是内容被重定位的地址,在可执行文件内是内容的尺寸
        PhysicalAddress dd ?
        VirtualSize dd      ?
    ends
    VirtualAddress dd ?// 本节的 RVA (相对虚拟地址)。 PE 装载器将节映射至内存时会读取本值,因此如果域值是 1000h ,而 PE 文件装在地址 400000h 处,那么本节就被载到 401000h
    SizeOfRawData dd        ?// 经过文件对齐处理后节尺寸, PE 装载器提取本域值了解需映射入内存的节字节数。
    PointerToRawData dd     ?// 这是节基于文件的偏移量, PE 装载器通过本域值找到节数据在文件中的位置
    PointerToRelocations dd ?// 仅用于目标文件
    PointerToLinenumbers dd ? // 仅用于目标文件
    NumberOfRelocations dw ? // 仅用于目标文件
    NumberOfLinenumbers dw ? // 仅用于目标文件
    Characteristics dd      ?// 包含标记以指示节属性,比如节是否含有可执行代码、初始化数据、未初始数据,是否可写、可读等
IMAGE_SECTION_HEADER ENDS
IMAGE_SIZEOF_SHORT_NAME equ 8
 
IMAGE_SECTION_HEADER. Characteristics 详解:
    bit 5 (IMAGE_SCN_CNT_CODE)
,置 1 ,节内包含可执行代码。     
    bit 6 (IMAGE_SCN_CNT_INITIALIZED_DATA)
1 ,节内包含的数据在执行前是确定的。     
    bit 7 (IMAGE_SCN_CNT_UNINITIALIZED_DATA) 
1 ,本节包含未初始化的数据,执行前即将被初始化为 0 。一般是 BSS.
    bit 9 (IMAGE_SCN_LNK_INFO) 
1 ,节内不包含映象数据除了注释,描述或者其他文档外,是一个目标文件的一部分,可能是针对链接器的信息。比如哪个库被需要。
    bit 11 (IMAGE_SCN_LNK_REMOVE) 
1 ,在可执行文件链接后,作为文件一部分的数据被清除。
    bit 12 (IMAGE_SCN_LNK_COMDAT) 
1 ,节包含公共块数据,是某个顺序的打包的函数。
    bit 15 (IMAGE_SCN_MEM_FARDATA) 
1 ,不确定。
    bit 17 (IMAGE_SCN_MEM_PURGEABLE) 
1 ,节的数据是可清除的。
    bit 18 (IMAGE_SCN_MEM_LOCKED) 
1 ,节不可以在内存内移动。
    bit 19 (IMAGE_SCN_MEM_PRELOAD)
1   节必须在执行开始前调入。
    Bits 20 to 23
指定对齐。一般是库文件的对象对齐。
    bit 24 (IMAGE_SCN_LNK_NRELOC_OVFL) 
1   节包含扩展的重定位。
    bit 25 (IMAGE_SCN_MEM_DISCARDABLE) 
1 ,进程开始后节的数据不再需要。
    bit 26 (IMAGE_SCN_MEM_NOT_CACHED) 
1 ,节的  数据不得缓存。
    bit 27 (IMAGE_SCN_MEM_NOT_PAGED) 
1 ,节的  数据不得交换出去。
    bit 28 (IMAGE_SCN_MEM_SHARED) 
1 ,节的数据在所有映象例程内共享,如 DLL 的初始化数据。
    bit 29 (IMAGE_SCN_MEM_EXECUTE) 
1 ,进程得到 执行 访问节内存。
    bit 30 (IMAGE_SCN_MEM_READ) 
1 ,进程得到 读出 访问节内存。
    bit 31 (IMAGE_SCN_MEM_WRITE)
1 ,进程得到 写入 访问节内存。
 
PE 装载器的大概流程 :
1. 读取  IMAGE_FILE_HEADER   NumberOfSections 域,知道文件的节数目。
2.SizeOfHeaders  域值作为节表的文件偏移量,并以此定位节表。
3. 遍历整个结构数组检查各成员值。
4. 对于每个结构,我们读取 PointerToRawData 域值并定位到该文件偏移量。然后再读取 SizeOfRawData 域值来决定映射内存的字节数。将 VirtualAddress 域值加上 ImageBase 域值等于节起始的虚拟地址。然后就准备把节映射进内存,并根据 Characteristics 域值设置属性。
5. 遍历整个数组,直至所有节都已处理完毕。
 
遍历节表的步骤 :
1.PE 文件有效性校验。
2. 定位到  PE header  的起始地址。
3.  file header   NumberOfSections 域获取节数。
4. 通过 PE header 的起始地址 + PE header 结构大小定位节表 ( 节表紧随  PE header) 。如果不是使用文件映射的方法,可以用 SetFilePointer  直接将文件指针定位到节表。
5. 处理每个  IMAGE_SECTION_HEADER  结构。
 
iczelion pe 教程中的 asm 代码定位节表的方法如下:
mov edi, pMapping        //文件映射后,pMapping指向文件头偏移为0的地方
assume edi:ptr IMAGE_DOS_HEADER //强制指针类型转换
add edi, [edi].e_lfanew//获得pe头的偏移
assume edi:ptr IMAGE_NT_HEADERS //强制指针类型转换
mov ax,[edi].FileHeader.NumberOfSections//获得节表中节个数
mov NumberOfSections,ax
add edi,sizeof IMAGE_NT_HEADERS//pe头的偏移加上pe头的大小就是节表地址了,同时edi指向了第一个 IMAGE_SECTION_HEADER
add edi, sizeof IMAGE_SECTION_HEADER//edi指向了下一个 IMAGE_SECTION_HEADER ,可以累加多少次由NumberOfSections确定。
 
六.Import Table(引入表):
一个引入函数是被某模块调用的但又不在调用者模块中的函数,因而命名为 "import (引入) " 。引入函数实际位于一个或者更多的 DLL 里。调用者模块里只保留一些函数信息,包括函数名及其驻留的 DLL 名。
IMAGE_NT_HEADERS. OptionalHeader. DataDirectory[2] 中存储的就是引入表的管理信息。
IMAGE_DATA_DIRECTORY STRUCT 
  VirtualAddress dd ? 
  isize dd ? 
IMAGE_DATA_DIRECTORY ENDS
引入表实际上是一个  IMAGE_IMPORT_DESCRIPTOR  结构数组。每个结构包含 PE 文件引入函数的一个相关 DLL 的信息。比如,如果该 PE 文件从 10 个不同的 DLL 中引入函数,那么这个数组就有 10 个成员。该数组以一个全 0 的成员结尾。
IMAGE_IMPORT_DESCRIPTOR STRUCT 
  union 
    Characteristics dd ? 
    OriginalFirstThunk dd ? // 该成员项含有指向一个  IMAGE_THUNK_DATA  结构数组的 RV A
  ends 
  TimeDateStamp dd ? 
  ForwarderChain dd ? 
  Name1 dd ? // 指向 DLL 名字的 RVA ,即指向 DLL 名字的指针,也是一个 ASCIIZ 字符串
  FirstThunk dd ? / / 指向一个  IMAGE_THUNK_DATA  结构数组的 RVA ,同OriginalFirstThunk一样
IMAGE_IMPORT_DESCRIPTOR ENDS
通常我们将 IMAGE_THUNK_DATA 解释为指向一个  IMAGE_IMPORT_BY_NAME  结构的指针
 
IMAGE_THUNK_DATA EQU <IMAGE_THUNK_DATA32>
IMAGE_THUNK_DATA32 STRUCT
    union u1
        ForwarderString dd ?
        Function dd         ?
        Ordinal dd          ?
        AddressOfData dd    ?
    ends
IMAGE_THUNK_DATA32 ENDS
 
IMAGE_IMPORT_BY_NAME STRUCT
Hint dw     ?// 指示本函数在其所驻留 DLL 的引出表中的索引号。该域被 PE 装载器用来在 DLL 的引出表里快速查询函数。该值不是必须的,一些连接器将此值设为 0
Name1 db    ?// 引入函数的函数名。函数名是一个 ASCIIZ 字符串。注意这里虽然将 Name1 的大小定义成字节,其实它是可变尺寸域,只不过我们没有更好方法来表示结构中的可变尺寸域。
IMAGE_IMPORT_BY_NAME ENDS
OriginalFirstThunk   FirstThunk  所指向的这两个数组大小取决于 PE 文件从 DLL 中引入函数的数目。比如,如果 PE 文件从 kernel32.dll 中引入 10 个函数,那么 IMAGE_IMPORT_DESCRIPTOR  结构的  Name1 域包含指向字符串 "kernel32.dll" RVA ,同时每个 IMAGE_THUNK_DATA  数组有 10 个元素。
为什么我们需要两个完全相同的数组 ? PE 文件被装载到内存时, PE 装载器将查找 IMAGE_THUNK_DATA   IMAGE_IMPORT_BY_NAME  这些结构数组,以此决定引入函数的地址。然后用引入函数真实地址来替代由 FirstThunk 指向的  IMAGE_THUNK_DATA  数组里的元素值。由 OriginalFirstThunk  指向的 RVA 数组始终不会改变,所以若还反过头来查找引入函数名, PE 装载器还能找寻到。
 
列出某个 PE 文件的所有引入函数,可以照着下面步骤走 :
1 校验文件是否是有效的 PE
2  DOS header  定位到  PE header
3 获取位于  OptionalHeader  数据目录地址。
4 转至数据目录的第二个成员提取其 VirtualAddress 值。
5 利用上值定位第一个  IMAGE_IMPORT_DESCRIPTOR  结构。
6 检查  OriginalFirstThunk 值。若不为 0 ,顺着  OriginalFirstThunk  里的 RVA 值转入那个 RVA 数组。若  OriginalFirstThunk  0 ,就改用 FirstThunk 值。有些连接器生成 PE 文件时会置 OriginalFirstThunk 值为 0 ,这应该算是个 bug 。不过为了安全起见,我们还是检查  OriginalFirstThunk 值先。
7 对于每个数组元素,我们比对元素值是否等于 IMAGE_ORDINAL_FLAG32 。如果该元素值的最高二进位为 1 那么函数是由序数引入的,可以从该值的低字节提取序数。
8 如果元素值的最高二进位为 0 ,就可将该值作为 RVA 转入  IMAGE_IMPORT_BY_NAME  数组,跳过  Hint  就是函数名字了。
9 再跳至下一个数组元素提取函数名一直到数组底部 ( 它以 null 结尾 ) 。现在我们已遍历完一个 DLL 的引入函数,接下去处理下一个 DLL
10 跳转到下一个  IMAGE_IMPORT_DESCRIPTOR  并处理之,如此这般循环直到数组见底。 (IMAGE_IMPORT_DESCRIPTOR  数组以一个全 0 域元素结尾 )
 
七.Export Table(引出表):
PE 装载器执行一个程序,它将相关 DLLs 都装入该进程的地址空间。然后根据主程序的引入函数信息,查找相关 DLLs 中的真实函数地址来修正主程序。 PE 装载器搜寻的是 DLLs 中的引出函数。 DLL/EXE 要引出一个函数给其他 DLL/EXE 使用,有两种实现方法 通过函数名引出或者仅仅通过序数引出。
通过数据目录找到引出表的位置。这儿,引出表是数据目录的第一个成员,又可称为 IMAGE_EXPORT_DIRECTORY
IMAGE_EXPORT_DIRECTORY STRUCT
 Characteristics           DWORD      ?// 一般为 0
 TimeDateStamp  DWORD      ?// 输出表创建的时间,并非总是有效的,有些链接器置 0
 MajorVersion              WORD       ?//  16 位的版本信息,一般置 0
 MinorVersion              WORD       ? //  16 位的版本信息,一般置 0
 nName    DWORD      ?//  模块的真实名称。本域是必须的,因为文件名可能会改变。这种情况下, PE 装载器将使用这个内部名字。
 nBase  DWORD      ?//  基数,加上序数就是函数地址数组的索引值了。
 NumberOfFunctions         DWORD      ?//  模块引出的函数 / 符号总数。
 NumberOfNames             DWORD      ?//  通过名字引出的函数 / 符号数目。该值不是模块引出的函数 / 符号总数,这是由上面的 NumberOfFunctions 给出。本域可以为 0 ,表示模块可能仅仅通过序数引出。如果模块根本不引出任何函数 / 符号,那么数据目录中引出表的 RVA 0
 AddressOfFunctions        DWORD      ?//  模块中有一个指向所有函数 / 符号的 RVAs 数组,本域就是指向该 RVAs 数组的 RVA 。简言之,模块中所有函数的 RVAs 都保存在一个数组里,本域就指向这个数组的首地址。 多数情况下, 'nBase' 1, 意思是第一个输出的序号为 1 ,第二个是 2
 AddressOfNames            DWORD      ?//  类似上个域,模块中有一个指向所有函数名的 RVAs 数组,本域就是指向该 RVAs 数组的 RVA
 AddressOfNameOrdinals     DWORD      ?// RVA ,指向包含上述  AddressOfNames 数组中相关函数之序数的 16 位数组。 序数= BASE+INDEX
IMAGE_EXPORT_DIRECTORY ENDS
由引出函数名获取函数地址 :
1. 定位到 PE header
2. 从数据目录读取引出表的虚拟地址。
3. 定位引出表获取名字数目 (NumberOfNames)
4. 并行遍历 AddressOfNames AddressOfNameOrdinals 指向的数组匹配名字。如果在 AddressOfNames  指向的数组中找到匹配名字,从 AddressOfNameOrdinals  指向的数组中提取索引值。例如,若发现匹配名字的 RVA 存放在 AddressOfNames  数组的第 77 个元素,那就提取 AddressOfNameOrdinals 数组的第 77 个元素作为索引值。如果遍历完 NumberOfNames  个元素,说明当前模块没有所要的名字。
5. AddressOfNameOrdinals  数组提取的数值作为 AddressOfFunctions  数组的索引。也就是说,如果值是 5 ,就必须读取 AddressOfFunctions  数组的第 5 个元素,此值就是所要函数的 RVA
由函数的序数获取函数地址 :
1. 定位到 PE header
2. 从数据目录读取引出表的虚拟地址。
3. 定位引出表获取 nBase 值。
4. 减掉 nBase 值得到指向 AddressOfFunctions  数组的索引。
5. 将该值与 NumberOfFunctions 作比较,大于等于后者则序数无效。
6. 通过上面的索引就可以获取 AddressOfFunctions  数组中的 RVA 了。
如果想通过名字获取函数地址,需要遍历 AddressOfNames   AddressOfNameOrdinals  这两个数组。如果使用函数序数,减掉 nBase 值后就可直接索引 AddressOfFunctions  数组。
 
八.各节的描述(PE文件格式 翻译:QduWg,原作LUEVELSMEYER)
概要 -------
所有的节调入内存后按照 'SectionAlignment' 对齐, FileAlignment' 是节在文件内对齐字节数。  节由节头内的项目来描述,可以通过 'PointerToRawData' 在文件内找到节,在内存内通过 'VirtualAddress' 找到节,长度是 'SizeOfRawData'.
根据他们包含的内容,有几种节。一般至少有一个数据目录指向的内容保存在一个节内。

代码节 code section
------------
本节至少含有一个标志位 'IMAGE_SCN_CNT_CODE', 'IMAGE_SCN_MEM_EXECUTE' and
'IMAGE_SCN_MEM_READ'
的集合,并且 可选头 的成员 'AddressOfEntryPoint' 指向这个节内的某个地方。 可选头 的成员 'BaseOfCode' 将指向这个节的开始,如果把非代码放在代码之前,也有可能指向后面某个地方。一般除了可执行代码,没有其他东西,一般只有一个代码节。一般的名字如: ".text", ".code", "AUTO"

数据节 data section
------------
本节包含初始化过的静态变量,例如  "static int i = 5;". 他含有这些位 'IMAGE_SCN_CNT_INITIALIZED_DATA','IMAGE_SCN_MEM_READ' 'IMAGE_SCN_MEM_WRITE'  . 有的链接器把常量数据放在没有可写标志位的节内。如果部分数据是共享的,或者有其他特性的话,节将包含更多的特征位集。节一般在 'BaseOfData' 'BaseOfData'+'SizeOfInitializedData' 的范围内 . 典型名字如: '.data', '.idata', 'DATA'

bss section
-----------
还有未初始化的数据,例如 "static int k;" 该节的 'PointerToRawData' 0 ,表明其内容不在文件内,特征位 'IMAGE_SCN_CNT_UNINITIALIZED_DATA' 指明所有内容必须在加载时间置 0 。这意味着有节头,但没有节在文件内,该节被加载器创建,并且包含全 0 字节。其长度是 'SizeOfUninitializedData'. 典型名字如 '.bss', 'BSS'

有的节数据没有被数据目录指向,其内容和结果被编译器支持,不是被链接器支持。堆栈段和堆段不是可执行文件的节,但是被加载器创建。其大小为 optional header 内的 stacksize heapsize 
.
版权 copyright
---------
开始于一个简单的目录 'IMAGE_DIRECTORY_ENTRY_COPYRIGHT'. 其内容是一个版权或者一个非 0 结尾的串,象 "Gonkulator control application, copyright (c) 1848 Hugendubel & Cie".
该串被使用命令行或者描述文件提供给链接器。该串不是必须的,可以被舍弃。他不是可写的,实际上程序不必访问他。链接器将找出是否有一个可以舍弃的不可写的段,创建一个名字为 '.descr' 的段。然后把串填入该段,让版权目录指针指向他。该节的特征字 'IMAGE_SCN_CNT_INITIALIZED_DATA'  必须置 1

资源 resources
---------
资源比如对话框,菜单,图标等等保存在由 IMAGE_DIRECTORY_ENTRY_RESOURCE 指向的数据目录内 那是一个至少含有一个位集合 'IMAGE_SCN_CNT_INITIALIZED_DATA' 'IMAGE_SCN_MEM_READ' 的节。  
资源的根基是一个 'IMAGE_RESOURCE_DIRECTORY'; 它包含几个 'IMAGE_RESOURCE_DIRECTORY_ENTRY'   ,每个指向一个 'IMAGE_RESOURCE_DIRECTORY'. 这样你得到一个 'IMAGE_RESOURCE_DIRECTORY' 树。  'IMAGE_RESOURCE_DIRECTORY_ENTRY' 是树叶,指向实际的资源数据。
层次是这样的,一个目录是根,指向一些目录,每一个针对一个资源类型。这些目录指向子目录,每个含有一个名字或者 ID ,并指向一个为该资源提供的语言目录。对于每个语言你会发现资源入口,它指向数据。
IMAGE_RESOURCE_DIRECTORY STRUCT
    Characteristics dd      ?
    TimeDateStamp dd        ?
    MajorVersion dw         ?
    MinorVersion dw         ?
    NumberOfNamedEntries dw ?// 
带名字的项目个数
    NumberOfIdEntries dw    ?// ID
项目个数
IMAGE_RESOURCE_DIRECTORY ENDS
紧接在该结构后面的是 'NumberOfNamedEntries'+'NumberOfIdEntries' 个结构。他们是  'IMAGE_RESOURCE_DIRECTORY_ENTRY' 目录项 , 他们可能指向下一个 IMAGE_RESOURCE_DIRECTORY' 或者实际的资源数据。
IMAGE_RESOURCE_DIRECTORY_ENTRY
结构:
IMAGE_RESOURCE_DIRECTORY_ENTRY STRUCT
    union
        rName  RECORD NameIsString:1,NameOffset:31//
资源标识或者其描述的目录
        Name1 dd ?       
        Id dw ?// ID
的意义依赖与在树内的层次, ID 可能是一个数字(高位清 0 )或者名字(高位置 1 ),如果是名字,低 31 位是从资源节的原始数据开始到名字的偏移量。名字是 16 位长度,以宽字符结尾,不是 0
    ends
    union
        OffsetToData dd ? //
数据偏移量或者下个子目录的偏移量
        rDirectory  RECORD DataIsDirectory:1,OffsetToDirectory:31
    ends
IMAGE_RESOURCE_DIRECTORY_ENTRY ENDS
如果在根目录下,如果 ID 是数字,是资源类型:
    1: cursor
    2: bitmap
    3: icon
    4: menu
    5: dialog
    6: string table
    7: font directory
    8: font
    9: accelerators
    10: unformatted resource data
    11: message table
    12: group cursor
    14: group icon
    16: version information
任何其他数字都是自定义的,任何带类型名字的资源类型都是自定义的。更深一层的话, ID 是资源 ID 或者资源名字。
如果再深入一层, ID 必须是数字,它是特定资源实例的语言 ID ,例如,可以具有不同本地化的语言的对话框。他们使用统一的资源 ID ,系统会选择基于线程的地域加载对话框,反过来反应用户的区域设置。如果对于线程本地没有资源发现,系统首先试图发现中立语言为资源的区域。如果还不能够发现,带有最小语言 ID 的实例将被使用。解码语言 ID ,拆分为主语言 ID 和子语言 ID ,使用宏 PRIMARYLANGID()  SUBLANGID(), 分别是 0 9 10 15 。其值定义在 "winresrc.h".
语言资源只被加速键,对话框,菜单,串表支持,其他资源类型必须是 LANG_NEUTRAL/SUBLANG_NEUTRAL.
要找出资源目录的下级目录是否是另一个目录,检查偏移量的高位,如果置 1 ,其余 31 位是从资源原始数据开始到下一个目录的偏移量。格式还是 IMAGE_RESOURCE_DIRECTORY  后面跟着 IMAGE_RESOURCE_DIRECTORY_ENTRYs 项目 .
如果高位清 0 ,偏移量是从节开始到资源原始数据描述结构(一个 IMAGE_RESOURCE_DATA_ENTRY )的偏移量。一个 IMAGE_RESOURCE_DATA_ENTRY 包括
IMAGE_RESOURCE_DATA_ENTRY STRUCT
    OffsetToData dd ?
    Size1 dd        ?
    CodePage dd     ?
    Reserved dd     ?
IMAGE_RESOURCE_DATA_ENTRY ENDS
32
'OffsetToData'  从资源节开始到原始数据的偏移量, 32 位数据大小 Size 32 'CodePage'
32 保留。
原始数据格式依赖于资源类型,任何字符串资源都是 UNICODE 格式。

重定位 relocations
-----------
最后一个数据目录是重定位,基重定位目录被 IMAGE_DIRECTORY_ENTRY_BASERELOC 指向,典型的包括一个自己的节,名字是 ".reloc" 以及 IMAGE_SCN_CNT_INITIALIZED_DATA,IMAGE_SCN_MEM_DISCARDABLE
IMAGE_SCN_MEM_READ
位集合。

如果映象没有被加载器调入优先地址,则该数据被加载器使用。这种情况下给链接器填入的地址无效了。加载器必须修复用于静态变量的绝对地址,串等。

IMAGE_BASE_RELOCATION STRUCT
    VirtualAddress dd   ?
    SizeOfBlock dd      ?
IMAGE_BASE_RELOCATION ENDS
重定位目录是一系列块,每个块包括重定位信息针对 4K 的映象。一个块起始于一个
'IMAGE_BASE_RELOCATION'
结构,包括 32 位的 'VirtualAddress' 32 位的 'SizeOfBlock'. 随后是实际的重定位数据每个都是 16 位的。 'VirtualAddress' 是本块要应用的基地址。 'SizeOfBlock'  是整个块的大小。后面的重定位的数目是 ('SizeOfBlock'-sizeof(IMAGE_BASE_RELOCATION))/2 ,重定位信息以 VirtualAddress' 0 IMAGE_BASE_RELOCATION 结构结束。

每个 16 位重定位信息包括低 12 位的重定位位置和高 4 位的重定位类型。要得到重定位的 RVA,IMAGE_BASE_RELOCATION' 'VirtualAddress' 需要加上 12 位位置偏移量 类型是下列之一:

    IMAGE_REL_BASED_ABSOLUTE (0) 
使块按照 32 位对齐,位置为 0
    IMAGE_REL_BASED_HIGH (1) 
16 位必须应用于偏移量所指高字 16 位。
    IMAGE_REL_BASED_LOW (2)  
16 位必须应用于偏移量所指低字 16 位。
    IMAGE_REL_BASED_HIGHLOW (3) 
全部 32 位应用于所有 32 位。 .
    IMAGE_REL_BASED_HIGHADJ (4) 
需要 32 位,高 16 位位于偏移量,低 16 位位于下一个偏移量数组元素,组合为一个带符号数,加上 32 位的一个数,然后加上 8000 然后把高 16 位保存在偏移量的 16 位域内。
    IMAGE_REL_BASED_MIPS_JMPADDR (5)        Unknown
    IMAGE_REL_BASED_SECTION (6)        Unknown
    IMAGE_REL_BASED_REL32 (7)        Unknown

举例:
    0x00004000      (32 bits, starting RVA)
    0x00000010      (32 bits, size of chunk)
    0x3012          (16 bits reloc data)
    0x3080          (16 bits reloc data)
    0x30f6          (16 bits reloc data)
    0x0000          (16 bits reloc data)
    0x00000000      (next chunk's RVA)
    0xff341234
第一块描述重定位起始于 RVA 0x4000 长度 16 字节。因为头用去 8 字节,一个重定位用 2 个字节,总共( 16 8)/2 4 个重定位,第一个重定位被用于 0x4012 ,下一个重定位于 4080, 第三个定位于 0x40f6.  最后一个无用。最后一个结束。
 
九.Pe文件简单解析程序(基于iczelion的pe教程中的asm源代码修改综合)
一共四个文件,拼凑得来。编译环境需要安装 masm32 ,编译方法:修改 bat 文件中的 masm 目录,点击 bat 文件即可。
Bat 文件:设置编译环境,调用 makefile
Makefile 文件:调用编译器、资源编译器、连接器编译 asm rc 文件
Asm 文件: win32asm 代码
Rc 文件:资源文件
 
Bat 文件:
set Masm32Dir=d:/Masm32
set include=%Masm32Dir%/Include;%include%
set lib=%Masm32Dir%/lib;%lib%
set path=%Masm32Dir%/Bin;%Masm32Dir%;%PATH%
set Masm32Dir=
nmake clean
nmake
 
makefile 文件:
NAME=pedump2
$(NAME).exe: $(NAME).obj $(NAME).res
        Link /SUBSYSTEM:WINDOWS /LIBPATH:d:/masm32/lib $(NAME).obj $(NAME).res
$(NAME).res: $(NAME).rc
        rc $(NAME).rc
$(NAME).obj: $(NAME).asm
        ml /c /coff /Cp $(NAME).asm
 
clean:
        del *.obj
        del *.res
        del *.exe
 
rc 文件:
;#include "resource.h"
 
    #define DS_CENTER                  0x0800L
    #define DS_MODALFRAME       0x80L
    #define WS_POPUP            0x80000000L
    #define WS_CAPTION          0x00C00000L
    #define WS_SYSMENU          0x00080000L
   
   
    #define ES_MULTILINE        0x0004L
    #define ES_AUTOVSCROLL      0x0040L
    #define ES_AUTOHSCROLL      0x0080L
    #define ES_WANTRETURN       0x1000L
    #define WS_VSCROLL          0x00200000L
    #define WS_EX_CLIENTEDGE        0x00000200L
 
 
#define IDD_MAINDLG                     101
#define IDR_MAINMENU                    102
#define IDC_EDIT                        1000
#define IDM_OPEN                        40001
#define IDM_EXIT                        40003
 
IDD_MAINDLG DIALOGEX 0, 0, 244, 144
STYLE DS_CENTER | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "PE dump"
MENU IDR_MAINMENU
FONT 8, "MS Sans Serif"
BEGIN
    EDITTEXT        IDC_EDIT,7,7,230,130,ES_MULTILINE | ES_AUTOVSCROLL |
                    ES_AUTOHSCROLL | ES_WANTRETURN | WS_VSCROLL ,WS_EX_CLIENTEDGE
END
 
IDR_MAINMENU MENU DISCARDABLE
BEGIN
    POPUP "&File"
    BEGIN
        MENUITEM "&Open",                       IDM_OPEN
        MENUITEM SEPARATOR
        MENUITEM "E&xit",                       IDM_EXIT
    END
END
 
Asm 文件:
.386
.model flat, stdcall ;32 bit memory model
option casemap :none ;case sensitive
 
include windows.inc
include kernel32.inc
include comdlg32.inc
include user32.inc
 
includelib user32.lib
includelib kernel32.lib
includelib comdlg32.lib
 
IDD_MAINDLG      equ 101
IDC_EDIT         equ 1000
IDM_OPEN         equ 40001
IDM_EXIT             equ 40003
 
s_memcpy                 proto :PTR BYTE,:PTR BYTE,:DWORD
DlgProc             proto :DWORD,:DWORD,:DWORD,:DWORD
ShowFunctions                  proto :DWORD
ShowTheFunctions_inport        proto :DWORD,:DWORD
ShowTheFunctions_export        proto :DWORD,:DWORD
ShowTheDosHeader         proto :DWORD,:DWORD
AppendText              proto :DWORD,:DWORD
 
SEH struct
         PrevLink dd ?             ; the address of the previous seh structure
         CurrentHandler dd ? ; the address of the new exception handler
         SafeOffset dd ?          ; The offset where it's safe to continue execution
         PrevEsp dd ?              ; the old value in esp
         PrevEbp dd ?              ; The old value in ebp
SEH ends
 
WORDtoDWORD macro d, w
         mov ax, w
         movzx eax,ax
         mov d,eax
endm
 
DWORDtoDWORD macro t, f
         push f
         pop t
endm
 
.data
AppName         db "PE dump",0
ofn                   OPENFILENAME <>
FilterString                 db "Executable Files (*.exe, *.dll)",0,"*.exe;*.dll",0
                        db "All Files",0,"*.*",0,0
FileOpenError            db "Cannot open the file for reading",0            
FileOpenMappingError     db "Cannot open the file for memory mapping",0
FileMappingError     db "Cannot map the file into memory",0
NotValidPE                db "This file is not a valid PE",0
CRLF                          db 0Dh,0Ah,0
ImportDescriptor      db 0Dh,0Ah,"================[ IMAGE_IMPORT_DESCRIPTOR ]=============",0
IDTemplate db "OriginalFirstThunk = %lX",0Dh,0Ah
            db "TimeDateStamp = %lX",0Dh,0Ah
            db "ForwarderChain = %lX",0Dh,0Ah
            db "Name = %s",0Dh,0Ah
            db "FirstThunk = %lX",0
NameHeader db 0Dh,0Ah,"Hint         Function",0Dh,0Ah
            db "-----------------------------------------",0
NameTemplate          db "%u     %s",0
OrdinalTemplate db "%u   (ord.)",0
 
 
NoExportTable db "No export information in this file",0
ExportTable db 0Dh,0Ah,"======[ IMAGE_EXPORT_DIRECTORY ]======",0Dh,0Ah
                             db "Name of the module: %s",0Dh,0Ah
                             db "nBase: %lu",0Dh,0Ah
                             db "NumberOfFunctions: %lu",0Dh,0Ah
                             db "NumberOfNames: %lu",0Dh,0Ah
                             db "AddressOfFunctions: %lX",0Dh,0Ah
                             db "AddressOfNames: %lX",0Dh,0Ah
                             db "AddressOfNameOrdinals: %lX",0Dh,0Ah,0
Header db "RVA        Ord. Name",0Dh,0Ah
                   db "----------------------------------------------",0
template db "%lX      %u   %s",0
; 显示 dos header 的信息,似乎有点长
DosHeaderInfor        db 0Dh,0Ah
                   db "===================[dos header]======================",0Dh,0Ah
                   db "IMAGE_DOS_HEADER's size    = 0x%lX",0Dh,0Ah
                   db "IMAGE_DOS_HEADER.e_magic    = %s"        ,0Dh,0Ah
                   db "IMAGE_DOS_HEADER.e_cblp     = 0x%lX",0Dh,0Ah
                   db "IMAGE_DOS_HEADER.e_cp       = 0x%lX",0Dh,0Ah
                   db "IMAGE_DOS_HEADER.e_crlc     = 0x%lX",0Dh,0Ah
                   db "IMAGE_DOS_HEADER.e_cparhdr = 0x%lX",0Dh,0Ah
                   db "IMAGE_DOS_HEADER.e_minalloc = 0x%lX",0Dh,0Ah
                   db "IMAGE_DOS_HEADER.e_maxalloc = 0x%lX",0Dh,0Ah
                   db "IMAGE_DOS_HEADER.e_ss       = 0x%lX",0Dh,0Ah
                   db "IMAGE_DOS_HEADER.e_sp       = 0x%lX",0Dh,0Ah
                   db "IMAGE_DOS_HEADER.e_csum     = 0x%lX",0Dh,0Ah
                   db "IMAGE_DOS_HEADER.e_ip       = 0x%lX",0Dh,0Ah
                   db "IMAGE_DOS_HEADER.e_cs       = 0x%lX",0Dh,0Ah
                   db "IMAGE_DOS_HEADER.e_lfarlc   = 0x%lX",0Dh,0Ah
                   db "IMAGE_DOS_HEADER.e_ovno     = 0x%lX",0Dh,0Ah
                   db "IMAGE_DOS_HEADER.e_oemid    = 0x%lX",0Dh,0Ah
                   db "IMAGE_DOS_HEADER.e_oeminfo = 0x%lX",0Dh,0Ah
                   db "IMAGE_DOS_HEADER.e_lfanew       = 0x%lX",0Dh,0Ah            
                   db "==================[dos header end]==================",0Dh,0Ah,0
 
.data?
buffer db 512 dup(?)
hFile dd ?
hMapping dd ?
pMapping dd ?
ValidPE dd ?
 
.code
start:
         invoke GetModuleHandle,NULL
         invoke DialogBoxParam, eax, IDD_MAINDLG,NULL,addr DlgProc, 0; 显示对话框
         invoke ExitProcess, 0        
        
DlgProc proc hDlg:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD                ; 对话框消息回调函数
         .if uMsg==WM_INITDIALOG
                   invoke SendDlgItemMessage,hDlg,IDC_EDIT,EM_SETLIMITTEXT,0,0
         .elseif uMsg==WM_CLOSE
                   invoke EndDialog,hDlg,0
         .elseif uMsg==WM_COMMAND
                   .if lParam==0
                            mov eax,wParam
                            .if ax==IDM_OPEN
                                     invoke ShowFunctions,hDlg
                            .else ; IDM_EXIT
                                     invoke SendMessage,hDlg,WM_CLOSE,0,0
                            .endif
                   .endif
         .else
                   mov eax,FALSE
                   ret
         .endif
         mov eax,TRUE
         ret
DlgProc endp   
        
SEHHandler proc C uses edx pExcept:DWORD,pFrame:DWORD,pContext:DWORD,pDispatch:DWORD
         mov edx,pFrame                 
         assume edx:ptr SEH
         mov eax,pContext
         assume eax:ptr CONTEXT
         push [edx].SafeOffset
         pop [eax].regEip
         push [edx].PrevEsp
         pop [eax].regEsp
         push [edx].PrevEbp
         pop [eax].regEbp
         mov ValidPE, FALSE
         mov eax,ExceptionContinueExecution
         ret
SEHHandler endp
 
ShowFunctions proc uses edi hDlg:DWORD ; 文件解析
         LOCAL seh:SEH
         mov ofn.lStructSize,SIZEOF ofn
         mov ofn.lpstrFilter, OFFSET FilterString
         mov ofn.lpstrFile, OFFSET buffer
         mov ofn.nMaxFile,512
         mov ofn.Flags, OFN_FILEMUSTEXIST or /
                       OFN_PATHMUSTEXIST or OFN_LONGNAMES or/
                       OFN_EXPLORER or OFN_HIDEREADONLY
         invoke GetOpenFileName, ADDR ofn
         .if eax==TRUE
                   invoke CreateFile, addr buffer, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL
                   .if eax!=INVALID_HANDLE_VALUE
                            mov hFile, eax
                            invoke CreateFileMapping, hFile, NULL, PAGE_READONLY,0,0,0
                            .if eax!=NULL
                                     mov hMapping, eax
                                     invoke MapViewOfFile,hMapping,FILE_MAP_READ,0,0,0
                                     .if eax!=NULL
                                               mov pMapping,eax
                                               assume fs:nothing
                                               push fs:[0]
                                               pop seh.PrevLink
                                               mov seh.CurrentHandler,offset SEHHandler
                                               mov seh.SafeOffset,offset FinalExit
                                               lea eax,seh
                                               mov fs:[0], eax
                                               mov seh.PrevEsp,esp
                                               mov seh.PrevEbp,ebp
                                               mov edi, pMapping
                                               invoke SetDlgItemText,hDlg,IDC_EDIT,0   ; 清空文本框
                                               invoke AppendText,hDlg,addr buffer          ; 显示 pe 文件名
                                               assume edi:ptr IMAGE_DOS_HEADER
                                               .if [edi].e_magic==IMAGE_DOS_SIGNATURE
                                                        invoke ShowTheDosHeader, hDlg, edi;dos header
                                                        add edi, [edi].e_lfanew
                                                        assume edi:ptr IMAGE_NT_HEADERS
                                                        .if [edi].Signature==IMAGE_NT_SIGNATURE
                                                                 mov ValidPE, TRUE
                                                        .else
                                                                 mov ValidPE, FALSE
                                                        .endif
                                               .else
                                                        mov ValidPE,FALSE
                                               .endif
FinalExit:
                                               push seh.PrevLink
                                               pop fs:[0]
                                               .if ValidPE==TRUE
                                                        invoke ShowTheFunctions_inport, hDlg, edi
                                                       
                                                        invoke UnmapViewOfFile, pMapping
                                                        invoke CloseHandle,hMapping
                                                        invoke CloseHandle, hFile
                                                        invoke CreateFile, addr buffer, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL
                                                        .if eax!=INVALID_HANDLE_VALUE
                                                                 mov hFile, eax
                                                                 invoke CreateFileMapping, hFile, NULL, PAGE_READONLY,0,0,0
                                                                 .if eax!=NULL
                                                                           mov hMapping, eax
                                                                           invoke MapViewOfFile,hMapping,FILE_MAP_READ,0,0,0
                                                                           .if eax!=NULL
                                                                                    mov pMapping,eax
                                                                                    assume fs:nothing
                                                                                    push fs:[0]
                                                                                    pop seh.PrevLink
                                                                                    mov seh.CurrentHandler,offset SEHHandler
                                                                                    mov seh.SafeOffset,offset FinalExit
                                                                                    lea eax,seh
                                                                                    mov fs:[0], eax
                                                                                    mov seh.PrevEsp,esp
                                                                                    mov seh.PrevEbp,ebp
                                                                                    mov edi, pMapping
                                                                                    assume edi:ptr IMAGE_DOS_HEADER
                                                                                    add edi, [edi].e_lfanew
                                                                                    assume edi:ptr IMAGE_NT_HEADERS
                                                                                    push seh.PrevLink
                                                                                    pop fs:[0]
                                                                                    invoke ShowTheFunctions_export, hDlg, edi
                                                                           .endif                                             
                                                                 .endif                                             
                                                        .endif                                             
                                               .else
                                                        invoke MessageBox,0, addr NotValidPE, addr AppName,MB_OK+MB_ICONERROR
                                               .endif
                                               invoke UnmapViewOfFile, pMapping
                                     .else
                                               invoke MessageBox, 0, addr FileMappingError, addr AppName,MB_OK+MB_ICONERROR
                                     .endif
                                     invoke CloseHandle,hMapping
                            .else
                                     invoke MessageBox, 0, addr FileOpenMappingError, addr AppName,MB_OK+MB_ICONERROR
                            .endif
                            invoke CloseHandle, hFile
                   .else
                            invoke MessageBox, 0, addr FileOpenError, addr AppName, MB_OK+MB_ICONERROR
                   .endif
         .endif        
         ret
ShowFunctions endp
 
AppendText proc hDlg:DWORD,pText:DWORD        ; edit 框添加文本
         invoke SendDlgItemMessage,hDlg,IDC_EDIT,EM_REPLACESEL,0,pText
         invoke SendDlgItemMessage,hDlg,IDC_EDIT,EM_REPLACESEL,0,addr CRLF
         invoke SendDlgItemMessage,hDlg,IDC_EDIT,EM_SETSEL,-1,0
         ret
AppendText endp
 
RVAToOffset PROC uses edi esi edx ecx pFileMap:DWORD,RVA:DWORD         ;RVA 地址转换
         mov esi,pFileMap
         assume esi:ptr IMAGE_DOS_HEADER
         add esi,[esi].e_lfanew
         assume esi:ptr IMAGE_NT_HEADERS
         mov edi,RVA     ; edi == RVA
         mov edx,esi
         add edx,sizeof IMAGE_NT_HEADERS
         mov cx,[esi].FileHeader.NumberOfSections
         movzx ecx,cx
         assume edx:ptr IMAGE_SECTION_HEADER
         .while ecx>0       ; check all sections
                   .if edi>=[edx].VirtualAddress
                            mov eax,[edx].VirtualAddress
                            add eax,[edx].SizeOfRawData
                            .if edi<eax ; The address is in this section
                                     mov eax,[edx].VirtualAddress
                                     sub edi,eax        ; edi == difference between the specified RVA and the section's RVA
                                     mov eax,[edx].PointerToRawData
                                     add eax,edi        ; eax == file offset
                                     ret
                            .endif
                   .endif
                   add edx,sizeof IMAGE_SECTION_HEADER
                   dec ecx
         .endw
         assume edx:nothing
         assume esi:nothing
         mov eax,edi
         ret
RVAToOffset endp
 
RVAToFileMap PROC uses edi esi edx ecx pFileMap:DWORD,RVA:DWORD
         mov esi,pFileMap
         assume esi:ptr IMAGE_DOS_HEADER
         add esi,[esi].e_lfanew
         assume esi:ptr IMAGE_NT_HEADERS
         mov edi,RVA     ; edi == RVA
         mov edx,esi
         add edx,sizeof IMAGE_NT_HEADERS
         mov cx,[esi].FileHeader.NumberOfSections
         movzx ecx,cx
         assume edx:ptr IMAGE_SECTION_HEADER
         .while ecx>0       ; check all sections
                   .if edi>=[edx].VirtualAddress
                            mov eax,[edx].VirtualAddress
                            add eax,[edx].SizeOfRawData
                            .if edi<eax ; The address is in this section
                                     mov eax,[edx].VirtualAddress
                                     sub edi,eax        ; edi == difference between the specified RVA and the section's RVA
                                     mov eax,[edx].PointerToRawData
                                     add eax,edi        ; eax == file offset
                                     add eax,pFileMap
                                     ret
                            .endif
                   .endif
                   add edx,sizeof IMAGE_SECTION_HEADER
                   dec ecx
         .endw
         assume edx:nothing
         assume esi:nothing
         mov eax,edi
         ret
RVAToFileMap endp
 
ShowTheFunctions_inport proc uses esi ecx ebx hDlg:DWORD, pNTHdr:DWORD ; 解析并显示 IMAGE_IMPORT_DESCRIPTOR
         LOCAL temp[512]:BYTE
         mov edi,pNTHdr
         assume edi:ptr IMAGE_NT_HEADERS
         mov edi, [edi].OptionalHeader.DataDirectory[sizeof IMAGE_DATA_DIRECTORY].VirtualAddress
         invoke RVAToOffset,pMapping,edi
         mov edi,eax
         add edi,pMapping
         assume edi:ptr IMAGE_IMPORT_DESCRIPTOR
         .while !([edi].OriginalFirstThunk==0 && [edi].TimeDateStamp==0 && [edi].ForwarderChain==0 && [edi].Name1==0 && [edi].FirstThunk==0)
                   invoke AppendText,hDlg,addr ImportDescriptor
                   invoke RVAToOffset,pMapping, [edi].Name1
                   mov edx,eax
                   add edx,pMapping
                   invoke      wsprintf, addr temp, addr IDTemplate,[edi].OriginalFirstThunk,[edi].TimeDateStamp,[edi].ForwarderChain,edx,[edi].FirstThunk
                   invoke AppendText,hDlg,addr temp
                   .if [edi].OriginalFirstThunk==0
                            mov esi,[edi].FirstThunk
                   .else
                            mov esi,[edi].OriginalFirstThunk
                   .endif
                   invoke RVAToOffset,pMapping,esi
                   add eax,pMapping
                   mov esi,eax
                   invoke AppendText,hDlg,addr NameHeader
                   .while dword ptr [esi]!=0
                            test dword ptr [esi],IMAGE_ORDINAL_FLAG32
                            jnz ImportByOrdinal
                            invoke RVAToOffset,pMapping,dword ptr [esi]
                            mov edx,eax
                            add edx,pMapping
                            assume edx:ptr IMAGE_IMPORT_BY_NAME
                            mov cx, [edx].Hint
                            movzx ecx,cx
                            invoke wsprintf,addr temp,addr NameTemplate,ecx,addr [edx].Name1
                            jmp ShowTheText
ImportByOrdinal:
                            mov edx,dword ptr [esi]
                            and edx,0FFFFh
                            invoke wsprintf,addr temp,addr OrdinalTemplate,edx
ShowTheText:                    
                            invoke AppendText,hDlg,addr temp
                            add esi,4
                   .endw                                   
                   add edi,sizeof IMAGE_IMPORT_DESCRIPTOR
         .endw
         ret
ShowTheFunctions_inport endp
 
 
ShowTheFunctions_export proc uses esi ecx ebx hDlg:DWORD, pNTHdr:DWORD
         LOCAL temp[512]:BYTE
         LOCAL NumberOfNames:DWORD
         LOCAL Base:DWORD
        
         mov edi,pNTHdr
         assume edi:ptr IMAGE_NT_HEADERS
         mov edi, [edi].OptionalHeader.DataDirectory.VirtualAddress
         .if edi==0 
                   invoke AppendText,hDlg,addr NoExportTable
                   ret
         .endif
         ;invoke SetDlgItemText,hDlg,IDC_EDIT,0
         ;invoke AppendText,hDlg,addr buffer
         invoke RVAToFileMap,pMapping,edi
         mov edi,eax
         assume edi:ptr IMAGE_EXPORT_DIRECTORY
         mov eax,[edi].NumberOfFunctions
         invoke RVAToFileMap, pMapping,[edi].nName
         invoke wsprintf, addr temp,addr ExportTable,eax,[edi].nBase,[edi].NumberOfFunctions,[edi].NumberOfNames,[edi].AddressOfFunctions,[edi].AddressOfNames,[edi].AddressOfNameOrdinals
         invoke AppendText,hDlg,addr temp
         invoke AppendText,hDlg,addr Header
         push [edi].NumberOfNames
         pop NumberOfNames
         push [edi].nBase
         pop Base
         invoke RVAToFileMap,pMapping,[edi].AddressOfNames
         mov esi,eax
         invoke RVAToFileMap,pMapping,[edi].AddressOfNameOrdinals
         mov ebx,eax
         invoke RVAToFileMap,pMapping,[edi].AddressOfFunctions
         mov edi,eax
         .while NumberOfNames>0                            
                   invoke RVAToFileMap,pMapping,dword ptr [esi]
                   mov dx,[ebx]
                   movzx edx,dx
                   mov ecx,edx
                   add ecx,Base
                   shl edx,2
                   add edx,edi
                  invoke wsprintf, addr temp,addr template,dword ptr [edx],ecx,eax
                   invoke AppendText,hDlg,addr temp
                   dec NumberOfNames
                   add esi,4
                   add ebx,2
         .endw
         ret
ShowTheFunctions_export endp
 
ShowTheDosHeader proc uses esi ecx ebx hDlg:DWORD, pDosHdr:DWORD   ; 解析 dos
         LOCAL temp[1024]   :BYTE           ; 显示用缓存  
         LOCAL e_magic[3] :BYTE        ; 魔术数字
         LOCAL e_cblp     :DWORD       ; 文件最后页的字节数        
         LOCAL e_cp       :DWORD       ; 文件页数                  
         LOCAL e_crlc     :DWORD       ; 重定义元素个数            
         LOCAL e_cparhdr :DWORD       ; 头部尺寸,以段落为单位    
         LOCAL e_minalloc :DWORD       ; 所需的最小附加段          
         LOCAL e_maxalloc :DWORD       ; 所需的最大附加段          
         LOCAL e_ss       :DWORD       ; 初始的 SS 值(相对偏移量)  
         LOCAL e_sp       :DWORD       ; 初始的 SP                        
         LOCAL e_csum     :DWORD       ; 校验和                    
         LOCAL e_ip       :DWORD       ; 初始的 IP                
         LOCAL e_cs       :DWORD       ; 初始的 CS 值(相对偏移量)  
         LOCAL e_lfarlc   :DWORD       ; 重分配表文件地址          
         LOCAL e_ovno     :DWORD       ; 覆盖号                    
         ;LOCAL e_res      :DWORD       ; 保留字                            
         LOCAL e_oemid    :DWORD       ;OEM 标识符(相对 e_oeminfo
         LOCAL e_oeminfo :DWORD       ;OEM 信息                   
         ;LOCAL e_res2     :DWORD       ; 保留字  
         LOCAL e_lfanew       :DWORD               ; exe 头部的文件地址  
         LOCAL pByteTemp :ptr BYTE    ; 拷贝字符串用的临时指针
         mov e_magic[2], 0
         mov edi, pDosHdr
         assume edi:ptr IMAGE_DOS_HEADER
         mov pByteTemp, edi
         invoke s_memcpy,addr e_magic, pByteTemp, sizeof IMAGE_DOS_HEADER.e_magic
         WORDtoDWORD e_cblp    ,[edi].e_cblp
         WORDtoDWORD e_cp      ,[edi].e_cp     
         WORDtoDWORD e_crlc    ,[edi].e_crlc   
         WORDtoDWORD e_cparhdr ,[edi].e_cparhdr
         WORDtoDWORD e_minalloc,[edi].e_minalloc
         WORDtoDWORD e_maxalloc,[edi].e_maxalloc
         WORDtoDWORD e_ss      ,[edi].e_ss     
         WORDtoDWORD e_sp      ,[edi].e_sp     
         WORDtoDWORD e_csum    ,[edi].e_csum   
         WORDtoDWORD e_ip      ,[edi].e_ip     
         WORDtoDWORD e_cs      ,[edi].e_cs     
         WORDtoDWORD e_lfarlc ,[edi].e_lfarlc 
         WORDtoDWORD e_ovno    ,[edi].e_ovno   
         WORDtoDWORD e_oemid   ,[edi].e_oemid  
         WORDtoDWORD e_oeminfo ,[edi].e_oeminfo
         DWORDtoDWORD e_lfanew,[edi].e_lfanew
         invoke wsprintf, addr temp, addr DosHeaderInfor,
                                     sizeof IMAGE_DOS_HEADER,
                                     addr e_magic,
                                     e_cblp    ,
                                     e_cp      ,
                                     e_crlc    ,
                                     e_cparhdr ,
                                     e_minalloc,
                                     e_maxalloc,
                                     e_ss      ,
                                     e_sp      ,
                                     e_csum    ,
                                     e_ip      ,
                                     e_cs      ,
                                     e_lfarlc ,
                                     e_ovno    ,
                                     e_oemid   ,
                                     e_oeminfo ,
                                     e_lfanew
 
         invoke AppendText,hDlg,addr temp
         ret
ShowTheDosHeader endp
 
s_memcpy proc uses esi edi ecx dest_str:PTR BYTE,source_str:PTR BYTE,n:DWORD ; 内存拷贝函数
         cld
         mov esi, [source_str]
         mov edi, [dest_str]
         mov ecx, [n]
 
         shr ecx, 2
         rep movsd
 
         mov ecx, [n]
         and ecx, 3
         rep movsb
 
         ret
s_memcpy endp
 
 
; 代码段结束,所有代码放在代码段内
end start

猜你喜欢

转载自blog.csdn.net/jiangqin115/article/details/78624094