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创建流程

  1. socket()调用sock_create()创建套接字
  2. sock_create()调用__sock_create()
  3. __sock_create()通过sock_alloc()分配socket结构体
  4. 根据family获取协议模块
  5. 通过协议模块的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()函数:

  1. 检查套接字type
  2. 使用sk_alloc分配独立object
  3. 设置函数指针(ops)
  4. 初始化数据包和锁等结构

关键操作结构:

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);

调用流程:

  1. __sys_setsockopt()查找关联的socket
  2. 进行BPF程序处理
  3. 根据level选择操作方式:
    • SOL_SOCKET层:调用sock_setsockopt()
    • 其他层:调用sock->ops->setsockopt

3.2 环形缓冲区设置

当设置PACKET_RX_RINGPACKET_TX_RING时:

  1. 根据版本设置请求长度
  2. 复制数据到req_u.req
  3. 调用packet_set_ring()设置环形缓冲区

packet_set_ring()关键操作:

  1. 分配pg_vec(页面向量)
  2. 根据版本初始化环形缓冲区
    • TPACKET_V3:调用init_prb_bdqc()
  3. 释放旧缓冲区

3.3 漏洞原理

漏洞存在于TPACKET_V3版本的环形缓冲区处理中:

  1. init_prb_bdqc()使packet_ring_buffer.prb_bdqc.pkbdq持有pg_vec引用
  2. 释放pg_vec时未清除该引用
  3. 当版本切换为TPACKET_V2时:
    • rx_owner_mapprb_bdqc是联合体,偏移相同
    • 残留的pg_vec变成rx_owner_map指针
  4. 导致double free

4. USMA技术

4.1 mmap机制

mmap调用链:

  1. sys_mmap() -> ksys_mmap_pgoff()
  2. vm_mmap_pgoff() -> do_mmap()
  3. mmap_region()完成实际映射

对于socket的mmap:

  • 调用packet_mmap()
  • pg_vec中的pages映射到用户空间

4.2 USMA利用原理

  1. 通过漏洞控制pg_vec指向任意内核地址
  2. 使用mmap将这些地址映射到用户空间
  3. 直接读写内核内存

关键检查点:

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 基本利用流程

  1. 创建AF_PACKET套接字(type为SOCK_RAW)
  2. 调用TPACKET_V3的setsockopt创建环形缓冲区
  3. 再次调用setsockopt(TPACKET_V3)释放缓冲区
  4. 使用堆喷重新申请释放的pg_vec
  5. 调用TPACKET_V2的setsockopt触发double free

5.2 实际利用案例

案例1:2024SCTF-ker

  1. 利用double free喷上user_key_payload
  2. 使用setxattr修改payload中的datalen实现越界读
  3. 泄露内核.text地址
  4. 喷上rx_rings覆盖user_key_payload
  5. 修改pg_vec指向modprobe页表
  6. 映射ring到用户态修改modprobe路径

案例2:N1CTF2022-praymoon

  1. 利用userfaultfd+setxattr精确控制对象释放
  2. 喷上user_key_payload并修改datalen
  3. 泄露.text地址
  4. 释放user_key_payload但延迟释放setxattr对象
  5. 喷上pg_vec后再释放setxattr对象
  6. 修改__sys_setreuid的页表绕过权限检查

6. 防御与绕过

6.1 常见防御措施

  1. 内存隔离(memcg)
  2. 随机页面分配(CONFIG_SHUFFLE_PAGE_ALLOCATOR)
  3. Hardened freelist
  4. 命名空间隔离

6.2 绕过技术

  1. 使用userfaultfd精确控制时序
  2. 多次尝试确保命中
  3. 创建用户命名空间获取root权限
  4. 选择不受隔离影响的分配方式(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技术是一种强大的内核漏洞利用技术,它结合了:

  1. AF_PACKET套接字的内存管理漏洞
  2. mmap机制将内核内存映射到用户空间
  3. 精确的堆布局控制

关键点:

  • 理解socket和内存管理子系统交互
  • 掌握环形缓冲区的分配和释放流程
  • 利用联合体结构实现类型混淆
  • 绕过内核页表保护机制

这种技术可以用于提权、绕过安全机制等场景,是内核漏洞利用中的重要技术之一。

Linux内核漏洞利用:USMA技术详解 1. 背景知识 1.1 Socket系统调用 Socket系统调用用于创建网络套接字,可以发送和接收数据。基本调用形式: 参数说明: 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 中: packet_create() 函数: 检查套接字type 使用 sk_alloc 分配独立object 设置函数指针(ops) 初始化数据包和锁等结构 关键操作结构: 3. 漏洞分析 3.1 setsockopt机制 setsockopt 用于配置套接字行为: 调用流程: __sys_setsockopt() 查找关联的socket 进行BPF程序处理 根据level选择操作方式: SOL_ SOCKET层:调用 sock_setsockopt() 其他层:调用 sock->ops->setsockopt 3.2 环形缓冲区设置 当设置 PACKET_RX_RING 或 PACKET_TX_RING 时: 根据版本设置请求长度 复制数据到 req_u.req 调用 packet_set_ring() 设置环形缓冲区 packet_set_ring() 关键操作: 分配 pg_vec (页面向量) 根据版本初始化环形缓冲区 TPACKET_ V3:调用 init_prb_bdqc() 释放旧缓冲区 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 将这些地址映射到用户空间 直接读写内核内存 关键检查点: 可以绕过检查的内核页类型: 内核代码页(非匿名页、非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 设置环形缓冲区 7.2 USMA映射 8. 总结 USMA技术是一种强大的内核漏洞利用技术,它结合了: AF_ PACKET套接字的内存管理漏洞 mmap机制将内核内存映射到用户空间 精确的堆布局控制 关键点: 理解socket和内存管理子系统交互 掌握环形缓冲区的分配和释放流程 利用联合体结构实现类型混淆 绕过内核页表保护机制 这种技术可以用于提权、绕过安全机制等场景,是内核漏洞利用中的重要技术之一。