Skip to content

Instantly share code, notes, and snippets.

@easyaspi314
Last active February 3, 2021 01:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save easyaspi314/3afcce27a97938dab9e91c687f0a7125 to your computer and use it in GitHub Desktop.
Save easyaspi314/3afcce27a97938dab9e91c687f0a7125 to your computer and use it in GitHub Desktop.

My solution to https://codegolf.stackexchange.com/a/217992/94093

        .text
        .arch armv5te
        .arm
        .globl _start
_start:
        ldr     r0, =0xe38f0001
        ldr     r0, =0xe12fff10
        ldr     r0, =0xa1032001
        ldr     r0, =0x27042207
        ldr     r0, =0x2701df00
        ldr     r0, =0xdf002000
        ldr     r0, =0x42737062
        ldr     r0, =0x002e5350

The way we solve this problem is with the ldr pseudo-instruction:

        ldr     r0, =constant

If the assembler can't translate this to a mov, it will place it in a pool after the last instruction and use a PC relative load.

Therefore, we can just encode the instructions manually inside the literal pool.

We must switch to Thumb first, as the syscall instruction, svc #0, is encoded as 0xef000000, which can be encoded in a mov. Thumb instructions are also half the size. 😉

The ldrs themselves just set r0 to nonsense values, and are, for our intents and purposes, a nop sled.

After the ldr sled, the constant pool will be interpreted as the following code:

        .arm
real_start:
        orr     r0, pc, #1
        bx      r0
        .thumb
.Lthumb_start:
        movs    r0, #1
        adr     r1, .Lstr
        movs    r2, #7
        movs    r7, #4
        svc     #0 // write(1, .Lstr, 7)
        movs    r7, #1
        movs    r0, #0
        svc     #0 // exit(0)
.Lstr:
        .asciz  "bpsBPS."

The first two instructions are a typical "ARM to Thumb" springboard. Whenever bx, pop {pc}, bl, or blx attempt to jump to an odd address, the CPU will switch to Thumb and read 16-bit instructions instead.

Note that the PC points two instructions ahead of the current instruction, that is why we just |1 instead of +9.

        orr     r0, pc, #1
        bx      r0

After that, it is a pretty standard write() followed by exit(), using svc #0 for syscalls.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment