PE文件是Windows系统下的可执行文件格式, 是在COFF(Common Object File Format, 通用对象文件格式)基础上制作, 目的是提高不同操作系统下的移植性, 不过最后只是用在Windows系列的系统中.
PE特指PE32, 32位可执行文件, 不包括PE64(也不存在PE64这种说法), 64位是PE+或称为PE32+.
分类
种类 | 主扩展名 |
---|---|
可执行系列 | EXE, SCR |
库系列 | DLL, OCX, CPL, DRV |
驱动程序系列 | SYS, VXD |
对象文件系列 | OBJ |
(严格来说, OBJ之外所有类型的PE文件都是可执行的, DLL, SYS文件虽不能直接运行, 但是可以在调试器, 服务等方式下执行
基本结构
PE分为PE头和PE体两个部分
- PE头: 由DOS头(DOS header)和节区头(section header)两个部分组成
- PE体: 余下的部分
文件中保存是用偏移offset表示, 内存中用VA(虚拟地址 Virtual Address)
文件内容一般划分为代码.text, 数据.data, 资源.rsrc三节
但是根据编译选项不同, 节区的名称, 大小, 个数, 数据都是不同的, 但是都是按同类划分到不同的节区
节区
- 尾部存在NULL填充(NULL padding)
- 起始位置在最小单位的倍数位置上, 空白区域用NULL填充.
虚拟地址
- VA绝对虚拟地址
- RVA相对虚拟地址
- VA = ImageBase(基准地址) + RVA
- 一般文件保存的是RVA, 可以通过重定位访问信息
PE头
主要由结构体组成, 所以相当于需要掌握各个结构体
DOS头
typedef struct _IMAGE_DOS_HEADER{
WORD e_magic;
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];
LONG e_lfanew;
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
- e_magic是DOS签名
- e_lfanew指示NT头偏移
DOS存根
DOS头的下方, 可以没有, 是可选项.
DOS存根中若包含16进制指令, 32位环境不会识别这些指令, 但识别出PE头就可以运行32位代码; 在DOS模式下可以识别为DOS EXE文件, 并运行16位代码.
NT头
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
- Signature 50450000h(“PE\0\0”)
- File Header 文件头
typedef struct _IMAGE_FILE_HEADER {
WORD Machine;
WORD NumberOfSections;
DWORD TimeDateStamp;
DWORD PointerToSymbolTable;
DWORD NumberOfSymbols;
WORD SizeOfOptionalHeader;
WORD Characteristics;
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
(1) Machine 每种架构的CPU都会唯一的Machine码对应
(2) NumberOfSections 节区数量
(3) SizeOfOptionalHeader 给装载器指示可选头的大小, 区分IMAGE_OPTIONAL_HEADER32还是IMAGE_OPTIONAL_HEADER64结构体
(4) Characteristics 标识文件属性(额外需要注意0002h, 2000h分别表示exe和DLL)
- Optional Header 可选头
typedef struct _IMAGE_OPTIONAL_HEADER {
WORD Magic;
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
DWORD SizeOfCode;
DWORD SizeOfInitializedData;
DWORD SizeOfUninitializedData;
DWORD AddressOfEntryPoint;
DWORD BaseOfCode;
DWORD BaseOfData;
DWORD ImageBase;
DWORD SectionAlignment;
DWORD FileAlignment;
WORD MajorOperatingSystemVersion;
WORD MinorOperatingSystemVersion;
WORD MajorImageVersion;
WORD MinorImageVersion;
WORD MajorSubsystemVersion;
WORD MinorSubsystemVersion;
DWORD Win32VersionValue;
DWORD SizeOfImage;
DWORD SizeOfHeaders;
DWORD CheckSum;
WORD Subsystem;
WORD DllCharacteristics;
DWORD SizeOfStackReserve;
DWORD SizeOfStackCommit;
DWORD SizeOfHeapReserve;
DWORD SizeOfHeapCommit;
DWORD LoaderFlags;
DWORD NumberOfRvaAndSizes;
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
(1) Magic 32–10B, 64–20B
(2) AddressOfEntryPoint 指出代码起始执行地址
(3) ImageBase 指出优先装入的代码地址,EXE, DLL载入0~7FFFFFFF地址, SYS载入80000000~FFFFFFFF. EXE的ImagegBase一般是00400000, DLL是10000000
(4) SectionAlignment, FileAlignment指定最小单位(S是内存, F是磁盘)
(5) SizeOfImage 指定虚拟内存中的空间大小
(6) SizeOfHeaders 指定PE头大小.(注意PE头大小也符合FileAlignment的整数倍)
(7) Subsystem 区分系统驱动和普通可执行文件
(8) NumberOfRvaAndSizes 指定DataDirectory的数组个数
(9) DataDirectory 特定定义的数组[0…F]每位表示不同的含义.
节区头
内存属性访问权限
类别 | 访问权限 |
---|---|
code | 执行, 读权限 |
data | 非执行, 读写权限 |
resource | 非执行, 读权限 |
重要参数
额外注意, 节区的name可以填入任何值(所以当data节区name为.code时不要被迷惑, name不代表实际作用), 结尾可以不是NULL
(映像Image特指加载到内存中的PE文件, 因为磁盘中的PE和内存中的PE具有不同形态, 内存中的PE是按节区起始地址和大小加载的, 用术语映像来区分二者
RVA to RAW
关于PE文件从磁盘到内存的映射
文件偏移RAW = RVA - VirtualAddress + PointerToRawData
- VirtualAddress是第x个节区的首地址
- 当VirtualSize比SizeOfRawData大时, 会出现无法定义的RAW值.
- 计算的要点就是注意节区之间的对应, 减掉VA再加文件节区起始地址其实就是计算相对偏移, 就是同一个位置相对内存中节区起始地址和相对文件中节区起始地址的偏移. 这个偏移相等才有的方程(等式).
IAT
IAT(Import Address Table)导入地址表.
与Windows的核心进程, 内存, DLL结构相关, 是Windows中最重要的基础.
DLL
Dynamic Link Library动态链接库
- 程序中不包含库
- 内存映射技术使DLL在多个进程*享
- 更新库只需替换DLL, 简便易行
两种链接方式
- 显示链接, 程序使用DLL时才加载, 用完后释放
- 隐式链接, 程序加载时一同加载相应的DLL, 全部结束后才释放
(IAT即与隐式链接有关
IMAGE_IMPORT_DESCRIPTOR
记录PE文件需要导入的库文件, 一个库对应一个IMAGE_IMPORT_DESCRIPTOR结构体, 通常是形成以NULL结尾的结构体数组, 记录需要的所有库文件.
PE装载器将导入函数输入IAT的流程
IMAGE_IMPORT_DESCRIPTOR数组存在PE体(注意不是PE头), PE头存IMAGE_IMPORT_DESCRIPTOR的位置信息, 即IMAGE_OPTIONAL_HEADER32.DataDirectory[1].VirtualAddress.
IMAGE_IMPORT_DESCRIPTOR数组也会被称为IMPORT Directory Table
EAT
通过EAT从相应库中导出函数的起始地址. 让不同程序可以调用库文件提供的函数.
IMAGE_EXPORT_DIRECTORY
重要参数
获取地址的过程(关键)
通过GetProcAddress()引用EAT获取指定API的地址
- 利用AddressOfNames转到函数名称数组
- 函数名称中存储着字符串地址. 通过比较字符串, 找到指定的的函数名称(数组索引为name_index).
- 利用AddressOfNameOrdinals, 转到ordinal数组.
- 在ordinal数组中通过name_index索引到相应的ordinal值.
- 利用AddressOfFunctions转到EAT.
- 在函数地址数组中求得ordinal作为数组索引, 获得指定函数起始地址.
注意IAT和EAT的区别(之后补充… …
IAT和EAT是压缩器, 反调试, DLL注入, API钩取等众多中高级逆向技术的核心知识点, 务必完整掌握.可以说是练成高深内功的一道重要的玄关, 必须认真对待
必要: 自己写一个PE viewer, 可以深入地理解PE文件格式
独门秘籍
http://blogs.securiteam.com/index.php/archives/675
http://www.phreedom.org/solar/code/tinype/
分析以上特殊PE文件, 可以提高内力
第二个似乎404了…