PE格式系列_0x02:PE头部信息(WinDbg查看)

文章目录

  • 1.notepad基本信息
  • 2.扩展命令!dh
  • 3.映像的头部信息
    • part 1.数据结构
    • part 2.Dos Header(配角)
    • part 3.NT Headers(`主角`)
      • part 3-1.File Header(配角)
      • part 3-2.Optional Header(`主角`)
        • DataDirectory(配角)
    • part 4.Section Headers
      • part 4-1.Section Headers
      • part 4-2.notepad的Section Headers
  • 4.扩展信息
    • 扩展1:区块对齐
    • 扩展2:自定义区块方法
    • 扩展3:区块合并
    • 扩展4:RVA 和 RAW转换
  • 5.参考

使用CFF、Stud_PE等专用工具可以直接查看PE文件的格式,那还有必要学习PE的格式吗?前面学习目的就是答案

单纯的看PE格式介绍没什么意思,下面结合分析notepad软件来学习PE格式

工具:调试器是WinDbg、PE查看工具是Stud_PE和PEview(任何一款PE工具都可以)

提示:WinDbg是windows下调试的神器,如果有时间的话,建议学习一个使用

1.notepad基本信息

先使用WinDbg简单了解一下C:\Windows\SysWOW64\notepad.exe的基本信息,主要看一下Base AdressFile version和符号是否加载正确
PE格式系列_0x02:PE头部信息(WinDbg查看)_第1张图片

最重要信息就是加载基址:00f20000,这个值要记住,后面很多访问内存操作都是以这个基址为前提

2.扩展命令!dh

前面提都过,学习PE文件格式,主要是学习header的结构,那么header里面都有什么?有没有什么比较简介的命令可以一次性的查看头部信息呢?

有,简洁的命令;!dh (display header)
PE格式系列_0x02:PE头部信息(WinDbg查看)_第2张图片

  • 使用!dh查看头部信息
    PE格式系列_0x02:PE头部信息(WinDbg查看)_第3张图片
  • dump节区的头部信息
    PE格式系列_0x02:PE头部信息(WinDbg查看)_第4张图片

3.映像的头部信息

简单命令介绍完了,下面介绍一个复杂的查看PE文件格式方法(WinDbg+PE专用工具),目的是更好的理解PE格式;研究anti debug、脱壳和写壳时,一定要熟悉下面的变量之间的对应关系

还记得上面查看的加载基址:00f20000吗?后面会看到

注意:下面标识了配角和主角是为了方便自己回顾PE文件时可以抓住重点

  • 配角:结构里面被关注的部分很少,里面主要是一些引出主角的偏移量等信息,主要起到桥梁作用
  • 主角:PE格式的核心,逆向时要重点学习的地方

下面是头部信息里需要关注信息的层次结构
PE格式系列_0x02:PE头部信息(WinDbg查看)_第5张图片

part 1.数据结构

下面是PE头信息常用的数据结构,详细可以看WinNT.h中的定义

//WinNT.h中的定义
#define IMAGE_NT_SIGNATURE                  0x00004550  // PE00

//File header format
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;
#define IMAGE_SIZEOF_FILE_HEADER           20

//文件头:CPU拥有的唯一Machine码
#define IMAGE_FILE_MACHINE_I386            0x014c  // Intel 386.
#define IMAGE_FILE_MACHINE_IA64            0x0200  // Intel 64

//可选头:可选头的类型
#define IMAGE_NT_OPTIONAL_HDR32_MAGIC      0x10b
#define IMAGE_NT_OPTIONAL_HDR64_MAGIC      0x20b

//Directory format
typedef struct _IMAGE_DATA_DIRECTORY {
    DWORD   VirtualAddress;
    DWORD   Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES    16

//Directory Entries
#define IMAGE_DIRECTORY_ENTRY_EXPORT          0   // Export Directory
#define IMAGE_DIRECTORY_ENTRY_IMPORT          1   // Import Directory
#define IMAGE_DIRECTORY_ENTRY_BASERELOC       5   // Base Relocation Table
#define IMAGE_DIRECTORY_ENTRY_IAT            12   // Import Address Table

//加载基址
   +0x01c ImageBase     //文件优先载入的地址(dll有可能要重定位,exe不需要)
   						//C++开发的exe,此值默认是0x1000,dll是0x10000000(可修改)
   						//PE装载器:1.创建进程,2.将文件载入内存,3.把EIP的值设置成

part 2.Dos Header(配角)

大小是40个字节,WinDbg附加32位的notepad.exe,查看notepad的header信息
PE格式系列_0x02:PE头部信息(WinDbg查看)_第6张图片
说明:DOS首部中主要关注2个字段

  • e_magic:windows平台需要被设置为0x5a4d(是WinNT.h中的定义的宏IMAGE_DOS_SIGNATURE)

    可以用来简单标识一个文件是不是PE格式,当然结合其他信息会更准确

  • e_lfanew:PE文件头的相对偏移(RVA),位于从PE文件开始偏移的3Ch处;这个字段是引出NT头的关键

数据结构:

//WinNT.h中的定义
#define IMAGE_DOS_SIGNATURE                 0x5A4D      // MZ
typedef struct _IMAGE_DOS_HEADER {      // DOS .EXE header
    WORD   e_magic;                     // Magic number
	...
    LONG   e_lfanew;                    // File address of new exe header
  } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

问题:DOS首部关注的变量这么少,为什么还保留呢?

主要是为了对DOS文件的兼容,后面的NT头部分才是PE文件格式的主角;由于DOS首部里面有很多不关心的变量,这部分会被一些壳利用进行特殊操作的

使用PE相关程序,查看的Dos Header
PE格式系列_0x02:PE头部信息(WinDbg查看)_第7张图片

重点关注一下e_lfanew 变量的值:0x100=256,定位NT Headers的关键偏移,这个后面还会反复用到
在这里插入图片描述

part 3.NT Headers(主角

大小是0xF8个字节,NT Headers才是PE文件格式的重点;对于逆向来说,Optional Header和DataDirectory里面指向的一张张表才是重点

  • WinDbg求结构体大小

下面有时会求一个结构体的大小,因此总结了WindDbg常用的命令
PE格式系列_0x02:PE头部信息(WinDbg查看)_第8张图片

  • 示例

查看notepad.exe的NT headers的整体信息如下;就是一个结构体而已,包含文件头(偏移是0x004)和可选头(偏移是0x018);注意这里不是指针,是一个真实的结构
PE格式系列_0x02:PE头部信息(WinDbg查看)_第9张图片

part 3-1.File Header(配角)

  • WinDbg查看

NT头基址有如下公式:pe_header = ImageBase + dos_header->elfanew

notepad.exe示例基址计算:ImageBase(0x00f20000) + dos_header->elfanew(0x100) = 0x00f20100;因此File Header的起始地址应该是0x00f20100 + 4
PE格式系列_0x02:PE头部信息(WinDbg查看)_第10张图片

主要关注的信息说明如下,这些信息错误可能会导致程序不能运行

提示:后续章节中写一个壳的示例中会看到LIEF源码处理有问题,导致SizeOfOptionalHeader长度少8个bytes,致使程序不能运行的案例

变量 说明
Machine 运行平台,14Ch:Intel 386;0x0200:Intel 64
NumberOfSections 区块(Section)的数目
SizeOfOptionalHeader _IMAGE_OPTIONAL_HEADER结构体的长度;32位:00E0h;64位:00F0h
Characteristics 文件属性;普通exe一般是010fh,dll是2102h

Characteristics取值0x102是下面的组合:

#define IMAGE_FILE_EXECUTABLE_IMAGE          0x0002  //文件可执行 
#define IMAGE_FILE_32BIT_MACHINE             0x0100  //32位机器

扩展:借助_IMAGE_DOS_HEADER的e_lfanew 和 _IMAGE_FILE_HEADER的SizeOfOptionalHeader,可以创建一个不同于常规的PE文件


  • 专用工具查看

Stud_PE查看结果
PE格式系列_0x02:PE头部信息(WinDbg查看)_第11张图片

CFF的查看结果(截图是后补充的,TimeDateStamp对不上是正常的)
PE格式系列_0x02:PE头部信息(WinDbg查看)_第12张图片

part 3-2.Optional Header(主角

别看名字是可选,但是妥妥的是主角;对于普通的exe、dll等文件,一定是存在的且这个结构才是PE文件的核心

NT headers中可以看到,可选头的偏移是0x18,下面是可选头的信息
PE格式系列_0x02:PE头部信息(WinDbg查看)_第13张图片

关于可选头有2点要注意

  • 注意1:上面信息量明显增加了,为什么不删减呢?不删减是因为真的很重要,要不是篇幅限制,都想把整个可选头列出来了
  • 注意2:第一次看可能觉得这些信息没什么用,逆向软件时自然会反复看这里,因此不用死记,多看几次自然就会了

PE专用工具查看的可选头内存数据:
在这里插入图片描述

下面对在逆向中经常使用的3个成员进行说明

  • 1.AddressOfEntryPoint:程序执行入口RVA

    脱壳时梦寐以求的值,即源程序的OEP,壳程序的EP都用到这个变量

    执行PE文件时,PE装载器先创建进程,再将文件载入内存,最后将EIP寄存器的值设置为ImageBase + AddressOfEntryPoint
    在这里插入图片描述


    二进制文件(.exe)和动态链接库(.dll)区别:

    • 对于dll:这个入口点在进程/线程的创建和销毁都会被调用
    • 对于exe:这个地址不是main、DllMain等函数,而是执行运行时的库代码(来调用main函数)

  • 2.ImgeBase:文件在内存中的优先默认载入地址

    exe基本就是按照这个地址加载(不支持ASLR时),dll有可能有重定向现象(就是想加载的基址的坑被别人用了,换一个地方加载的现象)
    在这里插入图片描述

  • 3.DataDirectory:数据表索引,下面单独列出一个部分讨论

DataDirectory(配角)

里面的输入表,重定位表等知识是写壳和脱壳的必备知识,hook函数也会经常用这里,这个目录表就是一个索引

  • WinDbg查看的信息

这也是一个配角,类似于虚函数表一样,只是程序里各种表的相对地址的一个集合而已
PE格式系列_0x02:PE头部信息(WinDbg查看)_第14张图片

  • PE专用工具查看的信息

输入表的起始地址(0x2647c)和大小(0x21c)与上面查看的相同
PE格式系列_0x02:PE头部信息(WinDbg查看)_第15张图片

输入表的整体结构,可以看到一张输入表里面实际就是一个个dll的记录而已,每个dll都有一个记录(dll名称和相应的导出函数)
PE格式系列_0x02:PE头部信息(WinDbg查看)_第16张图片

可以看到dll之间的记录都是以一个0数值的DWORD进行区分的
PE格式系列_0x02:PE头部信息(WinDbg查看)_第17张图片

part 4.Section Headers

PE文件将具有相似属性的数据统一保存在被称为节区的地方,然后将各个节区属性记录在节区表;节区表用IMAGE_SECTION_HEADER结构体表示的

part 4-1.Section Headers

这个部分也叫做Section Table,最有用的几个信息下面已经添加了注释

//WinNT.h中的定义
#define IMAGE_SIZEOF_SHORT_NAME              8
typedef struct _IMAGE_SECTION_HEADER {
    BYTE    Name[IMAGE_SIZEOF_SHORT_NAME];	//区块名字(8字节)
    union {									//内存中区块的尺寸
            DWORD   PhysicalAddress;
            DWORD   VirtualSize;
    } Misc;
    DWORD   VirtualAddress;					//内存中偏移RVA
    DWORD   SizeOfRawData;					//磁盘中区块的尺寸
    DWORD   PointerToRawData;				//磁盘中偏移RVA
    DWORD   PointerToRelocations;
    DWORD   PointerToLinenumbers;
    WORD    NumberOfRelocations;
    WORD    NumberOfLinenumbers;
    DWORD   Characteristics;
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
#define IMAGE_SIZEOF_SECTION_HEADER          40

常用的一些区块信息,也是_IMAGE_SECTION_HEADER中Name常用的取值

名称 说明(仅做参考,没有强制规定)
.text 代码块,内容都是code;linker会将目标文件的.text链接成一个大的.text块
.data 读/写数据区块;一般的全局变量,static变量存放在这里
.rdata 只读数据区块
.idata 输入表(包含其他使用的dll的函数和数据信息),一般会合并到其他如.rdata区块
.rsrc 资源,只读,不能别合并
.bss 未初始化数据
.reloc 基址重定位,一般dll需要
.didat 延迟载入的输入数据,非Release才有可能有

注意:上面都是编译器产生的标准区块,不了解这些也不影响正常编程,但是学习逆向要了解这些

part 4-2.notepad的Section Headers

  • 1.WinDbg查看notepad.exe的Section Headers信息
    PE格式系列_0x02:PE头部信息(WinDbg查看)_第18张图片

  • 2.WinDbg查看.text的Section Headers
    PE格式系列_0x02:PE头部信息(WinDbg查看)_第19张图片

  • 3.专用PE工具进行查看

    可以看到notepad.exe有6个节区,且每个节区的主要信息都列举出来,分析壳时会重点分析这里

    里面最有用的信息就是内存中的起始地址偏移(VirtualOffset)和磁盘中的起始地址偏移(RawOffset),偏移是针对基址说的
    PE格式系列_0x02:PE头部信息(WinDbg查看)_第20张图片

4.扩展信息

扩展1:区块对齐

如果细心观察,就会发现,内存中的起始地址偏移(VirtualOffset)是0x1000的整数倍;磁盘中的起始地址偏移(RawOffset)是0x200的整数倍,这是谁规定的呢?

PE文件中ntdll!_IMAGE_OPTIONAL_HEADER里面的2个变量规定的

  • FileAlignment:定义了磁盘区块的对齐值,在PE文件中,典型的对齐值是200h;PointerToRawData必须是FileAlignment的整数倍
  • SectionAlignment:定义了内存中区块的对齐值,文件映射到内存中,区块至少从一个页边界开始;32位:内存页是按照4K(1000h)排列;62位:内存页是按照8K(2000h)排列

扩展2:自定义区块方法

如果想要定义自己的区块,可以使用下面的方法;当然也可以使用第三方库LIEF(这里先不介绍,后续写壳时会介绍这个库)

//用#pragma进行声明,可以让编译器将输入放入自定义的区块
//下面处理后,编译器会将数据放在名称为my_test_data的区块,而不是默认的.data区块
#pragma data_seg("my_test_data")

扩展3:区块合并

写壳时,通常外壳dll有用的部分只有代码和数据段,这几个段(.text.data.rdata)最终都要迁移到原程序中;为了方便代码和数据迁移,在外壳dll中将代码和数据合并

#pragma comment(linker, "/merge:.data=.text") 
#pragma comment(linker, "/merge:.rdata=.text")
#pragma comment(linker, "/section:.text,RWE")

扩展4:RVA 和 RAW转换

PE文件被加载到内存时,磁盘文件的地址(RAW)和加载到内存后的实际地址(RVA)之间有什么关系?(注意这里说都是相对地址);一般使用如下方法计算2者关系:

  • 1.查找RVA所在的节区(因为RVA是以所在节区为基准的偏移)
  • 2.根据映射进内存前后,偏移不变的原理,有如下公式:RAW - PointerToRawData = RVA - VirtualAddress

PE文件的头部信息就先介绍这么多吧…,如果想专门研究PE,可以看《Windows PE权威指南》(看过一部分,现在用来垫显示器了…)

5.参考

  • 1.《加密与解密》第11章 PE文件格式
  • 2.《逆向工程核心原理》第13章 PE文件格式

你可能感兴趣的:(#,PE文件格式,安全,windows)