漏洞位於內核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中很像