系列汇总
- 写一个PE的壳_Part 1:加载PE文件到内存
- 写一个PE的壳_Part 2:ASLR+修复输入表(IAT)+重定位表支持(.reloc)
- 写一个PE的壳_Part 3:Section里实现PE装载器
- 写一个PE的壳_Part 4:修复对ASLR支持+lief构建新PE
- 写一个PE的壳_Part 5:PE格式修复+lief源码修改
- 写一个PE的壳_Part 6:简单的混淆
文章目录
用原教程中Part 4的py文件处理test.exe,生成名为packed_bug1.exe
程序;运行时直接报错
下面给出了整个修复的思路
1.CFF查看
CFF看一下packed_bug1.exe,直观上会发现2处异常,导致PE格式解析出现问题
-
异常1:Data Directory中的Import Directory没有识别
构建程序会经历
unpacked.exe -> shifted_unpack.exe ->packed.exe
的过程,packed.exe的header是shifted_unpack.exe的header的简单拷贝;CFF发现shifted_unpack.exe的header是正常的,说明问题应该不是出在这里
- 异常2:Section Header中section table记录的信息错乱了
可以看到信息都被正确记录,但是解析时出现了错误,其他有用信息暂时没有,因为整体都是LIEF
第三方库处理的
2.python再次加载
此时尝试用LIEF
加载一下packed_bug1.exe试试(binary = lief.parse("C:\packed_bug1.exe")
),发现报如下错误
SizeOfOptionalHeader的大小不对???
抓紧看一下packed_bug1.exe和shifted_unpack.exe的SizeOfOptionalHeader
;果然不同,应该是0xE0才对,怎么少了8个bytes?
验证一下,给packed_bug1.exe的SizeOfOptionalHeader加上8个bytes试一下,在[step5].重新排列section中加入如下一行代码
再次构建一下,生成名为packed_bug2.exe
的可执行文件(注意名字变了),发现异常1得到解决;异常2部分得到解决(Raw相关数据不对)
扩展:LIEF
修改建议
- 问题原因
[step5]中,使用output_PE = lief.PE.Binary("pe_from_scratch", lief.PE.PE_TYPE.PE32)
生成一个空白的PE文件,里面有基本的header信息
LIEF
产生的SizeOfOptionalHeader为什么会少8个bytes呢?
LIEF
中Binary函数的源码截图如下,可以看到在计算sizeof_headers时,data directory的个数默认是15
如果你对PE文件格式熟悉,会知道这个data directory默认有16个(这个值可以修改,但是一般的带有PE查看器功能的软件都会默认使用16个);而一个data directory的元素大小就是8个bytes;因此找到了错误位置
下面是可执行文件的Optional Header中data directory个数的标识:
LIEF源码:
- 修改建议
修改建议:直接将data directory个数改成16
可能隐患:因为我的环境LIEF
不能构建,理论上这么改可以解决当前问题;既然LIEF
是很多人使用的第三方库,这里写默认是15就一定有别的原因,暂时没有深究
3.异常2的Raw问题
下面是解决异常2中raw问题的主要处理思路
step 1:运行程序
假设没有看出异常2的Raw数据有问题,此时直接运行packed_bug2.exe,会报如下错误
step 2:调试器运行程序
x32dbg调试一下程序,直接运行程序,会报内存访问访问的错误(也就是俗称的“踩内存”),错误的位置是0x411001
(记住这个位置,后面会用)
step 3:单步调试
单步调试,会发现在0x410079
处有一个函数调用(调用的其实是mystrcmp),操作码是E8
(后面地址是相对于当前地址的offset)
F7,进入函数发现反汇编解析出来不是一个函数正常起始汇编指令;继续一路F8,单步调试很快就会发现错在0x411001
位置
0x410671处的机器码(50 72 6F 63 41 …)确实有问题,此时需要看一下二进制文件里,0x410671的位置的原始数据是什么?
step 4:CFF查看二进制
因为程序入口地址是0x410000(也是.text的起始地址),所以这个函数(packed_bug2.410671)的偏移地址应该是0x671,因此直接查看.text的起始地址中0x671的内容即可
.text
的Raw Size的值是0x800
(理论上0x671一定在里面),但是CFF查看时发现区段大小只有0x600
,为什么会没有显示全呢?
0x671 - 0x600 = 0x71,按理说数据应该在后面section中,下面验证一下猜想
.rdata
大小只有0x40,小于0x71;最后,在.idata
中的发现内容,但是.idata
没有可执行权限,才会报内存访问访问的错误
CFF中查看的结果,验证了
的产生的原因,根因还是Raw数据大小有问题导致.text
区段大小显示异常导致的问题
step 5:查看LIEF源码
CFF中看到section的文件的起始地址有问题,python中操作section暂时只用了LIEF
中的add_section
函数,以这个函数作为突破口查看LIEF源码
add_section
函数每次添加section,要做2件事
- 1.检查setion前面的整个header中是否能放下一个section table?问题就出现在这里
- 2.添加setion的具体操作,这里没有什么问题
分析源码前,先要对PE文件有一个基本的知识积累:PE中每个具体section在文件中的对齐默认是0x200
如果“DOS首部 + NT首部 + Section Table的大小”小于0x200,.text
的起始地址是0x200;如果大于0x200 < 尺寸 <=0x400
,.text
的起始地址是0x400
下面是源码中涉及到的关键部分:
源码中问题就出现在这里,当超过0x200时,能存放的个数available_sections_space_
重新计算只是简单的加1;导致每次连续调用add_seciton时,会不断的平移整个已有的section,导致.alloc和后面的section一直被移动,最终section table整体错乱
step 6:解决办法
既然section table整体错乱,最终调用LIEF
的build函数前,重新手动修改一下section table中相关raw数据,修改结果如下(这只是临时修改,可以用程序计算每个section大小进行动态指定大小会更好)
其中:名为.alloc的section的大小被设置为了0x200
step 7:结果验证
再次使用CFF查看raw相关数据,符合预期
运行程序也弹出了this is a test for compil with MinGW, no reloc
,符合预期,说明不论是否支持ASLR的程序,我们现在都能处理了
step 8:源码修改建议
既然available_sections_space_
计算有问题,直接在make_space_for_new_section
函数修改如下:
available_sections_space_ = (0x200 - /* sizeof headers */ sizeof_headers) / sizeof(details::pe_section);
起始上面的公式是Binary函数的方法,部分截图: