很久以前,笔者就知道Windows PE可执行文件当中有一个MZ头部,而这个MZ头部是一个完整的DOS程序。这次2023新年红包,笔者终于把这个鸽了很久的点子拿来出了一个红包。

在动手之前,笔者以为改MZ头部这事不会很复杂,结果最终踩了几个坑搞了好久才搞好,看来还是我太菜了 :(

下面笔者就来分享下,给一个PE可执行文件替换MZ头部方法。

0x00 准备一个DOS可执行文件

既然MZ头部是一个完整的DOS程序,那当然首先你得有一个DOS EXE程序。

由于考虑Windows桌面应用的部分用Lazarus来开发(编译器是Free Pascal),一开始我找到了Free Pascal的DOS版本。

写了一个hello, world程序在DOS下测试,发现还真能跑:

Hello Dos

但是后续发现Free Pascal的DOS Runtime实在是太庞大,一个hello, world程序要链接大量Pascal Runtime,导致其大小随随便便就要10K+,给后面的步骤造成了困难,遂只能回到原点重新考虑方案。

除开上面说的Free Pascal,看了一遍2023年的现代编译器,大多已不支持构建纯DOS应用程序了。GCC虽然支持以80386实模式编译,但是Binutils没有现成的工具链接一个合法的DOS可执行程序,所以GCC的80386兼容模式看起来只能用来构建系统引导过程中的有关程序。最终看来还是得翻箱倒柜找找上个世纪的老软件,于是笔者回到了用汇编写程序,然后用MASM直接生成可执行文件的90年代。

谢天谢地一下子yy出来了 pass = 2^key % 1000000007 这个在汇编上极其好实现,而且求解时间复杂度不高也不低,在10^9以内没有重码的Hash,这个程序最终成功和大家见面了。

0x01 分析待修改的Windows可执行文件

准备好红包在x86_64下的可执行文件版本,然后用CFF Explorer等软件打开它,查看其Nt Headers -> Optional Header部分的信息:

原始PE头

这里有两个比较重要的关注点:

  1. FileAligment:修改后的PE文件的节需要按FileAlignment对齐,所以在修改MZ头以后,需要进行适当的填充来满足FileAligment的需求。大多数PE文件的FileAlignment是0x200。
  2. BaseOfCode:这里就是限制我们准备的DOS可执行文件大小的地方。参考下面“PE文件在内存中的布局”一图,由于Windows会将PE头全部Load进虚拟内存的低位,所以修改MZ头部以后,需要避免新的PE头长度超过BaseOfCode,使得PE文件无法自洽。这就是前面说的10K+的DOS可执行文件带来的麻烦,当MZ头超长,只能考虑在文件的其他地方加入DOS可执行文件的部分,会有许多更为复杂的操作。

PE文件在内存中的布局

0x02 修改DOS可执行文件的头部

打开Hex编辑器,根据MZ头部的定义找到其末尾的e_lfanew字段,根据FileAlignment计算一个合法的偏移值。这个偏移值一般是原PE文件MZ头的e_lfanew+FileAlignment的n倍。

0x03 替换Windows可执行文件的MZ头

这里使用dd可以较快达到替换MZ头的目的。笔者这里找了一台Linux服务器进行了这些操作。大家可以尝试WSL等环境来使用dd

使用dd if=path_to_pe_file.exe of=path_to_pe_file_without_mz.exe bs=1 skip=$mz_header_size命令获取一个移除原MZ头部的PE文件。

使用dd if=/dev/zero of=zeros.bin bs=1 count=$padding_zero_count生成一个全0文件,以填充MZ头到PE头中间的部分,满足FileAlignment需求

现在大家可以用 0x0.zip 来生成全0的binary文件。这是我买来好玩的域名2333。

使用cat new_mz.exe zeros.bin path_to_pe_file_without_mz.exe > path_to_pe_file_modified_pe.exe实现MZ头的替换。

0x04 修复Windows可执行文件的节表

由于MZ头部长度变化以后,原PE文件中的节表偏移发生了变化,所以这里需要修复节表,以确保后续可执行文件可以被正常执行。

这里还是使用CFF Explorer编辑PE文件:

  1. 修复PE头长度。 修复后PE头
  2. 打开节表,然后根据节表中的偏移值和大小逐个修改,以满足新的PE头部长度。 修复后节表

经过上面的步骤,一个PE文件的MZ头部就替换完成了。现在可以分别用Windows和DOS打开这个文件,看看他在不同的平台下有什么不同的行为吧!

写在后面

最近 ChatGPT 很火,笔者也去尝试了一下 ChatGPT。好在(至少对目前的笔者来说)目前来看,询问GPT-4怎么给PE换MZ头,GPT-4并不能给出相对可行的解决方案。

抱歉拖更了这么久。感觉自己再拖更下去,可能就有什么模型能写这篇文章了。希望以后还能有机会能产出一些 ChatGPT 不知道的知识。