Самая маленькая исполняемая программа (x86-64) - PullRequest
0 голосов
/ 20 ноября 2018

Я недавно натолкнулся на этот пост , описывающий наименьший возможный исполняемый файл ELF, однако пост был написан для 32-битной версии, и я не смог получить окончательную версию для компиляции на моей машине.Это подводит меня к вопросу: какой самый маленький исполняемый файл x86-64 ELF, который можно написать, работает без ошибок?

1 Ответ

0 голосов
/ 20 ноября 2018

Начиная с из моего ответа о "реальной" точке входа исполняемого файла ELF в Linux и "сырых" системных вызовах, мы можем сократить ее до

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 байтов (в отличие от 7 байтов 32-битной полезной нагрузки):

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

Простой способ (собрать с nasm, ссылка с ld) дает мне исполняемый файл размером 352 байта.

Первое «настоящее» преобразование, которое он делает, - это построение ELF «вручную»; делать это (с некоторыми изменениями, так как заголовок ELF для x86_64 немного больше)

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-битной системы, поэтому вам просто нужно поэкспериментировать с более смелыми перекрытиями

...