PE框架基础学习
PE结构
虚拟地址(VA): 在一个程序运行起来的时候,会被加载到内存中,并且每个进程都有自己的4GB,这个4GB当中的某个位置叫做虚拟地址,也就是内存中的地址,由物理地址映射过来的,4GB的空间,并没有全部被用到。
基地址( Imagebase ): 磁盘中的文件加载到内存当中的时候可以加载到任意位置,而这个位置就是程序的基址。EXE默认的加载基址是400000h,DLL文件默认基址是10000000h。需要注意的是基地址不是程序的入口点。
相对虚拟地址(RVA):。RVA是在内存中相对与载入地址(基地址)的偏移量,所以你可以发现前三个概念的关系 : 虚拟地址(VA)= 基地址+ 相对虚拟地址(RVA)
文件偏移地址(FOA):当PE文件储存在某个磁盘当中的时候,某个数据的位置相对于文件头的偏移量。
入口点(OEP):首先明确一个概念就是OEP是一个RVA,,然后使用 OEP + Imagebase == 入口点的VA,通常情况下,OEP指向的不是main函数。
VOffset:节起始地址对于ImageBase的偏移量
ROffset:该节起始地址对于文件的偏移量
现在让你求某个虚拟地址VA,对应的文件偏移地址 fRVA
fRVA-ROffset=RVA-VOffset
fRVA=RVA-VOffset+ROffset
(RVA=VA-ImageBase)
RVA 和文件偏移的转换
PE 指纹
文件开头DOS标志“MZ” -> 在0x3c(e_lfanew)处可找到“PE”头地址
由此可确定该文件为PE文件
DOS MZ文件头
IMAGE_DOS_HEADER STRUCT
{
WORD e_magic // MZ(4Dh 5Ah) DOS可执行文件标记
WORD e_cblp
WORD e_cp
WORD e_crlc
WORD e_cparhdr
WORD e_minalloc
WORD e_maxalloc
WORD e_ss
WORD e_sp
WORD e_csum
WORD e_ip
WORD e_cs
WORD e_lfarlc
WORD e_ovno
WORD e_res[4]
WORD e_oemid
WORD e_oeminfo
WORD e_res2[10]
DWORD e_lfanew // RVA 指向PE文件头
} IMAGE_DOS_HEADER ENDS
IMAGE_DOS_HEADER通常有0x40(64)字节
其中重要的成员为第一个成员和最后一个成员,即PE指纹,其他部分修改后不影响程序运行
PE 头
x32 :
typedef struct IMAGE_NT_HEADERS { DWORD Signature //PE标识 IMAGE_FILE_HEADER FileHeader //标准PE头 IMAGE_OPTIONAL_HEADER32 OptionalHeader //扩展PE头 } IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
x64:
typedef struct IMAGE_NT_HEADERS { DWORD Signature //PE标识 IMAGE_FILE_HEADER FileHeader //标准PE头 IMAGE_OPTIONAL_HEADER64 OptionalHeader //扩展PE头 } IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;
IMAGE_FILE_HEADER标准PE头
通常有0x14(20)字节
struct IMAGE_ FILE_ HEADER {
WORD Machine ;
WORD NumberOfSections ;
DWORD TineDatestanp;
DWORD Pointer ToSymbolTable;
DWORD Number0fSymbols ;
WORD SizeOfOptionalHeader ; //OptionHeader成员大小
WORD Characteristics;
}
IMAGE_OPTIONAL_HEADER32扩展PE头
(其大小通常在标准PE头中指出,即上面结构体中的SizeOfOptionalHeader
在32位系统下通常有0xE0(224)字节
在64位系统下通常为0xF0(240)字节
typedef struct _IMAGE_OPTIONAL_HEADER
{
//
// Standard fields.
//
WORD Magic; // 标志字, ROM 映像(0107h),普通可执行文件(010Bh)
BYTE MajorLinkerVersion; // 链接程序的主版本号
BYTE MinorLinkerVersion; // 链接程序的次版本号
DWORD SizeOfCode; // 所有含代码的节的总大小
DWORD SizeOfInitializedData; // 所有含已初始化数据的节的总大小
DWORD SizeOfUninitializedData; // 所有含未初始化数据的节的大小
DWORD AddressOfEntryPoint; // 程序执行入口RVA
DWORD BaseOfCode; // 代码的区块的起始RVA
DWORD BaseOfData; // 数据的区块的起始RVA
//
// NT additional fields. 以下是属于NT结构增加的领域。
//
DWORD ImageBase; // 程序的首选装载地址
DWORD SectionAlignment; // 内存中的区块的对齐大小
DWORD FileAlignment; // 磁盘中文件中的区块的对齐大小
WORD MajorOperatingSystemVersion; // 要求操作系统最低版本号的主版本号
WORD MinorOperatingSystemVersion; // 要求操作系统最低版本号的副版本号
WORD MajorImageVersion; // 可运行于操作系统的主版本号
WORD MinorImageVersion; // 可运行于操作系统的次版本号
WORD MajorSubsystemVersion; // 要求最低子系统版本的主版本号
WORD MinorSubsystemVersion; // 要求最低子系统版本的次版本号
DWORD Win32VersionValue; // 一般为0
DWORD SizeOfImage; // 映像装入内存后的总尺寸
DWORD SizeOfHeaders; // 所有头 + 区块表的尺寸大小
DWORD CheckSum; // 映像的校检和
WORD Subsystem; // 可执行文件期望的子系统
WORD DllCharacteristics; // DllMain()函数何时被调用,默认为 0
DWORD SizeOfStackReserve; // 初始化时的栈大小
DWORD SizeOfStackCommit; // 初始化时实际提交的栈大小
DWORD SizeOfHeapReserve; // 初始化时保留的堆大小
DWORD SizeOfHeapCommit; // 初始化时实际提交的堆大小
DWORD LoaderFlags;
DWORD NumberOfRvaAndSizes;
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
// 数据目录表
}
RVA与块对齐
其中SizeOfHeaders(位于标准PE头IMAGE_FILE_HEADER后0x20字节后)是FileAlignment的整数倍
图片中第一个方块中为内存中的区块的对齐大小0x1000
第二个方块为磁盘文件中的区块的对齐大小0x200
节表/块表
IMAGE_SECTION_HEADER 各个块通常有0x28(40)字节