文章目录
- 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 Adress
、File version
和符号是否加载正确文章图片
最重要信息就是加载基址:
00f20000
,这个值要记住,后面很多访问内存操作都是以这个基址为前提2.扩展命令!dh 前面提都过,学习PE文件格式,主要是学习header的结构,那么header里面都有什么?有没有什么比较简介的命令可以一次性的查看头部信息呢?
有,简洁的命令;
!dh
(display header)文章图片
- 使用
!dh
查看头部信息
文章图片
- dump节区的头部信息
文章图片
简单命令介绍完了,下面介绍一个复杂的查看PE文件格式方法(WinDbg+PE专用工具),目的是更好的理解PE格式;研究anti debug、脱壳和写壳时,一定要熟悉下面的变量之间的对应关系还记得上面查看的加载基址:
00f20000
吗?后面会看到注意:下面标识了配角和主角是为了方便自己回顾PE文件时可以抓住重点下面是头部信息里需要关注信息的层次结构
- 配角:结构里面被关注的部分很少,里面主要是一些引出主角的偏移量等信息,主要起到桥梁作用
- 主角:PE格式的核心,逆向时要重点学习的地方
文章图片
part 1.数据结构 下面是PE头信息常用的数据结构,详细可以看
WinNT.h
中的定义//WinNT.h中的定义
#define IMAGE_NT_SIGNATURE0x00004550// PE00//File header format
typedef struct _IMAGE_FILE_HEADER {
WORDMachine;
WORDNumberOfSections;
DWORDTimeDateStamp;
DWORDPointerToSymbolTable;
DWORDNumberOfSymbols;
WORDSizeOfOptionalHeader;
WORDCharacteristics;
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
#define IMAGE_SIZEOF_FILE_HEADER20//文件头:CPU拥有的唯一Machine码
#define IMAGE_FILE_MACHINE_I3860x014c// Intel 386.
#define IMAGE_FILE_MACHINE_IA640x0200// Intel 64//可选头:可选头的类型
#define IMAGE_NT_OPTIONAL_HDR32_MAGIC0x10b
#define IMAGE_NT_OPTIONAL_HDR64_MAGIC0x20b//Directory format
typedef struct _IMAGE_DATA_DIRECTORY {
DWORDVirtualAddress;
DWORDSize;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES16//Directory Entries
#define IMAGE_DIRECTORY_ENTRY_EXPORT0// Export Directory
#define IMAGE_DIRECTORY_ENTRY_IMPORT1// Import Directory
#define IMAGE_DIRECTORY_ENTRY_BASERELOC5// Base Relocation Table
#define IMAGE_DIRECTORY_ENTRY_IAT12// 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信息
文章图片
说明:DOS首部中主要关注2个字段
-
e_magic
:windows平台需要被设置为0x5a4d
(是WinNT.h中的定义的宏IMAGE_DOS_SIGNATURE)
可以用来简单标识一个文件是不是PE格式,当然结合其他信息会更准确
-
e_lfanew
:PE文件头的相对偏移(RVA),位于从PE文件开始偏移的3Ch处;这个字段是引出NT头的关键
//WinNT.h中的定义
#define IMAGE_DOS_SIGNATURE0x5A4D// MZ
typedef struct _IMAGE_DOS_HEADER {// DOS .EXE header
WORDe_magic;
// Magic number
...
LONGe_lfanew;
// File address of new exe header
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
问题:DOS首部关注的变量这么少,为什么还保留呢?
主要是为了对DOS文件的兼容,后面的NT头部分才是PE文件格式的主角;由于DOS首部里面有很多不关心的变量,这部分会被一些壳利用进行特殊操作的使用PE相关程序,查看的Dos Header
文章图片
重点关注一下
e_lfanew
变量的值:0x100=256,定位NT Headers的关键偏移,这个后面还会反复用到文章图片
part 3.NT Headers(
主角
) 大小是0xF8个字节,NT Headers才是PE文件格式的重点;
对于逆向来说,Optional Header和DataDirectory里面指向的一张张表才是重点
- WinDbg求结构体大小
文章图片
- 示例
0x004
)和可选头(偏移是0x018
);注意这里不是指针,是一个真实的结构文章图片
part 3-1.File Header(配角)
- WinDbg查看
pe_header = ImageBase + dos_header->elfanew
notepad.exe示例基址计算:ImageBase(
0x00f20000
) + dos_header->elfanew(0x100
) = 0x00f20100
;因此File Header的起始地址应该是0x00f20100 + 4文章图片
主要关注的信息说明如下,这些信息错误可能会导致程序不能运行
提示:后续章节中写一个壳的示例中会看到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 |
#define IMAGE_FILE_EXECUTABLE_IMAGE0x0002//文件可执行
#define IMAGE_FILE_32BIT_MACHINE0x0100//32位机器
扩展:借助_IMAGE_DOS_HEADER的e_lfanew 和 _IMAGE_FILE_HEADER的SizeOfOptionalHeader,可以创建一个不同于常规的PE文件
- 专用工具查看
文章图片
CFF的查看结果(截图是后补充的,TimeDateStamp对不上是正常的)
文章图片
part 3-2.Optional Header(
主角
)别看名字是可选,但是妥妥的是主角;对于普通的exe、dll等文件,一定是存在的且这个结构才是PE文件的核心
NT headers中可以看到,可选头的偏移是
0x18
,下面是可选头的信息文章图片
关于可选头有2点要注意
- 注意1:上面信息量明显增加了,为什么不删减呢?不删减是因为真的很重要,要不是篇幅限制,都想把整个可选头列出来了
- 注意2:第一次看可能觉得这些信息没什么用,逆向软件时自然会反复看这里,因此不用死记,多看几次自然就会了
文章图片
下面对在逆向中经常使用的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
:数据表索引,下面单独列出一个部分讨论
- WinDbg查看的信息
文章图片
- PE专用工具查看的信息
0x2647c
)和大小(0x21c
)与上面查看的相同文章图片
输入表的整体结构,可以看到一张输入表里面实际就是一个个dll的记录而已,每个dll都有一个记录(dll名称和相应的导出函数)
文章图片
可以看到dll之间的记录都是以一个0数值的DWORD进行区分的
文章图片
part 4.Section Headers PE文件将具有相似属性的数据统一保存在被称为节区的地方,然后将各个节区属性记录在节区表;节区表用
IMAGE_SECTION_HEADER
结构体表示的part 4-1.Section Headers
这个部分也叫做Section Table,最有用的几个信息下面已经添加了注释
//WinNT.h中的定义
#define IMAGE_SIZEOF_SHORT_NAME8
typedef struct _IMAGE_SECTION_HEADER {
BYTEName[IMAGE_SIZEOF_SHORT_NAME];
//区块名字(8字节)
union {//内存中区块的尺寸
DWORDPhysicalAddress;
DWORDVirtualSize;
} Misc;
DWORDVirtualAddress;
//内存中偏移RVA
DWORDSizeOfRawData;
//磁盘中区块的尺寸
DWORDPointerToRawData;
//磁盘中偏移RVA
DWORDPointerToRelocations;
DWORDPointerToLinenumbers;
WORDNumberOfRelocations;
WORDNumberOfLinenumbers;
DWORDCharacteristics;
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
#define IMAGE_SIZEOF_SECTION_HEADER40
常用的一些区块信息,也是
_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信息
文章图片
- 2.WinDbg查看
.text
的Section Headers
文章图片
- 3.专用PE工具进行查看
可以看到notepad.exe有6个节区,且每个节区的主要信息都列举出来,分析壳时会重点分析这里
里面最有用的信息就是内存中的起始地址偏移(VirtualOffset
)和磁盘中的起始地址偏移(RawOffset
),偏移是针对基址说的
文章图片
如果细心观察,就会发现,内存中的起始地址偏移(扩展2:自定义区块方法VirtualOffset
)是0x1000的整数倍;磁盘中的起始地址偏移(RawOffset
)是0x200的整数倍,这是谁规定的呢?
PE文件中ntdll!_IMAGE_OPTIONAL_HEADER
里面的2个变量规定的
FileAlignment
:定义了磁盘区块的对齐值,在PE文件中,典型的对齐值是200h;PointerToRawData
必须是FileAlignment
的整数倍SectionAlignment
:定义了内存中区块的对齐值,文件映射到内存中,区块至少从一个页边界开始;32位:内存页是按照4K(1000h)排列;62位:内存页是按照8K(2000h)排列
如果想要定义自己的区块,可以使用下面的方法;当然也可以使用第三方库LIEF
(这里先不介绍,后续写壳时会介绍这个库)
//用#pragma进行声明,可以让编译器将输入放入自定义的区块
//下面处理后,编译器会将数据放在名称为my_test_data的区块,而不是默认的.data区块
#pragma data_seg("my_test_data")
扩展3:区块合并
【#|PE格式系列_0x02(PE头部信息(WinDbg查看))】写壳时,通常外壳dll有用的部分只有代码和数据段,这几个段(.text
、.data
、.rdata
)最终都要迁移到原程序中;为了方便代码和数据迁移,在外壳dll中将代码和数据合并
#pragma comment(linker, "/merge:.data=https://www.it610.com/article/.text")
#pragma comment(linker, "/merge:.rdata=https://www.it610.com/article/.text")
#pragma comment(linker, "/section:.text,RWE")
扩展4:RVA 和 RAW转换
PE文件被加载到内存时,磁盘文件的地址(PE文件的头部信息就先介绍这么多吧…,如果想专门研究PE,可以看《Windows PE权威指南》(看过一部分,现在用来垫显示器了…)RAW
)和加载到内存后的实际地址(RVA
)之间有什么关系?(注意这里说都是相对地址);一般使用如下方法计算2者关系:
- 1.查找RVA所在的节区(因为RVA是以所在节区为基准的偏移)
- 2.根据映射进内存前后,偏移不变的原理,有如下公式:
RAW - PointerToRawData = https://www.it610.com/article/RVA - VirtualAddress
5.参考
- 1.《加密与解密》第11章 PE文件格式
- 2.《逆向工程核心原理》第13章 PE文件格式
推荐阅读
- 安全|OpenHarmony安全子系统之应用签名与验签
- windows bat批处理语法简析
- #|docker-compose启动redis多机集群(6台服务器3主3从)
- #|docker-compose中变量的使用
- #|docker-compose启动redis集群
- #|helm部署etcd集群
- #|docker-compose 启动pgweb
- #|docker-compose限制容器cpu和内存
- 数字政府建设提档,融云协同办公护航