PE,说白了就是一个文件。要减小一个文件的体积,会怎么做,压缩,可能是我们目前最流行的方法。但看一下我们的文件,180字节,可能一个一般的压缩算法都超过180字节了,即使把我们的文件压缩到0字节,也不会减小体积(因为我们是一个可执行文件,要有自解压的功能,所以把压缩算法的体积算进文件大小)。 PE虽然是文件,但它不是一般的文件,它有一个特性,PE文件加载到内存后,不足一个分页的位置会用0填充,也就是说,PE在文件末尾,有初值为0的默认数据(请注意黑体字部分,所有工作都是建立在这句话的基础上)。 有了这个特性,我们减小PE体积有就思路了,如果文件末尾的字节为0,就可以把这个字节删掉,进而减小文件体积。我们就从后往前看,看看位于文件末尾的这些数据,是不是能移到前面的空闲位置,并且这些位置是不是能填0。 1 180 to 157 从文件末尾往前看,首先看到3个0字节,这么快就可以减小文件体积。删掉吗?想了一下还是算了,现在还不是扣这3字节的时候,现在删了,反而影响字段的查看。 最后1行4字节是导入表。导入表有3项有意义,其实正真用到的就是最后两项,也就是后8字节。8字节,前面拼凑一下,好像可以满足。但它又有点儿矫情,需要在后面跟1行4字节的全0,以表示结束,现在这个位置,后面要多少0都有,但是往前移一移,好像没看到1行4字节的全0,这就有点儿麻烦了。要是有一个字段专门存放导入表项数多好,才4字节,比1行4字节小多了。 刚刚我们说过,导入表占1行4字节,3项有意义,真正用到的是最后8字节,其实操作系统检查的是最后4字节,也就是FirstThunk字段,如果该字段指向无效的IAT,则遍历下一个。是不是可以这么理解,如果紧挨着导入表记录的1行4字节的后4字节是全0,则表示导入表结束了呢?我们试一下。 把位于0x8C处的call往前移,把导入表移到0x80处。修改后如下,双击可以运行,看来我们的猜想是正确的。 2 157 to 153 再次从后往前看,这次位于文件末尾的1行1字节,是节表(后面的内容都为0,省去了)。节表大小为2行半,其中有意义的有6项: 前8字节:节名(NAME) 之后4字节:内存大小(Misc.VirtualSize) 之后4字节:内存地址(VirtualAddress) 之后4字节:文件大小(SizeofRawData) 之后4字节:文件地址(PointerToRawData) 最后4字节:节属性(Characteristics) 试验发现,以下情况程序可以运行: 1)如果函数名的第1个字节为0,后面的所有字节都可以填任意值。 2)如果函数名的第1个字节不为0,Misc.VirtualSize可以有计划修改,SizeofRawData可以有计划修改,VirtualAddress和PointerToRawData必须填相同的值,其它字段可以填任意值。 虽然Name是长度为8的字节数组,从这里可以看出来,操作系统是以’\0’为结束标志来读这个字段的。因为PointerToRawData位于可以有计划修改字段的最后面,并且必须和VirtualAddress一致,所以我们把VirtualAddress和PointerToRawData填0字节。设计如下方案: 从后往前,找符合如下特征的12字节: 4个可以有计划修改的字节 | 4个0字节 | 4个可以有计划修改的字节 移动节表到0x88处(同时要修改位于FileHeader的SizeOfOptionalHeader字段,该字段表示选项头的大小,间接决定了节表的位置),修改后如下,双击可以运行。 3 153 to 145 继续查找12字节的特征码,移动节表到0x80处,修改后如下,双击可以运行。 4 145 to 137 继续查找12字节的特征码,比较幸运的是,在0x58的位置,我们找到的是满足20字节特征的节表。 移动节表到0x58处,修改后如下,双击可以运行。 5 137 to 133 现在位于文件末尾的是导入表数据目录,其中0x28为导入表长度。我们知道操作系统是以全0的1行4字节,表示导入表结束,那么这个值是不是可以填0呢?我们试一下。 修改后如下,双击可以运行,看来我们的猜想是正确的。 PE下载: Smallest PE.rar
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课