本文主要介绍Bochspwn Reloaded 内核未初始化漏洞检测技术,它采用污点追踪对内核层向用户层泄露数据的行为进行检测。
关于bochs插桩技术参考《Bochspwn漏洞挖掘技术深究(1):Double Fetches 检测》 ,此处不再赘述。
直接先看下instrument.h中实现插桩函数有哪些:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void bx_instr_initialize (unsigned cpu) ; void bx_instr_exit (unsigned cpu) ;void bx_instr_interrupt (unsigned cpu, unsigned vector ) ;void bx_instr_before_execution (unsigned cpu, bxInstruction_c *i) ;void bx_instr_after_execution (unsigned cpu, bxInstruction_c *i) ;void bx_instr_lin_access (unsigned cpu, bx_address lin, bx_address phy, unsigned len, unsigned memtype, unsigned rw) ;void bx_instr_wrmsr (unsigned cpu, unsigned addr, Bit64u value) ;
初始化工作 第一篇中讲过bx_instr_initialize
主要用来加载配置信息,针对不同的系统环境设置不同的数据结构偏移地址,用来提供需要的进程/线程等重要信息。在这里它另外增加污点追踪功能的初始化工作:
1 2 3 4 5 6 7 8 9 taint::initialize(); 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异常处理回调设置:
1 2 3 4 5 6 7 8 void initialize () { taint_area = (uint8_t *)VirtualAlloc(NULL , kTaintAreaSize, MEM_RESERVE, PAGE_READWRITE); AddVectoredExceptionHandler(1 , OvercommitHandler); }
VEH回调函数实现如下,当发生访问违例时,若异常地址不在污点内存区域,则将其设置为可读写内存,然后继续执行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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
函数实现,主要是发生中断时,检测该中断地址是否可写,并设置全局标志:
1 2 3 4 5 6 7 8 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)
标记为污点
1 2 3 4 5 6 7 8 9 10 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: ... case BX_IA_PUSH_Eq: ... }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 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, 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 ; } }
2、堆/Pools污点标记
检测内核内存分配操作的指令,则调用taint::set_taint(address, size, /*tainted=*/true)
进行污点标记,主要通过bx_instr_wrmsr
函数来实现,当写入的地址是MSR_LSTAR
寄存器时,它代表着syscall调用:
1 #define MSR_LSTAR 0xc0000082
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void bx_instr_wrmsr (unsigned cpu, unsigned addr, Bit64u value) { if (addr == MSR_LSTAR) { globals::nt_base = value - globals::config.KiSystemCall64_offset; 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_prologues
与pool_alloc_epilogues
分别代表alloc函数的前序与后序函数,以下是windows-x64系统配置下的地址:
1 2 pool_alloc_prologues = 0x1E0590 pool_alloc_epilogues = 0x1E07AD
3、污点清除 当栈顶弹出或者堆块调用free函数前序指令(Linux下配置地址),以及内存拷贝的目标地址是内核地址时,均将其污点标记清除,如果是win平台则主要依靠bx_instr_lin_access
来实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 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, false ); }
4、污点传播 在bx_instr_before_execution
中主要对以下操作指令作检测,指令形式主要为 <REP> MOVS{B,D}
,用于污点传播追踪:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 const unsigned int opcode = i->getIaOpcode(); switch (opcode) { case BX_IA_MOV_GqEq: ... case BX_IA_REP_MOVSB_YbXb: case BX_IA_REP_MOVSW_YwXw: case BX_IA_REP_MOVSD_YdXd: case BX_IA_REP_MOVSQ_YqXq: ... 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: ...
对于非<REP> MOVS{B,D}
指令的内存访问:
写操作:清除内存污点标记,标记为已初始化;
读操作:检测污点标记,如果shadow memory中标记为未初始化读取,则在guest memory中验证:标记不匹配则清除污点,否则若真为未初始化读取就当漏洞报告出来
1 2 3 4 5 6 7 8 9 10 { 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, 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、源地址被标记为污点