Star CTF heap_master
前言
需要做下大型比赛的题目,包括0CTF/CTF/Balsn CTF等比赛的题,这里先从2019年的CTF开始。这道题主要参考xiaoxiaorenwu的博客,堆的利用确实精彩,这是期末考试前的最后一篇博客(再不复习就要挂了)
heap_master
程序逻辑
程序有仨功能,Malloc/Edit/Free。Malloc分配指定size的块,Edit可以在一块事先分好的随机map处的区域任意编辑,Free释放这块区域内指定位置的chunk。
漏洞利用
使用Edit在map的地址伪造chunk,之后可以释放,分配的size没有限制,可以极大,开始想能用上hitcon那道题map地址同libc地址差值固定,但是没法Edit输入,目测没什么好的利用思路。最后stuck之后查了xxrw和e3pem的博客,花了一天调试,感觉收获了巨多干货。下面从泄露和get shell分别介绍一下。
泄露libc
程序里用printf和puts(仅用write是没办法从stdout泄露的),我们通过修改文件结构体来泄露。
large bin attack修改stdout
之前红帽的比赛中看到陆晨学长用了这个攻击方式,自己也试了一下,能做到的效果是往任意两个地址里写victim_addr(链表插入中使用到的unsorted bin地址)。一般构造方式如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19Malloc(0x320)#0
Malloc(0x410)#1
Malloc(0x20)
Malloc(0x420)#3
Malloc(0x20)
Malloc(0x430)#5
Free(0)
Free(1)
Malloc(0x90)# put 0 to ub & 1 to large bin
Free(3)# now put 3 to ub , now 1 still in large bin
Edit(chunk2+0x8,0x3f1)
Edit(chunk2+0x10,0)
Edit(chunk2+0x18,addr1-0x10)
Edit(chunk2+0x20,0)
Edit(chunk2+0x28,addr2-0x20)
Malloc(0x90)#trigger inserting to large bin
首先我们确定一下这里的chunk2,其fd、bk、fd_nextsize、bk_nextsize构造出main_arena+n(libc相关)(方法是通过构造两个small bin之后释放,Edit原ub的size复原释放,再部分写bk和bk_nextsize为stdout_addr-0x10和stdout_addr+0x19-0x20错位写write_ptr最低一字节和write_base的7字节),需要满足的条件是_flags & 0x1a00 != 0
以及_IO_write_base != _IO_write_ptr
,因为我们写入的是victim_addr
,所以只需要满足victim_addr & 0x1a00 != 0
,我们知道map的地址最低三字节为000,所以a00这个可以通过构造map地址实现,而0x1000这个通过map地址的随机性实现,最终可以成功修改_IO_2_1_stdout,泄露libc地址和map地址。
伪造文件结构体泄露
通过ub attck修改global_max_fast(同样是部分写small bin的残留libc指针),使得我们用到的size对应的chunk基本都进fastbin。我们知道main_arena的+8开始存储的是各个size(0x20-0x80)的fastbin的头指针。一旦突破这个限制之后就可以将main_arena后的地址覆写为map地址
。也就是说我们可以将main_arena后的stdout指针的内容覆写为一个map_addr,进而使得stdout使用map_addr上伪造的文件结构体进行puts和printf。这里的伪造最好先copy一下正常_IO_2_1_stdout_的结构体内容(一直到vtable)。之后部分写里面的内容,最终泄露libc地址和map地址(_IO_write_base和_IO_write_ptr之间的就是泄露的内容,改成stdout地址及其+8即可)。
get flag/shell
get shell的方法也有很多,非常地精妙,本来我是应该把自己复现过程再搞一遍截图发的,但是期末复习时间实在太紧,就只讲思路了,后面会给参考链接去看大佬们的博客有调试详情。原题是glibc 2.25,开了chroot限制get shell,所以只能orw。
_IO_list_all
先用large bin attack改_IO_list_all为map地址,伪造map地址为fake file。在调用exit的时候会执行_IO_flush_all_lockp,经过fflush
获取_IO_FILE_plus调用其中的_IO_str_overflow。
1 | Edit(fake_file+0xd8,_IO_str_jumps) |
跟着调试会发现_IO_str_overflow里调用的参数rdi为map_addr,往后看会有一次赋值操作将rdi+0x28的值放入了rdx,我们在map地址把这个值改成pop rsp;pop r13;ret
的地址,在fake_file+0xe0的位置我们设置其值为pop rbx ; pop rbp ; jmp rdx
的地址,最后会跳转到pop rsp这里,进而将栈劫持到我们的map地址处。最后构造orw读取flag。
_dl_open_hook && setcontext+53
这里有个有趣的知识,是官网wp给的。通过largebin attack将_dl_open_hook覆盖为map_addr,通过malloc或者free报错的方式,程序会把该值加载到寄存器,再call寄存器,在题目的libc下,寄存器为rbx。
题目的libc还有这样一个gadges:1
2
30x7FD7D: mov rdi, [rbx+48h]
mov rsi, r13
call qword ptr [rbx+40h]
我们通过设置rbx+0x48可以设置rdi,设置rbx+0x40可以设置call_func。我们的call_func设置的是setcontext+53
,有了rdi,可以控制rdi+0xa8
,即rcx
,这是最后会调用的函数地址,以及rdi+0xa0
,这是函数执行完会ret到的地址,所以我们设置参数,调用mprotect修改map地址属性为rwx,ret到map里存shellcode的地方执行orw。
1 | 0x7ffff7a7a565 <setcontext+53>: mov rsp,QWORD PTR [rdi+0xa0] |
利用fastbin放入fastbinY的性质
先拿ub改global_max_fast,之后可以通过计算算出free_hook这里作为fastbinY应当存的fastbin的size,之后释放一个伪造成这个size的chunk,从而使得free_hook里放入这个map_addr,再修改map_addr->fd为system,分配得到这个chunk,即可让__free_hook里写入system(原理同fastbin改fd是一样的)。
除了释放,我们也可以直接largebin attack改_free_hook为victim地址。计算改size,再分配这个chunk,最后是一样的效果。
_IO_list_all.py
1 | #coding=utf-8 |
_dl_open_hook_setcontext.py
1 | #coding=utf-8 |
leak_by_ub_attack.py
这里泄露之后本想结束的,后来看xxrw里介绍了另一种orw的方法,利用__morecore,这个函数在main_arena+2024+0xa8处,它是用来平衡栈平衡的。我们用IO_list_all伪造文件结构体,调用_IO_str_overflow设置调用的rdi和调用函数,最后去调用setcontext(main_arena+2024),在
(rdi+0xa0)利用之前的fastbinY的利用设置为rop_chain地址,在(rdi+0xa8)设置为刚才的morecore,最后成功跳到rop_chain。
1 | 0x7f09c5962308 <main_arena+2024>: 0x00007f09c59622f8 0x00007f09c59622f8 |
伪造文件结构体绕过
1 | 1. fp->_flags & _IO_NO_WRITES为假 |
伪造的文件结构体
1 | _IO_FILE = ( p64(0) + |
1 | #coding=utf-8 |
fatbinY.py
1 | #coding=utf-8 |