内容
题目给了一个libcseccomp.so.2,用seccomptools检查发现程序开了seccomp沙盒,只允许orw和mprotect的系统调用。那就意味着要orw读取flag同时还很可能涉及到内存权限修改。
看下字符串发现给的libc是2.35,大部分的常用攻击可能都不好使了
检查程序,保护全开,即使没有full relro
也没用因为有沙箱。很简单,就是一个orw+mprotect的沙盒以及edit不检查size的堆溢出(不过分配和编辑都要求0x500以内大小),没有别的限制。堆块和size都存在一个数组里最多80个堆块。那主要就是看怎么打2.35的glibc了。另外,如果输入的size大于0x500的话程序会从exit退出。
house of apple 一个看起来似乎是通用的ROP链沙箱orw wp复现
考虑高版本的常用攻击house of apple。利用一次large bin attack将io_list_all劫持,然后合理配置fs并将vtable改成_IO_wfile_jumps
(不唯一),同时将wide_data段改成我们可控的地址。最后在exit触发的时候调用到wide_data->wvtable->0x68偏移处指针来实现劫持控制流(控制流劫持思路不唯一,参考house_of_apple的初始blog)
攻击思路
最终要实现orw shellcode,sc布局在堆上,那么就同时要mprotect让堆可执行。
泄露
沙箱的存在让初始的堆块非常混乱,泄露需要先进行一些堆风水。参考两个wp有两个思路,一种是malloc一个大堆块借助consolidate清空fast和unsortedbin,然后通过溢出和printf 0截断的特性泄露脏数据。另一种就是直接借助切割,不停malloc让unsortedbin里剩下一个last remainder然后借助溢出overlap取出来进行读取。总之我们能借助溢出随意修改size那么泄露不成问题,随便overlap一下应该就行。
1 | # defragment |
1. large bin attack
这里其实就可以开始考虑最后堆布局的问题了(在空间不够的情况下),幸运的是这道题给了我们很多可以用的堆块(管理数组是80个,0x50大小),所以其实乱了就重新申请就行了()。
large bin attack在2.34版本及以上必须让unsorted bin里的chunk比largebin里的chunk小才能进到能触发的分支(glibc只在另一个分支添加了检查,看到有师傅说这个地方就是留一个口子用来测试的不会修复)。公式参考之前的blog:https://zjw1nd.github.io/2024/07/26/How2Heap-7-——Large-Bin-Attack
下面的注释其实是待会布局ROP链的时候用的,这个ROP链调了很久才完全懂,第一个找到的真的思路太清晰了
1 | # large bin attack |
此时我们拿到了IO_list_all的控制权,它指向此时在unsorted bin中的old 19 块。
2. 伪造_IO_list_all
核心是将vtable修改为wfile的,然后将wdata指向我们可控的区域,最后把wdata->wvtable->0x68写为gadget。
注意这里面涉及到的检查(大部分可以全0绕过,write_ptr>write_base要赋值一下)
1 | payload = ( |
yh师傅这个payload涉及了很多东西,静态分析看了半天宣告失败,还得是不停打断点看内存来明白发生了什么。这里先认为它通过一次溢出,伪造好了IO_list_all指向的file结构体。
3. 开启沙盒的orw与ROP链
参考这篇文章,里面提到了很多在后面用到的东西。其实看了上面这个blog感觉其实是做多了的公式打法,只是给我第一次见带来了很大的震撼。
我们要想执行自己的shellcode需要很多东西,自由度高一点或者方便一点的话我们可以用栈迁移(劫持rsp)加上ROP来进行攻击。这里涉及到很多的跳转,先拿出几个关键的节点说一下。
首先是万能的gadget setcontext(glibc 2.35):
1 | <setcontext+61>: mov rsp,QWORD PTR [rdx+0xa0] |
这里要说明,glibc 2.29即以后才变成的rdx寻址,原来都是rdi寻址(偏移也是+53)。
那我们要控制rdx,就要用到下面这段magic_gadget(libc_base+0x167420):
1 | 0x0000000000167420 : mov rdx, qword ptr [rdi + 8] ; mov qword ptr [rsp], rax ; call qword ptr [rdx + 0x20] |
这段gadget可以将rdx变成rdi+0x8然后接着call rdi+0x28. 而要知道我们fsop利用的woverflow最后只传入了一个文件结构体地址的参数。也就是在我们触发劫持控制流的时候rdi会存有io_file结构体的地址!而这是我们堆上的可控的一个地址,这就给了我们机会,通过布局我们理论上就可以劫持rsp到我们想要的堆块上了。
4. 堆块布局
从前往后看,我们的起点是由于rdi保存有fs的信息,要想控制rsp就要控制rdx,控制rdx就要上面的magic_gadget(将rdi相关的值传入rdx)。那么首先我们在fp._wide_data->_wvtable->0x68偏移的地方就要放magic_gadget,它是我们调用的起点。
然后,它会让rdx中保存[rdi+8],并调用[rdx+0x20],因此在rdi+0x28,也就是fs+0x28的地方要放下一跳,也就是setcontext+0x61
。
这还没完,我们刚刚提到rdx变成了[rdi+8],同时我们依赖于它为rsp和下一跳赋值,rsp是[rdx+0xa0],返回的下一跳是[rdx + 0xa8]。因此fs+8的位置要放一个+0xa0后是fake_rsp, +0xa8后是ret的一个地址。不过堆上我们随便写 所以挑一个风水宝地放上fake_rsp和ret就好。fake_rsp要指向我们写入的mprotect+orw的shellcode。
然后就有了上面看到的内容,yh师傅选择在刚刚的large堆块后面+0x200来写,其实随便挑地方都可以的。
5. wdata,wvtable和shellcode
wdata和wvtable选择了一个复用结构(只用0x68和0xe0, 0xe0作为vtable指向自己的开头正好错位访问0x68)
1 | wdata = fit( # overlap 等于wide_vtable和wide_data在一起 |
总结
这道题只是house of appe的一条链,还有其他的利用手段(v3,v2其他两条调用链)。这道题看wp感觉要疯了,真正打通之后又觉得也就那样。。难点还是见得少,现在开始感觉要多刷题了。