
miniL-2024
miniL-2024
Pwn
2bytes
jmp 短跳
1 | from pwncli import * |
ottoshop
scanf的利用
1 | from pwncli import * |
PhoneBook
简单的堆题,打IO_FILE
就是堆比较分散,需要一点堆风水
1 | from pwncli import * |
EasyVM
🐧师傅出的VM题,针不戳啊(😭
就一次的syscall利用,有一段data空间 (0x2024/05/10/miniL-2024/0000) 任意读写
利用的syscall目前想到的只有 mremap, vm_process_writev, io_uring
这题我是用mremap将0x2024/05/10/miniL-2024/0000重新map到got表处,劫持got,并利用栈迁移的gadget
0x00000000004a4319 : mov rsp, rcx ; pop rcx ; jmp rcx
随后ROP打orw
1 | from pwncli import * |
HardVM
相比easy就“只”开了pie和full reload😭
赛后得知这种可以用一个syscall,利用io_uring实现“一键”orw
io_uring相关资料
找gpt搓的demo
1 |
|
gcc ./4.c -o 4 -luring -static -g
接下来从demo开始分析io_uring的具体实现过程
初始化
首先调用io_uring_queue_init初始化
源码如下
1 | __cold int io_uring_queue_init(unsigned entries, struct io_uring *ring, |
1 | int io_uring_queue_init_params(unsigned entries, struct io_uring *ring, |
1 | static int io_uring_queue_init_try_nosqarr(unsigned entries, struct io_uring *ring, |
1 | int __io_uring_queue_init_params(unsigned entries, struct io_uring *ring, |
简单来说就是初始化了一个fd,来和内核做交互,并将其mmap到虚拟内存方便交互
接下来的操作都是对映射到虚拟地址的部分做操作
写入想执行的操作
sqe = *io_uring_get_sqe*(&ring);
拿到sqe队列的映射地址
*io_uring_prep_openat*(sqe, AT_FDCWD, FILE_PATH, O_RDONLY, 0);
对sqe队列这个结构体做调整
1 | IOURINGINLINE void io_uring_prep_openat(struct io_uring_sqe *sqe, int dfd, |
1 | IOURINGINLINE void io_uring_prep_rw(int op, struct io_uring_sqe *sqe, int fd, |
sqe结构体
1 | struct io_uring_sqe { |
可以看到这些操作就是在布置sqe结构体,告诉内核我们想做什么
*io_uring_prep_read*(sqe, -1, buffer, BUFFER_SIZE, 0);
*io_uring_prep_write*(sqe, STDOUT_FILENO, buffer, BUFFER_SIZE, 0);
同理,这些也是在布置sqe队列
提交队列
ret = *io_uring_submit*(&ring);
1 | int io_uring_submit(struct io_uring *ring) |
1 | static int __io_uring_submit_and_wait(struct io_uring *ring, unsigned wait_nr) |
1 | static int __io_uring_submit(struct io_uring *ring, unsigned submitted, |
ret = __sys_io_uring_enter(ring->enter_ring_fd, submitted, wait_nr, flags, NULL);
最终利用syscall sys_io_uring_enter向内核提交队列
总结
初始化io队列 –> 将操作写入sqe队列(sqe结构体) –> 提交队列
但是
有人就会问了,诶,这题不是限制一次syscall吗,sys_io_uring_setup 和 sys_io_uring_enter 不是两个syscall吗,这不是超了吗
诶,其实io_uring正在飞速的迭代,每个内核版本都是不一样的
题目给的内核版本
./bzImage: Linux kernel x86 boot executable bzImage, version 6.6.1 (root@fuzz) #2 SMP PREEMPT_DYNAMIC Sat Nov 18 21:11:32 CST 2023, RO-rootFS, swap_dev 0XB, Normal VGA
初始化的时候,sys_io_uring_setup 就自带了提交队列的功能
源码如下
1 | SYSCALL_DEFINE2(io_uring_setup, u32, entries, |
可以看到,setup的2参通过copy_from_user传给了p
1 | static long io_uring_setup(u32 entries, struct io_uring_params __user *params) |
1 | static __cold int io_uring_create(unsigned entries, struct io_uring_params *p, |
简单来说就是对传入的队列进行解析参数,也就是说setup中已经完成了队列提交这一个动作
分析完成,伪造结构体
先把程序生成的结构体dump下来看看
&ring
结构体
1 | struct io_uring { |
但6.6.1有所不同,setup的2参结构体如下
1 | struct io_uring_params { |
其中
1 | struct io_sqring_offsets { |
1 | struct io_cqring_offsets { |
三次设置的sqe,结构体见上文
理论上我们就每个结构体都成功伪造并setup就成功了
openat
- 标题: miniL-2024
- 作者: InkeyP
- 创建于 : 2024-11-14 14:28:23
- 更新于 : 2025-03-14 14:13:37
- 链接: https://blog.inkey.top/202411/14/miniL-2024/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。