【CVE-2017-7184】Linux xfrm模塊越界讀寫提權漏洞分析

1、漏洞分析

漏洞位於內核xfrm模塊,該模塊是IPSEC協議的實現模塊。ISO文件下載利用腳本下載html

1.簡介

(1)IPSEC協議簡介

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數據包提供完整性檢查、認證和加密。算法

(2)Linux內核的IPSEC實現

在linux內核中的IPSEC實現便是xfrm這個框架(讀做transform轉換,表示內核協議棧收到的IPsec報文須要通過轉換才能還原爲原始報文),不少解決方案如StrongSwanOpenSwan都使用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)。

(3)Netlink介紹

簡介: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() 來接收和處理。

2.漏洞分析

(1)xfrm_state結構體生成

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_esnpreplay_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];
    };

xfrm_add_sa函數

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;

(2)xfrm_replay_state_esn結構體更新

目的:修改replay_esn成員,也即xfrm_alloc_replay_state_esn申請的第1個內存塊。

xfrm_new_ae函數

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進行成員內容修改。

(3)數組越界寫定位

// 對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->replx->replxfrm_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的內存寫,或者一段空間的置零


2、漏洞觸發與利用

1.Netlink內核協議解析

(1)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。*/

(2)數據處理分析

首先從xfrm_netlink_rcv函數中調用netlink_rcv_skb函數,會檢查nlmsg_typenlmsg_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_typexfrm_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_NEWSAXFRM_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);

2.利用思路

(1)權限限制

權限限制:便是在上文中提到的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用戶。

(2)利用方法

  • 繞過權限限制:在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_operationstty_struct這樣的虛表結構,達到劫持控制流的目的,將ROP數據經過do_msgsnd這樣的函數佈置在內核裏,從而繞過SMEPSMAP,最終利用控制流劫持跳轉回ROP

(3)注意

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點這裏

(4)數據包構造

本漏洞屬於一個利用條件比較寬鬆的漏洞。首先,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_jarkmalloc-192
1

根據內核分配使用的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_hiseqreplay_window進行設定,replay_window即將要置零的長度大小,因爲連續申請了兩塊大小相同的結構體,而置零的時候是從第一次申請的位置操做的,有可能出現兩者相鄰,所以須要將replay_window設置稍大一些。而seq_hiseq兩個數據須要結合以後發送的ah數據包中的seq參數,引導xfrm_replay_advance_esn走向置零bmp[0]~bmp[n]這個分支。

AH數據包

AH數據包的要求即spi須要和以前申請SAspi相同用於尋找xfrm_state,而且須要知足

diff >= replay_esn->replay_window,其中diff的數據由xfrm_replay_state_esn中的seqseq_hiAHseq共同決定。還行需在後續單字節寫的位置,將cred結構體中usage置回原值。

xfrm_replay_advance_esn函數執行先後發現,相鄰cred中的成員被置零。

2

3

(5)成功提權

注意給exp權限CAP_NET_ADMINsudo 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的內核實現原理

linux用戶空間與內核空間通訊——Netlink通訊機制——詳細描述了用戶態數據結構

Netlink+內核實現分析(二):通訊——有示例代碼,跟exp中很像

Netlink 基於事件的信號機制

XFRM -- IPsec協議的內核實現框架

Linux Namespace : 簡介

相關文章
相關標籤/搜索