Skip to main content
We’ve updated our Terms of Service. A new AI Addendum clarifies how Stack Overflow utilizes AI interactions.
Code Golf

Return to Answer

Add packed elf version at 52 bytes
Source Link
EasyasPi
  • 5.4k
  • 20
  • 23

ARM Thumb-2 Linux ELF executable, 52 bytes

We can actually pack the first version into an ELF executable quite nicely using the muppet labs method , for a full program at 52 bytes:

xxd -g1 :

00000000: 7f 45 4c 46 01 00 00 00 00 00 00 00 00 00 01 22 .ELF..........."
00000010: 02 00 28 00 1b 00 01 22 1b 00 01 22 04 00 00 00 ..(...."..."....
00000020: 03 27 f8 1e 69 46 00 df 47 43 20 00 01 00 87 f0 .'..iF..GC .....
00000030: 07 07 f6 e7 ....

Assembly with comments (le ugly hack intensifies):

// Adaptation of
// https://www.muppetlabs.com/~breadbox/software/tiny/teensy.html
// for an ARM cat program.
// $ arm-none-eabi-as -march=armv6t2 -mthumb cat-arm.s -o cat-arm.o
// $ arm-none-eabi-ld -Ttext 0x22010000 cat-arm.o -o cat-arm.elf
// $ arm-none-eabi-objcopy -O binary cat-arm.elf cat-arm
 .text // A lie.
 .arch armv6t2 // Set architecture version
 .thumb // Needs to be Thumb to fit. ARM is too thicc.
 .syntax unified
 .org 0 // Start at offset 0
Elf32_Ehdr:
 .byte 0x7F,'E','L','F' // e_ident
Elf32_Phdr:
 .word 1 // p_type
 .word 0 // p_offset
 // squeeze an instruction into the start offset
 // 0x2201 -> movs r2, #1
 .word 0x22010000 // same as -Ttext // p_vaddr
 .short 2 // ET_EXEC // e_type // p_paddr
 .short 40 // EM_ARM // e_machine
 .word _start + 2 // e_version // p_filesz
 .thumb_func
 .globl _start
_start:
 // start halfway into this word, it will be an instruction
 // movs r2, #1 // len = 1
 .word _start + 2 // e_entry // p_memsz
 // 0004 0000 -> just messing with r4 and r0, harmless
 .word 4 // Elf32_Phdr // e_phoff // p_flags
 // start with r7 == write (3)
 movs r7, #3 // write // e_shoff // p_align
.Lloop:
 subs r0, r7, #3 // fd (0 or 1)
 mov r1, sp // buffer is sp // e_flags
 swi #0 // do syscall
 // We multiply the syscall by r0.
 // On an error, r0 will be zero or negative.
 muls r7, r0 // e_ehsize
 // finish the ELF header. This is basically harmless.
 // movs r0, r4
 .short 0x20 // e_phentsize
 // movs r1, r0
 .short 1 // e_phnum
 // If r0 was 1, flip between read (3) and write (4) with xor.
 // Otherwise, any negative or zero value will set this to a
 // bad syscall, raising a SIGSYS.
 // Yes, ARM doesn't have syscall 7.
 eor.w r7, r7, #7
 // loop indefinitely
 b .Lloop

One cool thing is that we don't need to branch over any header fields: 00xx is a simple movs, which we can deal with easily.

I actually start halfway into e_entry itself, using a custom p_vaddr which acts as an instruction, and ignoring the next two instructions.

ARM Thumb-2 Linux ELF executable, 52 bytes

We can actually pack the first version into an ELF executable quite nicely using the muppet labs method , for a full program at 52 bytes:

xxd -g1 :

00000000: 7f 45 4c 46 01 00 00 00 00 00 00 00 00 00 01 22 .ELF..........."
00000010: 02 00 28 00 1b 00 01 22 1b 00 01 22 04 00 00 00 ..(...."..."....
00000020: 03 27 f8 1e 69 46 00 df 47 43 20 00 01 00 87 f0 .'..iF..GC .....
00000030: 07 07 f6 e7 ....

Assembly with comments (le ugly hack intensifies):

// Adaptation of
// https://www.muppetlabs.com/~breadbox/software/tiny/teensy.html
// for an ARM cat program.
// $ arm-none-eabi-as -march=armv6t2 -mthumb cat-arm.s -o cat-arm.o
// $ arm-none-eabi-ld -Ttext 0x22010000 cat-arm.o -o cat-arm.elf
// $ arm-none-eabi-objcopy -O binary cat-arm.elf cat-arm
 .text // A lie.
 .arch armv6t2 // Set architecture version
 .thumb // Needs to be Thumb to fit. ARM is too thicc.
 .syntax unified
 .org 0 // Start at offset 0
Elf32_Ehdr:
 .byte 0x7F,'E','L','F' // e_ident
Elf32_Phdr:
 .word 1 // p_type
 .word 0 // p_offset
 // squeeze an instruction into the start offset
 // 0x2201 -> movs r2, #1
 .word 0x22010000 // same as -Ttext // p_vaddr
 .short 2 // ET_EXEC // e_type // p_paddr
 .short 40 // EM_ARM // e_machine
 .word _start + 2 // e_version // p_filesz
 .thumb_func
 .globl _start
_start:
 // start halfway into this word, it will be an instruction
 // movs r2, #1 // len = 1
 .word _start + 2 // e_entry // p_memsz
 // 0004 0000 -> just messing with r4 and r0, harmless
 .word 4 // Elf32_Phdr // e_phoff // p_flags
 // start with r7 == write (3)
 movs r7, #3 // write // e_shoff // p_align
.Lloop:
 subs r0, r7, #3 // fd (0 or 1)
 mov r1, sp // buffer is sp // e_flags
 swi #0 // do syscall
 // We multiply the syscall by r0.
 // On an error, r0 will be zero or negative.
 muls r7, r0 // e_ehsize
 // finish the ELF header. This is basically harmless.
 // movs r0, r4
 .short 0x20 // e_phentsize
 // movs r1, r0
 .short 1 // e_phnum
 // If r0 was 1, flip between read (3) and write (4) with xor.
 // Otherwise, any negative or zero value will set this to a
 // bad syscall, raising a SIGSYS.
 // Yes, ARM doesn't have syscall 7.
 eor.w r7, r7, #7
 // loop indefinitely
 b .Lloop

One cool thing is that we don't need to branch over any header fields: 00xx is a simple movs, which we can deal with easily.

I actually start halfway into e_entry itself, using a custom p_vaddr which acts as an instruction, and ignoring the next two instructions.

Add note about fork fail.
Source Link
EasyasPi
  • 5.4k
  • 20
  • 23

I had an epic fail when writing this.

I thought "oh, I can switch between read and write by XORing 1".

And so I tried it, and it didn't work.

I ran echo "test" | ./a.out aaaaaand my phone froze.

3 XOR 1 == 2. Syscall 2 is fork. I accidentally fork bombed my phone because I can't do math. πŸ˜‚

I don't have a way to make an interactive demo yet until someone adds QEMU to TIO.

I don't have a way to make an interactive demo yet until someone adds QEMU to TIO.

I had an epic fail when writing this.

I thought "oh, I can switch between read and write by XORing 1".

And so I tried it, and it didn't work.

I ran echo "test" | ./a.out aaaaaand my phone froze.

3 XOR 1 == 2. Syscall 2 is fork. I accidentally fork bombed my phone because I can't do math. πŸ˜‚

I don't have a way to make an interactive demo yet until someone adds QEMU to TIO.

Source Link
EasyasPi
  • 5.4k
  • 20
  • 23

ARM Thumb-2 machine code (Linux, no libc), 18 bytes

This version exits quite impolitely by crashing with Bad system call.

However, the program itself doesn't print anything to stderr, the shell does. 😏

Machine code:

4669 2201 2703 1ef8 df00 4347 f087 0707
e7f9

Commented assembly:

 .syntax unified
 .arch armv6t2
 .thumb
 .globl _start
 .thumb_func
 // cat program (exits with SIGSYS)
 // read = syscall 3, write = syscall 4
_start:
 // use sp as a pointer for read/write
 mov r1, sp
 // one byte
 movs r2, #1
 // start with read (3)
 movs r7, #3
.Lloop:
 // read (3) - 3 = 0 = stdin, write (4) - 3 = 1 = stdout
 subs r0, r7, #3
 // syscall
 svc #0
 // We multiply the syscall by r0.
 // On an error, r0 will be zero or negative, setting r7 to that
 muls r7, r0
 // If r0 was 1, flip between read (3) and write (4) with xor.
 // Otherwise, any negative or zero value will set this to a
 // bad syscall, raising a SIGSYS.
 // Yes, ARM doesn't have syscall 7.
 eor.w r7, r7, #7
 // loop indefinitely
 b .Lloop

If read and write return 1, the syscall will be multiplied by 1, doing nothing, and the eor.w will flip it between read (3) and write (4) by XORing 7.

If read or write returns zero, the multiply will set r7 to 0 XOR 7, so it will try to run syscall 7, which, for some reason, doesn't exist on ARM. Therefore it crashes with a bad system call.

If it returns negative, well, there obviously aren't any negative syscalls, and it also crashes with a bad system call.

Otherwise, it is a fairly standard read/write loop, aside from using syscall arithmetic to swap between stdin and stdout.

No errors, 22 bytes

Machine code:

4669 2201 2703 1ef8 df00 2801 bf14 2701
f087 0707 e7f7

Commented assembly:

 .syntax unified
 .arch armv6t2
 .thumb
 .globl _start
 .thumb_func
 // cat program (clean exit)
 // AFAIK, there is nothing that is smaller than 22 bytes that
 // exits nicely.
 // exit = syscall 1, read = syscall 3, write = syscall 4
_start:
 // use sp as a pointer for read/write
 mov r1, sp
 // one byte
 movs r2, #1
 // start with read (3)
 movs r7, #3
.Lloop:
 // read (3) - 3 = 0 = stdin, write (4) - 3 = 1 = stdout
 // exit doesn't really matter
 subs r0, r7, #3
 // syscall
 svc #0
 // On an error, r0 will not be 1.
 cmp r0, #1
 ite ne
 // If we failed, set the syscall to exit (1).
 movne r7, #1
 // Otherwise, flip between read (3) and write (4) with xor.
 eoreq.w r7, r7, #7
 // loop indefinitely
 b .Lloop

This is roughly the same thing, only it wastes 4 bytes setting the syscall to exit when the syscall fails.

I don't have a way to make an interactive demo yet until someone adds QEMU to TIO.

AltStyle γ«γ‚ˆγ£γ¦ε€‰ζ›γ•γ‚ŒγŸγƒšγƒΌγ‚Έ (->γ‚ͺγƒͺγ‚ΈγƒŠγƒ«) /