《逆向工程》之PE文件结构补充

PE文件格式

13. PE文件格式

PE是微软基于UNIX的COFF(common object file format)制作的。本想提高移植性,但实际上只能在windows上使用。

种类主扩展名种类主扩展名
可执行exe, scr驱动程序sys, vxd
dll, ocx, cpl, drv对象文件obj

可以说,obj之外的文件都是可执行的。

PE头:dos头 —— section header ;PE体:余下部分。

PE头与各区块的尾部存在一个区域,NULL padding,因为有最小使用单位。

dos头之后有一个可选项,dos存根(dos stub),大小不固定,即使没有,文件也能运行。它由代码和数据混合而成。

对于32位的程序,dos下执行会输出"this program cannot be run in DOS mode";windows中会运行32位代码。

exe和dll被装载到用户内存的0-7fffffffh;sys被装载到内核内存的80000000h-ffffffffh

装载到内存中的形态称为映像(image)。

13.4 RVA to RAW

在这里插入图片描述

关于RVA to RAW的转换公式,也有例外。

RVA=ABA8,根据上图,则raw file offset = aba8 - 9000 + 7c00 = 97a8,显然不对,因为VirtualSizeSizeOfRawData大。注意,VirtualSize是对齐之前的值。

13.5 IAT

加载dll有两种方式:

  • explicit linking,显式链接,使用dll时加载,使用完毕后释放内存;
  • implicit linking,隐式链接,程序开始时即加载dll,程序终止时再释放内存。

IAT提供的机制为隐式链接。


为什么要call [函数地址的地址],而不是不直接call dll函数地址(这是dos时代的方式)?

  1. dll版本各不相同(如kernel32),程序员也不知道程序将来在哪种系统上运行,为确保所有环境下都能正常调用,编译器保存了函数地址的地址,执行时,将函数地址写在该处;
  2. 另一个原因是dll重定位,导致无法对实际地址硬编码;
  3. 最后,PE头中表示的地址不是VA,而是RVA。

在这里插入图片描述

IAT输入顺序:

  1. 读取IID.Name,获取库名称字符串(“kernel32.dll”);
  2. 装载库,LoadLibrary("kernel32.dll")
  3. 读取IID.OriginalFirstThunk,获取INT地址;
  4. 逐一读取INT数组的值,获取相应_IMAGE_IMPORT_BY_NAME的地址(RVA);
  5. 使用_IMAGE_IMPORT_BY_NAME.NameHint,获取相应函数的起始地址;
  6. 读取IID.FirstThunk,获得IAT地址;
  7. 将函数地址写入IAT数组;
  8. 重复4-7,知道INT结束。

13.6 EAT

从库中获得函数地址的api为GetProcAddress()。原理如下:

  1. 它通过_IMAGE_EXPORT_DIRECTORY.AddressOfNames
  2. 遍历函数名称数组,strcmp()比较字符串,查找函数,假设找到的索引为name_index
  3. 利用AddressOfNameOrdinals转到ordinals数组;
  4. ordinals[name_index]查找对应序号;
  5. 通过AddressOfFunctions转到函数地址数组EAT;
  6. EAT[ordinal]即指定函数地址。

以kernel32.dll为例,所有导出函数j均有名称,且AddressOfNameOrdinals数组以index==ordinal的形式存在。也有dll的导出函数没有名称,只能通过ordinal导出,ordinal也未必等于AddressOfNameOrdinals数组索引。

对于没有名称的函数,AddressOfFunctions[ordinal-IED.Base]即对应函数地址。

_IMAGE_FILE_HEADER.TimeDateStamp可以用ctime_s()转换为可读格式。

#include<stdio.h>
#include<time.h>

int main()
{
	time_t curtime = 0x47918EA2;
	char buf[30] = { 0 };
	ctime_s(buf, 30, &curtime);
	printf("当前时间 = %s\n", buf);

	return 0;
}

14-15. 运行时压缩

关键词:运行时压缩器(run-time packer),lossless/loss data compression.

运行时压缩是针对PE而言的,文件内部有解压缩代码。

把普通PE文件创建为运行时压缩文件的程序叫做压缩器(packer);经过反逆向(anti-reversing)处理的压缩器叫做保护器(protector)。

压缩器

压缩器目的:

  • 较小文件,便于传输和保存;
  • 隐藏代码和资源(解压缩后可以通过内存dump窗口查看)。

压缩器分类:

  • 单纯用于压缩的,UPX,ASPack;
  • 破坏PE头、隐藏目的的恶意压缩器,UPack,PESpin,NSAnti。

有很多工具可以帮助划分。

保护器

保护器压缩后的文件反而会变大。用于防止破解、保护代码与资源。

常用于游戏、恶意代码。

分类:

  • 商用:ASProtect, Themida, SCKP
  • 公用:UltraProtect, Morphine.

upx举例

压缩前的exe,一般有以下部分:

  • 调用GetModuleHandle()api,获取exe的ImageBase
  • cmp比较MZ、PE签名。

ollydbg打开压缩文件时,判断文件为压缩文件,选择是或不是。

在这里插入图片描述

upx压缩后,区块表会多出upx0,upx1,且upx0RawDataSize为0。解压缩代码与压缩的源码都在第二个区块upx1,运行瞬间会将源码解压到第一个区块。

pushad ;push eax~edi
mov esi,addr_of_upx1
lea edi,addr_of_upx0

像这样的开头,可以预见要从esi缓冲区解压缩数据复制到edi缓冲区。

遇到循环,先了解作用再跳出。

UPX壳的特征之一,是EP代码位于pushad,popad之间。popad之后紧跟着jmp oep;

跟踪过程有4个循环:

  1. 复制一部分数据;
  2. 解压缩后复制;
  3. 恢复call/jmp
  4. upx1有INT,反复调用GetProcAddress()获取api地址,写入ebx所指的原IAT区域。

16. 基址重定位

使用DDK(driver development kit)创建的sys文件默认ImageBase为010000h。

windows vista之后引入aslr,每次运行exe都会加载到随即地址。aslr也适用于dll、sys文件。同一系统的kernel32.dll、user32.dll等会被加载到固有的ImageBase,所以,系统的dll实际不会发生重定位。

无法加载到ImageBase地址时,若未进行PE重定位处理,就会导致“内存地址引用错误”,异常终止。

_IMAGE_OPTIONAL_HEADER64.DataDirectory[5]为基址重定位表的地址。

恶意代码通常把TypeOffset.Type修改为0,IMAGE_REL_BASED_ABSOLUTE,略去PE装载器的重定位过程。

17. 从可执行文件中删除.reloc

exe中基址重定位表对运行没什么影响,可用PEView或HexEditor通过以下4步删除:

  1. 整理reloc节区头,记下VirtualSize(设为e40),然后用0覆盖头部;
  2. 删除reloc节区,;
  3. 修改IMAGE_FILE_HEADERNumberOfSections减1;
  4. 修改IMAGE_OPTIONAL_HEADERSizeOfImage减去SectionAlignment对齐后的VirtualSize(e40->1000),。

18. UPack PE文件头

多数恶意代码使用UPack,大部分杀软干脆将UPack压缩的文件直接识别为恶意文件。所以分析时要关闭杀软。

UPack直接压缩源文件,所以压缩前记着备份。

PEView等工具无法读取压缩后的PE头,推荐使用Stud_PE工具。

重叠文件头

也是其它压缩器常用的方法,可以把dos头和pe头重叠,节约文件头空间,提高分析难度。

_IMAGE_FILE_HEADER.SizeOfOptionalHeader

该字段在32位中是eo,64位PE32+中是f0。

_IMAGE_OPTIONAL_HEADER64的起始地址加上SizeOfOptionalHeader就是_IMAGE_SECTION_HEADER

UPack把SizeOfOptionalHeader改为148h。UPack基本特征是使PE文件头变形,向文件头添加解码的代码。增大该字段后,就在_IMAGE_OPTIONAL_HEADER64_IMAGE_SECTION_HEADER之间添加了额外空间,添加代码。

PE工具把这段代码解析为PE头,就会引发错误。

_IMAGE_OPTIONAL_HEADER64.NumberOfRvaAndSizes

改变该字段也是为了插入代码。

这个字段指出DataDirectory数组的元素个数。UPack把它从10h改为了0ah,导致后6个元素被忽略,被忽略的这块区域可以用来覆写自己的代码。

_IMAGE_SECTION_HEADER

UPack会用自身数据覆盖程序运行不需要的结构体成员。

重叠节区

UPack的另一个特征是可以随意重叠PE节区与文件头。

例如,可以让两个节区的PointerToRawDataSizeOfRawData一致,但内存相关的VirtualSizeVirtualAddress不冲突。PE规范没有明确这样是不行的。

RVA to RAW

_IMAGE_SECTION_HEADER.PointerToRawData应该是_IMAGE_OPTIONAL_HEADER64.FileAlignment(0x200)的整数倍。PE装载器发现不是整数倍时,会强制将其识别为整数倍(10–>0),这样UPack文件就能运行了。

很多PE工具,包括ollydbg的早期版本,都不能找出UPack的EP。

IID 数组???

IID[0]之后,既不是第二个IID,也不是空结构体。这里是第3个节区的结束,之后的部分不会映射到第3个节区内存。

从文件看导入表好像损坏了,其实已经在内存中准确表现。

IID.FirstThunk

有时IID.OriginalFirstThunk(INT)为0时,不能通过它来找API名称字符串,那么跟踪FirstThunk(IAT)也可以。

20. 内嵌补丁

inline code patch。

当OEP经过运行时压缩或加密时难以直接修改,可以让EP代码解密后修改jmp,运行洞穴代码(code cave),再跳转到OEP对内存代码打补丁。

普通补丁内嵌补丁
对象文件文件&内存
次数1次文件1次,内存中每次运行时
方法直接间接

在这里插入图片描述

解密示例代码:

mov ebx,xxx
mov ecx,xxx
00010203:
xor byte ptr ds:[ebx], xxx;
sub ecx
inc ebx
cmp ecx,0
jnz 00010203

code cave 可以填充在NULL padding的区域。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值