CVE-2017-16995 分析報告
漏洞背景
要說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 為 0xffffffff
ffff`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 分析報告