PWNABLE.TW-seccomp-tools-分析

Posted on Jul 30, 2021

有一段时间没更新博客了,一方面是被这道题卡住,另一方面也是最近比较颓废,基本上每天都在睡觉。这道题感觉已经做不出来了,所以我就把分析放在这里,以后看看能不能解掉吧。

程序的功能为:读取、模拟、加载用户输入的 bpf 代码,其中加载时使用的是 prctl 系统调用,功能号为 PR_GET_SECCOMP。

与 BPF 相关的知识可以参考 seccomp 中的 bpf

为了分析方便,根据 cBPF 和 filter 的相关定义,可以建立两个结构体

struct sock_filter
{
  unsigned __int16 code;
  unsigned __int8 jt;
  unsigned __int8 jf;
  unsigned int k;
};
struct sock_fprog
{
  unsigned __int16 len;
  struct sock_filter *filter;
};

代码量比较大的是在 emulate 里面,这里实现了一个虚拟机,猜测虚拟机结构体为

struct BPF_VM
{
  __u32 syscall_num;
  __u32 magic_num;
  __u64 dummy;
  __u64 argv[6];
  __u32 PC;
  __u32 ret_val;
  int BPF_A;
  __u32 BPF_X;
  __u32 mem[16];
};

对于其中的各个函数我简单重命名了一下

void emulate()
{
  __u32 result; // eax
  int i; // [rsp+Ch] [rbp-14h]
  struct BPF_VM *VM; // [rsp+18h] [rbp-8h]

  if ( !bpf_bytes_len )
  {
    puts("Create a rule first");
    return;
  }
  VM = (struct BPF_VM *)malloc(0x90uLL);
  init_filter_prog(VM);
  printf("syscall number? ");
  if ( scanf("%d", VM) != 1 )
    exit(1);
  printf("arguments? ");
  for ( i = 0; i <= 5; ++i )
  {
    if ( scanf("%llu", &VM->argv[i]) != 1 )
      exit(1);
  }
  while ( (unsigned __int8)run_vm(VM) )
    ;
  result = VM->ret_val & 0x7FFF0000;
  if ( result == 0x50000 )
  {
    printf("\nResult: %s\n", "ERRNO");
  }
  else if ( result > 0x50000 )
  {
    if ( result == 0x7FF00000 )
    {
      printf("\nResult: %s\n", "TRACE");
    }
    else
    {
      if ( result != 0x7FFF0000 )
      {
LABEL_24:
        printf("\nResult: %s\n", "KILL");
        goto LABEL_25;
      }
      printf("\nResult: %s\n", "ALLOW");
    }
  }
  else
  {
    if ( !result || result != 0x30000 )
      goto LABEL_24;
    printf("\nResult: %s\n", "TRAP");
  }
LABEL_25:
  free(VM);
}
__int64 __fastcall run_vm(struct BPF_VM *VM)
{
  unsigned __int16 last_PC; // [rsp+1Ch] [rbp-4h]

  last_PC = 8 * VM->PC;
  if ( last_PC >= (unsigned __int16)bpf_bytes_len )
    return 0LL;
  emulate_one_insn(&bpf_code_arr[last_PC / 8u], VM);
  if ( VM->PC == -1 )                           // ret
    return 0LL;
  if ( (unsigned __int16)(8 * VM->PC) > last_PC )// move forward?
    return 1LL;
  puts("Loop detected");                        // moved back, err
  return 0LL;
}
unsigned __int64 __fastcall emulate_one_insn(struct sock_filter *insn, struct BPF_VM *VM)
{
  unsigned __int64 result; // rax
  __u32 v3; // edx
  __u32 v4; // edx
  __u32 idx; // ecx
  __u32 *v6; // rax
  __u32 *v7; // rax
  int jump_n; // eax
  __u32 PC_next; // edx
  bool flag; // [rsp+17h] [rbp-19h]
  __u32 *reg_ptr; // [rsp+18h] [rbp-18h] MAPDST

  bpf_decode(insn);
  result = (unsigned int)BPF_CLASS;
  switch ( BPF_CLASS )
  {
    case 0:                                     // BPF_LD or BPF_LDX
      ++VM->PC;
      if ( BPF_tmp )
        result = (unsigned __int64)&VM->BPF_X;  // LDX store to X
      else
        result = (unsigned __int64)&VM->BPF_A;  // LD  store to A
      reg_ptr = (__u32 *)result;
      if ( BPF_MODE )
      {
        if ( BPF_MODE == 0x60 )                 // BPF_MEM
        {
          v3 = VM->mem[ret_val_in_interval(BPF_GENERAL, 0, 0xFu)];// BPF_GENERAL -> idx
          result = (unsigned __int64)reg_ptr;
          *reg_ptr = v3;
        }
        else
        {
          result = BPF_MODE;
          if ( BPF_MODE == 0x20 )               // BPF_ABS
          {
            v4 = *(&VM->syscall_num + ret_val_in_interval(BPF_GENERAL >> 2, 0, 0xFu));
            result = (unsigned __int64)reg_ptr;
            *reg_ptr = v4;
          }
        }
      }
      else
      {
        *(_DWORD *)result = BPF_GENERAL;        // BPF_IMM
      }
      break;
    case 2:                                     // BPF_ST or BPF_STX
      ++VM->PC;
      idx = ret_val_in_interval(BPF_MODE, 0, 0xFu);
      if ( BPF_tmp )
        result = VM->BPF_X;                     // STX
      else
        result = (unsigned int)VM->BPF_A;       // ST
      VM->mem[idx] = result;
      break;
    case 4:                                     // BPF_ALU
      ++VM->PC;
      if ( BPF_tmp == 0x80 )                    // BPF_NEG
      {
        result = (unsigned __int64)VM;
        VM->BPF_A = -VM->BPF_A;
      }
      else
      {
        if ( BPF_MODE )
          v6 = &VM->BPF_X;
        else
          v6 = &insn->k;
        reg_ptr = v6;
        if ( BPF_tmp )
        {
          switch ( BPF_tmp )                    // calc
          {
            case 0x10:
              result = (unsigned __int64)VM;
              VM->BPF_A -= *reg_ptr;
              break;
            case 0x20:
              result = (unsigned __int64)VM;
              VM->BPF_A *= *reg_ptr;
              break;
            case 0x30:
              result = (unsigned __int64)VM;
              VM->BPF_A /= *reg_ptr;
              break;
            case 0x40:
              result = (unsigned __int64)VM;
              VM->BPF_A |= *reg_ptr;
              break;
            case 0x50:
              result = (unsigned __int64)VM;
              VM->BPF_A &= *reg_ptr;
              break;
            case 0x60:
              result = (unsigned __int64)VM;
              VM->BPF_A <<= *reg_ptr;
              break;
            case 0x70:
              result = (unsigned __int64)VM;
              VM->BPF_A = (unsigned int)VM->BPF_A >> *reg_ptr;
              break;
            default:
              result = (unsigned int)BPF_tmp;
              if ( BPF_tmp == 0xA0 )
              {
                result = (unsigned __int64)VM;
                VM->BPF_A ^= *reg_ptr;
              }
              break;
          }
        }
        else
        {
          result = (unsigned __int64)VM;
          VM->BPF_A += *reg_ptr;
        }
      }
      break;
    case 5:                                     // BPF_JMP
      ++VM->PC;
      if ( BPF_MODE )
        v7 = &VM->BPF_X;
      else
        v7 = &insn->k;
      if ( BPF_tmp )
      {
        switch ( BPF_tmp )
        {
          case 0x20:
            flag = VM->BPF_A > *v7;             // jump above
            break;
          case 0x30:
            flag = VM->BPF_A >= *v7;            // jump above equal
            break;
          case 0x10:
            flag = VM->BPF_A == *v7;            // jump equal
            break;
          case 0x40:
            flag = (VM->BPF_A & *v7) != 0;      // JSET
            break;
        }
        if ( flag )
          jump_n = insn->jt;
        else
          jump_n = insn->jf;
        PC_next = jump_n + VM->PC;
        result = (unsigned __int64)VM;
        VM->PC = PC_next;
      }
      else
      {
        result = (unsigned __int64)VM;          // always jump
        VM->PC += insn->k;
      }
      break;
    case 6:                                     // BPF_RET
      VM->PC = -1;
      result = (unsigned __int64)VM;
      if ( BPF_tmp >= 0 )
        VM->ret_val = BPF_tmp;                  // BPF_RET + BPF_else, k; ret k
      else
        VM->ret_val = VM->BPF_A;                // BPF_RET + BPF_A
      break;
    case 7:                                     // BPF_MISC
      ++VM->PC;
      result = (unsigned __int64)VM;
      if ( BPF_tmp )
        VM->BPF_A = VM->BPF_X;                  // BPF_MISC + 0x80
      else
        VM->BPF_X = VM->BPF_A;                  // BPF_MISC + 0x00
      break;
    default:
      return result;
  }
  return result;
}
__int64 __fastcall bpf_decode(struct sock_filter *insn)
{
  __int64 result; // rax

  BPF_CLASS = insn->code & 7;                   // get instruction classes
  result = (unsigned int)BPF_CLASS;
  switch ( BPF_CLASS )
  {
    case 0:                                     // BPF_LD
    case 1:                                     // BPF_LDX
      BPF_tmp = BPF_CLASS == 1;
      BPF_CLASS = 0;
      BPF_MODE = insn->code & 0xE0;             // get instruction mode
      result = insn->k;
      BPF_GENERAL = insn->k;
      break;
    case 2:                                     // BPF_ST
    case 3:                                     // BPF_STX
      BPF_tmp = BPF_CLASS == 3;
      BPF_CLASS = 2;
      result = insn->k;
      BPF_MODE = insn->k;
      break;
    case 4:                                     // BPF_ALU
      BPF_tmp = insn->code & 0xF0;
      result = (insn->code & 8) != 0;
      BPF_MODE = (insn->code & 8) != 0;         // get reg
      break;
    case 5:                                     // BPF_JMP
      BPF_tmp = insn->code & 0x70;
      result = (insn->code & 8) != 0;
      BPF_MODE = (insn->code & 8) != 0;
      break;
    case 6:                                     // BPF_RET
      if ( (insn->code & 0x18) == 0x10 )
        result = 0xFFFFFFFFLL;                  // BPF_RET + BPF_A
      else
        result = insn->k;                       // BPF_RET + else
      BPF_tmp = result;                         // ret k
      break;
    case 7:                                     // BPF_MISC
      result = (insn->code & 0x80) != 0;
      BPF_tmp = (insn->code & 0x80) != 0;
      break;
    default:
      return result;
  }
  return result;
}

简单地分析了一下虚拟机的实现,只发现 Loop 检测有问题,跳转到负数是可以的,但是似乎没啥用。