学习成为人体 PE Parser

看日常分享: AwesomeCS FB看技术文章: AwesomeCS Wiki

笔者最近在阅读 aaaddress1 的大作: Windows APT Warfare:恶意程式前线战术指南,因为书中围绕着 PE File 进行,脑容量太小的我又一直忘记 PE 的档案结构,最后决定还是认真的把它研究一遍并写成笔记。
PE (Portable Executable) 是一种用于可执行文件、目标文件和动态连结库的文件格式,主要使用在 32 位和 64 位的 Windows 作业系统上。

有点像是 Linux 作业系统中的 elf 档。

DOS Header

在一串连续的记忆体中, DOS Header 一定会是记忆体中的首段内容, DOS Header 中的几项资讯会是比较重要的:

e_magic
e_magic 帮助我们辨认该 PE 档案是否合法,一般来说,它应该永远等于 MZ 字串。
如果以 C/C++ 检查 PE File ,可以这样做:

#include <windows.h>// ...void parser(char* filePtr){    IMAGE_DOS_HEADER* dosHdr = (IMAGE_DOS_HEADER *)filePtr;    if(dosHdr->e_magic != IMAGE_DOS_SIGNATURE){        return;    }    // ...}// ...

e_lfanew
观察 e_lfanew 之前,必须先了解什么是 RVA (Relative Virtual Address), RVA 是程式入口点的参考位址,举例来说:
如果程式被放入虚拟地址(Virtual address, VA)的 0x01000000 处,且 RVA 位于 0x102D6C 处,那么程式在记忆体中的实际入口就会是 VA + RVA:

  0x01000000+ 0x00102D6C= 0x01102D6C

e_lfanew 其实就是指向了 NT Headers 的 RVA ,换个角度思考,我们将前面的 dosHdr 加上偏移量 (在这边指 RVA),就可以获得 NT Headers 的起始位址:

IMAGE_NT_HEADERS* ntHdrs = (IMAGE_NT_HEADERS *)((size_t)dosHdr + dosHdr->e_lfanew);

NT Headers

透过读取 DOS Header 获得 NT Headers 的起始位址以后,我们就可以对 PE 档案做更进一步的检验。
NT Headers 共包含了两大结构,分别是 File Header 以及 Optional Header 。

File Header


参考上图,在 File Header 结构中有多个属性,每个属性代表的资讯如下:

Machine
纪录 PE 档案所存放的机械码属于哪一种指令集架构:

x86ARMx64

NumberOfSections
一个 PE File 通常会有好几段块状区域, NumberofSections 纪录了 PE 档案的区段数量。

这个参数对我们撰写程式解析 PE File 非常有帮助,至于那些块状区段存了什么,晚点会提到。

TimeDateStamp
纪录程式编译时间的时间戳。

PointerToSymbolTable
符号表地址,用于除错,一般为 0 。

NumberOfSymbols
如果符号表存在,这边会记录符号数量。

Characteristics
纪录了整个 PE 的属性,包含:

ExecutableInfo of redirection32-bit or notDLL modules

Optional Header

Source

Optional Header 的中文称可选段,实际上,如果要让 PE 能够顺利地被执行程式装载器使用, Optional Header 为必备的。

补充:
Optional Header 不存在于 Object File (COFF),而是在编译的连结阶段才会由连结器补上。

参考上图, Optional Header 包含了很多参数,下面针对重要的参数作介绍:

Address of entry point
程式码编译后,程式的入口点,也就代表当 Program 被作业系统载入时, Process 会从这边开始执行。

一般来说,入口点会指向 .text section 的函式开头。

ImageBase
记录了 PE 档案 mapping 到记忆体上的预设位址,通常为 0x400000 或是 0x800000

SizeOfImage
记录了当程式处于动态执行阶段需要多少空间才能存放整个 Image 。

Section alignment
动态的区域对齐, 32-bit 的环境下预设大小为 0x1000 bytes 。

File alignment
静态的区域对齐, 32-bit 的环境下预设大小为 0x200 bytes 。

假设有不足 0x200 bytes 的资料要放进块状区段,块状区段的大小为 0x200 bytes ,如果资料多于预设大小,块状区段的大小则为 0x400 bytes 。

Size of headers
DOS Header + NT Headers + Section Headers 的大小。

Data directory

Export tableImport tableRessource tableException tableImport Address table

Section Headers

Section Headers 的位址紧随在 NT Headers 的后方,使用 C/C++ 可以轻鬆的获得其位址:

IMAGE_SECTION_HEADER* sectHdr = (IMAGE_SECTION_HEADER *)((size_t)ntHdrs + sizeof(*ntHdrs));

至于 Section Headers 的本体到底是什么呢?它其实就是一个存放块状区段资讯的阵列:

for (size_t i = 0; i < ntHdrs->FileHeader.NumberOfSections; i++){printf("\t#%.2x - %8s - %.8x - %.8x \n", i, sectHdr[i].Name, sectHdr[i].PointerToRawData, sectHdr[i].SizeOfRawData);        }

每一块存放块状区段资讯的空间都会有以下属性:

PointerToRawData
该区段处存在静态档案的偏移量。

SizeofRawData
该区段的实际大小。

VirtualAddress
相较于映像基址的相对偏移量。

VirtualSize
显示该区段需要被分配多少动态空间。

Characteristics
纪录该区段是否可读、可写、可执行。

常见区段

.text
用于存放程式码。

.data
用来宣告已初始化的资料与常数。

.bss
存放已宣告但尚未初始化的变数。

.rdata
存放唯读资料。

.idata
存放引入的函式与资料,这些资料会在 Process 建立时,由执行程式装载器负责填充。

.edata
存放用来导出给其他程式使用的函式与资料。

.rsrc
用于记录程式使用了哪些资源。

reloc
重定位,当 PE 程式载入失败,会以此段作为参考进行调整。

总结

了解 PE File 的基本结构后,我们就可以将恶意 shellcode 添加至目标档案再将 Entry point 指向恶意程式区段的 Virtual Address 做些坏坏的事(?)

本文章介绍的内容大概只有 Windows APT Warfare:恶意程式前线战术指南整本书的皮毛,如果想学更多就去下单买一本 Windows APT Warfare:恶意程式前线战术指南吧!

References

Portable ExecutableWindows PE 档案结构及其载入机制Windows-APT-Warfare RepoWindows PE文件各个节(Section)分析

关于作者: 网站小编

码农网专注IT技术教程资源分享平台,学习资源下载网站,58码农网包含计算机技术、网站程序源码下载、编程技术论坛、互联网资源下载等产品服务,提供原创、优质、完整内容的专业码农交流分享平台。

热门文章