前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Bochspwn漏洞挖掘技术深究(2):未初始化漏洞检测

Bochspwn漏洞挖掘技术深究(2):未初始化漏洞检测

作者头像
泉哥
发布2019-07-18 17:56:39
2K0
发布2019-07-18 17:56:39
举报
文章被收录于专栏:漏洞战争漏洞战争

本文主要介绍Bochspwn Reloaded(https://github.com/googleprojectzero/bochspwn-reloaded)内核未初始化漏洞检测技术,它采用污点追踪对内核层向用户层泄露数据的行为进行检测。

关于bochs插桩技术参考《Bochspwn漏洞挖掘技术深究(1):Double Fetches 检测》,此处不再赘述。

直接先看下instrument.h中实现插桩函数有哪些:

代码语言:javascript
复制
// Bochs初始化CPU对象时的回调函数
void bx_instr_initialize(unsigned cpu);
// Bochs析构CPU对象时的回调函数
void bx_instr_exit(unsigned cpu);
//Bochs每次执行中断操作(软件中断、硬件中断或异常)时的回调函数
void bx_instr_interrupt(unsigned cpu, unsigned vector);
// Bochs执行指令前的回调函数
void bx_instr_before_execution(unsigned cpu, bxInstruction_c *i);
// Bochs执行指令后的回调函数
void bx_instr_after_execution(unsigned cpu, bxInstruction_c *i);
// Bochs访问线性内存时的回调函数
void bx_instr_lin_access(unsigned cpu, bx_address lin, bx_address phy,
                         unsigned len, unsigned memtype, unsigned rw);
// WRMSR指令(写模式定义寄存器)被执行时的回调函数,MSR寄存器数与值作为参数传递给回调函数
void bx_instr_wrmsr(unsigned cpu, unsigned addr, Bit64u value);

初始化工作

第一篇中讲过bx_instr_initialize主要用来加载配置信息,针对不同的系统环境设置不同的数据结构偏移地址,用来提供需要的进程/线程等重要信息。在这里它另外增加污点追踪功能的初始化工作:

代码语言:javascript
复制
  // Initialize the taint subsystem.
  taint::initialize();

  // Initialize helper taint allocations.
  globals::pool_taint_alloc = (uint8_t *)malloc(kTaintHelperAllocSize);
  memset(globals::pool_taint_alloc, kPoolTaintByte, kTaintHelperAllocSize);

  globals::stack_taint_alloc = (uint8_t *)malloc(kTaintHelperAllocSize);
  memset(globals::stack_taint_alloc, kStackTaintByte, kTaintHelperAllocSize);

主要作一些用于污点信息记录的内存结构分配与VEH异常处理回调设置:

代码语言:javascript
复制
void initialize() {
  // Reserve a memory region for the taint data.
  taint_area = (uint8_t *)VirtualAlloc(NULL, kTaintAreaSize, MEM_RESERVE, PAGE_READWRITE);

  // Register a VEH handler to commit taint memory touched in other taint
  // functions.
  AddVectoredExceptionHandler(/*FirstHandler=*/1, OvercommitHandler);
}

VEH回调函数实现如下,当发生访问违例时,若异常地址不在污点内存区域,则将其设置为可读写内存,然后继续执行:

代码语言:javascript
复制
static LONG CALLBACK OvercommitHandler(
  _In_ PEXCEPTION_POINTERS ExceptionInfo
) {
  if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) {
    const uint8_t *excp_address = (uint8_t *)ExceptionInfo->ExceptionRecord->ExceptionInformation[1];
    if (excp_address >= taint_area && excp_address < &taint_area[kTaintAreaSize]) {
      if (VirtualAlloc((void *)((uint64_t)excp_address & (~0xffff)), 0x10000, MEM_COMMIT, PAGE_READWRITE)) {
        return EXCEPTION_CONTINUE_EXECUTION;
      }
    }
  }

  return EXCEPTION_CONTINUE_SEARCH;
}

中断响应

再看下bx_instr_interrupt函数实现,主要是发生中断时,检测该中断地址是否可写,并设置全局标志:

代码语言:javascript
复制
void bx_instr_interrupt(unsigned cpu, unsigned vector) {
  if (globals::bp_active && vector == 3) {
    BX_CPU_C *pcpu = BX_CPU(cpu);
    write_lin_mem(pcpu, globals::bp_address, 1, &globals::bp_orig_byte);

    globals::bp_active = false;
  }
}

污点标记与追踪

bochspwn-reloaded会对内核分配的stack/heap/pools作污点标记:

  1. 栈污点标记

检测修改ESP寄存器的指令,比如:ADD ESP, ... SUB ESP, ... AND ESP, …,若在执行后(bx_instr_after_execution)ESP发生递减,则调用taint::set_taint(new_rsp, length, /*tainted=*/true)标记为污点

代码语言:javascript
复制
void bx_instr_before_execution(unsigned cpu, bxInstruction_c *i) {
    ...
  const unsigned int opcode = i->getIaOpcode();
  switch (opcode) {
    case BX_IA_SUB_EqId:
    case BX_IA_SUB_GqEq: /* Stack allocation handling */ 
          ...
    case BX_IA_PUSH_Eq: /* Allocator prologue handling. */ 
          ...
}
代码语言:javascript
复制
void bx_instr_after_execution(unsigned cpu, bxInstruction_c *i) {
  globals::rep_movs = false;

  if (globals::rsp_change) {
    BX_CPU_C *pcpu = BX_CPU(cpu);
    const uint64_t new_rsp = pcpu->gen_reg[BX_64BIT_REG_RSP].rrx;

    if (new_rsp < globals::rsp_value) {
      uint64_t length = globals::rsp_value - new_rsp;

      if (length <= kTaintHelperAllocSize) {
        taint::set_taint(new_rsp, length, /*tainted=*/true);
        write_lin_mem(pcpu, new_rsp, length, (void *)globals::stack_taint_alloc);

        if (globals::config.track_origins) {
          taint::set_origin(new_rsp, length, pcpu->prev_rip);
        }
      }
    }

    globals::rsp_change = false;
    globals::rsp_value = 0;
  }
}
  1. 堆/Pools污点标记

检测内核内存分配操作的指令,则调用taint::set_taint(address, size, /*tainted=*/true)进行污点标记,主要通过bx_instr_wrmsr函数来实现,当写入的地址是MSR_LSTAR寄存器时,它代表着syscall调用:

代码语言:javascript
复制
#define MSR_LSTAR   0xc0000082 /* long mode SYSCALL target */
代码语言:javascript
复制
void bx_instr_wrmsr(unsigned cpu, unsigned addr, Bit64u value) {
  if (addr == MSR_LSTAR) {
    globals::nt_base = value - globals::config.KiSystemCall64_offset; // ntoskrnl.exe中nt!KiSystemCall64偏移地址,用于获取内核基址

    for (size_t i = 0; i < globals::config.pool_alloc_prologues.size(); i++) {
      globals::config.pool_alloc_prologues[i] += globals::nt_base;
    }
    set_breakpoints_bulk(globals::config.pool_alloc_prologues, BP_POOL_ALLOC_PROLOGUE);

    for (size_t i = 0; i < globals::config.pool_alloc_epilogues.size(); i++) {
      globals::config.pool_alloc_epilogues[i] += globals::nt_base;
    }
    set_breakpoints_bulk(globals::config.pool_alloc_epilogues, BP_POOL_ALLOC_EPILOGUE);
  }
}

其中pool_alloc_prologuespool_alloc_epilogues分别代表alloc函数的前序与后序函数,以下是windows-x64系统配置下的地址:

代码语言:javascript
复制
pool_alloc_prologues  = 0x1E0590
pool_alloc_epilogues  = 0x1E07AD
  1. 污点清除

当栈顶弹出或者堆块调用free函数前序指令(Linux下配置地址),以及内存拷贝的目标地址是内核地址时,均将其污点标记清除,如果是win平台则主要依靠bx_instr_lin_access来实现:

代码语言:javascript
复制
void bx_instr_lin_access(unsigned cpu, bx_address lin, bx_address phy,
                         unsigned len, unsigned memtype, unsigned rw) {
  BX_CPU_C *pcpu = BX_CPU(cpu);
  const uint64_t pc = pcpu->prev_rip;

  if (rw != BX_WRITE && rw != BX_RW) {
    return;
  }

  if (!pcpu->long_mode() || !windows::check_kernel_addr(pc) || !windows::check_kernel_addr(lin)) {
    return;
  }

  if (globals::rep_movs) {
    return;
  }

  const uint64_t rsp = pcpu->gen_reg[BX_64BIT_REG_RSP].rrx;
  if (globals::rsp_locked.find(rsp) != globals::rsp_locked.end()) {
    return;
  }

  taint::set_taint(lin, len, /*tainted=*/false);
}
  1. 污点传播

bx_instr_before_execution中主要对以下操作指令作检测,指令形式主要为 <REP> MOVS{B,D},用于污点传播追踪:

代码语言:javascript
复制
const unsigned int opcode = i->getIaOpcode();
  switch (opcode) {
    case BX_IA_MOV_GqEq: /* Standard library memcpy() prologue handling. */ 
          ...
    case BX_IA_REP_MOVSB_YbXb:
    case BX_IA_REP_MOVSW_YwXw:
    case BX_IA_REP_MOVSD_YdXd:
    case BX_IA_REP_MOVSQ_YqXq: /* Inline memcpy handling */ 
          ...
        switch (opcode) {
            case BX_IA_REP_MOVSB_YbXb: mult = 1; break;
            case BX_IA_REP_MOVSW_YwXw: mult = 2; break;
            case BX_IA_REP_MOVSD_YdXd: mult = 4; break;
            case BX_IA_REP_MOVSQ_YqXq: mult = 8; break;
        }
        ...
   
    case BX_IA_RET_Op64: /* Allocator and memcpy() epilogue handling. */ 
          ...

对于非<REP> MOVS{B,D}指令的内存访问:

  • 写操作:清除内存污点标记,标记为已初始化;
  • 读操作:检测污点标记,如果shadow memory中标记为未初始化读取,则在guest memory中验证:标记不匹配则清除污点,否则若真为未初始化读取就当漏洞报告出来
代码语言:javascript
复制
/* src_in_kernel */ {
    uint64_t tainted_offset = 0;
    taint::access_type type = taint::check_taint(pcpu, src, size, &tainted_offset);

    if (type == taint::METADATA_MARKER_MISMATCH) {
      taint::set_taint(src, size, /*tainted=*/false);
    } else if (type == taint::ACCESS_INVALID) {
      process_bug_candidate(
          pcpu, i, pcpu->prev_rip, src, size, dst, taint::get_origin(src + tainted_offset));
    }

总结起来,是否为漏洞主要基于以下几点:

  1. <REP> MOVS{B,D}中 源地址为内核,目标地址为用户地址,从内核输出数据到用户
  2. 源地址被标记为污点
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-12-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 漏洞战争 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 初始化工作
  • 中断响应
  • 污点标记与追踪
相关产品与服务
脆弱性检测服务
脆弱性检测服务(Vulnerability detection Service,VDS)在理解客户实际需求的情况下,制定符合企业规模的漏洞扫描方案。通过漏洞扫描器对客户指定的计算机系统、网络组件、应用程序进行全面的漏洞检测服务,由腾讯云安全专家对扫描结果进行解读,为您提供专业的漏洞修复建议和指导服务,有效地降低企业资产安全风险。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档