我最近看到这篇文章描述了Linux的最小可能的ELF可执行文件,但是这篇文章是为32位编写的,我无法获得在我的机器上编译的最终版本。
这让我想到一个问题:可以编写且运行无错误的最小 x86-64 ELF 可执行文件是什么?
滥用或违反 ELF 规范是可以的,只要当前的 Linux 内核实际上能够运行它,就像 MuppetLabs 32 位 teensy ELF 可执行文章的最后几个版本一样。
从我的一个关于 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 位的基本布局和相关修改,因此您只需尝试更大胆的重叠
在阅读了@Matteo Italia的回答中的技巧后,我发现可以达到112字节,因为我们不仅可以隐藏字符串,还可以隐藏EFL标头中的代码。
说明: 关键思想是将所有内容隐藏到标头中,包括字符串“Hello World! ”以及打印字符串的代码。我们应该首先测试标头的哪些部分是可修改的(即修改值并且程序仍然可以执行)。然后,我们将数据和代码隐藏在标头中,如下代码所示:(使用命令编译
nasm -f bin ./x.asm
)
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
; 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