Tags: emulator kernel pwn
As description states, this challenge pretty much contains a userland binary, a kernel binary, and an emulator (written using the unicorn engine emulator framework) that emulates the userland and kernel together.
[You can find the final exploit script here](https://github.com/Super-Guesser/ctf/blob/master/pbCTF2020/pwn/pwnception/exploit.py).
The userland binary is a brainfuck interpreter. In brainfuck, you have whats called a **data pointer** that you can shift forwards and backwards using the `>` and `<` operators respectively. The data pointer in the userland binary pointed to a stack buffer, and there were no bounds checks on the `>` and `<` operators, so you could shift the data pointer up the stack to the return address and modify it. This was the userland bug you could use to craft your ROP chain on the stack and return to it.
Once you have code execution in the userland, you can talk to the kernel. The kernel was also a very small binary. Only the `sys_read`, `sys_write`, and `sys_open` system calls were implemented. `sys_open` had a stack buffer overflow where it copied the filename from userland to a kernel stack buffer without doing any bounds checking. The only catch was that the copy would stop at null bytes, so I had to find a nice way to ROP, map a page as RWX for shellcode, read shellcode into it, and finally jump to the shellcode.
Once you have code execution in the kernel, you are able to call `malloc` and `free` inside the emulator at will through the `int 0x71` interrupt. You are only ever allowed access to one malloc'd chunk at a time, and when freeing the chunk, the pointer is not zeroed, which results in a UAF. You are also allowed to read / write the chunks contents at will (any size), which can be used for a heap overflow (although I didn't do that).
I first used the UAF to leak a `libunicorn.so.1` address, then I did a tcache dup attack to leak the address of `vasprintf@LIBC` from the GOT of `libunicorn`, and finally I did a second tcache dup attack to overwrite `__free_hook` with `system` to get a shell.