你好,世界!
从以前的文章 GNU/Linux ELF introduction ,我们已经知道 ELF(Executable and Linkable Format)是 GNU/Linux 系统中可执行文件的标准格式。最近阅读了 如何打造一个体积小到极致的 ELF 文件 ,了解到对底层技术的趣味挑战,也对文件格式本质的有了更深刻理解。现在分享一些体会。
追求极致,超越常规,playful cleverness,是 黑客 精神的精髓之一。制造最小可执行文件 一直是黑客乐在其中的活动。
根据定义,一个可执行的 ELF 文件至少需要三个部分:ELF 头(64字节)、程序头表(通常56字节)和代码段。ELF 头位于文件开头,其中记录了程序头表的位置、大小、入口地址等关键信息。程序头表则告诉内核需要将文件的哪些部分加载到内存,以及加载到何处。
最小化的核心思路是让多个结构重叠——使代码段与 ELF 头或程序头表占据相同的字节,或者利用头部的某些字段作为指令的一部分。现代 GNU/Linux 内核(5.x以上)对 ELF 加载的检查比早期严格许多,但依然存在巧妙利用的空间。
一个著名的技巧是利用 ELF 头部的魔数`0x7F 0x45 0x4C 0x46`(即`x7FELF`)。在x86_64架构下,`x7F`不是合法指令,但紧随其后的`0x45 0x4C 0x46`恰好能解码为`rex.RB rex.WR rex.RX`——这些是冗余的 REX 前缀,可被处理器忽略。因此,我们可以从第2字节开始安排真正的指令。
另一个关键点是程序头表中的`p_filesz`和`p_memsz`字段。通过精心设置它们的值,可以使内核为程序映射出远大于文件本身的内存空间(例如数 TB)。虽然这在常规程序中毫无意义,但对于极小 ELF 而言,这些巨大的地址空间可以被用来存放字符串或指令的立即数。
传统可执行文件类型为`ET_EXEC`,其程序头表最小偏移为0x18,由此得到的程序(仅返回退出码)最小为80字节。但若将类型改为`ET_DYN`(位置无关可执行文件),程序头表可移至偏移0x0C处。最终得到一个73字节的版本: (source-code " 00000000: 7f45 4c46 b0e7 40b7 2a0f 0500 0100 0000 .ELF..@.*....... 00000010: 0300 3e00 0c00 0000 0000 0000 0c00 0000 ..>............. 00000020: 0c00 0000 0000 0000 0000 0000 0000 3800 ..............8. 00000030: 0100 0000 0000 3800 0100 0000 0000 0000 ......8......... 00000040: 0000 0000 0000 00eb bb ......... ") (p [这是x86_64 GNU/Linux 下最小可运行程序(返回42)的极限尺寸。
输出“Hello, world!n”比单纯返回退出码复杂得多,需要执行`write`和`exit_group`两个系统调用。通过反复迭代,最终可以得到77字节的版本(需要5级分页支持):
00000000: 7f45 4c46 4865 6c6c 6f2c 2077 0100 0000 .ELFHello, w....
00000010: 0300 3e00 0c00 0000 0000 0000 0c00 0000 ..>.............
00000020: 0c00 0000 0000 0000 ff71 f33c f4ff 71bb .........q.<..q.
00000030: 545e 1400 eb0c 3800 0100 1500 6f72 6c64 T^....8.....orld
00000040: 210a 52b2 0eb3 e60f 0593 5feb db !.R......._. .
其核心技巧包括:
若不需要5级分页,可得到一个81字节的版本,在普通x86_64机器上即可运行。
总之,严格符合 ELF 规范且能在现代 GNU/Linux 上运行的最小可执行文件(返回42)为73字节,其十六进制内容如上所示。若需输出字符串,目前已知的最小为77字节。
一个标准的、不依赖5级分页的最小“Hello, world!”为81字节:
00000000: 7f45 4c46 4865 6c6c 6f2c 2077 0100 0000 .ELFHello, w....
00000010: 0300 3e00 0c00 0000 0000 0000 0c00 0000 ..>.............
00000020: 0c00 0000 0000 0000 93b2 0e3c f45f 9015 ...........<._..
00000030: 0000 0000 eb06 3800 0100 0000 ff71 02ff ......8......q..
00000040: 71bb 545e b3e6 550f 05eb dd6f 726c 6421 q.T^..U....orld!
00000050: 0a .
研究极小 ELF,本质是在探索内核加载器对文件格式的容错边界。这种探索既是对编译器生成冗余信息的批判,也是对计算机系统抽象层的一次逆向工程。它让我们意识到:日常使用的可执行文件被填充了大量元数据,而真正“让程序跑起来”的核心要素,其实可以压缩到惊人的程度。这种“螺蛳壳里做道场”的极致优化,恰恰是理解计算机系统最底层运作的极佳途径。
如果你希望自己能够为自由软件作出贡献,并了解更多 GNU 可执行文件,那么 立伯乐 或许可以帮你。
让自由软件带你进入的美好自由世界!