sherl0ck's home

AceBear CTF: memo_heap Writeup

01 Feb 2018

I didn’t get a chance to try this challenge out during the CTF, but it was a pretty interesting and fun challenge. The method I am going to describe may not be the most efficient but this is what came to my mind first.

So let’s get started up with the write-up. First for the mitigations –

    gdb-peda$ checksec

    CANARY : ENABLED
    FORTIFY : disabled
    NX : ENABLED
    PIE : ENABLED
    RELRO : FULL

Okay. So pretty much all mitigations are enabled. Now let’s step into the functioning of the binary.

The create memo option basically allocates memory and takes in the input for the following structure-

struct memo {
  char* memodata;
  int memosize;
  int flag;
}

The memodata is a pointer to the data taken as input. The memory for this data is also allocated on the heap. memosize contains the length of memodata. The flag is set on creation of an instance of the structure and is unset whenever the entry is edited. We can view or edit an entry only if it’s flag is set. Thus an entry can be edited only once and can’t be viewed after that. The user has to provide the size of the data. After the creation of an instance of memo, the pointer to that instance is saved in a table in the bss segment.

The edit functionality reads the index to edit and first checks if the flag of the entry to edit is set or not. If not it returns. If the flag is set, then it unset’s it and asks the user for the new data. Here’s an interesting point. It realloc’s the memodata pointer with size same as that of memosize.

The option to show memo reads in the index and checks if the flag is set. If so it displays the memodata, memosize and the flag attributes of the memo structure.

The delete functionality takes in the index and first free’s memodata. It then free’s that particular instance of memo. There is no use-after-free here.

Vulnerability

If we give the size of the memo as zero and then edit it, the edit function basically does –

    realloc(pointer_to_entry, 0);

Now from the man page of realloc –

if size is equal to zero, and ptr is not NULL, then the call is equivalent to `free(ptr)`.  

So the memodata pointer has now been freed. After this, if we delete this same entry, then memodata is freed again we get a double free with which we can corrupt some fastbins.

Memory Leaks

For the libc leak, we first create a memo (say memo ‘A’) with large size (out of the fastbin range). We then create another memo (say ‘B’) of any size. Now delete memo ‘A’. The data chunk will now be a part of the unsorted bin list and thus will have the starting address of the unsorted bin in the first 8 bytes of the data chunk. Now, just create a memo of size 0 (to conserve the address in the first 8 bytes). The chunk that will be allocated will have a size of 0x20 bytes and will be the top part of the unsorted bin chunk that we freed previously. So the first 8 bytes now have a libc address. Just show the memo and we have a libc leak.

For the heap leak, we first delete memo ‘B’. This initializes the fastbin list. We now delete the memo we used to get the libc leak. The data chunk of this memo is now in the fastbin list with first 8 bytes pointing to the previous chunk (so the first 8 bytes contain a heap address). Now again allocate a memo of size 0 (size is zero for the same reason as above). The first 8 bytes of the data chunk contains a heap address. Show the memo to get a heap leak.

The exploit

Since RELRO is full, we can’t overwrite a GOT entry. So I focused on overwriting other targets in the memory – like malloc_hook, free_hook and realloc_hook. My plan was to somehow abuse the fastbin list to allocate a chunk above malloc_hook. I could thus overwrite the malloc_hook with any address. But in order to get a fastbin chunk allocated at a random location, it is necessary that there must be a size somewhere near the target location. Here is view of the memory just before malloc_hook –

malloc_hook

And before free_hook –

free_hook

All byte’s above and near free hook are null. So we can’t allocate a fastbin chunk to free_hook as there is nothing that we can use as size near free_hook. Now, let’s take a look at the area above malloc_hook from this view –

fastbin

We see that we can use the value at the address 0x7ffff7dd1af5 ( = 0x7f ) as the size field for our chunk. If we succeed in getting a chunk here, then we can overwrite both – malloc_hook and realloc_hook. Note that the size of the fastbin chunk that we want to allocate here must be of size 0x70 bytes (the last 3 bit’s of the size field are flag’s).

So now our challenge is to get a chunk allocated above malloc_hook. We can’t directly misuse the double-free vulnerability, as the chunk that is doubly freed has a size of 0x20 and we need to corrupt the next pointer of a free fastbin chunk of size 0x70.

Now imagine that we have a free fastbin chunk of size 0x70. Also, assume that the chunk before this fastbin chunk is also a memodata chunk. So we control the data that goes into the chunk. Now if we forge a 0x21 just before the end of the chunk, then we can use the double free vulnerability to allocate a chunk here. This image may make things clearer –

heap-fastbin

So we can now allocate a chunk that starts at address – 0x55d93bc1d0. The first 8 bytes are prev_size, the next 8 are the size and then the area for chunk data. So we have write access starting from 0x55d93bc1e0 for the next 24 bytes. Thus we can overwrite the next pointer (at address 0x55d93bc1f0) of the blue fastbin. We corrupt it with the address of the location just before malloc_hook (the one with value 0x7f). Once we have this done, we just have to create 2 memos of size 96 (this make’s the actual chunk size as 0x70). So malloc handles this request by allocating chunks from the fastbin of size=0x70. The second chunk that it allocates will be above malloc_hook.

Okay so now for the double free vulnerability. Create a memo (say memo ‘A’) of size zero and another (say memo ‘B’) of any random size. Now edit memo ‘A’. This will result in the memodata of ‘A’ being freed. Now we free memo ‘B’. This prevent’s the binary from aborting with a “double free corruption” message. After this delete memo ‘A’. Here’s an overview of what happen’s –

create(0) – memo A, memodata size = 0
create(200) – memo B, memodata size = 200
edit(A) – fastbin–>memodata(A)
delete(B) – fastbin–>B–>memodata(A)
delete(A) – fastbin–>A–>memodata(A)–>B–>memodata(A)

So if we now create a memo of size 0x18, the memodata(A) will be the memodata for this chunk as well. We overwrite the first 8 bytes with the address of the location where we want a chunk (the address above the chunk with size=0x70). If we again create a memo of size (say) 200 (we need a size outside the 0x20 fastbin), the ‘B’ chunk is taken from the fastbin. Now fastbin contains on the memodata(A) chunk. So if we create a memo of size 0x18 then the memodata chunk will be the target location where we want to write.

So compiling this all together here is a brief summary –

Use double-free vulnerability to allocate a chunk that overlap’s with a free chunk of size 0x70. We then edit the next pointer of the fastbin with our target location above malloc_hook. Allocate two chunks of size 0x70 to get write access in the region around malloc_hook. With the data of the second chunk we can overwrite malloc_hook. Now to decide what to overwrite – malloc_hook or realloc_hook. realloc_hook takes a pointer for an argument. So this is ideal to be overwritten by system. Thus overwrite realloc_hook with system and edit a memo that has its data as ‘/bin/sh’. The edit function’s realloc(ptr,size) is now transalated as system(pointer_to_binsh,size) which gives a shell :).

Here’s my exploit script for the same –

  from pwn import *
  import sys

  HOST='memoheap.acebear.site'
  PORT=3003

  if len(sys.argv)>1:
     r=remote(HOST,PORT)
  else:
     r=process('./memo_heap',env={"LD_PRELOAD":"./memoheap_libc.so.6"})

  libc=ELF("./memoheap_libc.so.6")

  def menu(no):
     r.sendlineafter("Your choice: ",str(no))

  def create(size,data=''):
     menu(1)
     r.sendlineafter("What is size of memo you want to create? ",str(size))
     if len(data)!=0:
         r.sendlineafter("Name of memo: ",data)

  def edit(idx,data=''):
     menu(2)
     r.sendlineafter("Index of memo you want to edit: ",str(idx))
     if len(data)!=0:
         r.sendlineafter("New name of memo: ",data)

  def show(idx):
     menu(3)
     r.sendlineafter("Index of memo you want to show: ",str(idx))
     r.recvuntil("Name: ")

  def delete(idx):
     menu(4)
     r.sendlineafter("Index of memo you want to delete: ",str(idx))

  def getleak():
     create(200,"AAAAAAAA") #0
     create(200,"BBBBBBBB") #1
     delete(0)
     create(0)   #0
     show(0)
     libc.address=u64(r.recvuntil('\n').strip().ljust(8,'\x00'))-0x3c4c38
     log.info("libc @ " +hex(libc.address))
     delete(1)
     delete(0)
     create(0)   #0
     show(0)
     heap=u64(r.recvuntil('\n').strip().ljust(8,'\x00'))-0xf0
     log.info("heap @ " +hex(heap))
     return heap

  def exploit(heap):
     create(150,"/bin/sh\x00") # 1
     create(0) # 2
     create(20,"EEEEEEEE") # 3
     delete(3)
     create(70,"A"*48+p64(0)+p64(0x21)) # 3
     edit(2)
     delete(3)
     delete(2)
     create(20,p64(heap+0x1d0)) #2 #overwrite next ptr of fastbin 0x20
     create(96,'w'*8) #3
     delete(3)
     create(200,"Q"*16)    #3
     create(24,p64(0)+p64(0x71)+p64(libc.address+0x3c4aed))  #4 #overwrite next ptr of fastbin 0x70
     create(96,'w'*16)   #5
     create(96,"A"*11+p64(libc.symbols['system'])) # overwritting realloc_hook
     edit(1)

  if __name__=='__main__':
     heap=getleak()
     exploit(heap)
     r.interactive()

And on running the exploit-

memo_heap

Had lots of fun solving this challenge. The CTF itself was pretty good with lots of decent quality challenges. Thanks for reading through!