安全矩阵

 找回密码
 立即注册
搜索
查看: 2463|回复: 0

网络安全攻防:软件逆向之PE文件

[复制链接]

991

主题

1063

帖子

4315

积分

论坛元老

Rank: 8Rank: 8

积分
4315
发表于 2021-3-28 08:25:26 | 显示全部楼层 |阅读模式
原文链接:网络安全攻防:软件逆向之PE文件

PE(Portable Executable)文件是可移植的可执行文件,常见的如 EXE、DLL、OCX、SYS、COM等都是PE文件。凡是以二进制形式被系统加载执行的文件都是PE文件。


其实PE文件是一种文件格式,所以对PE文件的透彻理解是逆向工程初学者的基本功。下面以HelloWorld.exe为例进行说明,用WinHex打开HelloWorld程序,如图1所示。

图1  用WinHex打开HelloWorld.exe

1. 基本结构

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

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

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

图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)。

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

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

图7  起始位置

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

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

图8  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

微信公众号:计算机与网络安全


回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2024-11-28 23:30 , Processed in 0.013619 second(s), 18 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表