环境搭建
论文链接:https://www.ndss-symposium.org/wp-content/uploads/2023/02/ndss2023_s232_paper.pdf
仓库链接:https://github.com/Stab1el/BAGUA
方法一(暂时失败/废弃)
选择docker搭建,因为代码中用到了python2;
安装pip2:
安装z3:
安装z3-solver:
路径:/home/qym/qym/heap/database/BAGUA
cd /home/qym/qym/heap/database/BAGUA |
(也可以直接运行start.sh脚本启动docker环境)
进入docker之后:
cd /bagua |
方法二
利用工具2to3将python2转换成python3:
import os |
安装z3和z3-solver:
pip3 install z3 |
源码分析
重点看constraint_generator.py这个文件,其中有一个solve_primitive_equation函数,
首先看原语的处理:
首先是循环处理每一个目标;
对于每一个目标,迭代遍历所有的原语;
对于每一个原语,得到的是一个原语对目标cache/chain的所有可能影响(这个可能是glibc有多种可能,然后通过笛卡尔积枚举出所有的可能,之后对于每一种可能通过求和然后返回一个列表)所以对于精准测量内存分配序列,我们可以认为每一个原语可以得到一个[num];
对于每一个target,tmp_list会得到所有原语对目标cache的影响;
all_primitive_possible_abis则是整合所有target、所有原语的影响;
[[原语们对target0的影响], [原语们对target1的影响], [原语们对target2的影响], [原语们对target3的影响]];
解方程:
对于每一个原语,定义变量time0,time1,…,timen;
限制 0 <= time_i < 10;
index_key = 1*time0 + 10*time1 + 100*time2 …… 这其实是将所有原语的次数编码成一个数字;
symbol_list则是,
target_list的格式是[[cache_size, distance], …, [cache_size, distance]]
对于每一个目标,会有一个等式,c1*t1 + c2 * t2 + … + cn * tn == distance
这样算出来的是最后的结果,并不能保证中间过程的正确性;
输入准备
- all_primitives:这个东西有很多用户态堆分配器的特性,所以我们需要直接替换,避免陷入其解析逻辑;
- target_distance:int,
- target_size:int,
- heap_layout:
其实上述参数都可以避开,在计算原语的次数的时候只有第一个参数用到了;
实验设计方法
选取哪些原语?
封装原语->生成需求->计算次数->顺序->代码拼装->验证结果
封装原语
我们exp中用到的原语+常用辅助原语;
原语都放在 /home/qym/qym/heap/database/BAGUA/primitives/ 目录下(后续还会不断扩充):
生成需求
如何理解这个距离?
难道是直接做然后得到一个距离?或者说我们只需要实现其思路
通过阅读源码+AI分析可知,这个距离是通过动态分析得到的当前布局和预期布局之间的距离,所以我们可以通过我们的patch来分析距离目标还差了多少来实现,那这样似乎就必须有一个开始的顺序?
代码修改
primitive的提取:
将这些信息提取出来之后就可以用原有解方程的代码了:
使用方法
在 /home/qym/qym/heap/database/BAGUA/primitives/目录下扩充原语(可选);
构造一个初始的脚本,给出所有主干原语;
运行原语,dmesg看我们patch的结果;
根据结果计算出distance,然后修改脚本/home/qym/qym/heap/database/BAGUA/BAGUA/constraint_generator.py的kernel函数:
测试用例
CVE-2022-0185_BridgeRouter
主干顺序确定后直接成功;
CVE-2022-0185_LEAK
无噪声;
CVE-2022-34918_KOOBE_BridgeRouter
kmalloc-64 oob
$obj0=(nft_set_elem, 0x40) //漏洞结构体本身就是一个临时obj
$obj1=(user_key_payload, 0x40) //如果payload长度在一定范围内,会分配一个同cache的临时结构体
$obj2=(per_cpu_ref_data, 0x40) //分配多个噪声,3个obj,第二个有用
$obj3=(pipe_buffer, 0x40) //无噪声
adjacent(0, 1) && adjacent(1, 2) && adjacent(2, 3) && before(1, 0)
初始分配顺序:
- alloc_user_key_payload_0x40
- alloc_vul_obj
- alloc_percpu_ref_data(这里会分配3个kmalloc-64中的obj,其中前两个都是percpu_ref_data,我们用第二个)
- alloc_pipe_buffer_0x40
由于user_key_payload有一个临时结构体,导致很巧合的将前两个要求自动满足了;
然后第三个要求的时候由于percpu_ref_data第三个kmalloc-64的obj的存在,导致无法将percpu_ref_data和pipe_buffer相邻,但是现在的问题是,无论如何在这两个操作之间添加任何操作,都无法满足,在percpu_ref_data分配之前释放也不行,会被第一个obj占据,所以BAGUA应该是无法成功的;
但是如果把问题抽象成percpu_ref_data和pipe_buffer相邻的问题,似乎slake能解决?
CVE-2022-34918_BridgeRouter
初始分配情况如下:
这里需要翻译一下:
msg_msg kmalloc-64 43d3ac0 //提前占位 |
主干顺序好了一下就成功了。。。
CVE-2022-0995_KOOBE_BridgeRouter
直接不用排布,只需要预先输入正确的主干顺序;
CVE-2022-0995_BridgeRouter
漏洞是kmalloc-96的oob;
使用论文方法:
目标是watch_filter和第一个kioctx_table相邻,然后第二个kioctx_table和urb相邻;
给的初始分配序列如下:
- alloc_kioctx_table_0x40
- alloc_kioctx_table_0xc0
- alloc_watch_filter
- alloc_urb
最开始的状态如下:
可以看到kmalloc-64中差距为(0xf80-0xd40)/0x40 == 9, 9 - 1 == 8;
kmalloc-192中没有差距;
在合适的位置插入8个free_msg_msg_0x40原语之后可以成功:
CVE-2023-4004_EOE
基础版
初始状态如下:
关键原语:
add_set_elem(sock); |
应该是无解的;
CVE-2022-25636_LEAK
flow_rule和userkey_payload相邻(kmalloc-128);
flow_rule是oob立即写,所以要提前占位;
初始状态如下:
多分配了3个obj?这也不对吧?应该是多分配两个;
0x680-0x800 == -0x180 -> -3
在释放掉预先分配的msg_msg之后再释放两个obj就可以解决问题了:
但是这个是不是存在计算错误的问题?
个人的理解是,噪声的位置对BAGUA成功与否影响很大;
问题记录
- primitive是在不同的cve的config上测量的,可能会不同;
- pipe_buffer 分配+释放,万一不在同一个slab怎么办?BAGUA也会有tcachebin满了的情况;
在cpio上使用ftrace
确保内核配置:
CONFIG_FTRACE=y |
在init脚本中挂载相应文件系统:
mount -t debugfs none /sys/kernel/debug |
ftrace本身的噪声就很大,需要pinCPU:
cat trace.log | grep '\[000\]' | grep -E "kmalloc|kfree" |