学习笔记:pe文件格式、pe部分信息解析程序代码(win32asm)

本文详细介绍了PE(Portable Executable)文件的整体结构,包括DOS MZ header、DOS stub、PE header、Section table等内容。通过解析DOS MZ header中的e_lfanew字段找到PE header,接着解释了PE header的Signature、FileHeader和OptionalHeader等关键信息,以及PE文件装载的主要步骤。此外,还探讨了DOS stub的作用。内容适合对PE文件格式感兴趣的读者和Windows程序开发者。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 
一: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 1PE文件的真正内容划分成块,称之为sections(节)。每节是一块拥有共同属性的数据,比如代码/数据、读/写等。当PE装载器映射节内容时,它会检查相关节属性并置对应内存块为指定属性。
Section n
}
装载一个PE文件的主要步骤:
1.当PE文件被执行,PE装载器检查 DOS MZ header 里的 PE header 偏移量。如果找到,则跳转到 PE header
2PE装载器检查 PE header 的有效性。如果有效,就跳转到PE header的尾部。
3.紧跟 PE header 的是节表。PE装载器读取其中的节信息,并采用文件映射方法将这些节映射到内存,同时付上节表里指定的节属性。
4PE文件映射入内存后,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, 00hPE/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文件的优先装载地址。比如,如果该值是400000hPE装载器将尝试把文件装到虚拟地址空间的400000h处。字眼"优先"表示若该地址区域已被其他模块占用,那PE装载器会选用其他空闲地址。
 SectionAlignment              DWORD      ?// 段加载后在内存中的对齐方式。内存中节对齐的粒度。例如,如果该值是4096 (1000h),那么每节的起始地址必须是4096的倍数。若第一节从401000h开始且大小是10个字节,则下一节必定从402000h开始,即使401000h402000h之间还有很多空间没被使用。
 FileAlignment                 DWORD      ?// 段在文件中的对齐方式。文件中节对齐的粒度。例如,如果该值是(200h),,那么每节的起始地址必须是512的倍数。若第一节从文件偏移量200h开始且大小是10个字节,则下一节必定位于偏移量400h: 即使偏移量5121024之间还有很多空间没被使用/定义。
 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子系统,所以只有23是合法的值
 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介绍(来自iczelionpe教程):
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 结构。
 
iczelionpe教程中的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 结构数组的RVA
  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,表示模块可能仅仅通过序数引出。如果模块根本不引出任何函数/符号,那么数据目录中引出表的RVA0
 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.并行遍历AddressOfNamesAddressOfNameOrdinals指向的数组匹配名字。如果在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内的stacksizeheapsize 
.
版权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(),分别是091015。其值定义在"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位数据大小Size32'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'0IMAGE_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个字节,总共(168)/24个重定位,第一个重定位被用于0x4012,下一个重定位于4080,第三个定位于0x40f6. 最后一个无用。最后一个结束。
 
九.Pe文件简单解析程序(基于iczelion的pe教程中的asm源代码修改综合)
一共四个文件,拼凑得来。编译环境需要安装masm32,编译方法:修改bat文件中的masm目录,点击bat文件即可。
Bat文件:设置编译环境,调用makefile
Makefile文件:调用编译器、资源编译器、连接器编译asmrc文件
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
 
参考资料:
iczelionpe教程(主要的资料)
网络资料
候捷的windows95 system programming secrets
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值