Quantcast
Channel: CodeSection,代码区,网络安全 - CodeSec
Viewing all articles
Browse latest Browse all 12749

【技术分享】使用结构化异常处理绕过CFG

0
0
【技术分享】使用结构化异常处理绕过CFG

2017-03-31 15:00:04
来源:improsec.com 作者:myswsun

阅读:564次
点赞(0)
收藏





【技术分享】使用结构化异常处理绕过CFG

翻译:myswsun

预估稿费:160RMB

投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿


0x00 前言

本文所讲的技术基于泄漏栈地址并覆盖结构化异常处理,从而绕过CFG。

为了方便绕过,我再次选择了使用IE11的漏洞(MS16-063),在我之前的绕过CFG的文章中使用过(1,2)。


0x01 泄漏栈

我已经有了PoC文件Clean_PoC.html,其利用漏洞获得一个读写原语,但是不够深入。在下个PoC文件Leaking_Stack.html中能泄漏当前线程的堆栈限制,这个可以通过使用kernelbase.dll中的GetCurrentThreadStackLimits做到。它执行的方法是,通过覆盖TypedArray对象的虚函数表,并使用下面的调用:


【技术分享】使用结构化异常处理绕过CFG

在虚表偏移0x188处,能直接在javascript代码中调用,并且有两个参数,这是重要的,因为这个函数必须有相同数量的参数,否则堆栈不会平衡会触发异常。

GetCurrentThreadStackLimits满足Javascript的调用要求,MSDN中介绍如下:


【技术分享】使用结构化异常处理绕过CFG

它有两个参数,并返回栈基址和栈的最大保留地址。通过两步能找到GetCurrentThreadStackLimits的地址,首先泄漏一个kernelbase.dll的指针,然后在DLL中定位函数。第一部分能通过在定位jscript9中的Segment::Initialize函数来完成,因为他使用了kernel32!VirtualAllocStub,继而调用kernelbase!VirtualAlloc。我通过扫描jscript9的虚函数地址并计算哈希找到了这个函数,然后使用读原语。算法如下:


【技术分享】使用结构化异常处理绕过CFG

通过5个DWORD相加,每次向前一个字节遍历直到正确的哈希被找到。调用Kernel32!VirtualAlloc的位置在Segment::Initialize中偏移0x37处:


【技术分享】使用结构化异常处理绕过CFG

读取指针得到:


【技术分享】使用结构化异常处理绕过CFG

在偏移0x6处包含了跳转向kernelbase!VirtualAlloc:


【技术分享】使用结构化异常处理绕过CFG

现在我们有了kernelbase.dll的地址,然后我们使用和Segment::Initialize一样的方法找到GetCurrentThreadStackLimits的地址,代码如下:


【技术分享】使用结构化异常处理绕过CFG

我们现在像Theori的原始利用中一样创建一个假的虚表,并在这个函数指针的偏移0x188处覆盖虚表入口,同时记住增大TypedArray的参数大小,代码如下:


【技术分享】使用结构化异常处理绕过CFG

运行并在GetCurrentThreadStackLimits中打断点:


【技术分享】使用结构化异常处理绕过CFG

上图显示了栈的上下限制。为了从这得到能控制的指令指针,我定位了栈中的SEH链,并覆盖了一个入口,然后触发异常。虽然做了这些,但是需要记住的是windows 10开启了SEHOP。因为SEH指针不被CFG保护,这将能绕过CFG和RFG。这些实现在文件Getting_Control.html中。

为了实现这个,我需要定位栈中的SEH链,泄漏栈限制后的SEH链如下:


【技术分享】使用结构化异常处理绕过CFG

调试异常将变得很清晰,5个异常处理指针指向jscript9,同时MSHTML!_except_handler4似乎是个死循环。因此如果我们能覆盖5个javascript异常中的任意一个,并触发一个异常,我们将得到可控制的指令指针。在古老过时的SEH覆盖利用中通常是通过栈缓冲区溢出来覆盖SEH链,但是这将触发SEHOP,因此我们只想覆盖一个异常处理的SEH记录同时保持NSEH完整。因此这个覆盖必须精确,且SEH记录的栈地址必须泄漏。为了完成这次泄漏,我们将扫描栈,搜索SEH链,为了确保我们找到它,我们能验证最后一个异常处理是ntdll!FinalExceptionHandlerPadXX。因为最后一个异常处理函数会随着程序重启而改变,因此泄漏分为两步,首先找到正确的最后一个异常处理函数,然后再是SEH链。为了完成第一个泄漏,搜索栈中ntdll!_except_handler4,因为在栈中上下搜索它只会遇到一次:


【技术分享】使用结构化异常处理绕过CFG

剩下的问题是,找到ntdll!_except_handler4的地址,但是这非常简单,因为能从任何被CFG保护的函数中找到ntdll.dll的指针并包含一个间接调用。CFG的验证包含ntdll!LdrpValidateUserCallTarget的调用,并且jscript9被CFG保护,任意间接调用的函数都包含ntdll.dll的指针。在TypedArray对象虚表中偏移0x10处就有这么一个函数:


【技术分享】使用结构化异常处理绕过CFG

使用读原语,找到ntdll.dll的指针的代码如下:


【技术分享】使用结构化异常处理绕过CFG

通过使用读原语搜索特征或哈希能从ntdll.dll中能得到_except_handler4的地址,_except_handler4看起来如下:


【技术分享】使用结构化异常处理绕过CFG

头0x10个字节总是相同的且非常特别,因此可以使用哈希搜索:


【技术分享】使用结构化异常处理绕过CFG

上面的函数使用了ntdll.dll的指针作为参数。一旦我们找到了函数指针,我们能搜索栈:


【技术分享】使用结构化异常处理绕过CFG

在这个地址,有如下:


【技术分享】使用结构化异常处理绕过CFG

因为这在那之前的DWORD能被读取并包含:


【技术分享】使用结构化异常处理绕过CFG

找到了最后一个异常处理的函数指针。然后可以进行第二步泄露了,现在我们能看到来自jscript9的异常处理,因此它的函数指针一定位于PE代码段中,能从DLL的PE头中找到这些地址:


【技术分享】使用结构化异常处理绕过CFG

现在有了这个,上下搜索栈,能看到栈的所有内容。算法如下:

如果一个DWORD小于0x10000000,它不在jscript9.dll中,因此移到下一个DWORD。

如果大于0x10000000,检查是否在jscript9.dll的代码段中

如果在,则栈上的4字节DWORD是指向栈的指针

如果上面两步正确,我们可能找到了SEH,因此我们尝试校验是否以最后一个异常处理结尾

如果指针中的某个不在指向栈,或者超过8个引用,它不是SEH链

在我的测试中,第一次指向jscript9.dll的指针,其中的DWORD是一个栈指针,它是SEH链,算法如下:


【技术分享】使用结构化异常处理绕过CFG

有了这个算法,意味着精确覆盖SEH记录是可能的,并且不会中断下一个异常处理,因此绕过SEHOP。

最后,触发异常,获得可控制的指令指针:


【技术分享】使用结构化异常处理绕过CFG

运行结果如下:


【技术分享】使用结构化异常处理绕过CFG

上图展示了调试器捕捉到的异常,并且用我们想要的0x42424242覆盖成功。这阐述了这个技术如何绕过CFG获得执行控制。它同样也能绕过RFG的实现。代码在github中。


【技术分享】使用结构化异常处理绕过CFG
【技术分享】使用结构化异常处理绕过CFG
本文由 安全客 翻译,转载请注明“转自安全客”,并附上链接。
原文链接:https://improsec.com/blog//back-to-basics-or-bypassing-control-flow-guard-with-structured-exception-handler

Viewing all articles
Browse latest Browse all 12749