学习成为人体 PE Parser

笔者最近在阅读 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 档案所存放的机械码属於哪一种指令集架构:

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

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

  • TimeDateStamp
    纪录程序编译时间的时间戳。

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

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

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

    • Executable
    • Info of redirection
    • 32-bit or not
    • DLL 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 table
    • Import table
    • Ressource table
    • Exception table
    • Import 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


<<:  Day 5 (CSS)

>>:  年龄为多少秒

[Day 14] - 初探永丰银行线上收款API - 丰收款 - HASH ID计算(1)

接续昨天 根据规格书,我们要用永丰提供的四组hash值拼出hash id 可以看出,hash id会...

Day 27 - Detect Capital

大家好,我是毛毛。ヾ(´∀ ˋ)ノ 废话不多说开始今天的解题Day~ 520. Detect Cap...

CLOUDWAYS主机限时6折优惠码,只到2021/12/1

优惠码BFCM2021 优惠时间:只到2021/12/01 折扣内容:首四个月6折(适用於所有方案)...

[Day 17] 我的资料哪有这麽平衡!第二季 (class weights)

前言 走过了资料分析、演算法选择後, 我们得知了有些可以改善模型的方向: 解决资料不平衡(Now) ...

D3JsDay06这包什麽馅,原来是折线—绘制折线图

这次相比长条图使用多一点的资料,阵列如下并且一样先宣告svg变数绘制一个宽800高450的画布 co...