[2021]利用错误的异常处理来检测基于cuckoo的沙箱 huoji 异常处理,沙箱,cuckoo 2021-07-04 1158 次浏览 0 次点赞 在上一篇文章中 [[2021]检测hypervisor -国内各大安全沙箱测评](https://key08.com/index.php/2021/07/03/1222.html "[2021]检测hypervisor -国内各大安全沙箱测评") 我要指正一个错误 那就是freebuf的和微步在线的沙箱并不是不支持icebp指令,而是因为他们是基于cuckoo的沙箱,而这个cuckoo的沙箱存在一个很明显的bug -**不会分发异常** 在沙箱里面启动进程的时候, cuckoo沙箱会注入一个monitor到进程里面通过hook监视进程操作,其中一个异常处理的hook实现如下: https://github.com/cuckoosandbox/monitor/blob/2deb9ccd75d5a7a3fe05b2625b03a8639d6ee36b/src/hooking.c#L172 ```cpp uint8_t *hook_addrcb_RtlDispatchException(hook_t *h, uint8_t *module_address, uint32_t module_size) { (void) h; (void) module_size; uint8_t *ki_user_exception_dispatcher = (uint8_t *) GetProcAddress((HMODULE) module_address, "KiUserExceptionDispatcher"); if(ki_user_exception_dispatcher == NULL) { pipe("WARNING:ntdll!RtlDispatchException unable to find " "KiUserExceptionDispatcher [aborting hook]"); return NULL; } // We are looking for the first relative call instruction. for (uint32_t idx = 0; idx < 32; idx++) { if(*ki_user_exception_dispatcher == 0xe8) { return ki_user_exception_dispatcher + *(int32_t *)(ki_user_exception_dispatcher + 1) + 5; } ki_user_exception_dispatcher += lde(ki_user_exception_dispatcher); } return NULL; } ``` 这个hook直接通向log页面: https://github.com/cuckoosandbox/monitor/blob/2deb9ccd75d5a7a3fe05b2625b03a8639d6ee36b/src/log.c#L667 ```cpp void log_exception(CONTEXT *ctx, EXCEPTION_RECORD *rec, uintptr_t *return_addresses, uint32_t count, uint32_t flags) { char buf[128]; bson e, r, s; static int exception_count; bson_init(&e); bson_init(&r); bson_init(&s); bson_append_start_object(&e, "exception"); bson_append_start_object(&r, "registers"); bson_append_start_array(&s, "stacktrace"); if(exception_count++ == EXCEPTION_MAXCOUNT) { our_snprintf(buf, sizeof(buf), "Encountered %d exceptions, quitting.", exception_count); log_anomaly("exception", NULL, buf); ExitProcess(1); } #if __x86_64__ static const char *regnames[] = { "rax", "rcx", "rdx", "rbx", "rsp", "rbp", "rsi", "rdi", "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15", NULL, }; uintptr_t regvalues[16] = {}; if(ctx != NULL) { uintptr_t registers[] = { ctx->Rax, ctx->Rcx, ctx->Rdx, ctx->Rbx, ctx->Rsp, ctx->Rbp, ctx->Rsi, ctx->Rdi, ctx->R8, ctx->R9, ctx->R10, ctx->R11, ctx->R12, ctx->R13, ctx->R14, ctx->R15, }; memcpy(regvalues, registers, sizeof(registers)); } #else static const char *regnames[] = { "eax", "ecx", "edx", "ebx", "esp", "ebp", "esi", "edi", NULL, }; uintptr_t regvalues[8] = {}; if(ctx != NULL) { uintptr_t registers[] = { ctx->Eax, ctx->Ecx, ctx->Edx, ctx->Ebx, ctx->Esp, ctx->Ebp, ctx->Esi, ctx->Edi, }; memcpy(regvalues, registers, sizeof(registers)); } #endif for (uint32_t idx = 0; regnames[idx] != NULL; idx++) { bson_append_long(&r, regnames[idx], regvalues[idx]); } char sym[512], number[20]; const uint8_t *exception_address = NULL; if(rec != NULL) { exception_address = (const uint8_t *) rec->ExceptionAddress; } our_snprintf(buf, sizeof(buf), "%p", exception_address); bson_append_string(&e, "address", buf); char insn[DISASM_BUFSIZ], insn_r[128]; if(range_is_readable(exception_address, 16) != 0) { if(disasm(exception_address, insn) == 0) { bson_append_string(&e, "instruction", insn); } for (uint32_t idx = 0; idx < 16; idx++) { our_snprintf(insn_r + 3*idx, sizeof(insn_r), "%x ", exception_address[idx]); } insn_r[3*16-1] = 0; bson_append_string(&e, "instruction_r", insn_r); } if((flags & LOG_EXC_NOSYMBOL) == 0) { symbol(exception_address, sym, sizeof(sym)); bson_append_string(&e, "symbol", sym); } our_snprintf(buf, sizeof(buf), "%p", rec != NULL ? (uintptr_t) rec->ExceptionCode : 0); bson_append_string(&e, "exception_code", buf); for (uint32_t idx = 0; idx < count; idx++) { if(return_addresses[idx] == 0) break; ultostr(idx, number, 10); sym[0] = 0; if((flags & LOG_EXC_NOSYMBOL) == 0) { symbol( (const uint8_t *) return_addresses[idx], sym, sizeof(sym)-32 ); } if(sym[0] != 0) { strcat(sym, " @ "); } our_snprintf(sym + our_strlen(sym), sizeof(sym) - our_strlen(sym), "%p", (void *) return_addresses[idx]); bson_append_string(&s, number, sym); } bson_append_finish_object(&e); bson_append_finish_object(&r); bson_append_finish_array(&s); bson_finish(&e); bson_finish(&r); bson_finish(&s); log_api(sig_index_exception(), 1, 0, 0, NULL, &e, &r, &s); bson_destroy(&e); bson_destroy(&r); bson_destroy(&s); } ``` 发现问题了吗! 他接管了异常分发,但是没有做任何异常处理.导致直接沙箱中进程崩溃 这是测试代码: ```cpp LONG CALLBACK veh_hanlde(struct _EXCEPTION_POINTERS* ExceptionInfo) { write_to_file("some_action.txt", "some_action"); return EXCEPTION_CONTINUE_SEARCH; } int main() { AddVectoredExceptionHandler(0, veh_hanlde); __debugbreak(); system("pause"); return 0; } ``` 正常来说,分发到异常会新建一个some_action.txt,如图所示:  然而在这些基于cuckoo的沙箱中,直接判定为崩溃  https://sandbox.freebuf.com/reportDetail?fileSha1=dfbb8e1893c2b81b8f88861113611e382d8118b4 **修正**,有一个引擎是能分发**已知**的异常  但是遇到mov ss,ax此类异常 > mov ss会产生一个异常,但是这个异常会在下一个指令执行的时候再抛出.因此 他首先mov ss了,这样下一个指令才会执行异常, 到icebp的时候, mov ss造成的异常就应该被抛出了。但是同时icebp也会造成一个异常,而且异常优先级哎比mov ss的高,你说巧不巧.这样CPU就会处理icebp的异常而不处理mov ss的异常,但是mov ss的异常是必须要求dr6的single_instruction bit位(也就叫做DR6.BS位)为1的,要不然就炸。很遗憾的是icebp的异常他不会设置这个bs位,导致直接炸虚拟机.很多时候虚拟机在2004跑起来了结果一阵子就虚拟机guest机异常了就是这个毛病 也是出问题了: https://sandbox.freebuf.com/reportDetail?fileSha1=9a294811b16503469a954bb7e0ff41d506146692 修复方案: 修改inject的monitor,让他分发异常或者org_call 此外,基于cuckoo的沙箱是愚蠢的,因为沙箱不稳定,注入才获取行为的直接通过syscall可以直接绕过.行为检测.因此沙箱应该考虑修改KVM底层hook kisystemcall64和io操作才行 本文由 huoji 创作,采用 知识共享署名 3.0,可自由转载、引用,但需署名作者且注明文章出处。 点赞 0
还不快抢沙发