PE文件
文件是存储数据的实体,不同的文件是给不同的软件去使用,不同的文件主要是格式不同
格式就是数组的排列组织方式,软件读取文件按照固定形式去解析文件
PE文件:
Portable Executable,可执行文件供 Windows 系统解析,解析完后就能创建出进程去运行
学习PE文件就是学习一堆结构体
FOA (Offset):文件偏移,某个数据距离文件开头的偏移
VA:虚拟地址,程序运行时是将PE文件加载到进程的内存空间中,这块内存空间称之为虚拟内存空间,32位虚拟内存空间以字节为单位,每个字节都有一个编号,从 0x00000000 到 0xFFFFFFFF
RVA:相对虚拟地址,PE文件不会占满整个虚拟内存空间,而是会占用一部分,那么就会有一个起始位置,这个起始位置也称为加载基址,PE文件中的数据相对于加载基址的偏移就是相对虚拟基址
EXE默认加载基址是 0x400000, DLL文件默认基址是 0x10000000,需注意基址不是程序的入口点
虚拟地址(VA) = 基地址 + 相对虚拟地址(RVA)
FOA - 该区段在文件中的起始地址 = RVA - 该区段在内存中的起始地址
系统加载PE文件,是将PE文件原封不动地复制到内存中,那么某一个数据的FOA和RVA就是相等的
DOS头
Windows 系统中的可执行文件在设计时考虑到兼容性问题,在正常的可执行文件开始部分嵌入一个DOS可执行文件,作用就是在MS-DOS系统下能够输出一行“这个程序不是运行在此系统下的”
只有2个字段是有用的:
- 第一个
e_magic:永远是 0x5A4D - 最后一个
e_ifanew:真正可执行文件的起始位置
- 第一个
NT头
DWORD Signature:永远是 0x00004550IMAGE_FILE_HEADER:文件头NumberOfSection:区段数量SizeOfOptionalHeader:扩展头大小,因扩展头中数据目录表的个数是不确定的,故需要一个大小
IMAGE_OPTIONAL_HEADER:扩展头ImageBase:程序默认的加载基址AddressOfEntryPoint:程序入口点(EP)SectionAlignment:内存对齐,0x1000(1页内存4KB)FileAlignment:文件对齐,0x200SizeOfImage:映像大小,即PE文件被加载到内存占用的空间大小SizeOfHeader:头部大小,DOS头 + NT头 + 区块表大小DllCharacteristics:PE的一组属性DataDirectory:数据目录表,描述了PE文件中16个或更多非常重要的数据块的大小和位置导入表、导出表、重定位表、资源表、TLS表、……
区段表
结构体数组,数组的元素个数由头文件中的 NumberOfSection 决定,区段表的一个元素描述的就是一个区段的信息
Name:区段名PointerToRawData:在文件中的位置SizeOfRawData:在文件中的大小VirtualAddress:在内存中的位置VirtualSize:在内存中的大小Characteristics:区段的属性:可读、可写、可执行
导出表
一个程序的运行实际由多个部分组成,通常有一个 exe 和多个 dll,dll 会提供函数、变量给其他模块使用,但并非所有 dll 中的函数都能提供给其他模块使用,只有在编写 dll 时,函数、变量被导出了才能提供给其他模块使用,导出表就是专门用来记录本文件导出信息的一个数据结构
导出函数地址表的 RVA:
AddressOfFunctions导出函数名称表的 RVA:
AddressNames导出函数序号表的 RVA:
AddressNameOrdinals名称表元素个数和序号表元素个数是相同的
地址表中元素可能会比序号表和名称表元素个数要多
Windows 的 PE 文件支持2种导出方式:无论何种导出方式,都有函数地址
- 名称导出:函数既有名称又有序号
- 序号导出:函数只有序号,没有名称
地址表中多出来的就是没有名称的函数或无效函数
通过导出表能够获得一个模块任何导出函数的地址,相当于自己能够实现
GetProcAddress- 若导入地址表被破坏了,可以修复导入地址表
- 可以检测 IAT-Hook,主要思路就是获取 IAT 位置原始的函数地址
- 适用于不方便使用
GetProcAddress而需要通过函数名或序号获取函数地址的情况
导入表
该模块使用了其他哪些模块提供的哪些函数,需要记录这些信息,记录的信息就在导入表中
重要字段:
OriginalFirstThunk(INT):导入名称表的 RVAFirstThunk(IAT):导入地址表的 RVAName:导入的 dll 名称的 RVA
INT 和 IAT 在还是文件的时候(即还未运行),里面存储的内容是一样的,因为在程序未运行时,无法得知此模块会加载到什么位置,也就无法得知函数的地址是多少,程序运行后,系统会将 IAT 填充上函数的地址,所有调用其他模块函数的代码全都是
call ds:[IAT地址]通过数据目录表的第1项,得到 RVA,就能找到
IMAGE_IMPORT_DESCRIPTOR结构体的数组,数组以全0元素结尾知道导入表能够知道模块之间是如何进行配合的
- 可以做 IAT-Hook,替换 IAT 表中的内容,就可以 Hook
- 知道一个 exe 用到了哪些模块的哪些函数,可以根据函数名猜测功能,利于分析程序
延迟加载
1 |
|
延迟加载前

延迟加载

