BITS 64 org 0x01000000 hello: db 0x7F, "ELF" ; e_ident db "o, world!", 10 db 0, 0 dw 2 ; e_type dw 62 ; e_machine dd 1 ; e_version dq _start ; e_entry dq phdr - $$ ; e_phoff ; e_shoff cont2: mov AL, 4 ; write = 4 int 0x80 xchg EAX, EDI ; exit(0) xchg EAX, EBX ; exit = 1 int 0x80 phdr: dd 1 ; e_flags & p_type dw 7 ; e_ehsize & p_flags dw 56 ; e_phentsize & p_flags dw 1 ; e_phnum & p_offset dw 0 ; e_shentsize & p_offset dw 0 ; e_shnum & p_offset dw 0 ; e_shstrndx & p_offset dq $$ + 1 ; p_vaddr ; p_paddr _start: inc EBX ; stdout = 1 mov DL, 14 ; strlen = 14 inc ECX jmp cont dq filesize - 1 ; p_filesz ; p_memsz cont: shl ECX, 24 db 0x25 ; and EAX, 0 (fall through) db 0, 0, 0, 0 ; p_align xor dword[RCX], 0x2a202037 jmp cont2 filesize equ $ - $$I also created a spreadsheet to explain this. If this is interesting to you, you may want to check my collection as well. My x86-64 code is much bigger than 58B hello because both ELF header and program header on x86-64 are much bigger than on x86-32. I couldn't find a better way to overlap ELF header and program header, and my code has all code and data in these headers. So, I'm assuming 104B is optimal. Although this work should be easier than binary golf for x86, there were a few challenges:
- 1 byte inc/dec has gone.
- As mmap for small addresses isn't allowed on recent linux (see /proc/sys/vm/mmap_min_addr), I used addresses bigger than 16bit. In fact, 58 bytes hello for x86-32 won't work due to this reason on recent Linux distributions. I needed to use inc&shl to set the address of "Hello, world!\n" to ECX.
- As we cannot access 0x0000-0x1000, most data cannot be executed. For example, 0x0000 is add [EAX], EAX.