一: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 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
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
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
参考资料:
iczelion
的
pe
教程(主要的资料)
网络资料
候捷的
windows95 system programming secrets