2024强网拟态ker题解(USMA手法)
0x01.题目分析
ker_ioctl具有add、delete、edit三个功能点,分别对应0x20、0x30、0x50
add允许申请0x28(实际0x40)大小的堆块,并可写入数据。
delete机会只有2次,且不会清空指针,造成uaf漏洞。
edit机会只有一次,且只能修改前8字节。
可以看到开启了cg隔离。
正常保护都开启了。
0x02.利用手法
setxattr辅助修改
setxattr系统调用主要作用就是设置文件扩展属性,其中可以设置扩展属性的名字,设置的时候会临时创建一个堆来存放属性名字。
1 | setxattr(struct mnt_idmap *idmap, struct dentry *d, |
但是结束后会还是会free掉该堆块,所以setxattr只能短暂占位进行辅助修改。
ukp堆喷+leak数据
ukp全称user_key_payload
,是用户创建并存放密钥的结构体。
1 | struct user_key_payload { |
它拥有0x18的头,其中0x10偏移存放着datalen。
我们可以通过add_key系统调用创建。
1 | //调用链 |
其中index_key.type->preparse
根据type来选择特定的fops,可以跟踪key_types_list
来寻找对应的fops。
当type为user时就会调用user_preparse
函数申请user_key_payload
结构体。
1 | struct key_type key_type_user = { |
可以看到这里申请的堆标志为GFP_KERNEL,申请堆块的范围在0x20-0x8000,十分宽大。
我们可以通过keyctl系统调用读取堆块,释放堆块。
1 | //KEYCTL_READ调用user_read读取堆块内容 |
可以看到user_read
是基于upk->datalen
进行读取的,所以我们可以通过劫持upk->datalen
从而越界读取数据。
1 | //user_revoke释放堆块 |
user_revoke释放堆块时并不会直接就释放,而是将其放进rcu_list中进行调度,等GP(Grace Period)即宽限期结束,rcu才会对其进行释放。
1 | struct callback_head { |
在GP期间,会存入释放函数到callback_head->func
中,这就让泄露内核基地址成为可能,且如果在GP期间再revoke一个upk还能让callback_head->next
指向下一个upk结构体,从而泄露堆地址。
再补充两个keyctl,
KEYCTL_UPDATE会给key重新分配一个upk并将原来的upk revoke掉;
KEYCTL_UNLINK则会将key给unlink掉。
USMA手法
USMA(User Space Mapping Attack)
源于CVE-2021-22600,关于此CVE的分析可以看看我先前的文章 。
当用户态与内核态进行socket通信时,为了节省开支允许用户调用setsockopt的PACKET_RX_RING功能创建一个环形缓冲区,针对同一片物理内存,用户和内核都分别映射一片虚拟内存,这样用户和内核就可以直接进行数据传输。
1 | //调用链packet_setsockopt->packet_set_ring->alloc_pg_vec |
alloc_pg_vec
函数会调用kcalloc为pg_vec分配内存,其中block_nr是我们可控的,所以我们可以用分配几乎任意size的GFP_KERNEL的堆块。
分配的pg_vec会用来存放环形缓冲区中所有page的虚拟地址,而用户则可以调用mmap来映射pg_vec中所有的page,即将pg_vec中所有虚拟地址对应的物理地址再映射一份虚拟地址给用户空间。
1 | static int packet_mmap(struct file *file, struct socket *sock, |
这就意味着我们只要能劫持pg_vec结构体就可以利用mmap映射任意符合条件的内核空间。
1 | static int validate_page_before_insert(struct page *page) |
前提就是page不能为匿名页、slab的堆块、含有type的page。
而内核代码段page是完全符合的,故我们可以利用usma修改任意内核代码段的数据,从而执行任意内核shellcode,同时modprobe_path所处page也是符合的,所以我们也能修改它实现提权。
0x03.漏洞利用
因为开启了内核隔离,而ko驱动使用的堆块都是GFP_KERNEL的,pipe和msgmsg都利用不了,所以我们选择使用ukp和usma分别进行泄露和攻击。
- 申请0x40堆块再释放,创建一个刚好0x40大小的ukp占住释放堆块的位置。
- 堆喷大量ukp在当前堆块后方,并revoke掉,此时就会有残留。
- 二次delete堆块释放最开始的ukp,然后调用setxattr修改ukp的datalen,并调用keyctl_read泄露内核基地址。
- 创建环形缓冲区pg_vec,再次占住我们最开始创建的堆块。
- 利用edit修改pg_vec存放的地址为modprobe_path所在page的地址。
- 最后mmap映射modprobe_path所在page,然后修改modprobe_path实现提权。
这里要补充的是,ukp在创建的时候会首先分配plen大小的堆块存放payload再去申请plen+0x18的堆块把payload放进去,所以我们要控制plen刚好为0x20,这样最初分配的就会是0x20的堆块,而ukp就能分配到0x18+0x20=0x38(实际是0x40)的堆块。
并且pg_vec创建了多少个page的环形缓冲区,mmap就必须映射多少个,所以必须保证整个pg_vec所有的虚拟地址都是满足条件的,不然会报错。
0x04.EXP
1 |
|
1 | //Kernel.h |
- Title: 2024强网拟态ker题解(USMA手法)
- Author: 0rb1t
- Created at : 2024-11-10 20:51:12
- Updated at : 2024-11-11 21:32:13
- Link: https://redefine.ohevan.com/2024/11/10/2024强网拟态ker题解(USMA手法)/
- License: This work is licensed under CC BY-NC-SA 4.0.