漏洞位于内核xfrm
模块,该模块是IPSEC
协议的实现模块。ISO文件下载,利用脚本下载。html
IPSEC是一个协议组合,它包含AH、ESP、IKE协议,提供对数据包的认证和加密功能,能帮助IP层创建安全可信的数据包传输通道。linux
SA(Security Associstion):安全关联。SA由spi、ip、安全协议标识(AH或ESP)这三个参数惟一肯定。SA定义了ipsec双方的ip地址、ipsec协议、加密算法、密钥、模式、抗重放窗口等。其实SA就是用xfrm_state
结构体表示。git
AH(Authentication Header):认证。AH为ip包提供数据完整性校验和身份认证功能,提供抗重放能力,验证算法由SA指定。github
ESP(Encapsulating security payload):加密。ESP为ip数据包提供完整性检查、认证和加密。算法
在linux内核中的IPSEC实现便是xfrm这个框架(读做transform转换,表示内核协议栈收到的IPsec
报文须要通过转换才能还原为原始报文),不少解决方案如StrongSwan、OpenSwan都使用XFRM
框架进行报文接收发送,关于xfrm的代码主要在net/xfrm以及net/ipv4下。shell
// 如下是/net/xfrm下的代码的大概功能 xfrm_state.c 状态管理 xfrm_policy.c xfrm策略管理 xfrm_algo.c 算法管理 xfrm_hash.c 哈希计算函数 xfrm_input.c 安全路径(sec_path)处理, 用于处理进入的ipsec包 xfrm_user.c netlink接口的SA和SP(安全策略)管理
其中xfrm_user.c中的代码容许咱们向内核发送netlink消息来调用相关handler实现对SA和SP的配置,其中涉及处理函数以下。ubuntu
xfrm_dispatch[XFRM_NR_MSGTYPES] = { [XFRM_MSG_NEWSA - XFRM_MSG_BASE] = { .doit = xfrm_add_sa }, [XFRM_MSG_DELSA - XFRM_MSG_BASE] = { .doit = xfrm_del_sa }, [XFRM_MSG_GETSA - XFRM_MSG_BASE] = { .doit = xfrm_get_sa, .dump = xfrm_dump_sa, .done = xfrm_dump_sa_done }, [XFRM_MSG_NEWPOLICY - XFRM_MSG_BASE] = { .doit = xfrm_add_policy }, [XFRM_MSG_DELPOLICY - XFRM_MSG_BASE] = { .doit = xfrm_get_policy }, [XFRM_MSG_GETPOLICY - XFRM_MSG_BASE] = { .doit = xfrm_get_policy, .dump = xfrm_dump_policy, .done = xfrm_dump_policy_done }, [XFRM_MSG_ALLOCSPI - XFRM_MSG_BASE] = { .doit = xfrm_alloc_userspi }, [XFRM_MSG_ACQUIRE - XFRM_MSG_BASE] = { .doit = xfrm_add_acquire }, [XFRM_MSG_EXPIRE - XFRM_MSG_BASE] = { .doit = xfrm_add_sa_expire }, [XFRM_MSG_UPDPOLICY - XFRM_MSG_BASE] = { .doit = xfrm_add_policy }, [XFRM_MSG_UPDSA - XFRM_MSG_BASE] = { .doit = xfrm_add_sa }, [XFRM_MSG_POLEXPIRE - XFRM_MSG_BASE] = { .doit = xfrm_add_pol_expire}, [XFRM_MSG_FLUSHSA - XFRM_MSG_BASE] = { .doit = xfrm_flush_sa }, [XFRM_MSG_FLUSHPOLICY - XFRM_MSG_BASE] = { .doit = xfrm_flush_policy }, [XFRM_MSG_NEWAE - XFRM_MSG_BASE] = { .doit = xfrm_new_ae }, [XFRM_MSG_GETAE - XFRM_MSG_BASE] = { .doit = xfrm_get_ae }, [XFRM_MSG_MIGRATE - XFRM_MSG_BASE] = { .doit = xfrm_do_migrate }, [XFRM_MSG_GETSADINFO - XFRM_MSG_BASE] = { .doit = xfrm_get_sadinfo }, [XFRM_MSG_NEWSPDINFO - XFRM_MSG_BASE] = { .doit = xfrm_set_spdinfo, .nla_pol = xfrma_spd_policy, .nla_max = XFRMA_SPD_MAX }, [XFRM_MSG_GETSPDINFO - XFRM_MSG_BASE] = { .doit = xfrm_get_spdinfo }, };
下面简单介绍一下其中几个函数的功能:c#
xfrm_add_sa:建立一个新的SA,并能够指定相关attr,在内核中,是用一个xfrm_state结构来表示一个SA的。segmentfault
xfrm_del_sa:删除一个SA,也即删除一个指定的xfrm_state。windows
xfrm_new_ae:根据传入参数,更新指定xfrm_state结构中的内容。
xfrm_get_ae:根据传入参数,查询指定xfrm_state结构中的内容(包括attr)。
简介:Netlink是linux提供的用于内核和用户态进程之间的通讯方式,也能用于用户空间的两个进程通讯(不多)。通常来讲用户空间和内核空间的通讯方式有三种:/proc、ioctl、Netlink。而前两种都是单向的,可是Netlink能够实现双工通讯。
Netlink 是一种特殊的 socket,它是 Linux 所特有的,相似于 BSD 中的AF_ROUTE 但又远比它的功能强大。目前在Linux 内核中使用netlink 进行应用与内核通讯的应用不少; 包括:路由 daemon(NETLINK_ROUTE),用户态 socket 协议(NETLINK_USERSOCK),防火墙(NETLINK_FIREWALL),netfilter 子系统(NETLINK_NETFILTER),内核事件向用户态通知(NETLINK_KOBJECT_UEVENT), 通用 netlink(NETLINK_GENERIC)等。
通讯方式:基于netlink
的内核通讯与socket
的通讯方式一致,都是经过sendto(),recvfrom(); sendmsg(), recvmsg()
的用户态API
。内核即可以调用 xfrm_netlink_rcv()
来接收和处理。
xfrm_state结构中比较重要的变量:
struct xfrm_id id
用于标识一个SA
身份,包含daddr、spi、proto
三个参数;
struct xfrm_id { xfrm_address_t daddr; __be32 spi; __u8 proto; };
两xfrm_replay_state_esn
结构体(replay_esn
和preplay_esn
),bmp是一个边长的内存区域,是一块bitmap
,用于标识数据包的seq
是否被重放过,其中bmp_len
表示变长结构体的大小,replay_window用于seq
索引的模数,即索引的范围,此结构体在建立xfrm_state
结构体时根据用户输入参数动态被建立,而程序漏洞存在于这个结构体的读写过程当中。bmp_len决定整个结构体的具体大小. 而replay_window则决定了bmp数组的索引范围。
struct xfrm_replay_state_esn { unsigned int bmp_len; __u32 oseq; __u32 seq; __u32 oseq_hi; __u32 seq_hi; __u32 replay_window; __u32 bmp[0]; };
static int xfrm_add_sa(struct sk_buff *skb, struct nlmsghdr *nlh, struct nlattr **attrs) (1)verify_newsa_info(p, attrs); // 协议及参数检查 verify_replay(p, attrs) // xfrm_replay_state_esn结构参数检查 /*检查:[1]bmp_len是否超过最大值,最大值定义为4096/4/8。[2]检查参数长度定义是否正确。[3]是否为IPPROTO_ESP或者IPPROTO_AH协议。*/ (2)xfrm_state_construct(net, p, attrs, &err) // 根据用户输入对结构体进行构造 - struct xfrm_state *x=xfrm_state_alloc(net); // 调用kzalloc函数新建xfrm_state结构 - copy_from_user_state(x, p); // 拷贝用户数据 - xfrm_alloc_replay_state_esn(); // 申请xfrm_replay_state_esn结构体 /*经过kzalloc函数分别申请了两块一样大小的内存(replay_esn和preplay_esn),大小为sizeof(*replay_esn) + replay_esn->bmp_len * sizeof(__u32),并将用户数据中attr[XFRMA_REPLAY_ESN_VAL]内容复制过去。*/ - xfrm_init_replay(); // 检查滑动窗口replay_window大小及flag,肯定检测使用的函数 /*对上述申请的结构体数据进行检查,replay_window不大于定义的bmp_len大小,并对x->repl进行初始化,该成员是一个函数虚表,做用是在收到AH或ESP协议数据包时进行数据重放检查。*/ x->repl = &xfrm_replay_esn;
目的:修改replay_esn成员,也即xfrm_alloc_replay_state_esn
申请的第1个内存块。
static int xfrm_new_ae(struct sk_buff *skb, struct nlmsghdr *nlh, struct nlattr **attrs) (1)xfrm_state_lookup() //循环查找hash表,获得xfrm_state结构体 (2)xfrm_replay_verify_len() //对用户输入的attr[XFRMA_REPLAY_ESN_VAL]参数进行检查,也就是修改后的replay_esn 成员内容。 /*主要检查了修改部分的bmp_len长度,该检查是由于replay_esn成员内存是直接进行复制的,再也不二次分配。但缺乏了对replay_window变量的检测,致使引用replay_window变量进行bitmap读写时形成的数组越界问题。*/ (3)xfrm_update_ae_params() //利用memcpy进行成员内容修改。
// 对xfrm模块代码中,replay_window关键字的查找,能够发现主要对这个关键字的操做位于xfrm_replay_advance_esn和xfrm_replay_check_esn函数中 static const struct xfrm_replay xfrm_replay_esn = { .advance = xfrm_replay_advance_esn, // 越界读写 .check = xfrm_replay_check_esn, // 越界读写 .recheck = xfrm_replay_recheck_esn, .notify = xfrm_replay_notify_esn, .overflow = xfrm_replay_overflow_esn, };
xfrm_replay_esn
结构体在xfrm_init_replay
函数中被使用,并赋值给x->repl
,x->repl
被xfrm_input
函数调用。xfrm_input
函数以前被xfrm4_rcv_spi <= xfrm4_rcv <= xfrm4_ah_rcv ,最终追溯到AH
协议的内核协议栈中。
static const struct net_protocol ah4_protocol = { .handler = xfrm4_ah_rcv, .err_handler = xfrm4_ah_err, .no_policy = 1, .netns_ok = 1, };
可见,经过发送AH
数据包能够触发越界读写。
xfrm_input函数,xfrm_replay_advance_esn函数。
int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type) (1)x = xfrm_state_lookup(net, mark, daddr, spi, nexthdr, family);//利用AH数据包数据找到对应的SA-xfrm_state (2)x->repl->check(x, skb, seq) //x->repl 在 xfrm_init_replay赋值,可调用xfrm_replay_check_esn。调用xfrm_replay_check_esn进行检查,再调用xfrm_replay_recheck_esn再次检查 *replay_esn = x->replay_esn; // 找到的仍是x->replay_esn成员 if (replay_esn->bmp[nr] & (1U << bitnr)) // 检查[1]处某bit是否为1,不然退出。即AH中的seq是否被处理过。 (3)x->repl->advance(x, seq); //调用xfrm_replay_advance_esn replay_esn->bmp[nr] &= ~(1U << bitnr); //if 对于某一个bit执行&0,将致使某一个bit被置零 nr = (replay_esn->replay_window - 1) >> 5; for (i = 0; i <= nr; i++) replay_esn->bmp[i] = 0; // else 对从bmp[0]到bmp[(replay_esn->replay_window - 1) >> 5]块内存均置零 replay_esn->bmp[nr] |= (1U << bitnr); // 对某一个bit写1。
所以,经过用户态空间发送一个AH数据包将致使,一个bit的内存写,或者一段空间的置零。
xfrm
数据包的协议格式/* ======================================================================== * Netlink Messages and Attributes Interface (As Seen On TV) * ------------------------------------------------------------------------ * Messages Interface * ------------------------------------------------------------------------ * * Message Format: * <--- nlmsg_total_size(payload) ---> * <-- nlmsg_msg_size(payload) -> * +----------+- - -+-------------+- - -+-------- - - * | nlmsghdr | Pad | Payload | Pad | nlmsghdr * +----------+- - -+-------------+- - -+-------- - - * nlmsg_data(nlh)---^ ^ * nlmsg_next(nlh)-----------------------+ * * Payload Format: * <---------------------- nlmsg_len(nlh) ---------------------> * <------ hdrlen ------> <- nlmsg_attrlen(nlh, hdrlen) -> * +----------------------+- - -+--------------------------------+ * | Family Header | Pad | Attributes | * +----------------------+- - -+--------------------------------+ * nlmsg_attrdata(nlh, hdrlen)---^ * * Data Structures: * struct nlmsghdr netlink message header * ------------------------------------------------------------------------ * Attributes Interface * ------------------------------------------------------------------------ * * Attribute Format: * <------- nla_total_size(payload) -------> * <---- nla_attr_size(payload) -----> * +----------+- - -+- - - - - - - - - +- - -+-------- - - * | Header | Pad | Payload | Pad | Header * +----------+- - -+- - - - - - - - - +- - -+-------- - - * <- nla_len(nla) -> ^ * nla_data(nla)----^ | * nla_next(nla)-----------------------------' * * Data Structures: * struct nlattr netlink attribute header */ // netlink(ROUTES类型) 数据结构可形象的表示为 /* <----- NLMSG_HDRLEN -----> <-------- RTM_PAYOAD(rtm) ---> <RTA_PAYLOAD(r)> +------------------+- - -+---------------+- - -+--------------+--------+ - -+ | Netlink Header | Pad | Family Header | Pad | Attributes | rtattr | Pad| | struct nlmsghdr | | struct rtmsg | | stuct rtattr | data | | +------------------+- - -+---------------+- - -+--------------+--------+ - -+ ^ ^ ^ ^ ^ nlh | | | | NLMSG_DATA(nlh) --------^ | | | RTM_RTA(rtm)-----------------------------------^ | | RTA_DATA(rta)-------------------------------------------------^ RTA_NEXT(rta) */
用户态数据结构的关系图
// 用 recv/send、readv/writev、recvfrom/sendto、recvmsg/sendmsg发送的消息结构 struct msghdr { void *msg_name; /* Address to send to/receive from. */ socklen_t msg_namelen; /* 目的地址数据结构的长度 */ struct iovec *msg_iov; /* 消息包的实际数据块,定义以下 */ size_t msg_iovlen; /* Number of elements in the vector. */ void *msg_control; /* 消息的辅助数据 (eg BSD filedesc passing). */ size_t msg_controllen; /* 消息辅助数据的大小 !! The type should be socklen_t but the definition of the kernel is incompatible with this. */ int msg_flags; /* 接收消息的标识 */ }; // Netlink的地址。源地址在bind函数中和相应socket绑定,目标地址填到msghdr的msg_name字段 struct sockaddr_nl { sa_family_t nl_family; /*该字段老是为AF_NETLINK */ unsigned short nl_pad; /* 目前未用到,填充为0*/ __u32 nl_pid; /* process pid 一般状况下nl_pid都设置为当前进程的进程号。*/ __u32 nl_groups; /* multicast groups mask 指明了调用者但愿加入的多播组号的掩码,为0则表示调用者不但愿加入任何多播组*/ }; // nlmsghdr头部结构 struct nlmsghdr { __u32 nlmsg_len; /* 数据总长度,包含头部 */ __u16 nlmsg_type; /* 消息类型,便是数据仍是控制消息 */ __u16 nlmsg_flags; /* 附加在消息上的额外说明信息 */ __u32 nlmsg_seq; /* Sequence number. */ __u32 nlmsg_pid; /* Sender port ID. */ }; struct iovec { void *iov_base; /* BSD uses caddr_t (1003.1g requires void *) */ __kernel_size_t iov_len; /* Must be size_t (1003.1g) */ };
从上图能够看出,发送到内核的数据须要以下形式nlmsghdr + Family Header + n * (nla + data)。
// 用户空间Netlink socket API //1.建立socket int socket(int domain, int type, int protocol) /*domain指代地址族,即AF_NETLINK; 套接字类型为SOCK_RAW或SOCK_DGRAM,由于netlink是一个面向数据报的服务; protocol选择该套接字使用哪一种netlink特征。*/ //2.地址绑定bind() bind(fd, (struct sockaddr*)&, nladdr, sizeof(nladdr)); //3.发送netlink消息 sendmsg(fd, &, msg, 0); /*若是该消息是发送至内核的,那么nl_pid和nl_groups都置为0. 若是消息是发送给另外一个进程的单播消息,nl_pid是另一个进程的pid值而nl_groups为零。 若是消息是发送给一个或多个多播组的多播消息,全部的目的多播组必须bitmask必须or起来从而造成nl_groups域。 */ //4.接收netlink消息 recvmsg(fd, &, msg, 0); /*当消息被正确的接收以后,nlh应该指向刚刚接收到的netlink消息的头。nladdr应该包含接收消息 的目的地址,其中包括了消息发送者的pid和多播组。同时,宏NLMSG_DATA(nlh),定义在 netlink.h中,返回一个指向netlink消息负载的指针。调用close(fd)关闭fd描述符所标识的socket。*/
首先从xfrm_netlink_rcv
函数中调用netlink_rcv_skb函数,会检查nlmsg_type
及nlmsg_len
范围,并交由cb
函数处理,其赋值为xfrm_user_rcv_msg
。
int netlink_rcv_skb(struct sk_buff *skb, int (*cb)(struct sk_buff *, struct nlmsghdr *)) { struct nlmsghdr *nlh; int err; while (skb->len >= nlmsg_total_size(0)) { int msglen; nlh = nlmsg_hdr(skb); err = 0; if (nlh->nlmsg_len < NLMSG_HDRLEN || skb->len < nlh->nlmsg_len) return 0; /* Only requests are handled by the kernel */ if (!(nlh->nlmsg_flags & NLM_F_REQUEST)) goto ack; /* Skip control messages */ if (nlh->nlmsg_type < NLMSG_MIN_TYPE) goto ack; err = cb(skb, nlh); if (err == -EINTR) goto skip; ack: if (nlh->nlmsg_flags & NLM_F_ACK || err) netlink_ack(skb, nlh, err); skip: msglen = NLMSG_ALIGN(nlh->nlmsg_len); if (msglen > skb->len) msglen = skb->len; skb_pull(skb, msglen); } return 0; }
在xfrm_user_rcv_msg函数中,会根据nlmsg_type
到xfrm_dispatch
中查找对应要调用的函数,并在[2]处检查对应须要的权限,而在[3]处会根据nla
中参数类型,来初始化一个** attr
,做为用户输入参数的索引。最终调用link->doit
去执行。
static int xfrm_user_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh) { struct net *net = sock_net(skb->sk); struct nlattr *attrs[XFRMA_MAX+1]; const struct xfrm_link *link; int type, err; #ifdef CONFIG_COMPAT if (in_compat_syscall()) return -EOPNOTSUPP; #endif type = nlh->nlmsg_type; if (type > XFRM_MSG_MAX) return -EINVAL; type -= XFRM_MSG_BASE; [1] link = &xfrm_dispatch[type]; /* All operations require privileges, even GET */ [2] if (!netlink_net_capable(skb, CAP_NET_ADMIN)) //检查进程权限 return -EPERM; if ((type == (XFRM_MSG_GETSA - XFRM_MSG_BASE) || type == (XFRM_MSG_GETPOLICY - XFRM_MSG_BASE)) && (nlh->nlmsg_flags & NLM_F_DUMP)) { if (link->dump == NULL) return -EINVAL; { struct netlink_dump_control c = { .dump = link->dump, .done = link->done, }; return netlink_dump_start(net->xfrm.nlsk, skb, nlh, &c); } } [3] err = nlmsg_parse(nlh, xfrm_msg_min[type], attrs, link->nla_max ? : XFRMA_MAX, link->nla_pol ? : xfrma_policy); if (err < 0) return err; if (link->doit == NULL) return -EINVAL; return link->doit(skb, nlh, attrs); }
从xfrm_dispatch
可见,咱们所需的XFRM_MSG_NEWSA
及XFRM_MSG_NEWAE
,仅需将nlmsg_type
设置为相应值便可。 xfrm_dispatch
见上述Linux内核的IPSEC实现。
而Family Header
须要到对应的处理函数中找,以xfrm_add_sa
为例,其调用nlmsg_data
函数的赋值变量类型为xfrm_usresa_info
,即为Family Header
。
struct xfrm_usersa_info *p = nlmsg_data(nlh);
权限限制:便是在上文中提到的netlink_net_capable(skb, CAP_NET_ADMIN)
检查,所需为CAP_NET_ADMIN
权限。但在Linux
操做系统中存在命名空间这样的权限隔离机制,在每个NET
沙箱中,非ROOT
进程能够具备CAP_NET_ADMIN
权限。查看命名空间开启的方式为cat /boot/config* | grep CONFIG_USER_NS
,若为「y」,则启用了命名空间。
命名空间:namespace 是 Linux 内核用来隔离内核资源的方式(为虚拟化而生)。经过 namespace 可让一些进程只能看到与本身相关的一部分资源,而另一些进程也只能看到与它们本身相关的资源,这两拨进程根本就感受不到对方的存在。具体的实现方式是把一个或多个进程的相关资源指定在同一个 namespace 中。 Linux namespaces 是对全局系统资源的一种封装隔离,使得处于不一样 namespace 的进程拥有独立的全局系统资源,改变一个 namespace 中的系统资源只会影响当前 namespace 里的进程,对其余 namespace 中的进程没有影响。
绕过限制的两种方法:一是使用setcap
命令为EXP
赋予权限,即执行sudo setcap cap_net_raw,cap_net_admin=eip ./exp
。二是仿照CVE-2017-7308中设置namespace sandbox
,但注意此时没法利用getuid
来判断是否为root
用户。
绕过权限限制:在ubuntu,Fedora等发行版,User namespace是默认开启的。非特权用户能够建立用户命名空间、网络命名空间。在命名空间内部,咱们就能够触发漏洞了。
越界写:xfrm_replay_advance_esn()函数对bitmap操做是清除[last seq, current seq)的bit;设置bmp[current seq] = 1。能够指定好spi、seq等参数(内核是根据spi的哈希值以及ip地址来肯定SA的),并让内核来处理咱们发出的ESP数据包,屡次进行这个操做便可达到对越界任意长度进行写入任意值。
越界读:本例不须要。构造两个相邻的replay_state结构(首先分配足够多的一样大小的replay_state结构把堆上原来的坑填满,以后即可大几率保证连续分配的replay_state结构是相邻的),使用越界写,改大下一个replay_state_esn的结构中的bmp_len,以后利用下一个bitmap结构进行越界读。以下所示,为被改掉bmp_len的bitmap结构:
pwndbg> p *(struct xfrm_repaly_state_esn*)(0xffff9a6a28262b00+0x200) $1 = { bmp_len = 1024, oseq = 0, seq = 0, oseq_hi = 0, replay_window = 64, bmp = 0xffff9a6a8262d18 }
绕过kASLR:利用xfrm_del_sa函数删除没用的xfrm_state,再向内核喷射不少struct file结构体填在这些坑里,最后利用越界读能力,能够泄露一些内核里的指针来算出内核的加载地址和bitmap的位置。
代码执行:构造内核rop,先在bitmap中伪造一个file_operations结构;再利用越界写改写刚在内核中喷射的struct file结构体的file_operations指针,使其指向合适的ROPgadget;调用llseek函数(实际上已是rop gadget);屡次改写file_operations结构中的llseek函数指针来实现屡次执行ROPgadget实现提权。 ???方法存疑,还能屡次改seek指针???
利用思路是用每次写1bit
的方法,屡次写达到覆盖下一xfrm_replay_state_esn
中的bmp_len
,从而越界读泄露地址来绕过kaslr
。而且能够经过越界写的方法来写如file_operations
、tty_struct
这样的虚表结构,达到劫持控制流的目的,将ROP
数据经过do_msgsnd
这样的函数布置在内核里,从而绕过SMEP
和SMAP
,最终利用控制流劫持跳转回ROP
。
xfrm_replay_advance_esn()中,先清0,后续的还有一个置位操做,因此构造好 replay_window seq seq_no三个值,将cred结构体清0以后,再将cred->usage值还原(调试这个值为2)。
利用伪代码:
/* 用于向内核发送信息, 而后生成/更新xfrm_state结构体的套接字 */ xfrm_state_fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_XFRM); /* 用于接收IPPROTO_AH信息, 触发xfrm_input的接收套接字 */ recvfd = socket(AF_INET, SOCK_RAW, IPPROTO_AH); /* 用于发送自定义数据包的发送套接字 */ sendfd = socket(AF_INET, SOCK_RAW, IPPROTO_AH); alloc_xfrm_state(...); update_esn(...); trigger_oob(...);
内存布局:
xfrm_alloc_replay_state_esn函数中, 连续申请了两个esn 也就是说, 在alloc_xfrm_state
函数以后, 内存布局是这样的
内存低地址 | xfrm_replay_state_esn 结构0 | xfrm_replay_state_esn 结构1 | cred 结构 | 高地址
在测试系统中, cred对象的大小为0xa8,对齐到kmalloc-192的slab块中,也即每一个对象大小为0xc0。那么cred结构的相对于esn->bmp偏移为0xc0-0x18+0xc0,对应32位数据的数组索引,cred->usage的nr值为 (0xc0-0x18+0xc0)/4。 查看POC点这里
本漏洞属于一个利用条件比较宽松的漏洞。首先,xfrm_replay_state_esn
是一个变长的数据结构,而其长度能够由用户输入的bmp_len
来控制,并由kzalloc
申请bmp_len *4 + 0x18
大小的内存块。其次,越界读写能够每次写1bit
大小的数据,同时也能够将(replay_windows -1)>>5
比特大小的内存块清空。
而且cred
结构体的申请是经过prepare_creds
中的new = kmem_cache_alloc(cred_jar, GFP_KERNEL);
获得的,但在调试中发现,本内核的cred_jar
是kmalloc-192
。
根据内核分配使用的slub
+伙伴算法能够知道,对于同一个kmem_cache
分配出来的内存块有必定几率是相邻。所以一种很取巧的思路,就是将xfrm_replay_state_esn
结构体设置为192(0xc0)
之内,以利用kmalloc-192
进行分配,并利用fork
新建大量进程,使申请大量cred
,这样喷射以后有很大几率越界读写漏洞存在的位置以后就是一个cred
结构体,这样利用以前提到过的置零一段内存的操做就能够将cred
结构体中的部分红员(uid gid等)
置零,从而对该进程提权,并经过反弹shell
就能够获得一个root
权限的shell
。
所以对于数据包构造主要根据上述思路。
xfrm_add_sa
在触发xfrm_add_sa
函数的数据包中,须要知足128 < bmp_len * 4 +0x18 < 192
。而且须要参考以前源码分析中的各项flag
及参数检查。
xfrm_new_ae
在触发xfrm_new_ae
函数的数据包中,须要对seq_hi
、seq
及replay_window
进行设定,replay_window
即将要置零的长度大小,因为连续申请了两块大小相同的结构体,而置零的时候是从第一次申请的位置操做的,有可能出现两者相邻,所以须要将replay_window
设置稍大一些。而seq_hi
、seq
两个数据须要结合以后发送的ah
数据包中的seq
参数,引导xfrm_replay_advance_esn
走向置零bmp[0]~bmp[n]
这个分支。
AH数据包
AH
数据包的要求即spi
须要和以前申请SA
的spi
相同用于寻找xfrm_state
,而且须要知足
diff >= replay_esn->replay_window
,其中diff
的数据由xfrm_replay_state_esn
中的seq
、seq_hi
及AH
的seq
共同决定。还行需在后续单字节写的位置,将cred
结构体中usage
置回原值。
在xfrm_replay_advance_esn
函数执行先后发现,相邻cred
中的成员被置零。
注意给exp权限CAP_NET_ADMIN
:sudo setcap cap_net_raw,cap_net_admin=eip ./exp
p4nda—Linux xfrm模块越界读写提权漏洞分析(CVE-2017-7184)
长亭—Pwn2Own 2017 Linux 内核提权漏洞分析
cve-2017-7184 (长亭在Pwn2Own上用于提权Ubuntu的漏洞) 的分析利用
linux用户空间与内核空间通讯——Netlink通讯机制——详细描述了用户态数据结构
Netlink+内核实现分析(二):通讯——有示例代码,跟exp中很像