题目信息
测试运行
直接运行什么也不会显示,大概10秒后killed
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019081501.jpg)
strace
$ env -i strace -fi ./tp
- 省略前面的加载library部分
- 有一个alarm(10)的时间限制(造成killed的原因)
- clone,pipe2,prctl, syscall_317(seccomp)之类的,看起来像sandbox的system call。也有一个可疑的SIGSTOP信号
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019081502.jpg)
checksec
防御全开
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019081503.jpg)
vmmap
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019081504.jpg)
- 没有自身的elf
- 一个非常大的mmap
开启ASLR运行
$ gdb -q ./tp -ex 'set disable-randomization off'
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019081505.jpg)
这次正常了,也就是说,如果关掉ASLR就不能正常运行
Stage1
- 暂时不需要考虑绕过sandbox
- sandbox部分在Stage2
- 这部分主要是pwn
- 目标是读取/home/tp/flag1
解析 - 整体构成
- 父进程(sandbox)
- 子A(sandbox)
- 子B(main,note管理类)
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019081506.jpg)
子B主要5个功能,内部实现是红黑树,node持有指向note的指针,note持有指向data的指针
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019081507.jpg)
解析 - 主操作部分
do_command
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019081508.jpg)
- 右侧:恢复点
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019081509.jpg)
- 读取command
- 根据command进行分支
- 常规的note管理类服务
command0_new
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019081510.jpg)
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019081511.jpg)
- 接受要创建的note的size
- 但不对size进行检查
- 在没有对size检查的情况下生成新note
- 搜索已经free的note,如果存在,会再次使用它
build_node_and_insert_note
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019081512.jpg)
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019081513.jpg)
- 初始化与node相连的note
- 搜索能够构建tree的node插入点
- 初始化要插入tree的node
- 将node插入tree
- 在没有检查size的情况下,分配连接到note的buffer
- new(buffer)如果失败,产生std::bad_alloc异常,调用handler
恢复点
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019081514.jpg)
- note使用的buffer(data)如果存在,则释放它
- note自身也释放
- 但是data和note还是连接在一起的
- 这就产生了Use After Free
command1_read
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019081515.jpg)
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019081516.jpg)
- 接收note的id,以及要读取的size
- 搜索指定id的note
- 读取note的data
command2_write
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019081518.jpg)
- 接收note的id,以及要写入的size
- 搜索指定id的note
- 写入note的data
command3_free
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019081519.jpg)
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019081520.jpg)
- 接收要变更为不可利用的note的id
- 搜索指定id的note
- clear used 比特位
command4_delete
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019081521.jpg)
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019081522.jpg)
- 接收要废弃的note的id
- 搜索指定id的note
- 释放node和note
攻略(stage1)
环境
创建tp用户,创建测试flag:
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019082001.jpg)
不需要alerm(10),因为有PIE,不能用peda的deactive,直接patch为alerm(0)
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019082002.jpg)
之后socat启动服务,因为有ASLR,不能使用gdbserver(禁用ASLR时,二进制的.text段会消失)。所以这次需要使用gdb attach:
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019082003.jpg)
终端1启动服务,终端3使用gdb attach,终端2为攻击者发送数据:
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019082004.jpg)
pgrep -w <filename>
可以筛选LWP(包括线程的进程)列表,之后使用tail -1
选择最后一个生成的线程,也就是只选择子B。- 但是,因为有PIE,每次attach,text的基地址都会改变
根据attach后stack上的各种值,可以计算出PIE的base:
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019082005.jpg)
- 通过vmmap检查PIE的base,这次是0x7fdc7bdcc000
- 根据stack上的值计算出偏移:0x7fdc7bdce34f - 0x7fdc7bdcc000= 0x234f (diff)
*$rsp-0x234f == PIE的base,set $base = *(void**)$rsp-0x234f
,即使有PIE,偏移量是不会改变的,因此我们可以通过每次的rsp计算出PIE的base
生成command文件
将禁用SIGSYS信号的stop,print,读取heap信息,计算base等命令写到文件里,再次gdb attach:
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019082006.jpg)
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019082007.jpg)
将程序各个功能封装成函数:
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019082008.jpg)
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019082009.jpg)
生成dump函数
确认UAF之前,准备好dump node和note信息的函数比较好
- 这样会非常效率
- 便于理解heap内部情况
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019082010.jpg)
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019082011.jpg)
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019082012.jpg)
可以看到生成了一个存在note,buffer为NULL的异常node
Pwn - 生成共有状态
利用UAF可以达到任意内存读写
- 但首先需要生成共用内存的状态
- 即存在两个指针A和B,指向同一块内存区域
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019082013.jpg)
1. _new(size=1)
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019082014.jpg)
创建了一个size为1的note,之后tree是这样的
2. _new(size=1)
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019082015.jpg)
- 创建一个后面用于覆盖的元素
- 作为id为0的node的父node,插入到tree中
3. _new(size=-1)
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019082016.jpg)
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019082017.jpg)
- alloc_note内部,首先生成note
- 接下来准备生成data
- 但是因为size是负数,data生成失败,malloc内部生成了thread_arena
- 刚才生成的note被free后,来自node的引用仍然存在
- fastbins与这个note相连接,arena切换,这个note已经不能被使用
4. _new(size=-1)
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019082018.jpg)
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019082019.jpg)
- 同样,先在alloc_note内部,首先生成note
- 之后,data生成失败,如果已经存在thread_arena,malloc就不会再创建新的arena
- 因为data生成失败,data被free,来自node的引用仍然存在
- bins和note相连接
5. _free(id=1)
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019082020.jpg)
- tree开头的节点,id=1的note设置为used=0
- 如果存在used=0的note,它的size与指定的size不一致,下次alloc_note时,先free(data),然后重新分配一次
6. _new(size=0x28)
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019082021.jpg)
- 因为used=0的note的id=1,它将被再次使用
- 但是size不一致,会先将id=1的datafree掉
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019082022.jpg)
- 之后的malloc,会从thread_arena生成data
- 因为bins中存在sz=0x28的chunk,会直接再次使用,作为id=1的data
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019082023.jpg)
- 这时候,id=1的data和id=3的data是共有状态
- 但是,id=3的note,实际上id信息已经破坏,因此需要修正
Pwn - heap leak
生成了共有状态
- 接下来,对id=1的data的变更操作,也会对id=3的note的data操作
- 可以做任意读写
- 如果可以指定GOT地址进行读取,就不需要考虑libc相关地址
- 也就是说,无视ASLR
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019082101.jpg)
- 但是因为这里同时有PIE+ASLR,我们不知道GOT(.text)的地址
- 可以利用Partial Overwrite,来leak内存
- 首先需要泄漏heap的地址(thread_arena的mapped区域)
7. _read(id=1, data=pQ(0x1337))
- 更新id=1的data
- 这会导致id=3的note更新,变成id=0x1337
- 这时,id=0x1337变成used=0
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019082102.jpg)
8. _new(size=0x100)
- 因为找到的used=0的note的id=0x1337,通过alloc_note对这个note再次使用
- id=0x1337,生成data
- size=0x100,不在bins里的小的size就可以
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019082103.jpg)
- 这时,id=0x1337的note上,生成了指向data的指针
- 这个指向data的指针,指向的是从thread_arena的mapped区域申请的chunk
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019082104.jpg)
9. _read(id=1, data=pQ(0x1337) + “\x70\x08”)
- 使用id=1,来覆盖指向id=0x1337的note的data的指针,只能写两个字节
- 指向0x1337的data的指针,指向的是thread_arena内部的bin上的一个chunk
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019082105.jpg)
- 可以看到,指向data的指针,后两个字节已经被重写
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019082106.jpg)
- 这次是指向mapped+0x870作为data
- 里面是mapped+0x858
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019082107.jpg)
10. _write(id=0x1337, size=8)
- 通过写出id=0x1337,可以泄漏mapped+0x858之类的值
- 泄漏的值减去0x858就能够知道heap的base地址
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019082108.jpg)
Pwn - libc leak
- 能够泄漏heap的base地址
- 附近(mapped+0x888)存在和libc相关的地址
- 从thread_arena到main_arena的next指针
- 这个也可以被泄漏
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019082201.jpg)
11. _read(id=1, data=pQ(0x1337) + “\x88\x08”)
- 使用id=1,来覆盖指向id=0x1337的note的data的指针,只能写两个字节
- 指向0x1337的data的指针,指向的是thread_arena的next
- next中是main_arena的地址
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019082202.jpg)
12. _write(id=0x1337, size=8)
- 通过写出id=0x1337,可以泄漏main_arena的地址
- 泄漏的值减去0x3be760就能够知道libc的base地址
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019082203.jpg)
Pwn - stack leak
- 能够泄漏libc的base地址
- 结合偏移量就能够计算出libc中各种函数地址
- 但计算出system函数并不能结束
- 因为有sandbox,system,execve之类的不能使用
- 但是heap操作必要的,mprotect/mmap这类是被允许的
- 最快的方法是,通过mprotect使heap权限为RWX,然后在heap中加载执行open()->read()->write()这样的shellcode
- 因此,策略是覆盖stack返回地址,执行mprotect()->read()这样的ROP
13. _read(id=1, data=pQ(0x1337)+pQ(libc_base+0x3c14a0))
- libc中存在指向stack的
__environ,__libc_stack_end
变量 - 这里是将id=0x1337的note的data变更为
__environ
,也就是libc_base + 0x3c14a0
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019082204.jpg)
14. _write(id=0x1337, size=8)
- 通过写出id=0x1337,可以泄漏stack上envp[]的地址
- 泄漏的值减去0x200就是储存read()函数返回地址的地方
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019082205.jpg)
Pwn - stack伪造
15. _read(id=1, data=pQ(0x1337)+pQ(ret)+pQ(len(rop)))
- 让id=0x1337的note指向ret
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019082206.jpg)
16. _read(id=0x1337, data=rop)
- 之后在ret附近写入ROP
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019082207.jpg)
stage1的exploit
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019082208.jpg)
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019082209.jpg)