议题介绍了一种新颖的 Linux 内核栈缓冲区越界写漏洞利用技巧,结合了内核栈分配机制和 ebpf 实现任意地址写修改 modprobe_path 完成利用。
漏洞代码:
nla_parse_nested 解析用户态下发的数据,然后取出 TCA_TAPRIO_TC_ENTRY_INDEX 放到 tc 变量中
struct nla_policy taprio_tc_policy[TCA_TAPRIO_TC_ENTRY_MAX + 1] = {
[TCA_TAPRIO_TC_ENTRY_INDEX] = { .type = NLA_U32 },
[TCA_TAPRIO_TC_ENTRY_MAX_SDU] = { .type = NLA_U32 },
[TCA_TAPRIO_TC_ENTRY_FP] = NLA_POLICY_RANGE(NLA_U32,
TC_FP_EXPRESS,
TC_FP_PREEMPTIBLE),
};
由于 nla_get_u32 获取 TCA_TAPRIO_TC_ENTRY_INDEX 返回的类型为 u32,而 tc 变量是 int 类型,通过提供很大 TCA_TAPRIO_TC_ENTRY_INDEX 字段可以让 tc 变成负数,绕过后面的 tc 范围检查,写入 max_sdu 和 fp 数组
u32 max_sdu[TC_QOPT_MAX_QUEUE],
u32 fp[TC_QOPT_MAX_QUEUE],
跟踪调用路径 max_sdu 和 fp 为内核栈数组,通过负数的 tc ,可以向前越界写栈内存
漏洞的 Patch 比较简单:在 taprio_tc_policy 定义 TCA_TAPRIO_TC_ENTRY_INDEX 字段的最大值
struct nla_policy taprio_tc_policy[TCA_TAPRIO_TC_ENTRY_MAX + 1] = {
[TCA_TAPRIO_TC_ENTRY_INDEX] = NLA_POLICY_MAX(NLA_U32,
TC_QOPT_MAX_QUEUE),
[TCA_TAPRIO_TC_ENTRY_MAX_SDU] = { .type = NLA_U32 },
[TCA_TAPRIO_TC_ENTRY_FP] = NLA_POLICY_RANGE(NLA_U32,
TC_FP_EXPRESS,
TC_FP_PREEMPTIBLE),
};
漏洞利用前需要观察漏洞代码,确定漏洞的原语:
可以看到漏洞原语非常受限,最大只能写 0xffff,而且每次都是 u32 写,就没有办法部分修改栈上面的指针(改完后偏移太大很难控制,比如 0xffffffaabbccdd —> 0xffffff0000xxxx)
图中描述了内核栈布局, max_sdu 在调用者栈帧,通过向前越界写可以篡改 taprio_parse_tc_entry 函数栈帧中的数据,其中有用的数据就是一些指针,如果利用原语篡改,大概率得到的是非法指针,无法使用。
常规的栈越界写漏洞利用策略行不通后,可以考虑结合目标的情况,这里就用到了内核的实现机制,Linux 内核的进程栈其实是在 VMALLOC 区域中分配的,而内核的 vmalloc 接口也是在这个区域中分配内存,因此不同进程的 栈内存、vmalloc 堆内存都是相邻的。
在 VMALLOC 区域分配内存时,如果没有指定 NO_GUARD_PAGE 参数就会在分配的内存后面加上一个不可访问的页作为 grard page,防止溢出到相邻块,不过我们是 OOB 所以不受影响
下面顺便介绍下 VMALLOC 区域的内存分配/释放机制,以及内存布局的方式。在 Linux 内核的虚拟地址空间中由 [VMALLOC_START, VMALLOC_END] 组成的区域称为 VMALLOC 区域,用于分配虚拟地址连续但是物理地址不一定连续的内存, VMALLOC 区域中已经分配出去的虚拟地址空间通过 vm_area 结构体表示,如下图所示:
所有的 vm_area 通过链表和红黑树组织,在进行内存分配时,内核会从低地址往高地址遍历 vm_area 搜索其中处于空闲状态的地址区间,当 空闲空间大小 大于 申请大小 时就会把这个虚拟地址区间分配出去,类似于 ptmalloc 中 unsorted bin 的分配机制。
申请到虚拟地址空间后会调用 alloc_page 一次一页地向伙伴系统申请页面,比如请求分配 10 页,就会调用10次 alloc_page。
在释放 VMALLOC 中的内存时,首先会把虚拟地址空间中的物理地址释放回伙伴系统,但是对应的虚拟地址空间不会立马被释放,而是会被标记为 unpurged vm_area ,当所有 unpurged vm_area 的页面数大于一个阈值(ubuntu 20.10 上是 40000 页左右)时才会对这些 unpurged vm_area 进行回收,在 unpurged vm_area 被回收前其对应的虚拟地址是不可被申请的,这个特点在后面进行内存布局的时候需要考虑。
非常直观的想法是通过越界写修改其他进程的内核栈,类似 SVE-2020-18610 的利用策略,修改 do_select 栈内存实现栈溢出和 ROP,但是 CONFIG_RANDOMIZE_KSTACK_OFFSET 通过给栈布局引入随机性导致偏移无法确定,从而拦截了这种利用手段
VMALLOC 上面除了内核栈外还有很多其他用于的对象(比如安卓下的 Mali GPU 驱动管理结构), ebpf 字节码等,之前 CVE-2021-33909 的利用策略就是通过 vmalloc 越界写修改 ebpf 字节码完成利用
由于前期 ebpf 漏洞过多且容易用于写恶意软件,目前低权限进程无法直接使用 eBPF,不过可以通过 setsockopt 的方式创建 ebpf 字节码(参考 CVE-2023-3609 )
ebpf 的处理流程如下,首先会校验字节码,校验通过后将 filter 字节码转换为 ebpf 字节码并进行 jit
通过漏洞在校验通过生成 ebpf 字节码后, jit 前篡改 ebpf 字节码就能注入恶意 epbf 指令,形成任意地址写.
篡改注入 BPF_STX_MEM 做任意地址写,修改 modprobe_path 实现提权:
其他问题:
VMALLOC 区域的溢出、越界写漏洞最近讨论的比较多:
此外通过 RACE 在 ebpf 生成过程中篡改 ebpf 指令实现任意地址写的利用策略,在其他 vmalloc 相关漏洞利用中可以使用
19 篇文章
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!