PE格式
在Win32位平台可执行文件命名为可移植的可执行文件(Portable Executable File),该文件的格式就是PE格式
在Win32系统中,常见的EXE,DLL,SYS,COM等可执行文件都是PE文件
图示
映像
- PE文件的加载要完成虚拟地址(内存)和PE文件(硬盘)之间的映射关系,所以又被称为映像文件
- 当真正执行某个内存页的指令或访问一个页的数据时,这个页面才会真正读入内存
- 所以文件装入速度与文件大小关系不大
- 注意区分文件位置与虚拟地址与相对虚拟地址
相对虚拟地址(RVA)
虚拟地址,即我们前面提到的逻辑地址,指的是内存中的地址(注意和硬盘上文件中的位置相区分)
相对地址,即相对PE文件加载到内存后占用的最开始的那个内存单元的逻辑地址(基地址)
区分RVA和FOA
RVA:内存中的相对位置,相对的是加载到内存的基地址
FOA:文件中的相对位置,相对的是文件的开始位置(即0)
在文件中,一个节往往按512B(200H)的粒度对齐
在内存中,一个节通常按4096(1000H)的粒度对齐
所以内存的RVA和文件的FOA通常是不一致的
(由于装载时前面的一般不动,DOS部分、PE文件头部分、节表部分、和第一个节的RVA和FOA通常一致)
图示
在图示中,基地址是0x00400000,.text节的RVA是0x1560,VA是0x00401560
DOS头部分
图示,仅供参考无需记忆:
头部的e_magic,就是两个字符MZ,代表DOS文件
最后一个字段e_lfanew是偏移量,就是文件开始到PE文件头(NT头)的偏移量
PE头
包含3个部分
- PE文件标志(Signature)
- 映像文件头(IMAGE_FILE_HEADER)
- 可选映像文件头(IMAGE_OPTIONAL_HEADER32)
PE文件标志
两个字节为PE表明是PE格式文件
故而判断文件是否为PE格式可以通过:
- 先判断文件头2个字节是否为MZ
- 判断NT头(PE头)的Signature是否为PE
映像文件头
对病毒来说,可能需要用到NumberOfSections
可选映像头
1 | Option Header |
上述注释为可能用到的字段,“AddressOfEntryPoint”很关键
利用入口RVA实现病毒执行
修改入口地址对应指令
用PEView定位到AddressOfEntryPoint
用UE定位到AddressOfEntryPoint的值位置(注意值是RVA)
通过RVA找到文件的FOA
入口点的RVA(AddressOfEntryPoint)- 节的RVA =
入口点的FOA - 节的起始文件位置(PointerToRawData)
用eb 02 90 90替代原来内容,该指令汇编为
1
2
3
4jmp aa
nop
nop
aa:用OD启动修改后程序,OD将停在第一条指令,观察第一条指令是eb 02 90 90
成功
注:程序的ImageBase+AddressOfEntryPoint就是入口点地址
直接修改入口点地址
- 找到AddressOfEntryPoint字段在文件中的偏移
- 用UE修改就好
例题
- 下列哪个字段不在PE文件的可选头中( )
A.入口点地址
B.文件对齐大小
C.子系统
D.节表
参考答案:D
解析:由前面的图知道,节表是在PE文件头后面的
病毒加载到内存的问题
节表
- 节表紧跟在PE文件头后面,节表中每一个结构ImageSectionHeader(28H)都对应一个节,其中,SizeOfRawData描述了对应节的文件大小,VirtualSize描述了加载到内存的大小(两者可能不同,文件大小可以大于也可以小于内存大小,小于时将在内存补0)
- 在PE文件头的可选映像头中,SizeOfImage给出了整个程序包括所有头部加载到内存后的大小,其大小是SectionAlignment的整数倍(SectionAlignment是内存对齐的粒度、FileAlignment是文件对齐的粒度)
- 简单说,就是PE文件总大小和每个节的大小都有参数
图示:
末节寄生
思想
- 如果该节内存大小<文件大小,我们就在文件中将指令加载到该节的多余部分(对齐后的空洞)
- 然后修改节表SectionHeader中的VirtualSize字段(加载到内存的字节数)为修改后的大小,而对齐后的文件大小SizeOfRowData保持不变
- 注意,有时exe最后一个字节后有一些调试信息,但它不会被加载到内存,这也许就是SizeOfImage的意义,它阻止尾部多余信息进入内存
具体操作
- 首先找到最后一个节在文件中的位置,即其节表项中PointerToRawData字段
- 找到节中的寄生位置,就是VirtualSize字段后面
- 找到在文件中的寄生位置,PointerToRawData+VirtualSize
- 用UE在文件偏移到该处进行修改
- 修改最后一个节表中的VirtualSize
- 计算SizeOfImage=程序大小/SectionOfImage并向上取整,查看是否需要修改
- 修改AddressOfEntryPoint为新的程序入口RVA
文件长度变大
- 在reloc节原VirutalSize后添加JMP xx xx,在DOS部分已知机器码偏移量为两字节E9 xx xx
- 在reloc节后添加两个NOP指令
- 修改reloc节头的SizeOfRawData,加一个FileAlignment(1000h),为2000h
- 修改reloc节头的VirtualSize为原SizeOfRawData+2(两个nop) ,现在NOP才是实际结尾
- 修改可选映像头的SizeOfImage = (relocRVA+新VirutalSize)除以SectionAlign取上整
- 在NOP后手动增加1000h-2个字节,内容不论,为对齐后填充内容,以前是编译器自动填充
- 修改入口点RAV(AddressOfEntryPoint)
图示:
例题
- 关于PE文件病毒,下列说法不正确的是( )
A. 需要对PE文件头的某些字段进行修改,保证感染后的PE文件合法
B. PE文件感染必然会增加PE文件的大小
C. 病毒可以通过修改PE文件入口点的值或者入口点处的指令来获得执行机会
D. 病毒可以将自身且分为多段,分别插入到PE文件各节的空洞中
参考答案:B
解析:PE文件感染有的方式是不改变PE文件大小的