5 ms·
A variation, with a CALL/POP in place of the LEA/JMP, also clocks in at 91 bytes: 00000000: 7f45 4c46 50b0 01b2 0e89 c7e8 2000 0000 .ELFP....... ... 00000
by LegionMammal978 3y ago
A variation, with a CALL/POP in place of the LEA/JMP, also clocks in at 91 bytes:
00000000: 7f45 4c46 50b0 01b2 0e89 c7e8 2000 0000 .ELFP....... ...
00000010: 0200 3e00 0f05 eb30 0100 0000 0100 0000 ..>....0........
00000020: 1800 0000 0000 0000 1800 0000 0100 0000 ................
00000030: 5e40 b64d ebde 3800 0100 0000 0000 0000 ^@.M..8.........
00000040: 0100 0000 0000 0000 b0e7 5f0f 0548 656c .........._..Hel
00000050: 6c6f 2c20 776f 726c 6421 0a lo, world!.
0x01:
.ascii "ELF"
push rax
mov al, 1
mov dl, 14
mov edi, eax
call 0x30
0x14:
syscall
jmp 0x48
0x30:
pop rsi
mov sil, 0x4d
jmp 0x14
0x48:
mov al, 231
pop rdi
syscall
0x4d:
.ascii "Hello, world!\n"
I no longer think it's within my ability to get this any shorter, at least not without wackier tricks, like using the fixed header bytes within instructions, or interleaving instructions within each other, or pushing some wild code snippet onto the stack. (Any variation of pushing the string onto the stack takes too many bytes to pad the immediate operands and to put RSP into RSI; running any trivial snippets on the stack simply doesn't make the code any shorter; and in both the last variation and this one, there's no way I can find to put the string address 0x10000004d into RSI and jump to the next block in fewer than 9 instruction bytes. Note that 1 instruction byte in the first block can be saved in both of these variations by popping argc into RAX rather than moving a literal 1 into it, but that extra byte can't be used to save any instructions in later blocks.)
- LegionMammal978 3y agoAs it turns out, the idea of using the fixed header bytes in the instructions isn't so wacky after all! We can move the 0x00000001 at 0x18 into EDI, then use the next two bytes as a JMP to the next block. (Also, I'd totally forgotten that the SYSCALL instruction saves the return address in RCX, which lets us use a no-op read(2) to shave a byte off the address calculation and avoid the assumption that argc == 1.) This technique takes the program down to 86 bytes, the smallest it can become without breaking up the "Hello, world!\n" string: 00000000: 7f45 4c46 500f 05b0 01b1 4851 b20e eb04 .ELFP.....HQ.... 00000010: 0200 3e00 5eb3 e7bf 0100 0000 eb12 0000 ..>.^........... 00000020: 1800 0000 0000 0000 1800 0000 eb12 0000 ................ 00000030: 0f05 935f 0f05 3800 0100 0000 0000 0000 ..._..8......... 00000040: 0100 0000 0000 0000 4865 6c6c 6f2c 2077 ........Hello, w 00000050: 6f72 6c64 210a orld!. 0x01: .ascii "ELF" push rax syscall mov al, 1 mov cl, 0x48 push rcx mov dl, 14 jmp 0x14 0x14: pop rsi mov bl, 231 mov edi, 1 jmp 0x30 0x30: syscall xchg ebx, eax pop rdi syscall 0x48: .ascii "Hello, world!\n" I can't think of any possible trick that would allow the string to be broken up while also saving bytes overall. Perhaps there exists some wild self-modifying code that somehow produces the correct behavior, or perhaps this program truly is the shortest.