PE中涉及的主要的地址主要有:基地址(ImageBase)、虚拟内存地址(Virtual Address)、相对虚拟地址(Relative Virtual Address)、文件偏移地址(File Offset Address)。
基地址是进程在被加载到内存时的内存地址。
虚拟内存地址。当PE文件加载到内存时,操作系统会为每个进程分配独立的4 GB的虚拟空间。在这个空间里定位的地址称为虚拟内存地址(Virtual Address)。虚拟内存地址范围为00000000h~0fffffffh。进程的虚拟地址等于进程的基地址+相对虚拟地址。
相对虚拟地址。PE 文件主要是 DLL 加载到进程虚拟内存的某些位置时,该位置可能已经加载了其他 PE 文件。如果使用虚拟内存就会发生冲突,这时候必须通过重定位(Relocation)到其他位置才行。因为相对基地址的相对地址没有改变,所以可以通过相对虚拟地址进行访问。
文件偏移地址。文件偏移地址是指数据在 PE 文件中的地址,是文件在磁盘上存放时相对于文件开头的偏移。WinHex打开文件所显示的地址就是文件偏移地址。
(1)DOS头
在winnt.h中DOS头的结构如下。
typedef struct_IMAGE_DOS_HEADER{ //DOS.EXE header
WORD e_magic; //Magic number
WORD e_cblp; //Bytes on last page of file
WORD e_cp; //Pages in file
WORD e_crlc; //Relocations
WORD e_cparhdr; //Size of header in paragraphs
WORD e_minalloc; //Minimum extra paragraphs needed
WORD e_maxalloc; //Maximum extra paragraphs needed
WORD e_ss; //Initial(relative)SS value
WORD e_sp; //Initial SP value
WORD e_csum; //Checksum
WORD e_ip; //Initial IP value
WORD e_cs; //Initial(relative)CS value
WORD e_lfarlc; //File address of relocation table
WORD e_ovno; //Overlay number
WORD e_res[4]; //Reserved words
WORD e_oemid; //OEM identifier(for e_oeminfo)
WORD e_oeminfo; //OEM information;e_oemid specific
WORD e_res2[10]; //Reserved words
LONG e_lfanew; //File address of new exe header PE
} IMAGE_DOS_HEADER,*PIMAGE_DOS_HEADER;
DOS头对应的字段(00h~40h)如图3所示。
DOS头大小为64 B,其中最重要的字段为e_magic和e_lfanew。
e_magic:4D5Ah(exe标识“MZ”)。
e_lfanew:000000E8h(PE头相对文件的偏移地址)。
(2)DOS存根
DOS存根的大小并不是固定的。该部分是该程序在DOS系统下运行的指令字节码。对应的字节码(40h~E7h)如图4所示。
DOS头的e_lfanew字段指向NT头,以PE00标识开头,由3部分组成:PE标示、文件头和扩展头。NT头结构下。
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32,*PIMAGE_NT_HEADERS32;
对应的字节码如图5所示(E8h~1E0h)。
文件头包含了PE文件的基本信息。文件头的结构如下。
typedef struct _IMAGE_FILE_HEADER {
WORDMachine;
WORD NumberOfSections;
DWORD TimeDateStamp;
DWORD PointerToSymbolTable;
DWORD NumberOfSymbols;
WORD SizeOfOptionalHeader;
WORD Characteristics;
} IMAGE_FILE_HEADER,*PIMAGE_FILE_HEADER;
结构体中有4个重要成员。
① Machine:本例中值为014Ch。一种CPU对应一种Machine码。下面是在winnt.h中定义的Machine码如下。
#define IMAGE_FILE_MACHINE_UNKNOWN 0
#define IMAGE_FILE_MACHINE_I386 0x014c //Intel 386.
#define IMAGE_FILE_MACHINE_R3000 0x0162 //MIPS little-endian,0x160 big-endian
#define IMAGE_FILE_MACHINE_R4000 0x0166 //MIPS little-endian
#define IMAGE_FILE_MACHINE_R10000 0x0168 //MIPS little-endian
#define IMAGE_FILE_MACHINE_WCEMIPSV2 0x0169 //MIPS little-endian WCE v2
#define IMAGE_FILE_MACHINE_ALPHA 0x0184 //Alpha_AXP
#define IMAGE_FILE_MACHINE_POWERPC 0x01F0 //IBM PowerPC Little-Endian
#define IMAGE_FILE_MACHINE_SH3 0x01a2 //SH3 little-endian
#define IMAGE_FILE_MACHINE_SH3E 0x01a4 //SH3E little-endian
#define IMAGE_FILE_MACHINE_SH4 0x01a6 //SH4 little-endian
#define IMAGE_FILE_MACHINE_ARM 0x01c0 //ARM Little-Endian
#define IMAGE_FILE_MACHINE_THUMB 0x01c2
#define IMAGE_FILE_MACHINE_IA64 0x0200 //Intel 64
#define IMAGE_FILE_MACHINE_MIPS16 0x0266 //MIPS
#define IMAGE_FILE_MACHINE_MIPSFPU 0x0366 //MIPS
#define IMAGE_FILE_MACHINE_MIPSFPU16 0x0466 //MIPS
#define IMAGE_FILE_MACHINE_ALPHA64 0x0284 //ALPHA64
#define IMAGE_FILE_MACHINE_AXP64 IMAGE_FILE_MACHINE_ALPHA64
② NumberofSection:文件中节区的数量。本例中为0005h。该值一定大于0。
③ SizeOfOptionalHeader:该字段指出可选头(OptionalHeader)的大小。本例中为00E0h。
④ Characteristics:该字段表示文件的属性,通过几个值运算得到。本例的010Bh可通过下面的运算得到:00001h+0002h+0008h+0100h。含义为:不包含重定向信息,文件是可执行的,不包含符号信息,此文件运行于32位平台。下面是在winnt.h中定义的Characteristics,如下所示。
#define IMAGE_FILE_RELOCS_STRIPPED 0x0001 //Relocation info stripped from file.
#define IMAGE_FILE_EXECUTABLE_IMAGE 0x0002 //File is executable (i.e.no unresolved externel references).
#define IMAGE_FILE_LINE_NUMS_STRIPPED 0x0004 //Line nunbers stripped from file.
#define IMAGE_FILE_LOCAL_SYMS_STRIPPED 0x0008 //Lo cal symbols stripped from file.
#define IMAGE_FILE_AGGRESIVE_WS_TRIM 0x0010 //Agressively trim working set
#define IMAGE_FILE_LARGE_ADDRESS_AWARE 0x0020 //App can handle>2gb addresses
#define IMAGE_FILE_BYTES_REVERSED_LO 0x0080 //Bytes of machine word are reversed.
#define IMAGE_FILE_32BIT_MACHINE 0x0100 //32 bit word machine.
#define IMAGE_FILE_DEBUG_STRIPPED 0x0200 //Debugging info stripped from file in.DBG file
#define IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP 0x0400 //If Image is on removable media,copy and run from the swap file.
#define IMAGE_FILE_NET_RUN_FROM_SWAP 0x0800 //If Image is on Net,copy and run from the swap file.
#define IMAGE_FILE_SYSTEM 0x1000 //System File.
#define IMAGE_FILE_DLL 0x2000 //File is a DLL.
#define IMAGE_FILE_UP_SYSTEM_ONLY 0x4000 //File should only be run on a UP machine
#define IMAGE_FILE_BYTES_REVERSED_HI 0x8000 //Bytes of machine word are reversed.
在节区头中描述了后面各个节区的属性,其中包括前面提到的.text、.rdata、.data和.rsrc等。在IMAGE_SECTION_HEADER结构中详细描述了节区头各个字段,结构如下。
#define IMAGE_SIZEOF_SHORT_NAME 8
typedef struct _IMAGE_SECTION_HEADER {
BYTE Name[IMAGE_SIZEOF_SHORT_NAME];
union {
DWORD Physi calAddress;
DWORD VirtualSize;
} Misc;
DWORD VirtualAddress;
DWORD SizeOfRawData;
DWORD PointerToRawData;
DWORD PointerToRelocations;
DWORD PointerToLinenumbers;
WORD NumberOfRelocations;
WORD NumberOfLinenumbers;
DWORD Characteristics;
} IMAGE_SECTION_HEADER,*PIMAGE_SECTION_HEADER;
#define IMAGE_SIZEOF_SECTION_HEADER 40
重要字段说明如下。
Name:长度为8的ASCII节区名(比如.text)。
VirtualSize:未对齐处理前节区实际使用的大小。
VirtualAddress:载入内存后节区的RVA。
SizeOfRawData:节区在磁盘中的大小,是FileAlignment的整数倍。
PointerToRawData:节区在磁盘中的起始位置。
Characterstics:节区属性。
HelloWorld.exe的节区头如图6所示。
(3)导入地址表(IAT,Import Address Table)
下面介绍PE文件RVA和FOA的相互转换。在这个部分会经常用到RVA和FOA的转换,步骤如下。
① 找到RVA所在节区
② 求出该节区VA和PointerToRawData
③ FOA=RVA– VA+PointerToRawData
IMAGE_IMPORT_DESCRIPTOR
IMAGE_IMPORT_DESCRIPTOR 说明 PE 文件要导入的库。要导入几个就会有几个IMAGE_IMPORT_DESCRIPTOR结构体。最后以一个空的结构体结束,代码如下。
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
__C89_NAMELESS union {
DWORD Characteristics;
DWORD OriginalFirstThunk;
} DUMMYUNIONNAME;
DWORD TimeDateStamp;
DWORD ForwarderChain;
DWORD Name;
DWORD FirstThunk;
} IMAGE_IMPORT_DESCRIPTOR;
typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;
重要字段说明如下。
OriginalFirstThunk:INT的RVA地址。
Name:导入映像文件的名字的RVA。
FirstThunk:IAT地址的相对地址。
以HelloWorld.exe为例进行分析,下面是各节区头的RVA和PointToRawData。
.text:RVA为1000,PointToRawData为400。
.rdata:RVA为2000,PointToRawData为C00。
.data:RVA为3000,PointToRawData为1200。
.rsrc:RVA为4000,PointToRawData为1400。
.reloc:RVA为5000,PointToRawData为1600。
RVA=2234,通过计算得到 FOA=E34,大小为3C。图8阴影区域为 IMAGE_IMPORT_DESCRIPTIOR结构体数组。
第一个IMAGE_IMPORT_DESCRIPTIOR结构体成员结构如下。
INT:RVA为22B4,文件偏移为EB4。通过INT可以准确求出相关函数的起始地址,尾部以NULL填充,如图9所示。
图9 INT求起始地址
TimeDataStamp:00000000
ForwarderChain:00000000
Name:RVA为2322,文件偏移为F22,指向导入函数的库文件名称。第一个导入的是MSVCR100.dll,如图10所示。
图10 导入函数的库文件
IAT:RVA为2044,文件偏移为C44。与IAT类似,尾部用NULL填充,如图11所示。
图11 IAT
(4)导出地址表(EAT,Export Address Table)
PE文件可以为其他程序提供函数调用。通过EAT可以准确地求出相应库文件提供函数的起始地址,EAT的信息保存在IMAGE_EXPORT_DIRECTORY结构体中,结构体代码如下所示。
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics;
DWORD TimeDateStamp;
WORDMajorVersion;
WORD MinorVersion;
DWORD Name;
DWORD Base;
DWORD NumberOfFunctions;
DWORD NumberOfNames;
DWORD AddressOfFunctions;
DWORD AddressOfNames;
DWORD AddressOfNameOrdinals;
} IMAGE_EXPORT_DIRECTORY,*PIMAGE_EXPORT_DIRECTORY;
重要字段说明如下。
NumberOfFunction:导出函数的数量。
NumberOfName:函数名字的数量。
AddressOfFunction:EAT的数量。
AddressOfName:ENT的数量。
以Windows系统著名的kernel32.dll为例进行简单分析。
Kernel32.dll的个节区头的RVA和PointerToRawData分别如下。
.text:RVA为10000,PointToRawData为10000.。
.data:RVA为E0000,PointToRawData为E0000。
.rsrc:RVA为F0000,PointToRawData为F0000。
.reloc:RVA为100000,PointToRawData为100000。
图12阴影区域为IMAGE_EXPORT_DIRECTORY结构数组。
图12 IMAGE_EXPORT_DIRECTORY结构数组
Characteristic:00000000
TimeDateStamp:50E64345
MajorVersion:0000
MinorVersion:0000
Name:RVA为000C2CD0
Base:00000001
NumberOfFuctions:00000554
NumberOfNames:00000554
AddressOfFunction:RVA为000BF788,文件偏移为000BF788
AddressOfName:RVA为000C0CD8,文件偏移为000C0CD8
AddressOfNameOrdinary:RVA为000C2228,文件偏移为000C2228
原文始发于微信公众号(计算机与网络安全):软件逆向之PE文件
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论