系统安全及免杀(8) PE的前世今生(2)-浅谈PE文件结构(续)

本文所载网络安全技术仅为技术研究与学习之用,严禁用于非法攻击、侵入他人系统等违法违规行为。所有内容均需遵守国家相关法律法规,滥用所产生的一切法律责任,由行为人自行承担,与本文作者、发布者无关。


书接上回,本质上是对上一节的一些补充(

所以标题只加了一个续,由于一点强迫症的因素章节排号新开了一号,因此有希望成为最短的一集(

鄙人技术浅陋,经网上四处观看浏览总结得此文,如有不足希望师傅们多包容⊙﹏⊙

下面正文开始喵

知识点轰炸

欸捧油 一些小的点集中起来直接一个的给

小端序

正文还没真开始(

先说结论:小端序(Little Endian)是:低字节数据存放在内存的低地址处,高字节数据存放在内存的高地址处

那有小,就得有大吧,那大端序(Big Endian):高字节存低地址,低字节存高地址

从直觉上大端序才是更符合咋们人类的阅读习惯的,for example:

一串数据 0x0000357DF5,在内存中用不同方式存:

  • 大端序:00 00 35 7D F5
  • 小端序:F5 7D 35 00 00

区别就在于16进制数的位数是:从右往左,为从低位到高位,而内存是从第一个0偏移量的基地址开始最低,往后越来越大,所以小端序的规律虽然是低的在低,高的在高,但实际上是一个个反着存放的

在 x86 (32位) 和 x64 (64位) 中内存都统一使用小端序存放数据

偏移量和基地址

小端序的最后提到了这么个概念,稍微展开说一下

其实对照到物理里面的概念就瞬间好懂了,就是初始位置和位移

引入:RVA(Relative Virtual Address,相对虚拟地址):相对于映像基址的偏移地址,其中映像基址,就是PE文件被载入内存后的起始地址,也就是说实际地址 = 相对虚拟地址 + 映像基址

就像是末状态坐标等于初状态+位移

虚拟地址

上一段提到的一个概念,感觉一层层的都有新东西能说(

虚拟地址和物理地址的区别就是,虚拟地址是程序认为自己所在的地址,虚拟地址技术让程序认为自己占有了一片连续的内存地址空间,基地址都可以从 0x00000000 开始,而实际上真正的物理内存,同时被许许多多的进程动态的占用着

再往深处讲就是内存的页表、缺页处理还有 MMU 了,操作系统相关的教科书或者课程都有讲,也是大学中的操作系统课程必含内容,此处不再多赘述(


书接上集,PE 文件结构从上到下依次是:DOS Header -> DOS Stub -> Rich Header -> NT Headers -> Section Table -> Section1 Section2 … SectionN,这回咱给您说说这个 NT Headers 展开的一些内容

NT Headers

上文说到这段包含3部分:

  1. Image File Signature(镜像文件签名):一个4字节的内容,标识这是一个PE文件
  2. Image File Header(镜像文件头):是标准的 COFF 格式,本质是一个结构体,里面包含PE文件相关信息
  3. Image Optional Header(镜像可选头)这部分叫可选头,是因为像 .obj 这样的对象文件没有这部分,但像 exe 这样的可执行文件必须得有这部分,它可以说是 PE 文件结构中最重要的部分

NT Headers 和 DOS Headers 一样都是结构体:

typedef struct _IMAGE_NT_HEADERS64 {
    DWORD Signature;
    IMAGE_FILE_HEADER FileHeader;
    IMAGE_OPTIONAL_HEADER64 OptionalHeader;
} IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;

typedef struct _IMAGE_NT_HEADERS {
    DWORD Signature;
    IMAGE_FILE_HEADER FileHeader;
    IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

一样,也是在 winnt.h 进行实现,后缀有 64 的是针对64位的,没有的针对 32 位的

看一下里面的 3 个属性,就是对应上文说的三个部分,第一个是无符号整型,后两个是结构体类型

Image File Signature

结构体中的变量名是Signature,DWORD 类型(Windows 编程涉及到的内容) 表示它是一个 4 字节无符号整型

属性的值是一个固定值:0x00004550,上文说到 x86 架构使用小端序,所以在内存中的顺序为 50 45 00 00,转换成ASCII是P E \0 \0

Image File Header

IMAGE_FILE_HEADER类型的结构体,结构体定义就在 winnt.h:

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;
  • Machine:这字段表示 PE 是运行在 x86 还是 x64上: 值为 0x014C 表示 x86,值为 0x8664 表示 x64
  • NumberOfSections:表示节的数量(节的数量也就是节头的数量)
  • TimeDateStamp:一个unix时间戳,标注文件被创建的时间
  • PointerToSymbolTable 和 NumberOfSymbols:当有 COFF 符号表的时候,PointerToSymbolTable 表示相对 COFF 符号表的文件偏移,NumberOfSymbols 表示符号表中项的数量,但是由于不鼓励使用 COFF 调试信息,所以没有 COFF 符号表,这两项通常为0
  • SizeOfOptionalHeader:表示PE Optional Header的大小
  • Characteristics:文件属性的标志,文件可以是可执行文件,也可以是系统文件等等

Image Optional Header

上文说过,之所以叫可选头,是因为像 .obj 这样的对象文件没有这部分,但像 exe 这样的可执行文件必须得有这部分,它可以说是 PE 文件结构中最重要的部分

这个就比较特殊了,没有固定的大小,从上段的 Image File Header 那个 结构体里面的 SizeOfOptionalHeader 去获取大小

然后是定义:

typedef struct _IMAGE_OPTIONAL_HEADER {
    //
    // Standard fields.
    //
    WORD    Magic;
    BYTE    MajorLinkerVersion;
    BYTE    MinorLinkerVersion;
    DWORD   SizeOfCode;
    DWORD   SizeOfInitializedData;
    DWORD   SizeOfUninitializedData;
    DWORD   AddressOfEntryPoint;
    DWORD   BaseOfCode;
    DWORD   BaseOfData;

    //
    // NT additional fields.
    //
    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;



typedef struct _IMAGE_OPTIONAL_HEADER64 {
    WORD        Magic;
    BYTE        MajorLinkerVersion;
    BYTE        MinorLinkerVersion;
    DWORD       SizeOfCode;
    DWORD       SizeOfInitializedData;
    DWORD       SizeOfUninitializedData;
    DWORD       AddressOfEntryPoint;
    DWORD       BaseOfCode;
    ULONGLONG   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;
    ULONGLONG   SizeOfStackReserve;
    ULONGLONG   SizeOfStackCommit;
    ULONGLONG   SizeOfHeapReserve;
    ULONGLONG   SizeOfHeapCommit;
    DWORD       LoaderFlags;
    DWORD       NumberOfRvaAndSizes;
    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER64, *PIMAGE_OPTIONAL_HEADER64;

从最开始的 NT headers 的结构体属性中就能看出来,32 位和 64 位版本的不同就是这个的不同

结构体的前 8 个属性:

WORD    Magic;
    BYTE    MajorLinkerVersion;
    BYTE    MinorLinkerVersion;
    DWORD   SizeOfCode;
    DWORD   SizeOfInitializedData;
    DWORD   SizeOfUninitializedData;
    DWORD   AddressOfEntryPoint;
    DWORD   BaseOfCode;
    DWORD   BaseOfData;

COFF 文件格式的标准属性,其他的是专门给 PE 的

上文说过,32位 PE 和 64 位 PE 差别就在于 Optional Header:

  1. 个数:32位 OptionalHeader 有 31 个属性,64位版本的有 30 个成员,32位的里面中多出来的一个是 BaseOfData,DWORD类型,表示相对于 data 节的 RVA
  2. 一些属性的数据类型:有 5 个在 32 位 OptionalHeader 中是 DWORD 类型,64位版本中就是 ULONGLONG 类型,分别是:ImageBase、SizeOfStackReserve、SizeOfStackCommit、SizeOfHeapReserve、SizeOfHeapCommit

上面说的 data 节就是 .data 节

还有 .text .rdata .data…… 等等很多节,这些节的熵值对于免杀和 AV 还有 EDR 的初步静态检测具有一定意义,后面的其他集里面细说

与 PE 结构对应的就是 Section Table 后面的 Section 们,.data 就是其中一个


具体每个属性的含义……我实在懒得自己组织语言了,从网络上摘抄了一篇分析这部分结构体的文章的段落:

  • Magic:这个字段表示当前的PE文件是一个32位PE,还是一个64位PE,值为0x10B表示这是一个32位PE,值为0x20B表示这是一个64位PE,IMAGE_FILE_HEADER.Machine是被Windows PE Loader忽略的
  • MajorLinkerVersion和MinorLinkerVersion:这两个字段表示链接器的主版本号和次版本号
  • SizeOfCode:这个字段表示text段(代码段)的大小,如果有多个代码段,表示全部代码段大小的总和
  • SizeOfInitializedData:这个字段表示data段(初始化数据段)的大小,如果有多个初始化数据段,表示全部大小的总和
  • SizeOfUninitializedData:这个字段表示bss段(未初始化数据段)的大小,如果有多个未初始化数据段,表示全部大小的总和
  • AddressOfEntryPoint:程序被载入内存时入口点的RVA,当载入PE文件时,入口点是程序起始地址的RVA,当载入驱动程序时,入口点是初始化函数的RVA,对于DLL来说,入口点是可选的,当没有入口时,这个字段的值为0
  • BaseOfCode:PE文件被载入内存后,text段起始地址的RVA
  • BaseOfData:只存在32位PE中,PE文件被载入内存后,data段起始地址的RVA
  • ImageBase:这个字段表示PE文件被载入内存后第一个字节的地址,这个地址必须是64K的整数倍,实际上由于KASLR等内存保护措施,几乎不会使用这个地址作为PE的内存基址,PE Loader会选择一个未使用的内存地址作为PE的内存基址,然后开始地址修复,修改所有使用这个地址作为常量的地址,这个过程也叫重定位,这些常量位于一个特殊的节,称为reloc节(重定位节)
  • SectionAlignment:这个字段表示内存中节对齐的最小单位,默认值是特定架构(x86或x86-64)的内存页大小,微软规定它的值不能小于FileAlignment
  • FileAlignment:这个字段表示文件中节对齐的最小单位,不足的填充0,微软规定它的值应该是512到64 * 1024之间2的整次幂,如果SectionAlignment的值小于内存页的大小,那么FileAlignment的值和SectionAlignment的值必须一致
  • MajorOperatingSystemVersion、MinorOperatingSystemVersion、MajorImageVersion、MinorImageVersion、MajorSubsystemVersion、MinorSubsystemVersion:MajorOperatingSystemVersion和MinorOperatingSystemVersion指定了所需操作系统的主版本号和次版本号,后四个字段指定了PE文件的主版本号和次版本号,子系统的主版本号和次版本号
  • Win32VersionValue:一个保留字段,值为0
  • SizeOfImage:这个字段表示PE文件的大小(包含全部的头),单位是字节,当PE文件被载入到内存时,PE Loader会使用这个字段的值,所以值需要是SectionAlignment的整数倍
  • SizeOfHeaders:这个字段表示全部头的大小,包括从DOS Header的第一个字节到Section Header的最后一个字节
  • CheckSum:PE文件的校验值,当PE文件被载入到内存时用到
  • Subsystem:这个字段指定了运行当前PE文件需要的Windows子系统(如果有的话),完整的值可以参考:https://learn.microsoft.com/en-us/windows/win32/debug/pe-format
  • DLLCharacteristics:这个字段指定了PE文件的一些特征,比如是否兼容DEP机制、再比如能否在运行时进行重定位,完整的值可以参考:https://learn.microsoft.com/en-us/windows/win32/debug/pe-format
  • SizeOfStackReserve、SizeOfStackCommit、SizeOfHeapReserve、SizeOfHeapCommit:这几个字段指定了要保留的栈大小、要提交的栈大小、要保留的堆大小、要提交的堆大小
  • LoaderFlags:一个保留的字段,值为0
  • NumberOfRvaAndSizes:DataDirectory数组的大小
  • DataDirectory:IMAGE_DATA_DIRECTORY数组

好!这一节结束,没想到还是写了那么多hhh

预告

下一集大概率讲一讲导入表,以 IAT 为主要内容的一部分,对于免杀的研究来说,针对导入表的相关操作具有很重要的意义喵

收工睡大觉!

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇