[摘要]  最后一个块表必须同时满足三个不等式:
  1)“内存地址+对齐尺寸<=填装载后尺寸”
  2)“对齐尺寸+文件地址<=文件的实际长度”
  3)“实际尺寸+内存地址>=文件的实际长度”
[正文]
  我的上篇文章提到的Keygen.exe的那个壳,把PE头搞得个七零八落,程序居然运行得十分舒畅,我突然觉得PE头可能就像一个报关单或银行存款报单等什么的,虽然项目繁多,但可填可不填的占多数。姓名、金额必填,但存一千,填一万也是过不了的。无法否定比尔.盖茨不是受到它的启发?
  我说这些看是“废话”的东西,未必不含有哲理。PE头长期来被初学者奉若神明,十分小心地呵护它的每个字节,思维被它束缚得紧紧的,虽然它是学习的必然,但长期跳不出它的束缚未必是好事?好了,“废话”再说下去,你们也会拂袖而去。话归主题。下面我把对PE头的一些数据的实验结果报告如下,因谁也没有XP的源代码,无法证明其结论一定正确,甚至根本就是错的?请高手辅正。
  一、竟然栽倒在自己的“警示”之中
  上篇文章《菜鸟啄硬壳—我的脱壳手记》提到的脱壳后的dump.exe,运行都很正常,就是不能用eXeScope对资源进行编辑。当时我丢下一句话:它可能对资源加密了,我争取下次将它解密。我当然希望能在网友面前将它解密?^_^。找来几个有名的Resource Tool,如Resource Hacker,FreeRes和eXeScope,个个都说没办法!怪了,这个壳会这么深奥?下来后我只好硬着头皮去肯“资源结构”,从头到尾花了大半天的功夫,对比dump.exe资源竟然没有发现它有任何异样(最大收获是发现keygen壳的压缩比没有解压前后尺寸比那么大,脱壳后的dump.exe中垃圾占了1/3),为什么运行中windows不觉得dump.exe没有什么不对,而eXeScope.exe却认为它的结构不能识别?百思不得其解。难道eXeScope权限高于windows?这不叫人笑掉大牙?忙找来几个eXeScope可以识别和不能识别的软件比照一下才真相大白,原来赫赫有名的eXeScope等居然也没有跳出PE头的思维束缚!!它对资源的识别只认“.rsrc”这几个字符(注意:不是认IMAGE_DATA_DIRECTORY结构,更不是认数据目录中的IMAGE_DIRECTORY_ENTRY_RESOURCE),那怕是在.rsrc后加个a字符它都不认!忙把dump的第二个节表添上“.rsrc”字样(在0000012C处),咳,它认了(苦笑!)!想不通我曾经鼎礼膜拜的工具竟然也犯这样低级的错误!唉,我不止一次地提醒不要迷信这些工具软件,结果我竟然栽倒在自己的“警示”之下!原来,keygen的脱壳已经很完整了,只是原软件作者故意把PE头搞得乱七八糟,在windows允许通过的情况下欺骗了不少软件。
  二、PE头中的关键数据
  1.对NumberOfSections(偏移=06h)和SizeOfImage(偏移=50h)数据的认识:
  PE文件头中的IMAGE_NT_HEADERS数据结构看了头都大了,要弄清楚每个数据的作用实在是强人所难,有必要么?谁写软件是这样一个个数据填写出来的么?我看有“刨根癖”的我等也不要无事找事!到是该注意这些结构的数据成员,不用则罢,用了就必须在规定的地址填写,这点windows是寸步不让的。
  其中NumberOfSections(偏移=06h)块数目和SizeOfImage(偏移=50h)装载后尺寸到是要小心修改,否则给你一个“不是有效的win32文件”的脸色!后面再说。
  2.块表(IMAGE_SECTION_HEADER)数据中的秘密:
  上篇文章中提到kengen脱壳前PE头中没有一个块表的名称,只有简单的几个数据。脱壳后OD自作聪明地加上了名为“.irdata2”的区块表。就因为块表中没有“.rsrc”名称,令一大堆Resource软件束手无策!打开.irdata2指向的数据一看,完全傻眼了,它是一堆垃圾。脱壳后的dump从0001CD14起以后的部分完全是脱壳后的垃圾,或者叫碎壳更准确。连垃圾代码都可以在windows中自由通行,那么windows就远不想象那么严密不可侵犯了。
  (1)块表中的三个不等式;
  1)显然所有块名称都可以省去!
  2)各块表的内存地址和文件地址数据必须准确,而文件的实际尺寸和对齐尺寸则可以在一个相当的范围内变化,甚至对齐尺寸可以为0。如果乱填的数据超过了windows的忍耐范围,它就会要么不作声的退出,要么就来个发送错误的报告,当最后一个块表的数据严重违规时,又会给你不是有效的win32文件的脸色。具体规律也没弄清楚。例如,把某程序的最后一个块表的数据按实际顺序排列如下:
------------------------------------------------------------------------------------------
块名称    实际尺寸  内存地址  对齐尺寸  文件地址    属性
……     22A30      4000     22C00      1400    C00000E0
------------------------------------------------------------------------------------------
  3)最后一个块表必须同时满足下列三条:
  “内存地址+对齐尺寸<=填装载后尺寸”,即PE头中SizeOfImage的数据;
  “对齐尺寸+文件地址<=文件的实际长度”;即磁盘文件最后一个字节地址+1的长度;
  “实际尺寸+内存地址>=文件的实际长度”。
  这些结论(无法证实是否100%准确),对喜欢DIY软件的Player来说无疑很有用。很多脱壳后的软件最后都有很多0或无用字节,你一删除就得到警告,这就是破坏了上面的条件。其实磁盘文件可以在任意字节上结束,其长度并不是按200对齐规则的。
  (2)添加、删除或合并块表;
  如果一个文件是“无缝”装载(即磁盘文件除第1块外,各块内容块已经按1000对齐了,装载后不会再在各内容块间插入零字节了),几乎所有的脱壳文件都是无缝装载(不知是不是绝对如此?)。那么这样的程序文件块可以简单地合并。只须修改要合并或删除块表的前面那个块表的“对齐尺寸”和“实际尺寸”使之满足上述条件即可。(将SizeOfImage数据减小也可以,但要相应删除文件的一些无用字节)。以脱壳后的dump.exe为例:
  在.irdata2表(从00000154开始)中找到第5字段=400,即对齐尺寸为400(好笑的是它文件尺寸第3字段=1000,居然文件尺寸>对齐尺寸,可见windows对不紧要的数据是不管的!),现在将前一个无名表的第5字段(0000013C)实际尺寸7000改为7400(这样就满足了三个不等式了),或同时也将对齐尺寸改为7400也是可以的。然后从00000154将后面的块表连同一串垃圾都删除,最后不要忘记将00000012处的块表数(偏移=06h)由3改为2,存盘即可。照这样下去,将所有块表合并成一块也很轻松。只须将实际尺寸和对齐尺寸上下各自对应相加到最前面一个块表中(一定满足三个不等式)就行了。但始终不要忘记对NumberOfSections块数目的修改。
  有了这个规律,你要在喜爱的软件中添加一些块扩展它的功能,这有何难!
  3.对SizeOfHeaders(偏移=54h)数据的认识:
  谁都知道该数据表示“头文件+块表”尺寸。其实它也是一个内容块,只是不包含在后面的块表中。装载时windows始终都要为它留出0—FFF空间,这就是为什么程序总是从401000开始的原因。但是SizeOfHeaders又是一个双字,难道这个特殊块可以长达4G?空想不如实干,马上动手。
  (1)奇想:把所有块内容都放在头文件块中去;
  有了这个想法,就用dump.exe作实验了,将它的块表数设为0,并删去所有的块表,把SizeOfHeaders设为23400(该文件的总长度)。目的是当头文件装载完成后,所有的数据都装载了,没有块表不会有关系吧?嘟的一声,报告说不是有效的win32文件。看来,没有块表windows是不认的。那么就给它添上一个全0字节长度为400的块数据和相应的块表吧,SizeOfHeaders 也应减少400(有了前面的不等式,添加块表易如反掌)。再试,啊~,诺盾急了,首先跳出来说发现了“Bloodhound.w32.EP”病毒!?这真是皇帝不急太监急,关闭吵闹不修的诺盾。运行程序,windows保持沉默,一运行就退出了。结论就这样,再研究下去就有些无聊了!只是不知道诺盾为什么认为它是病毒?
  (2)乾坤大挪移;
  我想文件头的这块地址是一个“避风港”,壳可以把程序设置在里面,我何尝不可以把常用、常要修改的块放在里面呢?马上把dump的资源表(0001C000开始,长度160)拷贝到00000130开始的位置(注意,是拷贝不是插入),并将资源目录表地址指向00000130,资源表则个字不改。运行,成功的。只是未运行时,程序名称前的图标变成了DOS程序的图标,而运行后,标题拦上的图标很正常。我一时也没有找出原因来。
  资源表的成功移动,鼓励我再把IID表和IAT表也挪一挪,这一下得修改表中地址了,虽说RVA=0,但原地址的RVA不为0。修改到也很简单,因为地址是真实值,按实填上就可以了。当然相应的导入表,函数地址表也要改一改。一切就绪后运行,windows报告初始化失败。啊~,PE头块属性是禁止写入的!我怎么就没有想到?怎么众多的书都没有警告过?别乱说,好象罗云彬在书中什么地方提到过,叫我给忘了!
  现在算是初步认识了PE头了。
  谢谢你把本文读完。