kernel从小白到大神(六)-USMA
字数 2463 2025-08-22 12:22:15
Linux内核漏洞利用:USMA技术详解
1. 背景知识
1.1 Socket系统调用
Socket系统调用用于创建网络套接字,可以发送和接收数据。基本调用形式:
int socket(int family, int type, int protocol);
参数说明:
family: 协议族(AF_INET/IPv4, AF_INET6/IPv6, AF_UNIX/本地通信, AF_PACKET/原始数据包)type: 套接字类型(SOCK_STREAM/TCP, SOCK_DGRAM/UDP, SOCK_RAW/原始套接字)protocol: 协议类型
1.2 Socket创建流程
socket()调用sock_create()创建套接字sock_create()调用__sock_create()__sock_create()通过sock_alloc()分配socket结构体- 根据family获取协议模块
- 通过协议模块的ops(pf->create)初始化socket
关键数据结构:
struct socket: 表示一个套接字struct net_proto_family: 协议族操作集合
2. AF_PACKET套接字
2.1 初始化过程
AF_PACKET套接字注册在net/packet/af_packet.c中:
static const struct net_proto_family packet_family_ops = {
.family = PF_PACKET,
.create = packet_create,
.owner = THIS_MODULE,
};
packet_create()函数:
- 检查套接字type
- 使用
sk_alloc分配独立object - 设置函数指针(ops)
- 初始化数据包和锁等结构
关键操作结构:
static const struct proto_ops packet_ops = {
.family = PF_PACKET,
.owner = THIS_MODULE,
.release = packet_release,
.bind = packet_bind,
// ... 其他操作
.setsockopt = packet_setsockopt,
.getsockopt = packet_getsockopt,
.mmap = packet_mmap,
// ...
};
3. 漏洞分析
3.1 setsockopt机制
setsockopt用于配置套接字行为:
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
调用流程:
__sys_setsockopt()查找关联的socket- 进行BPF程序处理
- 根据level选择操作方式:
- SOL_SOCKET层:调用
sock_setsockopt() - 其他层:调用
sock->ops->setsockopt
- SOL_SOCKET层:调用
3.2 环形缓冲区设置
当设置PACKET_RX_RING或PACKET_TX_RING时:
- 根据版本设置请求长度
- 复制数据到
req_u.req - 调用
packet_set_ring()设置环形缓冲区
packet_set_ring()关键操作:
- 分配
pg_vec(页面向量) - 根据版本初始化环形缓冲区
- TPACKET_V3:调用
init_prb_bdqc()
- TPACKET_V3:调用
- 释放旧缓冲区
3.3 漏洞原理
漏洞存在于TPACKET_V3版本的环形缓冲区处理中:
init_prb_bdqc()使packet_ring_buffer.prb_bdqc.pkbdq持有pg_vec引用- 释放
pg_vec时未清除该引用 - 当版本切换为TPACKET_V2时:
rx_owner_map和prb_bdqc是联合体,偏移相同- 残留的
pg_vec变成rx_owner_map指针
- 导致
double free
4. USMA技术
4.1 mmap机制
mmap调用链:
sys_mmap()->ksys_mmap_pgoff()vm_mmap_pgoff()->do_mmap()mmap_region()完成实际映射
对于socket的mmap:
- 调用
packet_mmap() - 将
pg_vec中的pages映射到用户空间
4.2 USMA利用原理
- 通过漏洞控制
pg_vec指向任意内核地址 - 使用
mmap将这些地址映射到用户空间 - 直接读写内核内存
关键检查点:
static int validate_page_before_insert(struct page *page) {
if (PageAnon(page) || PageSlab(page) || page_has_type(page))
return -EINVAL;
flush_dcache_page(page);
return 0;
}
可以绕过检查的内核页类型:
- 内核代码页(非匿名页、非Slab分配、无特定类型)
5. 漏洞利用步骤
5.1 基本利用流程
- 创建AF_PACKET套接字(type为SOCK_RAW)
- 调用TPACKET_V3的setsockopt创建环形缓冲区
- 再次调用setsockopt(TPACKET_V3)释放缓冲区
- 使用堆喷重新申请释放的pg_vec
- 调用TPACKET_V2的setsockopt触发double free
5.2 实际利用案例
案例1:2024SCTF-ker
- 利用double free喷上user_key_payload
- 使用setxattr修改payload中的datalen实现越界读
- 泄露内核.text地址
- 喷上rx_rings覆盖user_key_payload
- 修改pg_vec指向modprobe页表
- 映射ring到用户态修改modprobe路径
案例2:N1CTF2022-praymoon
- 利用userfaultfd+setxattr精确控制对象释放
- 喷上user_key_payload并修改datalen
- 泄露.text地址
- 释放user_key_payload但延迟释放setxattr对象
- 喷上pg_vec后再释放setxattr对象
- 修改__sys_setreuid的页表绕过权限检查
6. 防御与绕过
6.1 常见防御措施
- 内存隔离(memcg)
- 随机页面分配(CONFIG_SHUFFLE_PAGE_ALLOCATOR)
- Hardened freelist
- 命名空间隔离
6.2 绕过技术
- 使用userfaultfd精确控制时序
- 多次尝试确保命中
- 创建用户命名空间获取root权限
- 选择不受隔离影响的分配方式(user_key_payload)
7. 关键代码片段
7.1 设置环形缓冲区
int set_packet_rx_ring(int block_nr, int block_size, int frame_size, int prvatie_size, int timeout) {
int sock = socket(AF_PACKET, SOCK_RAW, htonl(ETH_P_ALL));
int version = TPACKET_V3;
setsockopt(sock, SOL_PACKET, PACKET_VERSION, &version, sizeof(version));
struct tpacket_req3 req3;
req3.tp_block_nr = block_nr;
req3.tp_block_size = block_size;
req3.tp_frame_size = frame_size;
req3.tp_frame_nr = block_nr * block_size / frame_size;
req3.tp_retire_blk_tov = timeout;
req3.tp_sizeof_priv = prvatie_size;
setsockopt(sock, SOL_PACKET, PACKET_RX_RING, &req3, sizeof(req3));
struct sockaddr_ll sa;
memset(&sa, 0, sizeof(sa));
sa.sll_family = AF_PACKET;
sa.sll_protocol = htons(ETH_P_ALL);
sa.sll_ifindex = if_nametoindex("lo");
bind(sock, (struct sockaddr*)&sa, sizeof(sa));
return sock;
}
7.2 USMA映射
// 映射环形缓冲区到用户空间
char* page = (char*)mmap(NULL, 33 * sysconf(_SC_PAGE_SIZE),
PROT_READ | PROT_WRITE, MAP_SHARED, sock_packet, 0);
// 修改内核代码
page[0xfd8] = 0xeb; // 修改条件跳转
8. 总结
USMA技术是一种强大的内核漏洞利用技术,它结合了:
- AF_PACKET套接字的内存管理漏洞
- mmap机制将内核内存映射到用户空间
- 精确的堆布局控制
关键点:
- 理解socket和内存管理子系统交互
- 掌握环形缓冲区的分配和释放流程
- 利用联合体结构实现类型混淆
- 绕过内核页表保护机制
这种技术可以用于提权、绕过安全机制等场景,是内核漏洞利用中的重要技术之一。