0%

Inflated

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,//用于stack unwind
_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。

所以当我们在程序里执行了抛出异常的操作,编译器为我们做了如下的事情:

  1. 调用 __cxa_allocate_exception 函数,分配一个异常对象(__cxa_exception,数据结构如上)
  2. 调用 __cxa_throw 函数,这个函数会将异常对象做一些初始化
  3. __cxa_throw() 调用 Itanium ABI 里的 _Unwind_RaiseException() 从而开始 unwind
  4. _Unwind_RaiseException() 对调用链上的函数进行 unwind 时,调用 personality routine()
  5. 该异常如能被处理(有相应的 catch),则 personality routine 会依次对调用链上的函数进行清理
  6. _Unwind_RaiseException() 将控制权转到相应的catch代码
  7. 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;//landing pad
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/