HITB GSEC CTF 2017 babystack
作为一个pwn新手,前段时间刚好在学win平台的pwn,找了道Atum大佬的题,边学边怼。
期间参考文章有两篇,写的相当好,此处记录了我的一些粗糙的思考过程。
k0shl师傅
污师师傅
步骤1:
运行程序发现它初始化后已经显示了main和stack的地址,并允许我们查看任意内存中的数据。
通过ida的string窗口快速定位代码:
程序的逻辑如下:
初始化进入for循环之后,我们输入"yes"则进入"else"语句;输入"no"则"break";输入"其他字符"时则会进入sub_401000()
函数引发栈溢出。
sub_401060()
函数读入十进制地址值,再通过atoi()
将我们输入的地址中的内容以十进制输出。
同时查看汇编代码会发现倒数第二个puts()
中有:
步骤2:
以上信息可以得出:
* 程序给出了目标栈地址和main函数地址,所以就不用担心aslr和栈地址的变化。
* 代码段中包含了"cmd",故我们不需要考虑DEP,也不用再另外编写shellcode。
所以,我们的任务是如何在for的10次循环之内控制eip指向已有的"cmd"。
我们知道win平台有seh函数,在程序运行异常时会调用异常处理函数,可以尝试在这里下手,最主要的原因是可以看到程序是以exit(0)
退出的,而调用exit()
的时候会切换到一个新的栈,并不能通过栈溢出覆盖返回地址来控制eip。
对于seh有操作系统支持的safeSEH机制来保护其完整性,所以为了能够操作seh必须知道safeSEH的工作原理和绕过方法。
我们来深入了解一下seh和safeSEH机制。
关于seh
在 structure
窗口可以看到seh的结构体:
其原型为(拓展的):
seh chain是一个链结构,每一个seh是链表上的一个结点,next
指向下一个seh结构,而最后一个next
指针值为-1
(即0xFFFFFFFF
),在win程序上经常看见这家伙的身影。
在 ImmunityDebugger 中对栈进行追踪可以很快定位seh位置、熟悉其结构。我们运行 babystack.exe 并 attach 到调试器上输入一串AAAAAAAA...AAAA
:
虽然我们覆盖了seh handler但是seh链却不会触发。原因是即使我们知道输入的一堆A并不合法,但是异常处理函数处理的异常类型是代码中定义的,我们并不能确切知道它会处理什么类型的错误,不过却帮助我们定位了seh栈的位置。在要求输入地址时我们可以输入一个非法地址来触发异常处理函数。
在程序主要部分,puts()
执行之前有一段构造拓展的seh结构体的代码。
关于safeSEH
在调用异常处理函数之前,对要调用的异常处理函数进行一系列的有效性校验,如果发现异常处理函数不可靠(被覆盖了,被篡改了),立即终止异常处理函数的调用。
函数逻辑如下:
附:更多safeSEH相关信息
绕过safeSEH的必要条件:①GS关闭②攻击虚表函数③有可用堆④有no-safeSEH的dll⑤DEP关闭,其中任意一个条件满足就有可能绕过safeSEH,但是上边分析均排除②以外的可能,而检查汇编代码没有发现虚函数,故没办法绕过对seh的检查。
所以很遗憾我们并不能通过修改seh结构体的ExceptionHandler
函数指针来指向"cmd",这时我们注意到了ScopeTable
这个成员,这家伙是干啥的呢。
关于scopetable
scopetable结构体:
ScopeRecord:
查找相关资料得知scopetable
与附件中的vcruntime.dll
中的_except_handler4_common()
函数密切相关,我们来继续看看这个函数。由对seh结构体构造过程的分析知道成员ExceptionHandler
就是_except_handler4_common
这个函数。由于safeSEH对seh结构体的检测,我们可以考虑修改ExceptionHandler
指针指向的函数里的内容。
通过查询0x00000000
处的内容来触发seh,在ImmunityDebugger
把断点下在VCRUNTIME140.dll
模块的_except_handler_common
函数入口:
注意此时的babystac.0053688
,这个是babystack.exe
中构造seh结构体时存储scopetable
的地方:
看雪上的_except_handler_common伪代码:
void __cdecl ValidateLocalCookies(void (__fastcall *cookieCheckFunction)(unsigned int), _EH4_SCOPETABLE *scopeTable, char *framePointer)
{
unsigned int v3; // esi@2
unsigned int v4; // esi@3
if ( scopeTable->GSCookieOffset != -2 )
{
v3 = *(_DWORD *)&framePointer[scopeTable->GSCookieOffset] ^ (unsigned int)&framePointer[scopeTable->GSCookieXOROffset];
__guard_check_icall_fptr(cookieCheckFunction);
((void (__thiscall *)(_DWORD))cookieCheckFunction)(v3);
}
v4 = *(_DWORD *)&framePointer[scopeTable->EHCookieOffset] ^ (unsigned int)&framePointer[scopeTable->EHCookieXOROffset];
__guard_check_icall_fptr(cookieCheckFunction);
((void (__thiscall *)(_DWORD))cookieCheckFunction)(v4);
}
int __cdecl _except_handler4_common(unsigned int *securityCookies, void (__fastcall *cookieCheckFunction)(unsigned int), _EXCEPTION_RECORD *exceptionRecord, unsigned __int32 sehFrame, _CONTEXT *context)
{
// 异或解密 scope table
scopeTable_1 = (_EH4_SCOPETABLE *)(*securityCookies ^ *(_DWORD *)(sehFrame + 8));
// sehFrame 等于 上图 ebp - 10h 位置, framePointer 等于上图 ebp 的位置
framePointer = (char *)(sehFrame + 16);
scopeTable = scopeTable_1;
// 验证 GS
ValidateLocalCookies(cookieCheckFunction, scopeTable_1, (char *)(sehFrame + 16));
__except_validate_context_record(context);
if ( exceptionRecord->ExceptionFlags & 0x66 )
{
......
}
else
{
exceptionPointers.ExceptionRecord = exceptionRecord;
exceptionPointers.ContextRecord = context;
tryLevel = *(_DWORD *)(sehFrame + 12);
*(_DWORD *)(sehFrame - 4) = &exceptionPointers;
if ( tryLevel != -2 )
{
while ( 1 )
{
v8 = tryLevel + 2 * (tryLevel + 2);
filterFunc = (int (__fastcall *)(_DWORD, _DWORD))*(&scopeTable_1->GSCookieXOROffset + v8);
scopeTableRecord = (_EH4_SCOPETABLE_RECORD *)((char *)scopeTable_1 + 4 * v8);
encloseingLevel = scopeTableRecord->EnclosingLevel;
scopeTableRecord_1 = scopeTableRecord;
if ( filterFunc )
{
// 调用 FilterFunc
filterFuncRet = _EH4_CallFilterFunc(filterFunc);
......
if ( filterFuncRet > 0 )
{
......
// 调用 HandlerFunc
_EH4_TransferToHandler(scopeTableRecord_1->HandlerFunc, v5 + 16);
......
}
}
......
tryLevel = encloseingLevel;
if ( encloseingLevel == -2 )
break;
scopeTable_1 = scopeTable;
}
......
}
}
......
}
这个函数最后会调用scopetable->ScopeRecord
中的FilterFunc
和HandlerFunc
。
附:
TEB
scopetable
步骤3:
到这里,我们有一个栈溢出、一个任意地址读、一个“可控”的scopetable
,正是由于safeSEH
只检测EH handler
、safeOP
只检测seh chain的完整性,故能够伪造scopetable
中的HandlerFunc
,修复溢出后的栈结构触发seh即可。细节上要注意的是SecurityCookie。
攻击前后栈布局: