Минимальный ELF руками: 132 байта и exit(42)

28 июля 2025 · 7 минут

Обычный «hello world» на C после компоновки весит около 16 КБ. Большая часть — служебные секции, которые ядру для запуска не нужны. Ядру достаточно заголовка ELF и хотя бы одного сегмента PT_LOAD. Соберём такой файл вручную, без компоновщика.

Что нужно ядру

64-байтный заголовок ELF с точкой входа и 56-байтный program header, который говорит: «отобрази этот кусок файла по такому-то адресу с правами на чтение и исполнение». Секции, таблица символов, .dynamic — всё это нужно отладчику и динамическому загрузчику, а не execve.

BITS 64
        org     0x400000
ehdr:   db      0x7f, "ELF", 2, 1, 1, 0   ; 64-bit, LE, версия 1
        times 8 db 0
        dw      2               ; e_type: ET_EXEC
        dw      0x3e            ; e_machine: x86-64
        dd      1               ; e_version
        dq      _start          ; e_entry
        dq      phdr - $$       ; e_phoff
        dq      0               ; e_shoff: секций нет
        dd      0               ; e_flags
        dw      64, 56, 1       ; ehsize, phentsize, phnum
        dw      0, 0, 0         ; shentsize, shnum, shstrndx

phdr:   dd      1               ; p_type: PT_LOAD
        dd      5               ; p_flags: R + X
        dq      0               ; p_offset
        dq      $$, $$          ; p_vaddr, p_paddr
        dq      filesize, filesize
        dq      0x1000          ; p_align

_start: mov     edi, 42
        mov     eax, 60         ; SYS_exit
        syscall
filesize equ $ - $$
$ nasm -f bin tiny.asm -o tiny && chmod +x tiny
$ ./tiny; echo $?
42
$ stat -c %s tiny
132

Почему именно 132

64 байта заголовка, 56 байт program header и 12 байт кода: mov edi, imm32 и mov eax, imm32 по 5 байт, syscall — 2. Дальше можно ужимать, перекрывая заголовки и пряча код в неиспользуемые поля, но это уже спорт ради спорта. Для понимания того, что делает execve, 132 байт хватает с головой.

Что проверить самому

readelf -l tiny покажет единственный сегмент, а strace ./tiny — что между execve и exit нет ни одного системного вызова: ни mmap для libc, ни brk.

Исходники всех вариантов из заметки, включая ужатые до 105 байт: tiny-elf-examples.tar.gz.

elfnasmlinux