BAGUA复现笔记


环境搭建

论文链接: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
docker load --input ./bagua
docker run -it -v $(pwd)/BAGUA:/bagua bagua:v1.0

(也可以直接运行start.sh脚本启动docker环境)

进入docker之后:

cd /bagua
python2 .py

方法二

利用工具2to3将python2转换成python3:

import os

# 指定目录路径
directory = "./BAGUA"

# 遍历目录下所有文件
for filename in os.listdir(directory):
# 检查文件是否是以 .py 结尾
if filename.endswith(".py"):
# 打印出文件的完整路径
fname = os.path.join(directory, filename)
os.system(f"2to3 --write {fname}")

安装z3和z3-solver:

pip3 install z3
pip3 install z3-solver

源码分析

重点看constraint_generator.py这个文件,其中有一个solve_primitive_equation函数,

首先看原语的处理:

1751667123671

  1. 首先是循环处理每一个目标;

  2. 对于每一个目标,迭代遍历所有的原语;

  3. 对于每一个原语,得到的是一个原语对目标cache/chain的所有可能影响(这个可能是glibc有多种可能,然后通过笛卡尔积枚举出所有的可能,之后对于每一种可能通过求和然后返回一个列表)所以对于精准测量内存分配序列,我们可以认为每一个原语可以得到一个[num];

  4. 对于每一个target,tmp_list会得到所有原语对目标cache的影响;

  5. all_primitive_possible_abis则是整合所有target、所有原语的影响;

    [[原语们对target0的影响], [原语们对target1的影响], [原语们对target2的影响], [原语们对target3的影响]];

解方程:

1751675209157

1751675223265

对于每一个原语,定义变量time0,time1,…,timen;

限制 0 <= time_i < 10;

index_key = 1*time0 + 10*time1 + 100*time2 …… 这其实是将所有原语的次数编码成一个数字;

symbol_list则是,

1751675627462

target_list的格式是[[cache_size, distance], …, [cache_size, distance]]

对于每一个目标,会有一个等式,c1*t1 + c2 * t2 + … + cn * tn == distance

1751676653513

这样算出来的是最后的结果,并不能保证中间过程的正确性

输入准备

1751668413852

  1. all_primitives:这个东西有很多用户态堆分配器的特性,所以我们需要直接替换,避免陷入其解析逻辑;
  2. target_distance:int,
  3. target_size:int,
  4. heap_layout:

其实上述参数都可以避开,在计算原语的次数的时候只有第一个参数用到了;

实验设计方法

选取哪些原语?

封装原语->生成需求->计算次数->顺序->代码拼装->验证结果

封装原语

我们exp中用到的原语+常用辅助原语;

原语都放在 /home/qym/qym/heap/database/BAGUA/primitives/ 目录下(后续还会不断扩充):

1751782881842

生成需求

如何理解这个距离?

难道是直接做然后得到一个距离?或者说我们只需要实现其思路

通过阅读源码+AI分析可知,这个距离是通过动态分析得到的当前布局和预期布局之间的距离,所以我们可以通过我们的patch来分析距离目标还差了多少来实现,那这样似乎就必须有一个开始的顺序

代码修改

primitive的提取:

1751804731357

将这些信息提取出来之后就可以用原有解方程的代码了:

1751804763431

1751804774649

使用方法

  1. 在 /home/qym/qym/heap/database/BAGUA/primitives/目录下扩充原语(可选);

  2. 构造一个初始的脚本,给出所有主干原语;

  3. 运行原语,dmesg看我们patch的结果;

  4. 根据结果计算出distance,然后修改脚本/home/qym/qym/heap/database/BAGUA/BAGUA/constraint_generator.py的kernel函数:

    1751804662003

测试用例

CVE-2022-0185_BridgeRouter

主干顺序确定后直接成功;

CVE-2022-0185_LEAK

1751800270950

无噪声;

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)

初始分配顺序:

  1. alloc_user_key_payload_0x40
  2. alloc_vul_obj
  3. alloc_percpu_ref_data(这里会分配3个kmalloc-64中的obj,其中前两个都是percpu_ref_data,我们用第二个)
  4. 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

1751785926502

初始分配情况如下:

1751786701875

这里需要翻译一下:

msg_msg				kmalloc-64 	43d3ac0 //提前占位
msg_msg kmalloc-4k 45a5000 //提前占位
xfrm_sec_ctx kmalloc-64 43d3b00
xfrm_user_sec_ctx kmalloc-4k 44ef000
nft_set_elem kmalloc-96 4262b40
nft_set_elem kmalloc-64 43d3ac0 //临时结构体要提前占位
msg_msg kmalloc-4k 45a6000
xfrm_user_sec_ctx kmalloc-4k 45a5000 //临时结构体要提前占位

主干顺序好了一下就成功了。。。

CVE-2022-0995_KOOBE_BridgeRouter

直接不用排布,只需要预先输入正确的主干顺序;

CVE-2022-0995_BridgeRouter

漏洞是kmalloc-96的oob;

使用论文方法:

1751784801471

目标是watch_filter和第一个kioctx_table相邻,然后第二个kioctx_table和urb相邻;

给的初始分配序列如下:

  1. alloc_kioctx_table_0x40
  2. alloc_kioctx_table_0xc0
  3. alloc_watch_filter
  4. alloc_urb

最开始的状态如下:

1751785238075

可以看到kmalloc-64中差距为(0xf80-0xd40)/0x40 == 9, 9 - 1 == 8;

kmalloc-192中没有差距;

1751785141831

在合适的位置插入8个free_msg_msg_0x40原语之后可以成功:

1751785345101

CVE-2023-4004_EOE

基础版

1751787240397

初始状态如下:

1751789051575

1751789037434

1751789094153

关键原语:

add_set_elem(sock);
del_set_elem(sock);
del_set_elem(sock);
syscall(__NR_add_key, "user", description, payload, plen, KEY_SPEC_PROCESS_KEYRING);
fcntl(pipe1[1], F_SETPIPE_SZ, 0x1000 * 2 );
msgsnd(msgqid, msg_buf, 0x60 - 0x30, 0);
syscall(250, KEYCTL_REVOKE, key1, 0, 0, 0);
setxattr(path, name, value, size, flags);

应该是无解的;

CVE-2022-25636_LEAK

flow_rule和userkey_payload相邻(kmalloc-128);

flow_rule是oob立即写,所以要提前占位;

1751802548163

初始状态如下:

1751802531608

多分配了3个obj?这也不对吧?应该是多分配两个;

0x680-0x800 == -0x180 -> -3

在释放掉预先分配的msg_msg之后再释放两个obj就可以解决问题了:

1751803201172

但是这个是不是存在计算错误的问题?

个人的理解是,噪声的位置对BAGUA成功与否影响很大;

问题记录

  1. primitive是在不同的cve的config上测量的,可能会不同;
  2. pipe_buffer 分配+释放,万一不在同一个slab怎么办?BAGUA也会有tcachebin满了的情况;

在cpio上使用ftrace

确保内核配置:

CONFIG_FTRACE=y
CONFIG_FUNCTION_TRACER=y
CONFIG_TRACING=y
CONFIG_TRACEPOINTS=y
CONFIG_DEBUG_FS=y
CONFIG_KMEMTRACE=y # 可选,较旧
CONFIG_KPROBE_EVENTS=y # 如果你用 kprobe tracepoints
CONFIG_EVENT_TRACING=y
CONFIG_KMEM_EVENTS=y # 启用 kmalloc 等事件

在init脚本中挂载相应文件系统:

mount -t debugfs none /sys/kernel/debug
mount -t tracefs none /sys/kernel/debug/tracing

ftrace本身的噪声就很大,需要pinCPU:

cat trace.log | grep '\[000\]' | grep -E "kmalloc|kfree"

参考


文章作者: q1ming
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 q1ming !
  目录