[2021]RtlExecuteHandlerForException、RtlVirtualUnwind、RtlRaiseStatus与X64 SEH处理漫步 huoji 异常处理,SEH,RtlRaiseStatus,RtlExecuteHandlerForException,RtlVirtualUnwind 2021-03-28 764 次浏览 0 次点赞 最近在给沙箱增加异常处理 然后打算学习一下异常处理的内容 在上一文章我简单介绍了一下X64 SEH在PE里面的表现,但是没有细说系统是如何处理的,因此本文就会详细介绍系统是如何处理runtime function的: 请注意本文可能会有错误,如果以后我发现写错了我会随时修正 为了正确模拟X64异常,我们需要模拟操作系统的行为,第一步就是RtlRaiseStatus: ```cpp CONTEXT context; EXCEPTION_RECORD exception; capture_context(&context, params_process); exception.ExceptionCode = params_last_exception; exception.ExceptionRecord = NULL; exception.NumberParameters = 0; exception.ExceptionFlags = EXCEPTION_NONCONTINUABLE; exception.ExceptionAddress = (PVOID)context.Rip; ``` 函数作用 保存context(其实叫做RtlpCaptureContext,这里为了与系统区分开,换了个名字),然后进入异常分发指令: dispatch_exception(RtlDispatchException) RtlDispatchException实际上就几步操作: 1. 得到栈大小,拷贝上下文,得到异常代码 ```cpp /* 1.得到栈大小,拷贝上下文,得到异常代码 */ get_stack_limit(&m_low_limit, &m_hight_limit, params_process); copy_context(&context_copyed, params_context); m_exception_address = (uint64_t)params_exception->ExceptionAddress; m_exception_flag = params_exception->ExceptionFlags & EXCEPTION_NONCONTINUABLE; m_nested_frame = 0; ``` 2. 初始化unwind history表,我们这边不做,所以忽略... 3. 循环寻找最近的找异常处理程序(比如多个try catch嵌套,则会找最近的那一个,函数叫做RtlLookupFunctionEntry) 请记住,windows设计的时候x64的exception table似乎是全局共享的(加载一个dll,这个dll的exception table会被放到全局的一个表里面),也就是说其他模块能给不属于自己的领空的地址加SEH(有待商议,但是看情况是这样的),所以你会看到有image base什么的指名某个模块地址,实际上我这边做了简化,直接只负责主exe,不负责其他的模块异常处理 在rtllookupfunctionentry中,我偷了个懒, windows中是会搜索所有loaded moudle的KLDR_DATA_TABLE_ENTRY->ExceptionTable(由RtlLookupFunctionTable完成) 而我只解析我的主exe的eception table ```cpp auto uc_error = uc_mem_read(g_global->uc_engine, (params_process.m_image_base + params_process.exception_table_base), function_table, params_process.exception_table_size); if (uc_error != UC_ERR_OK) __debugbreak(); ``` exception_table_base这样来的: ```cpp auto exception_table_base = RtlImageDirectoryEntryToData((PVOID)process.m_image_base,TRUE,IMAGE_DIRECTORY_ENTRY_EXCEPTION,(PULONG)&process.exception_table_size); process.exception_table_base = process.m_image_base + ((PUCHAR)exception_table_base - (PUCHAR)process.m_image_base); printf("[PE] process.exception_table_base: %p process.exception_table_size %p \n", process.exception_table_base, process.exception_table_size); ``` 然后二分法找最近的那个异常处理函数: ```cpp if (function_table != NULL) { m_low = 0; m_high = (m_sizeof_table / sizeof(RUNTIME_FUNCTION)) - 1; m_relative_rip = (ULONG)(params_exception_address - params_process.m_image_base); while (m_high >= m_low) { /* try catch会有很多个嵌套,找到最里面的那个! */ m_middle = (m_low + m_high) >> 1; function_address = &function_table[m_middle]; RUNTIME_FUNCTION function_address_cell; uc_mem_read(g_global->uc_engine, (uint64_t)function_address, &function_address_cell, sizeof(function_address_cell)); if (m_relative_rip < function_address_cell.BeginAddress) { m_high = m_middle - 1; } else if (m_relative_rip >= function_address_cell.EndAddress) { m_low = m_middle + 1; } else { break; } } if (m_high < m_low) { function_address = NULL; } } else { function_address = NULL; } ``` 找到了后 ```cpp //找到了给history table插东西,但是我们这边不处理history table //.... //finished ``` 记住runtime table还会有直接寻址和间接寻址问题,所谓间接寻址就是我的异常处理信息在其他的模块里面而不是在我的领空里面,所以要转换一下 ```cpp PRUNTIME_FUNCTION expect_handle::convert_function_entry(IN PRUNTIME_FUNCTION params_function_address,IN ULONG64 params_image_base) { if (params_function_address) { RUNTIME_FUNCTION FunctionEntryCell; uc_mem_read(g_global->uc_engine, (uint64_t)params_function_address, &FunctionEntryCell, sizeof(FunctionEntryCell)); if ((FunctionEntryCell.UnwindData & RUNTIME_FUNCTION_INDIRECT) != 0) { params_function_address = (PRUNTIME_FUNCTION)(FunctionEntryCell.UnwindData + params_image_base - 1); } } return params_function_address; } ``` 到目前为止,一切正常.但是有一个问题,我们之前提到,runtime function结构不包含异常处理函数地址,我们需要手动解析runtime_function->unwind->opcode 没错 **是opcode,我们手动解析他然后按他的"说明"展开得到正确的函数处理地址,同时我们也要按照他的说明处理堆栈,这样目的是为了让程序恢复到之前的状态** #### 这是最复杂的一部分 微软里面用RtlVirtualUnwind解析opcode: 1.拿到function_address_cell: ```cpp RUNTIME_FUNCTION function_address_cell; uc_mem_read(g_global->uc_engine, (uint64_t)function_address, &function_address_cell, sizeof(function_address_cell)); ``` 2.解析出UNWIND_INFO: ```cpp unwind_info = (PUNWIND_INFO)(function_address_cell.UnwindData + ImageBase); function_address_prolog_offset = (ULONG)(ControlPc - (function_address_cell.BeginAddress + ImageBase)); ``` 3.拿到opcode ```cpp crt_buffer_t unwind_infoCell(offsetof(UNWIND_INFO, UnwindCode)); uc_mem_read(g_global->uc_engine, (uint64_t)unwind_info, unwind_infoCell.GetBuffer(), unwind_infoCell.GetLength()); PUNWIND_INFO unwind_infoCellPtr = (PUNWIND_INFO)unwind_infoCell.GetBuffer(); unwind_infoCell.GetSpace(offsetof(UNWIND_INFO, UnwindCode) + unwind_infoCellPtr->CountOfCodes * sizeof(UNWIND_CODE) + sizeof(DWORD) * 2); uc_mem_read(g_global->uc_engine, (uint64_t)unwind_info, unwind_infoCell.GetBuffer(), unwind_infoCell.GetLength()); unwind_infoCellPtr = (PUNWIND_INFO)unwind_infoCell.GetBuffer(); ``` 4.根据unwind里面的信息(unwind->FrameRegister)调整堆栈(这一步是虚拟的): ```cpp if (unwind_infoCellPtr->FrameRegister == 0) { *establisher_frame = ContextRecord->Rsp; } else if ((function_address_prolog_offset >= unwind_infoCellPtr->SizeOfProlog) || ((unwind_infoCellPtr->Flags & UNW_FLAG_CHAININFO) != 0)) { *establisher_frame = (&ContextRecord->Rax)[unwind_infoCellPtr->FrameRegister]; *establisher_frame -= unwind_infoCellPtr->FrameOffset * 16; } else { Index = 0; while (Index < unwind_info->CountOfCodes) { if (unwind_infoCellPtr->UnwindCode[Index].UnwindOp == UWOP_SET_FPREG) { break; } Index += 1; } if (function_address_prolog_offset >= unwind_infoCellPtr->UnwindCode[Index].CodeOffset) { *establisher_frame = (&ContextRecord->Rax)[unwind_infoCellPtr->FrameRegister]; *establisher_frame -= unwind_infoCellPtr->FrameOffset * 16; } else { *establisher_frame = ContextRecord->Rsp; } } ``` 5.拿到异常地址的15个byte内容 ```cpp context_rax = &ContextRecord->Rax; NextByte = (PUCHAR)exception_address; UCHAR NextByteBuffer[15]; uc_mem_read(g_global->uc_engine, (uint64_t)NextByte, NextByteBuffer, sizeof(NextByteBuffer)); ``` 6.根据字节码做对应堆栈处理 ```cpp if ((NextByteBuffer[0] == SIZE64_PREFIX) && (NextByteBuffer[1] == ADD_IMM8_OP) && (NextByteBuffer[2] == 0xc4)) { // // add rsp, imm8. // NextByte += 4; uc_mem_read(g_global->uc_engine, (uint64_t)NextByte, NextByteBuffer, sizeof(NextByteBuffer)); } ``` 7.展开代码,进行prologue的逆操作(RtlpUnwindPrologue) ```cpp function_address = unwind_prologue(ImageBase, exception_address, *establisher_frame, function_address, ContextRecord, ContextPointers, params_process); uc_mem_read(g_global->uc_engine, (uint64_t)function_address, &function_address_cell, sizeof(function_address_cell)); ``` 8. 此时,寻找到handler 否则返回空 ```cpp unwind_info = (PUNWIND_INFO)(function_address_cell.UnwindData + ImageBase); uc_mem_read(g_global->uc_engine, (uint64_t)unwind_info, unwind_infoCell.GetBuffer(), unwind_infoCell.GetLength()); unwind_infoCellPtr = (PUNWIND_INFO)unwind_infoCell.GetBuffer(); unwind_infoCell.GetSpace(offsetof(UNWIND_INFO, UnwindCode) + unwind_infoCellPtr->CountOfCodes * sizeof(UNWIND_CODE) + sizeof(DWORD) * 2); uc_mem_read(g_global->uc_engine, (uint64_t)unwind_info, unwind_infoCell.GetBuffer(), unwind_infoCell.GetLength()); unwind_infoCellPtr = (PUNWIND_INFO)unwind_infoCell.GetBuffer(); function_address_prolog_offset = (ULONG)(exception_address - (function_address_cell.BeginAddress + ImageBase)); if ((function_address_prolog_offset >= unwind_infoCellPtr->SizeOfProlog) && ((unwind_infoCellPtr->Flags & HandlerType) != 0)) { Index = unwind_infoCellPtr->CountOfCodes; if ((Index & 1) != 0) { Index += 1; } *HandlerData = (PVOID)((PUCHAR)unwind_info + ((PUCHAR)&unwind_infoCellPtr->UnwindCode[Index + 2] - (PUCHAR)unwind_infoCellPtr)); return (PEXCEPTION_ROUTINE)(*((PULONG)&unwind_infoCellPtr->UnwindCode[Index]) + ImageBase); } return NULL; ``` 终于,我们恢复了堆栈到异常处理前的信息并且找到了一个异常处理程序!恭喜恭喜 如果找不到,则会返回EXCEPTION_STACK_INVALID信息: ```cpp exception_routine = virtual_unwind(UNW_FLAG_EHANDLER, params_process.m_image_base, m_exception_address, function_address,&context_copyed, &HandlerData,&establisher_frame,NULL,params_process); if (is_frame_in_bound(establisher_frame , &m_low_limit, &m_hight_limit) == FALSE) { m_exception_flag |= EXCEPTION_STACK_INVALID; break; } else if (exception_routine != NULL) { ``` ### 执行异常处理程序! 是时候执行我们的异常处理程序了!由RtlExecuteHandlerForException来执行 给异常处理程序提供四个参数: _EXCEPTION_RECORD 、establisher_frame、CONTEXT、DispatcherContextBase(在栈上分配) 并且设置RIP为异常处理地址 然后判断RAX返回值ExceptionContinueExecution、ExceptionContinueSearch、ExceptionCollidedUnwind做对应的操作   至此 我们的异常处理完毕. hu.....真的很掉头发的 本文由 huoji 创作,采用 知识共享署名 3.0,可自由转载、引用,但需署名作者且注明文章出处。 点赞 0
还不快抢沙发