Inflated
这个题是很久以前ACTF的一道题了,但由于最近打ctf经常遇到混淆,所以先从简单的ollvm搞起,顺便仔细学习一下异常类。
Exception
首先是异常,c++异常我们基本上是通过手动patch块来找到catch块,如何分辩可以通过rdx的值。
当前函数抛出异常后,如果在该函数没有找到catch块,那么就继续向上找catch块,出现两种情况
第一:找到catch块
第二:没有找到catch块,调用std::terminal(),即把程序abort掉
当找到catch块后,会执行对应操作。
程序中代码块有个专有名词:landing pad,从抛异常到开始,再到执行landing pad代码,这整个过程叫做Stack unwind
Stack unwind主要作用是:沿着链向上找catch块,且清理调用链上栈中的局部变量
其中 stack unwind库在intel平台上,属于Itanium ABI接口中的一部分,由系统实现,跟语言无关,任何上层语言都能通过接口的基础实现各自异常处理
Itanium ABI定义了数据结构来建立异常处理流程和框架
1 2 3 4 5 6 7 8 9 10
| _Unwind_RaiseException, _Unwind_Resume, _Unwind_DeleteException, _Unwind_GetGR, _Unwind_SetGR, _Unwind_GetIP, _Unwind_SetIP, _Unwind_GetRegionStart, _Unwind_GetLanguageSpecificData, _Unwind_ForcedUnwind
|
_Unwind_RaiseException()函数用于进行stack unwind,它在用户执行throw时候被调用,主要功能就是从当前函数开始,对调用链上的每个函数都调用一个personality routine函数
当我们在代码中写下throw xxx,编译器会分配一个数据结构**__cxa_exception**来表示该异常,异常头部:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| struct __cxa_exception { std::type_info * exceptionType; void (*exceptionDestructor) (void *); unexpected_handler unexpectedHandler; terminate_handler terminateHandler; __cxa_exception * nextException;
int handlerCount; int handlerSwitchValue; const char * actionRecord; const char * languageSpecificData; void * catchTemp; void * adjustedPtr;
_Unwind_Exception unwindHeader; };
|
整个头部下面才是exception obj。
所以当我们在程序里执行了抛出异常的操作,编译器为我们做了如下的事情:
- 调用 __cxa_allocate_exception 函数,分配一个异常对象(__cxa_exception,数据结构如上)
- 调用 __cxa_throw 函数,这个函数会将异常对象做一些初始化
- __cxa_throw() 调用 Itanium ABI 里的 _Unwind_RaiseException() 从而开始 unwind
- _Unwind_RaiseException() 对调用链上的函数进行 unwind 时,调用 personality routine()
- 该异常如能被处理(有相应的 catch),则 personality routine 会依次对调用链上的函数进行清理
- _Unwind_RaiseException() 将控制权转到相应的catch代码
- unwind 完成,用户代码继续执行
这个题最骚的是把exception源码给改了,我们找到__gxx_personality_v0,点Y修改其函数类型
1 2 3 4 5 6
| _Unwind_Reason_Code __fastcall _gxx_personality_v0( int Version, _Unwind_Action actions, __int64 exceptionClass, _Unwind_Exception *exceptionObject, _Unwind_Context *context)
|
然后看scan_eh_tab(),这里面有两个关键值被魔改了,先shift+F1存入参数结构体,然后点Y修改scan_eh_tab
1 2 3 4 5 6 7 8 9
| struct scan_results { int64_t ttypeIndex; const uint8_t* actionRecord; const uint8_t* languageSpecificData; uintptr_t landingPad; void* adjustedPtr; _Unwind_Reason_Code reason; };
|
1
| void scan_eh_tab(scan_results *results, _Unwind_Action actions, bool native_exception, _Unwind_Exception *unwind_exception, _Unwind_Context *context)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| while ( 1 ) { if ( v33 <= (unsigned __int64)v22 ) call_terminate(native_exception, unwind_exception); v35 = readEncodedPointer(&v22, v19); v36 = readEncodedPointer(&v22, v19); v10 = readEncodedPointer(&v22, v19); v37 = (13 * (_BYTE)v22 + 95) & 0x3F ^ 0x33u ^ (unsigned __int64)v10; v38 = readULEB128(&v22); if ( v35 <= v30 && v30 < v35 + v36 ) break; if ( v30 < v35 ) call_terminate(native_exception, unwind_exception); }
|
此处landing pad被修改,所以catch分发处的地址已经被魔改了
第二处
1 2 3 4 5 6 7 8 9 10 11 12 13
| if ( v47 == &`typeinfo for'StdObfException ) { v48 = thrown_object_ptr; v27 = 27 * (42 * (char)*thrown_object_ptr + (int)v27) % 0x61u; } results->ttypeIndex = v27; results->actionRecord = v39; results->adjustedPtr = thrown_object_ptr; results->reason = _URC_HANDLER_FOUND; return; } } goto LABEL_54;
|
v27是ttypeindex,最后的ttypeIndex由 thrown_object_ptr(由我们的第一段输入所决定的thrown_object_ptr) 和 原始固定固定typeIndex 决定
OLLVM
这篇文章写的还是不错的:https://bluesadi.github.io/0x401RevTrain-Tools/angr/10_%E5%88%A9%E7%94%A8angr%E7%AC%A6%E5%8F%B7%E6%89%A7%E8%A1%8C%E5%8E%BB%E9%99%A4%E6%8E%A7%E5%88%B6%E6%B5%81%E5%B9%B3%E5%9D%A6%E5%8C%96/