1. 程式人生 > >CVE-2017-16995 分析報告

CVE-2017-16995 分析報告

_id exploit sta bpf 過去 media The tcpdump 漏洞分析

漏洞背景

要說eBPF就先說,BPF。BPF 的全稱是 Berkeley Packet Filter,顧名思義,這是一個用於過濾(filter)網絡報文(packet)的架構,其中著名的tcpdump,wireshark都使用到了它(詳細資料參考參考資料中的條目2)。其中eBPF就是BPF的一種擴展。然而在Linux內核實現中,存在一種繞過操作導致提權。

環境

內核版本: 4.4.98

漏洞介紹

造成該漏洞的根本原因是:驗證時模擬執行的結果與BPF虛擬機執行時的不一致造成的

該漏洞其實是個符號擴展漏洞,給個簡單的代碼描述該漏洞成因:

#include <stdio.h>
#include <stdint.h>
int main(void){
    int imm = -1;   
    uint64_t dst = 0xffffffff;

    if(dst != imm){
        printf("360 cert\n");
    }   

    return 0;
}

在比較時,會將 imm 進行擴展 導致 imm 為 0xffffffffffff`ffff 所以會導致輸出 360 cert

要利用該漏洞,還是比較有意識有趣的

用戶通過bpf函數,設置命名參數為BPF_PROG_LOAD,向內核提交bpf程序。內核在用戶提交程序的時候,會進行驗證操作,驗證bpf程序的合法性。但是只在提交時進行驗證,運行時並不會驗證,所以我們可以想辦法讓我們的惡意代碼饒過驗證,在執行的時候執行我們的惡意代碼

驗證過程如下

1.kernel/bpf/syscall.c:bpf_prog_load
2.kernel/bpf/verifier.c:bpf_check
3.kernel/bpf/verifier.c:do_check

在第3個函數中,會對每一條bpf指令進行驗證,我們可以分析該函數.發現該函數會使用類似分支預測的特性.對不會執行的分支根本不會去驗證(重點:我們可以讓我們的惡意代碼位於“不會”跳過去的分支中)

其中對條件轉移指令的解析位於:

1.kernel/bpf/verifier.c: check_cond_jmp_op

分析該函數可以發現:


if (BPF_SRC(insn->code) == BPF_K &&
(opcode == BPF_JEQ || opcode == BPF_JNE) &&
regs[insn->dst_reg].type == CONST_IMM &&
regs[insn->dst_reg].imm == insn->imm) {
    if (opcode == BPF_JEQ) {
        /* if (imm == imm) goto pc+off;
        * only follow the goto, ignore
        fall-through
        */
        *insn_idx += insn->off;
        return 0;
    } else {
        /* if (imm != imm) goto pc+off;
        * only follow fall-through branch,
        since
        * that‘s where the program will go
        */
        return 0;
    }
}
    

寄存器與立即數進行 “不等於” 條件判斷時,進行了靜態分析工作,分析到底執不驗證該分支(需結合kernel/bpf/verifier.c:do_check)。而在進行 立即數與 寄存器比較時,立即數的類型與寄存器的類型為:

struct reg_state {
    enum bpf_reg_type type;
    union {
        /* valid when type == CONST_IMM | PTR_TO_STACK */
        int imm;

        /* valid when type == CONST_PTR_TO_MAP | PTR_TO_MAP_VALUE |
         *   PTR_TO_MAP_VALUE_OR_NULL
         */
        struct bpf_map *map_ptr;
    };
};

struct bpf_insn {
    __u8    code;       /* opcode */
    __u8    dst_reg:4;  /* dest register */
    __u8    src_reg:4;  /* source register */
    __s16   off;        /* signed offset */
    __s32   imm;        /* signed immediate constant */
};

都為有符號,該比較不會發生什麽問題。

現在轉移到bpf虛擬機執行bpf指令的函數:
/kernel/bpf/core.c: __bpf_prog_run

分析該函數,發現

u64 regs[MAX_BPF_REG];

其中用 uint64_t 表示寄存器,而立即數繼續為:
struct bpf_insn 中的imm字段

查看其解析“不等於比較指令”的代碼

#define DST regs[insn->dst_reg]
#define IMM insn->imm

........
JMP_JEQ_K:
    if (DST == IMM) {
        insn += insn->off;
        CONT_JMP;
    }
    CONT;

進行了32位有符號與64位無符號的比較.

那麽我們可以這樣繞過惡意代碼檢查:

(u32)r9 = (u32)-1
if r9 != 0xffff`ffff goto bad_code
ro,0
exit

bad_code:
.........

在提交代碼進行驗證時,對 jne 分析,發現不跳,會略過bad_code的檢查。
但是真正運行時,會導致跳轉為真,執行我們的惡意代碼。

漏洞分析

從參考資料3中,下載exp。我們可以在用戶向內核提交bpf代碼前,將 union bpf_attr 結構中的 log_level 字段 設置為 1,log其他字段合理填寫。在調用提交代碼之後,輸出log。我們就可以發現我們的那些指令經過了驗證。驗證結果如下:

技術分享圖片

可以發現只驗證了4 條,但是該exp 有30多條指令(提權)......

我們查看造成漏洞的代碼(64位無符號與32位有符號的比較操作),

技術分享圖片

技術分享圖片

發現其成功跳過了退出指令

參考資料

1.漏洞修復:https://github.com/torvalds/linux/commit/95a762e2c8c942780948091f8f2a4f32fce1ac6f

2.eBPF簡史:https://www.ibm.com/developerworks/cn/linux/l-lo-eBPF-history/index.html

3.exp:
http://cyseclabs.com/exploits/upstream44.c

4.ubuntu調試:
https://sysprogs.com/VisualKernel/tutorials/setup/ubuntu/

5.bpf反編譯:
https://github.com/mrmacete/r2scripts/blob/master/bpf/README.md

6.poc:
https://bugs.chromium.org/p/project-zero/issues/detail?id=1454&desc=3

7.eBPF指令:
https://github.com/iovisor/bpf-docs/blob/master/eBPF.md

CVE-2017-16995 分析報告