最小的可执行程序(x86-64 Linux)

问题描述 投票:0回答:2

我最近看到这篇文章描述了Linux的最小可能的ELF可执行文件,但是这篇文章是为32位编写的,我无法获得在我的机器上编译的最终版本。

这让我想到一个问题:可以编写且运行无错误的最小 x86-64 ELF 可执行文件是什么?

滥用或违反 ELF 规范是可以的,只要当前的 Linux 内核实际上能够运行它,就像 MuppetLabs 32 位 teensy ELF 可执行文章的最后几个版本一样。

linux assembly x86-64 executable elf
2个回答
18
投票

从我的一个关于 Linux 上 ELF 可执行文件的“真实”入口点和“原始”系统调用的答案开始,我们可以将其简化为

bits 64 global _start _start: mov di,42 ; only the low byte of the exit code is kept, ; so we can use di instead of the full edi/rdi xor eax,eax mov al,60 ; shorter than mov eax,60 syscall ; perform the syscall

我认为您无法在不超出规格的情况下将其变得更小 - 特别是,psABI 不能保证

eax

 的状态。它被精确地组装成 10 个字节(而不是 32 位有效负载的 7 个字节):

66 bf 2a 00 31 c0 b0 3c 0f 05


直接的方法(用

nasm

 组装,用 
ld
 链接)生成一个 352 字节的可执行文件。

他所做的第一个“真正”改造是“手工”构建 ELF;这样做(进行一些修改,因为 x86_64 的 ELF 标头有点大)

bits 64 org 0x08048000 ehdr: ; Elf64_Ehdr db 0x7F, "ELF", 2, 1, 1, 0 ; e_ident times 8 db 0 dw 2 ; e_type dw 62 ; e_machine dd 1 ; e_version dq _start ; e_entry dq phdr - $$ ; e_phoff dq 0 ; e_shoff dd 0 ; e_flags dw ehdrsize ; e_ehsize dw phdrsize ; e_phentsize dw 1 ; e_phnum dw 0 ; e_shentsize dw 0 ; e_shnum dw 0 ; e_shstrndx ehdrsize equ $ - ehdr phdr: ; Elf64_Phdr dd 1 ; p_type dd 5 ; p_flags dq 0 ; p_offset dq $$ ; p_vaddr dq $$ ; p_paddr dq filesize ; p_filesz dq filesize ; p_memsz dq 0x1000 ; p_align phdrsize equ $ - phdr _start: mov di,42 ; only the low byte of the exit code is kept, ; so we can use di instead of the full edi/rdi xor eax,eax mov al,60 ; shorter than mov eax,60 syscall ; perform the syscall filesize equ $ - $$

我们减少到 130 字节。这比 91 字节的可执行文件大一点,但它来自于几个字段变成 64 位而不是 32 位的事实。


然后我们可以应用一些与他类似的技巧;

phdr

ehdr
的部分重叠是可以完成的,尽管
phdr
中的字段顺序不同,并且我们必须将
p_flags
e_shnum
重叠(但是由于
e_shentsize
,应该忽略它)为 0)。

将代码移至标头内稍微困难一些,因为它大了 3 个字节,但这部分标头与 32 位情况下一样大。我们通过提前开始 2 个字节,覆盖填充字节(好的)和 ABI 版本字段(不好,但仍然有效)来克服这个问题。

因此,我们得出:

bits 64 org 0x08048000 ehdr: ; Elf64_Ehdr db 0x7F, "ELF", 2, 1, ; e_ident _start: mov di,42 ; only the low byte of the exit code is kept, ; so we can use di instead of the full edi/rdi xor eax,eax mov al,60 ; shorter than mov eax,60 syscall ; perform the syscall dw 2 ; e_type dw 62 ; e_machine dd 1 ; e_version dq _start ; e_entry dq phdr - $$ ; e_phoff dq 0 ; e_shoff dd 0 ; e_flags dw ehdrsize ; e_ehsize dw phdrsize ; e_phentsize phdr: ; Elf64_Phdr dw 1 ; e_phnum p_type dw 0 ; e_shentsize dw 5 ; e_shnum p_flags dw 0 ; e_shstrndx ehdrsize equ $ - ehdr dq 0 ; p_offset dq $$ ; p_vaddr dq $$ ; p_paddr dq filesize ; p_filesz dq filesize ; p_memsz dq 0x1000 ; p_align phdrsize equ $ - phdr filesize equ $ - $$

长度为 112 字节。

我暂时停下来,因为我现在没有太多时间。您现在已经有了 64 位的基本布局和相关修改,因此您只需尝试更大胆的重叠


2
投票

更新答案

在阅读了@Matteo Italia的回答中的技巧后,我发现可以达到112字节,因为我们不仅可以隐藏字符串,还可以隐藏EFL标头中的代码。

说明: 关键思想是将所有内容隐藏到标头中,包括字符串“Hello World! ”以及打印字符串的代码。我们应该首先测试标头的哪些部分是可修改的(即修改值并且程序仍然可以执行)。然后,我们将数据和代码隐藏在标头中,如下代码所示:(使用命令编译

nasm -f bin ./x.asm

)

    此源代码基于@Matteo Italia的答案,但完成了未显示的部分:
bits 64 org 0x08048000 ehdr: ; Elf64_Ehdr db 0x7F, "ELF", ; e_ident _start: mov dl, 13 mov esi,STR pop rax syscall jmp _S0 dw 2 ; e_type dw 62 ; e_machine dd 0xff ; e_version dq _start ; e_entry dq phdr - $$ ; e_phoff STR: db "Hello Wo" ; e_shoff db "rld!" ; e_flags dw 0x0a ; e_ehsize, ther place where we hide the next line symbol dw phdrsize ; e_phentsize phdr: ; Elf64_Phdr dw 1 ; e_phnum p_type dw 0 ; e_shentsize dw 5 ; e_shnum p_flags dw 0 ; e_shstrndx ehdrsize equ $ - ehdr dq 0 ; p_offset dq $$ ; p_vaddr _S0: nop nop nop nop nop nop jmp _S1 ; p_paddr, These 8 bytes belong to p_paddr, I nop them to show we can add some asm code here dq filesize ; p_filesz dq filesize ; p_memsz _S1: mov eax,60 ; p_align[0:5] syscall ; p_align[6:7] nop ; p_align[7:8] phdrsize equ $ - phdr filesize equ $ - $$


原帖:

我有一个 129 字节的 x64“Hello World!”。

步骤1。使用

nasm -f bin hw.asm

 编译以下 asm 代码

; hello_world.asm BITS 64 org 0x400000 ehdr: ; Elf64_Ehdr db 0x7f, "ELF", 2, 1, 1, 0 ; e_ident times 8 db 0 dw 2 ; e_type dw 0x3e ; e_machine dd 1 ; e_version dq _start ; e_entry dq phdr - $$ ; e_phoff dq 0 ; e_shoff dd 0 ; e_flags dw ehdrsize ; e_ehsize dw phdrsize ; e_phentsize phdr: ; Elf64_Phdr dd 1 ; e_phnum ; p_type ; e_shentsize dd 5 ; e_shnum ; p_flags ; e_shstrndx ehdrsize equ $ - ehdr dq 0 ; p_offset dq $$ ; p_vaddr dq $$ ; p_paddr dq filesize ; p_filesz dq filesize ; p_memsz dq 0x1000 ; p_align phdrsize equ $ - phdr _start: ; write "Hello World!" to stdout pop rax mov dl, 60 mov esi, hello syscall syscall hello: db "Hello World!", 10 ; 10 is the ASCII code for newline filesize equ $ - $$
步骤2。使用以下Python脚本修改它

from pwn import * context.log_level='debug' context.arch='amd64' context.terminal = ['tmux', 'splitw', '-h', '-F' '#{pane_pid}', '-P'] with open('./hw','rb') as f: pro = f.read() print(len(pro)) pro = list(pro) cut = 0x68 pro[0x18] = cut pro[0x74] = 0x7c-(0x70-cut) pro = pro[:cut]+pro[0x70:] print(pro) x = b'' for _ in pro: x+=_.to_bytes(1,'little') with open("X",'wb') as f: f.write(x)
您应该是 129 字节的“Hello World”。

[18:19:02] n132 :: xps ➜ /tmp » strace ./X execve("./X", ["./X"], 0x7fffba3db670 /* 72 vars */) = 0 write(0, "Hello World!\n\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 60Hello World! ) = 60 exit(0) = ? +++ exited with 0 +++ [18:19:04] n132 :: xps ➜ /tmp » ./X Hello World! [18:19:11] n132 :: xps ➜ /tmp » ls -la ./X -rwxrwxr-x 1 n132 n132 129 Jan 29 18:18 ./X
    
© www.soinside.com 2019 - 2024. All rights reserved.