admin管理员组文章数量:1122851
本博客主要参考了以下两篇博客中的代码并加以整理:
52pj_zmblix的讲解
csdn某大佬的讲解
PE
PE即Portable Executable,是MS发明的32位操作系统的主要程序文件格式。理论上说,PE文件向下兼容了dos16位操作系统的程序文件。
PE文件结构关键不在于了解,在于熟谙。只有对PE文件头到了不用刻意去计算也能凭直觉找到所需要信息的地步才算入门了。然后才能进行手工打补丁等一系列的实验。仅仅了解了PE的结构,和没了解过并没有本质的区别。
本文只讲PE,不讲操作系统。
按主流的说法,PE文件可以分解成下面几个部分:
PE文件 | |
---|---|
Dos头 | 可能是出于安全目的,实际上对大多数人是多余的 |
1.PE签名 | |
NT头 | 2.PE文件头 |
3.PE可选头 | |
节表 | 常见的有.idata节,.text节,.data节等九大节 |
Dos头结构很简单,如下所示,一般32位PE文件的Dos头只会输出一句话,如果你使用dos虚拟机去运行一个PE程序,你会在终端看到:
C:\>hello.exe
This program can not run in dos mode
Dos头
typedef struct _IMAGE_DOS_HEADER { // DOS .EXE header
WORD e_magic; // 00 Magic number
WORD e_cblp; // 02 最后一页的字节数
WORD e_cp; // 04 页数
WORD e_crlc; // 06 重定位项数量
WORD e_cparhdr; // 08 文件头以节计算的长度
WORD e_minalloc; // 0a 需要的最小段数,1段=10h
WORD e_maxalloc; // 0c 需要的最大段数
WORD e_ss; // 0e SS
WORD e_sp; // 10 SP
WORD e_csum; // 12 Check num
WORD e_ip; // 14 IP
WORD e_cs; // 16 CS
WORD e_lfarlc; // 18 重定位表地址
WORD e_ovno; // 1a 覆盖号,必须为0
WORD e_res[4]; // 1c 保留字
WORD e_oemid; // 24 OEM identifier (for e_oeminfo)
WORD e_oeminfo; // 26 OEM information; e_oemid specific
WORD e_res2[10]; // 28 保留字
LONG e_lfanew; // 3c NT头入口地址
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
接着我们看NT头:
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature; //00 PE签名50 45 00 00
IMAGE_FILE_HEADER FileHeader; //映像文件头
IMAGE_OPTIONAL_HEADER32 OptionalHeader; //映像可选头
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
PE文件加载到内存中称为映像(image)
typedef struct _IMAGE_FILE_HEADER {
WORD Machine; //04 运行平台,如386是014c,64是0200
WORD NumberOfSections; //06 节数
DWORD TimeDateStamp; //08 创建时间
DWORD PointerToSymbolTable; //0c COFF符号表偏移
DWORD NumberOfSymbols; //10 符号表数量
WORD SizeOfOptionalHeader; //14 可选头大小
WORD Characteristics; //16 文件属性,如.dll为2000
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
可选头只是名字这么叫,实际上关键部分一项都不能少,可选头类型是由运行平台决定的
以下是一个386平台的可选头
typedef struct _IMAGE_OPTIONAL_HEADER {
WORD Magic; //18 可选头类型,386是010b
BYTE MajorLinkerVersion; //1a 最高链接库版本号
BYTE MinorLinkerVersion; //1b 最低链接库版本号
DWORD SizeOfCode; //1c 代码段总大小
DWORD SizeOfInitializedData; //20 .idata大小
DWORD SizeOfUninitializedData; //24 .data大小
DWORD AddressOfEntryPoint; //28 程序入口RVA
DWORD BaseOfCode; //2c 代码起始RVA
DWORD BaseOfData; //30 数据起始RVA
DWORD ImageBase; //34 内存映像基地址,一般是400000
DWORD SectionAlignment; //38 节对齐
DWORD FileAlignment; //3c 文件内对齐
WORD MajorOperatingSystemVersion; //40 最高操作系统版本号
WORD MinorOperatingSystemVersion; //42 最低操作系统版本号
WORD MajorImageVersion; //44 最高映像版本号
WORD MinorImageVersion; //46 最低映像版本号
WORD MajorSubsystemVersion; //48 最高子系统版本号
WORD MinorSubsystemVersion; //4a 最低子系统版本号
DWORD Win32VersionValue; //4c 保留,必须为0
DWORD SizeOfImage; //50 内存映像总大小
DWORD SizeOfHeaders; //54 所有文件头的总大小
DWORD CheckSum; //58 映像文件校验和
WORD Subsystem; //5c 子系统,0为Unknown,1为不需要
WORD DllCharacteristics; //5e Dll文件属性
DWORD SizeOfStackReserve; //60 线程栈保留内存
DWORD SizeOfStackCommit; //64 线程栈初始内存
DWORD SizeOfHeapReserve; //68 进程堆保留内存
DWORD SizeOfHeapCommit; //6c 进程堆初始内存
DWORD LoaderFlags; //70 保留,必须为0
DWORD NumberOfRvaAndSizes; //74 数据目录的项数
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
//78 从78开始为数据目录的具体项
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
数据目录表项的定义如下:
typedefstruct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress; //RVA
DWORD Size; //大小
} IMAGE_DATA_DIRECTORY,*PIMAGE_DATA_DIRECTORY;
VirtualAddress是一个RVA,Size是其大小。数据目录一共有16个,分别是:
#define IMAGE_DIRECTORY_ENTRY_EXPORT 0 //78 输出表
#define IMAGE_DIRECTORY_ENTRY_IMPORT 1 //80 输入表
#define IMAGE_DIRECTORY_ENTRY_RESOURCE 2 //88 资源表
#define IMAGE_DIRECTORY_ENTRY_EXCEPTION 3 //90 异常表
#define IMAGE_DIRECTORY_ENTRY_SECURITY 4 //98 安全表
#define IMAGE_DIRECTORY_ENTRY_BASERELOC 5 //a0 重定位表
#define IMAGE_DIRECTORY_ENTRY_DEBUG 6 //a8 调试表
// IMAGE_DIRECTORY_ENTRY_COPYRIGHT 7 // (X86 usage) 版权
#define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE 7 //b0 架构数据
#define IMAGE_DIRECTORY_ENTRY_GLOBALPTR 8 //b8 全局指针的RVA
#define IMAGE_DIRECTORY_ENTRY_TLS 9 //c0 TLS表
#define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 10 //c8 载入配置表
#define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT 11 //d0 绑定导入表
#define IMAGE_DIRECTORY_ENTRY_IAT 12 //d8 导入地址表
#define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT 13 //e0 延迟加载表
#define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14 //e8 COM运行时描述符目录
// IMAGE_DIRECTORY_ENTRY_RESERVE 15 //f0 保留
节表(Section table)又叫块表,中存放了文件各个节的描述信息,每个节的描述大小为28h,节表开始于PE+f8的位置,只要求节表中每个节的名称是唯一的,只是为了便于分辨,这些名字并没有实际意义,.code节也可以叫.data,反之亦然。其结构定义如下:
typedef struct_IMAGE_SECTION_HEADER {
BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; //00节表名称,一般以.开头
union {
DWORD PhysicalAddress; //物理地址
DWORD VirtualSize; //节在内存中的大小,是alignment的整数倍
} Misc; //08 Misc 一般是VisualSize
DWORD VirtualAddress; //0c RVA,一般第一个节的RVA总是1000
DWORD SizeOfRawData; //10 节在文件中的大小,
DWORD PointerToRawData; //14 节在文件中的偏移地址
DWORD PointerToRelocations; //18 重定位表地址,只在obj中有意义
DWORD PointerToLinenumbers; //1c 行号表(含调试信息)地址
WORD NumberOfRelocations; //20 重定位项数,只在obj中有意义
WORD NumberOfLinenumbers; //22 行号数目
DWORD Characteristics; //24 节的属性
} IMAGE_SECTION_HEADER,*PIMAGE_SECTION_HEADER;
一般一个节的名称不会超过8字节,如果超过8字节,最后就没有0字符了,这个节会被与它前面的一个带有$的节被链接器在载入时合并。
/*一些常见的节属性:*/
0x00000020 //代码段 (.text)
0x00000040 //已初始化数据段 (.data)
0x00000080 //未初始化数据段 (.bss)
0x04000000 //该段数据不能被缓存
0x08000000 //该段不能被分页
0x10000000 //共享段
0x20000000 //可执行段(.text)
0x40000000 //可读段
0x80000000 //可写段
输入表
输入表的结构:
typedef struct_IMAGE_IMPORT_DESCRIPTOR {
_ANONYMOUS_UNION union{
DWORD Characteristics;
DWORD OriginalFirstThunk;
} DUMMYUNIONNAME; //00 指向API名字表
DWORD TimeDateStamp; //04h 如果有绑定的话,这里会存有一个时间戳
//一般设成0xFFFFFFFF表示绑定
DWORD ForwarderChain; //08h 传递器链表
DWORD Name; //0Ch 指向dll名字表
DWORD FirstThunk; //10h 指向API地址表
}IMAGE_IMPORT_DESCRIPTOR,*PIMAGE_IMPORT_DESCRIPTOR;
输入地址表项的结构
typedef struct_IMAGE_THUNK_DATA32 {
union {
DWORD ForwarderString;
DWORD Function;
DWORD Ordinal;
DWORD AddressOfData;
} u1;
}IMAGE_THUNK_DATA32,*PIMAGE_THUNK_DATA32;
如上所示,输入表项是函数名的RVA地址。如果IMAGE_THUNK_DATA32的最高位为1时,表示函数以序号方式输入,若最高位为零,那么函数以函数名方式输入。
API名字表和地址表都以一个全为0的IMAGE_THUNK_DATA32项作为结尾标志。
typedef struct_IMAGE_IMPORT_BY_NAME {
WORD Hint; //如果最高位为1,则为API在库内的序号
BYTE Name[1]; //长度不固定,以'\0'结尾
} IMAGE_IMPORT_BY_NAME,*PIMAGE_IMPORT_BY_NAME;
输出表
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics; //00 一般为0
DWORD TimeDateStamp; //04 如果有库绑定,这里会有一个时间戳
WORD MajorVersion; //08 最高**版本(实际上没用,一般设为0)
WORD MinorVersion; //0a 最低**版本(同上)
DWORD Name; //0c dll名
DWORD Base; //10 API序号的基数
DWORD NumberOfFunctions; //14 总的API个数
DWORD NumberOfNames; //18 按名字导出的API个数
DWORD AddressOfFunctions; //1c API地址表
DWORD AddressOfNames; //20 API名字表
DWORD AddressOfNameOrdinals; //24 API序号表
}IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
比起输入表,多了一个地址表,这三个表都是RVA地址,实际上地址表的内容是对应序号的入口指针,查地址表到最后还是要查序号表(所以为什么有这个地址表呢?)
重定位表
重定位表的结构非常简单,它包含一堆重定位项的RVA,而第一项是重定位基址。:
struct_IMAGE_BASE_RELOCATION
{
DWORD VirtualAddress; //00 重定位数据开始的RVA地址
DWORD SizeOfBlock; //04 重定位块的长度
WORD TypeOffset; //08 重定位项位数组
}IMAGE_BASE_RELOCATION;
重定位数组的每项有4个字节,第一个字节是重定位类型,后三个字节是重定位的RVA。32位系统的的重定位过程和16位其实差别不大,主要区别在于前者使用相对虚拟地址(RVA)。
版权声明:本文标题:PE文件结构整理 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/biancheng/1726378826a1084551.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论