Windows中的可执行文件为PE (Portable Executable)格式。从其名称可以看出 这种格式是架构无关的。
实际上PE格式就是对COFF格式的扩展。
微软关于PE格式的说明列在了 https://docs.microsoft.com/en-us/windows/win32/debug/pe-format 。
下面我们结合前面关于COFF的介绍 LLVM LLD COFF格式分析,仍然以小用例的方式来剖析PE格式。

用例
$ cat form.c

int g_init_var = 0x1234;
int g_uninit_var;extern int Shared;extern int foo(int I, char *str);int main(){static int s_init_var = 0x1235;static int s_uninit_var;int loc_init_var = 1;int loc_uninit_var;int ret = foo(g_init_var + g_uninit_var + s_init_var + s_uninit_var + loc_init_var + loc_uninit_var + Shared, "xiang");return ret;
}

$ cat foo.c

int Shared = 16;
int foo(int I, char *str){if (str)return I;return 0;
}

为了简化起见这两个小用例都没调用标准库函数。
我们先生成目标文件(COFF):form.o foo.o

$clang form.c foo.c -c

链接之前我们先看看各个目标文件的段信息
xiangzh1@xiangzh1-mobl1 MINGW64 /c/Work/tests
$ objdump -h form.o

form.o:     file format pe-x86-64Sections:
Idx Name          Size      VMA               LMA               File off  Algn0 .text         00000053  0000000000000000  0000000000000000  0000012c  2**4CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE1 .data         00000008  0000000000000000  0000000000000000  000001c5  2**2CONTENTS, ALLOC, LOAD, DATA2 .bss          00000008  0000000000000000  0000000000000000  00000000  2**2ALLOC3 .xdata        00000008  0000000000000000  0000000000000000  000001cd  2**2CONTENTS, ALLOC, LOAD, READONLY, DATA4 .rdata        00000006  0000000000000000  0000000000000000  000001d5  2**0CONTENTS, ALLOC, LOAD, READONLY, DATA, LINK_ONCE_DISCARD (COMDAT ??_C@_05NGABEPOA@xiang?$AA@ 10)5 .pdata        0000000c  0000000000000000  0000000000000000  000001db  2**2CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA6 .llvm_addrsig 00000006  0000000000000000  0000000000000000  00000205  2**0CONTENTS, READONLY, EXCLUDE, NOREAD

xiangzh1@xiangzh1-mobl1 MINGW64 /c/Work/tests
$ objdump -h foo.o

foo.o:     file format pe-x86-64Sections:
Idx Name          Size      VMA               LMA               File off  Algn0 .text         00000037  0000000000000000  0000000000000000  00000104  2**4CONTENTS, ALLOC, LOAD, READONLY, CODE1 .data         00000004  0000000000000000  0000000000000000  0000013b  2**2CONTENTS, ALLOC, LOAD, DATA2 .bss          00000000  0000000000000000  0000000000000000  00000000  2**2ALLOC3 .xdata        00000008  0000000000000000  0000000000000000  0000013f  2**2CONTENTS, ALLOC, LOAD, READONLY, DATA4 .pdata        0000000c  0000000000000000  0000000000000000  00000147  2**2CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA5 .llvm_addrsig 00000000  0000000000000000  0000000000000000  00000171  2**0CONTENTS, READONLY, EXCLUDE, NOREAD

然后在链接成PE文件时,我们也通过指定入口函数为main来规避其它文件的介入。
$ lld-link -entry:main form.o foo.o -out:a.exe
$ objdump -h a.exe

a.exe:     file format pei-x86-64Sections:
Idx Name          Size      VMA               LMA               File off  Algn0 .text         00000097  0000000140001000  0000000140001000  00000400  2**4CONTENTS, ALLOC, LOAD, READONLY, CODE1 .rdata        00000018  0000000140002000  0000000140002000  00000600  2**4CONTENTS, ALLOC, LOAD, READONLY, DATA2 .data         00000014  0000000140003000  0000000140003000  00000800  2**4CONTENTS, ALLOC, LOAD, DATA3 .pdata        00000018  0000000140004000  0000000140004000  00000a00  2**2CONTENTS, ALLOC, LOAD, READONLY, DATA

链接后
我们发现PE文件段的数量减少了很多,之前的bss, xdata, llvm_addrsig 段都不见了。
pdata段视乎看起来是前面两个目标文件的pdata段累加 (0x18 = 0x0c + 0x0c)。
从size上看其它段好像并不是简单的累加,我们后面在接受静态链接的时候再仔细分析。
这里我们先关注 PE 格式本身。

查看段内容
用llvm-objdump查看段内容 ( -s Alias for --full-contents Display the content of each section)
$ llvm-objdump -s a.exe

a.exe:  file format coff-x86-64
Contents of section .text:140001000 4883ec38 c7442434 00000000 c7442430  H..8.D$4.....D$0140001010 01000000 8b0de61f 0000030d f01f0000  ................140001020 030dde1f 0000030d e01f0000 034c2430  .............L$0140001030 034c242c 030dce1f 0000488d 15bf0f00  .L$,......H.....140001040 00e81a00 00008944 24288b44 24284883  .......D$(.D$(H.140001050 c438c3cc cccccccc cccccccc cccccccc  .8..............
// 上半部分几乎来自form.o,可以看出有些地方有变化,应该是重定位或位置更新。
// form.o: file format coff-x86-64
// Contents of section .text:
//  0000 4883ec38 c7442434 00000000 c7442430  H..8.D$4.....D$0
//  0010 01000000 8b0d0000 0000030d 00000000  ................
//  0020 030d0000 0000030d 00000000 034c2430  .............L$0
//  0030 034c242c 030d0000 0000488d 15000000  .L$,......H.....
//  0040 00e80000 00008944 24288b44 24284883  .......D$(.D$(H.
//  0050 c438c3                               .8.140001060 4883ec18 48895424 08894c24 0448837c  H...H.T$..L$.H.|140001070 2408000f 840d0000 008b4424 04894424  $.........D$..D$140001080 14e90800 0000c744 24140000 00008b44  .......D$......D140001090 24144883 c418c3                      $.H....
下半部分来自foo.o,一点变化没有。
// foo.o:  file format coff-x86-64
// Contents of section .text:
//  0000 4883ec18 48895424 08894c24 0448837c  H...H.T$..L$.H.|
//  0010 2408000f 840d0000 008b4424 04894424  $.........D$..D$
//  0020 14e90800 0000c744 24140000 00008b44  .......D$......D
//  0030 24144883 c418c3// 这里我们来看下text段 size 的关系
// 我们发现 0x97 (a.exe) - [0x53(form.o) + 0x37(foo.o)] = 13
// 而从地址 140001053 开始正好多了13个字节的 “0xcc” 。
// 为什么会多出13个cc呢?
// 因为function foo有个16字节对齐要求。为了满足对齐,这里填充了13个cc。Contents of section .rdata:140002000 7869616e 67000000 01040100 04620000  xiang........b..140002010 01040100 04220000                    ....."..// form.o: file format coff-x86-64
// Contents of section .xdata:
//  0000 01040100 04620000                    .....b..
// Contents of section .rdata:
//  0000 7869616e 6700                        xiang.// foo.o:  file format coff-x86-64
// Contents of section .xdata:
//  0000 01040100 04220000                    ....."..// 通过仔细对比我们发现 链接后 的rdata段 正好是 目标文件 rdata段和xdata段的合并:
// rdata(a.exe) = rdata(form.o) + xdata(form.o) + xdata(foo.o)Contents of section .data:140003000 34120000 35120000 10000000 00000000  4...5...........140003010 00000000                             ....// form.o: file format coff-x86-64
// Contents of section .data:
//  0000 34120000 35120000                    4...5...
// Contents of section .bss:
// <skipping contents of bss section at [0000, 0008)>// foo.o:  file format coff-x86-64
// Contents of section .data:
// 0000 10000000// 可以看出 链接后 目标文件data段中的内容 都在 执行文件的data段中,
// 但 执行文件的data段 比 两个目标文件data段加起来还要大8字节 (0x14 > 0x08 + 0x04)
// 而这多出来的8个字节正好是目标文件中的bss段:
// data(a.exe) = data(form.o) + bss(form.o) + data(foo.o)Contents of section .pdata:140004000 00100000 53100000 08200000 60100000  ....S.... ..`...140004010 97100000 10200000                    ..... ..// form.o:  file format coff-x86-64
// Contents of section .pdata:
//  0000 00000000 53000000 00000000           ....S.......
// foo.o:  file format coff-x86-64
// Contents of section .pdata:
//  0000 00000000 37000000 00000000           ....7.......// 对于pdata段我们目前还不是很了解它,只知道它与异常有关,内容上也观察不出来什么。
// 但从size上看,链接后的pdata段的大小是两个目标文件的和:
// 0x18 size pdata(a.exe) = 0x0c size pdata(form.o) + 0x0c size pdata(foo.o)
// TODO: 这个我们以后再分析

列出所有信息
从各个段的分布图中我们发现有些地方并不连续,接下来我们用
llvm-readobj -a 来查看该PE文件中一共有哪些东西:
$ llvm-readobj -a a.exe

File: a.exe
Format: COFF-x86-64
Arch: x86_64
AddressSize: 64bit
ImageFileHeader {                                // size = 20Machine: IMAGE_FILE_MACHINE_AMD64 (0x8664)SectionCount: 4TimeDateStamp: 2022-08-30 06:22:44 (0x630DACB4)PointerToSymbolTable: 0x0SymbolCount: 0StringTableSize: 0OptionalHeaderSize: 240  // 在COFF中恒为0Characteristics [ (0x22)IMAGE_FILE_EXECUTABLE_IMAGE (0x2)IMAGE_FILE_LARGE_ADDRESS_AWARE (0x20)]
}
// 可以看出这里的ImageFileHeader结构和普通COFF文件格式中的一模一样。
// 最主要的区别是这里的OptionalHeaderSize不是0了// 下面的ImageOptionalHeader是PE文件的主要header
// size = sizeof(pe32plus_header) + sizeof(data_directory) * numberOfDataDirectory (lld/COFF/Writer.cpp)
// size =  112 + 8 * 16 = 240
ImageOptionalHeader { Magic: 0x20B             // PE32+  ; 0x10B: PE32 ; 0x107: a ROM imageMajorLinkerVersion: 14MinorLinkerVersion: 0SizeOfCode: 512              // (所有)代码段的大小 (Q:为什么不等于我们例子中的text段的大小0x97?见下)// 初始化的数据段大小,我们观察到 3个数据段 rdata,data,pdata都带有IMAGE_SCN_CNT_INITIALIZED_DATA标记// 因此这里的SizeOfInitializedData就是指这3个段在文件中占的大小. // 显然和SizeOfCode的情况一样,这里的 (1536 = 512 * 3)明显大于我们的真实所需的数据大小。SizeOfInitializedData: 1536SizeOfUninitializedData: 0// 入口地址:当执行文件被加载到内存后 入口地址 相对于整个执行文件(在内存中的镜像)的偏移。对于DLL共享文件来说,它是可选的。// The address of the entry point relative to the image base when the executable file is loaded into memory.// For program images, this is the starting address.// For device drivers, this is the address of the initialization function. // An entry point is optional for DLLs. When no entry point is present, this field must be zero.    AddressOfEntryPoint: 0x1000// 代码段起始位置 相对于 相对于整个执行文件(在内存中的镜像)的偏移。// (这里由于main就是代码段的开头,而且我们指定main为入口地址,所以它和AddressOfEntryPoint值一样。)BaseOfCode: 0x1000// 执行文件(镜像文件)加载到内存中的 “首选” 起始地址(preferred address). (64K的整数倍)ImageBase: 0x140000000    SectionAlignment: 4096   // 段加载到内存中的对齐,它必须大于或等于文件的对齐。默认为页的大小。FileAlignment: 512       // 段(section)在文件中的对齐要求。(A:现在我们明白为什么SizeOfCode = 512 了)MajorOperatingSystemVersion: 6MinorOperatingSystemVersion: 0MajorImageVersion: 0MinorImageVersion: 0MajorSubsystemVersion: 6MinorSubsystemVersion: 0// 镜像(在内存中的)大小:由于每个段都要求4096(页)对齐,所以4个段 + 文件头 一共占用了5个页 (20480=4096*5)SizeOfImage: 20480// 文件头(在文件中的)大小: MS-DOS stub, PE header 和 section header 合在一起 (对齐到FileAlignment)SizeOfHeaders: 1024Subsystem: IMAGE_SUBSYSTEM_WINDOWS_CUI (0x3)Characteristics [ (0x8160)IMAGE_DLL_CHARACTERISTICS_DYNAMIC_BASE (0x40)IMAGE_DLL_CHARACTERISTICS_HIGH_ENTROPY_VA (0x20)IMAGE_DLL_CHARACTERISTICS_NX_COMPAT (0x100)IMAGE_DLL_CHARACTERISTICS_TERMINAL_SERVER_AWARE (0x8000)]SizeOfStackReserve: 1048576  // 1MBSizeOfStackCommit: 4096      // The size of the stack to commit.SizeOfHeapReserve: 1048576SizeOfHeapCommit: 4096// 在windows装载时,往往要快速找到一些装载所需的数据结构,比如导入/导出表,重定位表等。这些常用数据的位置和大小// 被保存在下面的DataDirectory[{VirtualAddress, Size},]中。NumberOfRvaAndSize: 16   // 表示DataDirectory包含项目数(每项包含 数据的位置和长度)DataDirectory {ExportTableRVA: 0x0  ExportTableSize: 0x0ImportTableRVA: 0x0   // 导入表的位置ImportTableSize: 0x0  // 导入表的大小ResourceTableRVA: 0x0ResourceTableSize: 0x0ExceptionTableRVA: 0x4000ExceptionTableSize: 0x18CertificateTableRVA: 0x0CertificateTableSize: 0x0BaseRelocationTableRVA: 0x0  // 重定位表的位置BaseRelocationTableSize: 0x0 // 重定位表的大小DebugRVA: 0x0DebugSize: 0x0ArchitectureRVA: 0x0ArchitectureSize: 0x0GlobalPtrRVA: 0x0GlobalPtrSize: 0x0TLSTableRVA: 0x0TLSTableSize: 0x0LoadConfigTableRVA: 0x0LoadConfigTableSize: 0x0BoundImportRVA: 0x0BoundImportSize: 0x0IATRVA: 0x0IATSize: 0x0DelayImportDescriptorRVA: 0x0DelayImportDescriptorSize: 0x0CLRRuntimeHeaderRVA: 0x0CLRRuntimeHeaderSize: 0x0ReservedRVA: 0x0ReservedSize: 0x0}
}
// 参考链接: https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#optional-header-image-only// 下面是 DOS MZ可执行文件格式的文件头,主要用来兼容DOS系统的。
DOSHeader {Magic: MZUsedBytesInTheLastPage: 120FileSizeInPages: 1NumberOfRelocationItems: 0HeaderSizeInParagraphs: 4MinimumExtraParagraphs: 0MaximumExtraParagraphs: 0InitialRelativeSS: 0InitialSP: 0Checksum: 0InitialIP: 0InitialRelativeCS: 0AddressOfRelocationTable: 64OverlayNumber: 0OEMid: 0OEMinfo: 0AddressOfNewExeHeader: 120
}// 接下来是 段描述符表,也叫段头表,段表, 和coff格式中的一样
Sections [// struct coff_section {                    // Size = 40//   char Name[COFF::NameSize (8)];         // 段名//   support::ulittle32_t VirtualSize;      // 该段被加载到内存后的大小//   support::ulittle32_t VirtualAddress;   // 该段被加载到内存后的虚拟地址//   support::ulittle32_t SizeOfRawData;    // 该段的原始大小 (在文件中的大小)//   support::ulittle32_t PointerToRawData; // 该原始段的位置 (该段在文件中的位置)//   support::ulittle32_t PointerToRelocations; // 该段的重定位表在文件中的位置//   support::ulittle32_t PointerToLinenumbers; // 该段的行号表在文件中的位置 (debug)//   support::ulittle16_t NumberOfRelocations;  // 该段(重定位表中)的重定位项数量//   support::ulittle16_t NumberOfLinenumbers;  // 该段(行号表中)的行号数量//   support::ulittle32_t Characteristics;      // 标志位,如可读,可执行等Section {Number: 1Name: .text (2E 74 65 78 74 00 00 00)VirtualSize: 0x97VirtualAddress: 0x1000RawDataSize: 512PointerToRawData: 0x400PointerToRelocations: 0x0PointerToLineNumbers: 0x0RelocationCount: 0LineNumberCount: 0Characteristics [ (0x60000020)IMAGE_SCN_CNT_CODE (0x20)IMAGE_SCN_MEM_EXECUTE (0x20000000)IMAGE_SCN_MEM_READ (0x40000000)]}Section {Number: 2Name: .rdata (2E 72 64 61 74 61 00 00)VirtualSize: 0x18VirtualAddress: 0x2000RawDataSize: 512PointerToRawData: 0x600PointerToRelocations: 0x0PointerToLineNumbers: 0x0RelocationCount: 0LineNumberCount: 0Characteristics [ (0x40000040)IMAGE_SCN_CNT_INITIALIZED_DATA (0x40)IMAGE_SCN_MEM_READ (0x40000000)]}Section {Number: 3Name: .data (2E 64 61 74 61 00 00 00)VirtualSize: 0x14VirtualAddress: 0x3000RawDataSize: 512PointerToRawData: 0x800PointerToRelocations: 0x0PointerToLineNumbers: 0x0RelocationCount: 0LineNumberCount: 0Characteristics [ (0xC0000040)IMAGE_SCN_CNT_INITIALIZED_DATA (0x40)IMAGE_SCN_MEM_READ (0x40000000)IMAGE_SCN_MEM_WRITE (0x80000000)]}Section {Number: 4Name: .pdata (2E 70 64 61 74 61 00 00)VirtualSize: 0x18VirtualAddress: 0x4000RawDataSize: 512PointerToRawData: 0xA00PointerToRelocations: 0x0PointerToLineNumbers: 0x0RelocationCount: 0LineNumberCount: 0Characteristics [ (0x40000040)IMAGE_SCN_CNT_INITIALIZED_DATA (0x40)IMAGE_SCN_MEM_READ (0x40000000)]}
]// 我们可以看到下面的重定位表为空,因为重定位工作已经完成了
Relocations [
]UnwindInformation [RuntimeFunction {StartAddress: (0x140001000)EndAddress: (0x140001053)UnwindInfoAddress: (0x140002008)UnwindInfo {Version: 1Flags [ (0x0)]PrologSize: 4FrameRegister: -FrameOffset: -UnwindCodeCount: 1UnwindCodes [0x04: ALLOC_SMALL size=56]}}RuntimeFunction {StartAddress: (0x140001060)EndAddress: (0x140001097)UnwindInfoAddress: (0x140002010)UnwindInfo {Version: 1Flags [ (0x0)]PrologSize: 4FrameRegister: -FrameOffset: -UnwindCodeCount: 1UnwindCodes [0x04: ALLOC_SMALL size=24]}}
]
Symbols [
]

PE总体分布图
最后我们再重新绘制各段的分布图,填补之前的空缺:

各个段对应的数据结构如下
(这里我们并不关注DOS相关的格式,COFF部分的数据结构可以参看 COFF数据结构,因此只列出ImageOptionalHeader和OutputSection数据结构)

LLVM LLD PE 格式分析 (COFF)相关推荐

  1. PE格式: 分析IatHook并实现

    Ring 3层的 IAT HOOK 和 EAT HOOK 其原理是通过替换IAT表中函数的原始地址从而实现Hook的,与普通的 InlineHook 不太一样 IAT Hook 需要充分理解PE文件的 ...

  2. [PE格式分析] 3.IMAGE_NT_HEADER

    源代码如下: typedef struct _IMAGE_NT_HEADERS { +00h DWORD Signature; // 固定为 0x00004550 根据小端存储为:"PE.. ...

  3. PE文件和COFF文件格式分析——导出表

    在之前的<PE可选文件头>相关博文中我们介绍了可选文件头中很多重要的属性,而其中一个非常重要的属性是(转载请指明来源于breaksoftware的CSDN博客) IMAGE_DATA_DI ...

  4. PE文件和COFF文件格式分析--概述

    刚工作的时候,我听说某某大牛在做病毒分析时,只是用notepad打开病毒文件,就能大致猜到病毒的工作原理.当时我是佩服的很啊,同时我也在心中埋下了一个种子:我也得有这天.随着后来的工作进行,一些任务的 ...

  5. PE文件和COFF文件格式分析——导出表的应用——一种摘掉Inline钩子(Unhook)的方法

    在日常应用中,某些程序往往会被第三方程序下钩子(hook).如果被下钩子的进程是我们的进程,并且第三方钩子严重影响了我们的逻辑和流程,我们就需要把这些钩子摘掉(Unhook).本件讲述一种在32位系统 ...

  6. PE文件和COFF文件格式分析——导出表的应用——通过导出表隐性加载DLL

    通过导出表隐性加载DLL?导出表?加载DLL?还隐性?是的.如果觉得不可思议,可以先看<PE文件和COFF文件格式分析--导出表>中关于"导出地址表"的详细介绍.(转载 ...

  7. PE文件和COFF文件格式分析——导出表的应用——一种插件模型

    可能在很多人想想中,只有DLL才有导出表,而Exe不应该有导出表.而在<PE文件和COFF文件格式分析--导出表>中,我却避开了这个话题.我就是想在本文中讨论下载Exe中存在导出表的场景. ...

  8. PE文件和COFF文件格式分析——RVA和RA相互计算

    之前几节一直是理论性质的东西非常多.本文将会讲到利用之前的知识得出一个一个非常有用的一个应用.(转载请指明来源于breaksoftware的csdn博客) 首先我们说下磁盘上A.exe文件和正在内存中 ...

  9. PE文件和COFF文件格式分析——节信息

    在<PE文件和COFF文件格式分析--签名.COFF文件头和可选文件头3>中,我们看到一些区块的信息都有偏移指向.而我们本文讨论的节信息是没有任何偏移指向的,所以它是紧跟在可选文件头后面的 ...

  10. PE文件和COFF文件格式分析--MS-DOS 2.0兼容Exe文件段

    MS 2.0节是PE文件格式中第一个"节".其大致结构如下:(转载请指明来源于breaksoftware的csdn博客) 在VC\PlatformSDK\Include\WinNT ...

最新文章

  1. 初识 Knative: 跨平台的 Serverless 编排框架
  2. 清华姚班陈丹琦获斯隆奖!与去年得主马腾宇是同班同学,博士毕业论文是近十年最热之一...
  3. 桂林机场春运期间新增多条航线航班 实现接力承运无缝衔接
  4. vsscode beego 没有提示_轻松搭建基于 Serverless 的 Go 应用(Gin、Beego 举例)
  5. 字符串数组 和 字符串指针 的区别
  6. Fast CGI 工作原理
  7. 初探Apache Impala
  8. 嵌入式必会!C语言最常用的贪心算法就这么被攻略了
  9. 建立能持续处理请求的Client端改造
  10. 【NOIP1999】【codevs1083】Cantor表(找规律)
  11. python代码范文_Python any()用法及代码示例
  12. php.ini文件中的 session.save_path是个坑爹的玩意!
  13. 通信网络基本概念一览
  14. 华为od 面试题及流程 (前后端)
  15. 小爬虫爬起点中文网收藏榜
  16. Java.MapReduce处理全国各省市疫情数据,上传Hadoop平台处理后的数据
  17. 华为鸿蒙新闻发布会,华为鸿蒙2.0、EMUI 11齐发 打通手机、电视、PC全平台
  18. 经典兔子问题有一对兔子从出生后第3个月起每个月都生一对兔子
  19. 淘宝店铺装修(首页和宝贝详情页显示不一致)
  20. 菜鸟在学编程__LSJ

热门文章

  1. Win10安装Ubuntu双系统导致Win10时间有问题
  2. dota2 自定义官方服务器,起源2引擎DOTA2重生设置 自定义房间创建
  3. z17刷机miui12教程_小米6刷miui12教程
  4. mysql实现跨库多表查询
  5. mathematica画图
  6. 记一次编写刷浏览量,刷查看次数脚本(内附代码)
  7. OC Foundation框架 数组
  8. 六安一中2021高考成绩查询,六安一中
  9. PS魔棒工具的使用方法
  10. 3A游戏制作人员职位及部分职位要求