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 | _Unwind_RaiseException,//用于stack unwind |
_Unwind_RaiseException()函数用于进行stack unwind,它在用户执行throw时候被调用,主要功能就是从当前函数开始,对调用链上的每个函数都调用一个personality routine函数
当我们在代码中写下throw xxx,编译器会分配一个数据结构**__cxa_exception**来表示该异常,异常头部:
1 | struct __cxa_exception |
整个头部下面才是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 | _Unwind_Reason_Code __fastcall _gxx_personality_v0( |
然后看scan_eh_tab(),这里面有两个关键值被魔改了,先shift+F1存入参数结构体,然后点Y修改scan_eh_tab
1 | struct scan_results |
1 | void scan_eh_tab(scan_results *results, _Unwind_Action actions, bool native_exception, _Unwind_Exception *unwind_exception, _Unwind_Context *context) |
1 | while ( 1 ) |
此处landing pad被修改,所以catch分发处的地址已经被魔改了
第二处
1 | if ( v47 == &`typeinfo for'StdObfException ) |
v27是ttypeindex,最后的ttypeIndex由 thrown_object_ptr(由我们的第一段输入所决定的thrown_object_ptr) 和 原始固定固定typeIndex 决定