返回

文章详情

小巧可执行文件重访

Hacker News2026年6月24日 02:30

(或者,“雷云在地平线上聚集”)在几次场合,人们曾对我的原始文章作出回应,指出我最后创建的东西并不是真正的 ELF 可执行文件。相反,它是一个文件,当前形式的 Linux 内核恰好将其误认为 ELF 可执行文件。这是一个合理的观点。那个 45 字节的文件显然不符合 ELF 规格的许多要求。但是你能怪我吗?我怎么能停在我将 ELF 规格抛出窗外之前,明知还有其他可能性?但是为了满足这些纯粹主义者,以及我们每个人内心的清教徒一面,我创建了这个续集。那么。我们有一个被削减到 45 字节的可执行文件。我们现在希望使其严格符合已发布的标准,同时仍然尽可能小。我们背离正道的时刻是我们开始摆弄 ELF 头中的“未使用”字段时。那么让我们在那个之前退回一步: BITS 32 org 0x08048000 ehdr: ; Elf32_Ehdr db 0x7F, "ELF", 1, 1, 1 ; e_ident times 9 db 0 dw 2 ; e_type dw 3 ; e_machine dd 1 ; e_version dd _start ; e_entry dd phdr - $$ ; e_phoff dd 0 ; e_shoff dd 0 ; e_flags dw ehdrsz ; e_ehsize dw phdrsz ; e_phentsize dw 1 ; e_phnum dw 0 ; e_shentsize dw 0 ; e_shnum dw 0 ; e_shstrndx ehdrsz equ $ - ehdr phdr: ; Elf32_Phdr dd 1 ; p_type dd 0 ; p_offset dd $$ ; p_vaddr dd $$ ; p_paddr dd filesz ; p_filesz dd filesz ; p_memsz dd 5 ; p_flags dd 0x1000 ; p_align phdrsz equ $ - phdr _start: xor eax, eax inc eax mov bl, 42 int 0x80 filesz equ $ - $$ 这是我们九十一字节的版本。那么:我们是否被这个作为最佳大小困住了?不,还不完全是。当我们通过八个字节重叠 ELF 头和程序头表时,我们并没有违反任何规则。ELF 规格明确允许在文件中重叠不同的数据结构。那么让我们在这里这样做: ; tiny.asm BITS 32 org 0x08048000 ehdr: db 0x7F, "ELF", 1, 1, 1 ; e_ident times 9 db 0 dw 2 ; e_type dw 3 ; e_machine dd 1 ; e_version dd _start ; e_entry dd phdr - $$ ; e_phoff dd 0 ; e_shoff dd 0 ; e_flags dw ehdrsz ; e_ehsize dw phdrsz ; e_phentsize phdr: dd 1 ; e_phnum ; p_type ; e_shentsize dd 0 ; e_shnum ; p_offset ; e_shstrndx ehdrsz equ $ - ehdr dd $$ ; p_vaddr dd $$ ; p_paddr dd filesz ; p_filesz dd filesz ; p_memsz dd 5 ; p_flags dd 0x1000 ; p_align phdrsz equ $ - phdr _start: xor eax, eax inc eax mov bl, 42 int 0x80 filesz equ $ - $$ 这给了我们八十三字节。我们还能做些什么?看来没什么了。在绝望中,我们可能会再次回到 ELF 规格,试图查找一些东西。对于初始寄存器值有什么保证吗?只有一个寄存器:edx。而它说的是,它将包含零或最终关闭程序的地址。那么,实际上根本没有保证。继续寻找。啊哈:程序头表结构的 p_paddr 字段!头部的每个其他字段,如果不适用于英特尔架构,或不适用于可执行文件——或者,至少不适用于我们的可执行文件——必须根据 ELF 规格设置为零。但是对于 p_paddr 字段,规格说该字段的内容未指定。所以我们实际上有四个字节可以玩。那么我们能用它做些什么?当然,用它来保留我们程序的一部分。但我们不能完全将整个程序放在那里,因此我们需要将四个字节中的两个浪费在跳转指令上,以便到达其余部分。但这仍然留下了两个字节可以使用,而我们程序的第一条指令正好是两个字节长。 ; tiny.asm BITS 32 org 0x08048000 ehdr: db 0x7F, "ELF", 1, 1, 1 ; e_ident times 9 db 0 dw 2 ; e_type dw 3 ; e_machine dd 1 ; e_version dd _start ; e_entry dd phdr - $$ ; e_phoff dd 0 ; e_shoff dd 0 ; e_flags dw ehdrsz ; e_ehsize dw phdrsz ; e_phentsize phdr: dd 1 ; e_phnum ; p_type ; e_shentsize dd 0 ; e_shnum ; p_offset ; e_shstrndx ehdrsz equ $ - ehdr dd $$ ; p_vaddr _start: xor eax, eax ; p_paddr jmp short part2 dd filesz ; p_filesz dd filesz ; p_memsz dd 5 ; p_flags dd 0x1000 ; p_align phdrsz equ $ - phdr part2: inc eax mov bl, 42 int 0x80 filesz equ $ - $$ 所以。八十一字节。这就是全部吗? p_paddr 字段之后的下一个字段是 p_filesz 字段。如果我们能将跳转指令与该字段重叠,我们就能再挤入一条指令。但可惜,该字段的第一个字节是整个文件的大小,这将是一个不明智的跳跃。而剩下的字节是零。那种方法看起来不太有希望。那么 p_paddr 之前的字段呢?那是程序要加载的地址。好吧,我们已经知道我们不必使用默认值 0x08048000。我们至少需要保持地址页面对齐,但我们应该能够将一个两个字节的指令放入地址的上半部分。然而,我们的 xor 在这方面是行不通的。记住这是

赞助内容

NordVPN Next-gen Antivirus

本站免费、广告极少。如果觉得有帮助,可以请我们喝杯咖啡 —— 任何金额都对持续运营有实际帮助。

请我喝杯咖啡