写在前面
在pwn的泥潭里越陷越深,无法自拔。本次的bctf中,遇到了一个题目,算是我这个萌新的知识盲点,于是,总结一下,纪念一下自己菜菜的小进步
题目
bctf2017的baby_arena,64位,也是比较简单的一道题吧
漏洞
这个题,有一个任意地址写,位于login功能中,但是只能写’admin’或者’clientele’,而且之后的实践中发现貌似只能写’clientele’:
看变量:
就是说接受16个字符的v3是可以到覆盖到v4的。于是就可以任意写了,但是8个字符覆盖刚好到v4,于是后面的回车符被换成\x00被送到v1里,于是只能写’clientele’了。
还有就是这个题
free以后没有清空堆的内容,然后
这里我们申请刚free的堆块,只向里面写少量数据,就能泄露fd了,于是就能得到地址了
思路
leak libc地址和heap_base地址
这个当然很简单了,就是直接申请堆,然后释放,然后再次申请到那里,就可以读到了:
1 | create(0xa8,'0'*0xa8) |
大致就是这样。
修改global_max_fast
了解过fastbin的童鞋都知道global_max_fast是规定fastbins的最大大小的,而在得到一个fastbin的时候,为了构造fastbin链,方便查找,main_arena会存放fastbin链,main_arena存放fastbin的链时按照fastbin的大小计算位置,所以,如果我们申请的fastbin足够大,就会从main_arena溢出出去,而main_arena下就有file结构体之类比较重要的结构体,我们可以改这些结构体的虚表地址,使得其在查虚表时,查到我们的堆中,而我们在堆中布好假的虚表,就可以改变程序流。
但是这里题目还有一个限制:
我们最大只能申请5999的堆,但是这样的话我们就无法溢出到stdout和stdin了,只能到stderr,但是,stderr我一直触发不了,真的是无语,(吐槽一句,想触发stderr的时候触发不了,不想的时候全是error。。。),于是我换了另一种思路,也是刚好把file结构体学一遍,下面是原理和利用思路
FSOP
基础知识知识
首先看源码:
1 | struct _IO_FILE { |
这是FILE结构体的内容,FILE结构会通过_chain连接形成一个链表,链表头部用全局变量_IO_list_all表示。
接下来看一个函数的源码:
1 | int |
我们看到如果运行到这里
1 | if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base) |
就会调用_IO_OVERFLOW,就会调用vtable,也就是虚表。那么只要满足fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base
我们的虚表中的_IO_OVERFLOW就会调用,而且程序是通过_IO_list_all来查找file结构体的。
什么时候会调用_IO_flush_all_lockp呢?
1.libc执行abort流程时
2.执行exit函数时
3.执行流从main函数返回时
这个触发的条件就会简单很多了。于是,do it!!!
利用
首先,我们得改_IO_list_all,这个位置就在stderr的上面,只要知道libc的地址,我们就能得到_IO_list_all的地址,再通过main_arena的溢出,就可以试这个指针指向chunk。
然后我们只要在chunk上布好满足fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base
的file结构体就好,
其实这个是比较简单的。我们只要把file结构体的内容置0,然后我们使_IO_write_ptr为1,或者更大,开心就好。
但是,我们要考虑的一点是,我们修改后的_IO_list_all中的值是指向chunk的指针,也就是说chunk的prev_size,size是在fakefile结构体里的,当然这个不会有什么大影响,但是,我当时写的时候,就忽略了这一点,使得_IO_write_ptr和vtable都比应在的位置高0x10。
于是,在有prev_size,size的条件下,chunk被我布成这个样子了:
1 | //prev_size,size |
这个0x6020b0-0x18
其实就是我们构建的虚表,这个地址是在login功能中可写的,(上面有提到),当时我们v3溢出到v4赋值到任意地址,v3本身的8个bit也是我们可以写的,于是我把one_gadget写在了v3中,如果找不到地址也可以布在堆里,都是可以的。0x6020b0
是v3的地址,而OVERFLOW在vtable的位置位于偏移0x18处,于是得到0x6020b0-0x18
这时如果我们exit,我们就可以getshell了
pwn!!!
exp:
1 | #!/usr/bin/env python |
于是: