软件逆向之PE文件

admin 2023年12月17日09:20:55评论16 views字数 8297阅读27分39秒阅读模式
PE(Portable Executable)文件是可移植的可执行文件,常见的如 EXE、DLL、OCX、SYS、COM等都是PE文件。凡是以二进制形式被系统加载执行的文件都是PE文件。
其实PE文件是一种文件格式,所以对PE文件的透彻理解是逆向工程初学者的基本功。下面以HelloWorld.exe为例进行说明,用WinHex打开HelloWorld程序,如图1所示。

软件逆向之PE文件

图1 用WinHex打开HelloWorld.exe
1. 基本结构
通过观察可以发现,程序是以MZ标识开头的。下面是DOS加载模块标识字段“This is program cannot be run in DOS mode.”。这两个字段基本是不变的,基本上Windows程序的开头都是这两个字段。接下来是 PE 开头的文件头。再后面是.text(代码)、.data(数据)、.rsrc(资源)等组成的区段表。区段表主要作用是让PE加载器快速地加载对应的区段如图2所示。

软件逆向之PE文件

图2 区段表
2. 地址

PE中涉及的主要的地址主要有:基地址(ImageBase)、虚拟内存地址(Virtual Address)、相对虚拟地址(Relative Virtual Address)、文件偏移地址(File Offset Address)。

基地址是进程在被加载到内存时的内存地址。

虚拟内存地址。当PE文件加载到内存时,操作系统会为每个进程分配独立的4 GB的虚拟空间。在这个空间里定位的地址称为虚拟内存地址(Virtual Address)。虚拟内存地址范围为00000000h~0fffffffh。进程的虚拟地址等于进程的基地址+相对虚拟地址。

相对虚拟地址。PE 文件主要是 DLL 加载到进程虚拟内存的某些位置时,该位置可能已经加载了其他 PE 文件。如果使用虚拟内存就会发生冲突,这时候必须通过重定位(Relocation)到其他位置才行。因为相对基地址的相对地址没有改变,所以可以通过相对虚拟地址进行访问。

文件偏移地址。文件偏移地址是指数据在 PE 文件中的地址,是文件在磁盘上存放时相对于文件开头的偏移。WinHex打开文件所显示的地址就是文件偏移地址。

3. PE文件结构
PE文件头结构的定义都在winnt.h中。

(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所示。

软件逆向之PE文件

图3 DOS头字段

DOS头大小为64 B,其中最重要的字段为e_magic和e_lfanew。

e_magic:4D5Ah(exe标识“MZ”)。

e_lfanew:000000E8h(PE头相对文件的偏移地址)。

(2)DOS存根

DOS存根的大小并不是固定的。该部分是该程序在DOS系统下运行的指令字节码。对应的字节码(40h~E7h)如图4所示。

软件逆向之PE文件

图4 DOS存根

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文件

图5 NT头
NT头结构体的大小为 F8h,主要信息在文件头和扩展头中。下面分别介绍文件头和可选头。

文件头包含了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所示。

软件逆向之PE文件

图6 HelloWorld.exe节区头

(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。

首 先 需 要 知 道 IMAGE_IMPORT_DESCRIPTIOR 结 构 体 的 位 置。IMAGE_OPTIONAL_HEADER 结构中DataDirectory[1].VirtualAddress 就是结构体的起始位置,如图7所示。

软件逆向之PE文件

图7 起始位置

RVA=2234,通过计算得到 FOA=E34,大小为3C。图8阴影区域为 IMAGE_IMPORT_DESCRIPTIOR结构体数组。

第一个IMAGE_IMPORT_DESCRIPTIOR结构体成员结构如下。

软件逆向之PE文件

图8 IMAGE_IMPORT_DESCRIPTIOR结构体数组

INT:RVA为22B4,文件偏移为EB4。通过INT可以准确求出相关函数的起始地址,尾部以NULL填充,如图9所示。

软件逆向之PE文件

图9 INT求起始地址

TimeDataStamp:00000000

ForwarderChain:00000000

Name:RVA为2322,文件偏移为F22,指向导入函数的库文件名称。第一个导入的是MSVCR100.dll,如图10所示。

软件逆向之PE文件

图10 导入函数的库文件

IAT:RVA为2044,文件偏移为C44。与IAT类似,尾部用NULL填充,如图11所示。

软件逆向之PE文件

图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结构数组。

软件逆向之PE文件

图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文件

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年12月17日09:20:55
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   软件逆向之PE文件http://cn-sec.com/archives/2309907.html

发表评论

匿名网友 填写信息