样例0892792dc7783184451c6621b6e4a87c是一个MFC框架编写的恶意文件,上一篇文章《如何缩短人工分析样本的时间》提及了该文件在沙箱分析后是跑不出有效网络请求的,于是现在我们回归到手工分析的流程寻找一下原因,由于对MFC框架的文件静态分析会有些繁琐,需要一些技巧找到核心的代码位置,这篇文章需要对IDA的使用与C++相关知识和结构体有一定了解才可以比较好的理解与上手实践。
在沙箱记录的API信息中,我们可以很容易发现奇怪的痕迹(目的是省时间),例如一段由OutputDebugStringA函数调试打印输出的字符串。
根据这段字符串的线索,我们可以在IDA中通过字符串索引交叉引用功能找到对应的代码区域。
使用交叉引用后来到如下位置:
利用F5插件反编译功能后得到如下伪代码内容:
当我们继续对sub_401330函数进行交叉引用查询后发现位于如下位置,简单静态分析后发现这是在前期检查系统里是否存在对应的进程名的流程,符合上图中沙箱记录的API信息Process32Next被不断调用的内容。
不断地继续往前回溯发现已经结束了,到此可以确定这些代码属于该恶意文件的核心代码内容,被执行的原因是在其余位置被调用,但我们不用细究这里的细节,因为这不是我们需要关注的核心问题。
上面我们大致已经确认了核心代码区域,现在让我们回到最初的位置sub_401330,该函数调用完_CxxThrowException后就结束了,在IDA中切换到非图块模式也没有发现后续的代码执行流程。问题来了,此时静态分析看起来已经让人感觉模糊了,那么它是如何执行后续核心代码的呢?我们现在不关心该恶意文件的免杀能力、如何去检测它,如何去对抗它的恶意技术以及涉及的相关攻击事件,或者是比较恶意技术能力等这些与我们核心问题不相干的问题。
带着具体的问题,我们可以从上述沙箱截图里记录的API调用信息获取到的信息是后续还会存在其他的API调用,并不是在IDA截图中调用完_CxxThrowException直接就结束了,那么根据这种动静态分析结果存在不同的场景,短暂思考后我们可以断定这是一种阻碍静态分析的手段。既然这是一种很明显的阻碍静态分析的手段,我们首先要想到的是这个手法是不是已经公开了?或者说有没有比较好的解决这个问题的好方法?我们不要重复造轮子浪费时间解决重复的问题。
通过搜索“_CxxThrowException”我们可以找到与C++异常处理相关的内容,此时我们与接近最核心的问题更近了一步。为了后续能理解为什么恶意文件会采用C++异常处理,我们需要对C++异常处理的基本原理有一些了解。
C++异常处理的基本原理
C++标准规定了异常处理的语法,各编译器厂商需要遵循这些语法。然而,由于标准没有规定具体的实现过程,不同厂商的编译器产生的异常处理代码会有所不同。
异常处理的作用
异常处理是一种类型安全的处理方式,用于确保在函数结束的栈解退(stack unwinding)过程中,所有对象的析构函数都能被正确调用。栈解退的过程如下:
1. 当函数因异常而不是正常返回而终止时,程序会释放该函数的栈内存。
2. 程序不会立即结束,而是继续释放多级函数栈,直到找到一个在try块中的返回地址。
3. 然后控制权会转移到相应的异常处理程序。
4. 在这个过程中,所有相关对象的析构函数都会被调用。
关键字
try块用于监视异常,一个try块后可以跟随多个catch块,每个catch块用于捕获一种异常。
catch声明语句中的省略号(...)可以捕获所有未被捕获的异常,包括C类型异常和系统异常。
throw表达式用于抛出各种类型的异常。
在捕获异常时,通常会指定标准库中定义的 std::exception 类及其派生类作为异常类型,当然,C++也允许用户自定义异常类。
MSVC的异常处理机制
在MSVC(微软的编译器)中,异常处理机制建立在SEH(结构化异常处理)机制之上。处理C++异常时,编译器会在具有异常处理的函数入口处注册一个异常回调函数,该函数会将一种异常信息结构体(FuncInfo)压栈并调用库函数__CxxFrameHandler来处理异常。
抛出异常:使用库函数__CxxThrowException来完成,它接受两个参数:产生异常的对象指针和异常信息结构体(ThrowInfo)指针。
异常处理:异常回调函数在获得执行权后,会根据这两个参数和FuncInfo表结构地址,进行try块匹配操作。如果匹配失败,则析构异常对象并继续搜索;如果找到对应的try块,则通过ThrowInfo表结构的类型遍历找到匹配的catch块,之后进行栈解退和析构对象操作,最终执行catch块。
这种机制确保了在异常处理中,对象的析构函数能够被正确调用,确保资源的正确释放。
在了解了上述关于C++异常处理的基本原理后,我们现在需要找到是不是有类似的恶意文件也采用了这种手法,是不是有相关的分析文章可供我们参考?这是一种比较好的解决具体问题的思路与分析过程。人生短暂,总的核心是节省我们解决问题的时间,留有更多的时间享受生活。
经过搜索,我们在互联网上找到了一篇分析文章,里面提及了关于使用C++的异常处理来阻碍分析的手法,这正好可以作为我们解决具体问题的参考。
Earth Preta Spear-Phishing Governments Worldwide
反分析:C++中的自定义异常处理程序
有趣的是,对手通过实现自定义异常处理程序来隐藏实际的代码流。根据进程名称检查的结果,不同的异常处理程序将被调用,通过调用_CxxThrowException触发异常来继续执行恶意程序。在触发异常后,C++运行时将从ThrowInfo结构开始,一直到_msRttiDscr结构中的CatchProc成员,找到相应的异常处理程序,该成员包含真正的恶意代码。在这个示例中,异常处理程序位于偏移量0x10005300处。这种技术不仅隐藏了执行流程,还能停止分析人员调试器的执行。
具体过程简单总结如下:
调用_CxxThrowException:
恶意软件通过调用_CxxThrowException函数来触发异常。这个函数是C++运行时库的一部分,用于抛出C++异常。
ThrowInfo结构:
ThrowInfo结构包含了异常的信息,包括如何处理异常。当异常被抛出时,C++运行时会根据这个结构来查找相应的异常处理程序。
_msRttiDscr结构中的CatchProc成员:
在这个结构中,有一个名为CatchProc的成员,包含实际的异常处理代码。在这种情况下,恶意代码被隐藏在这个成员中。
文章提到了如何找到由C++异常处理而“隐藏”的具体核心代码的方法,并给出了可供寻找的数据结构路线图,这可以作为我们解决这个问题的参考手册。
在本次样例的_CxxThrowException最后,这段代码会抛出一个异常。由于在sub_401330函数入口处注册了SEH(结构化异常处理),处理C++异常时,编译器会在具有异常处理的函数入口处注册一个异常回调函数,该函数会将一种异常信息结构体(FuncInfo)压栈并调用库函数__CxxFrameHandler来处理异常。要找到上述伪代码中真正的异常处理程序,我们需要了解C++异常处理机制中的几个关键结构和步骤。
上图的红框是SEH位置,接着来到SEH位置如下:
找到了FuncInfo异常信息结构体,如下:
经过上述的步骤找到了具体的异常信息结构体(FuncInfo)位置,该结构体具体分布如下,我们发现pTryBlockMap位于结构体第五个位置,于是根据IDA已经解析好的结构体分布,找到第五个位置为stru_4037C8(如上图)。
根据TryBlockMapEntry结构体分布与IDA解析后的位置对照,我们可以找到pHandlerArray位置,位于第五个位置,不过这里存在三个不同的位置,分别为stru_403808、stru_403818和stru_403828。
pHandlerArray位置找到后,继续寻找异常处理函数的具体位置,结合HandlerType结构体与IDA解析后的位置分布找到addressOfHandler的具体位置,分别为loc_401422、loc_401406和loc_4013EA。
我们可以找到其中一个异常处理程序所在的位置以及接下来会执行的核心代码逻辑,如下,按照下图红框中的执行流程,我们会发现在地址00401422处存在mov eax, offset loc_401428指令,接着执行了retn指令,初步看这里的话会觉得可疑,因为这两条指令执行后好像不会执行到最终的00401434处的核心代码?带着这个疑问,我们可以对其进行简单的动态调试,经过简单的动态调试会发现当执行retn指令后程序的执行流来到了msvcrt内存空间中,表明是MSVC运行时内部代码区域,在这里经过对eax的一些操作,最终将执行流指向了00401434处的核心代码,因此整个执行流程是合理的也符合沙箱记录的相关API调用序列信息。
该函数内部的截图确实是会在沙箱记录的API信息中出现过的API调用记录,如下。
至此,经过对相关问题解决思路的梳理与提炼,我们发现了在静态分析中会存在模糊的原因,原来恶意文件是利用了C++与MSVC编译环境下的异常处理技术导致恶意代码出现了“隐藏”行为,我们也成功地找到了被“隐藏”在静态分析过程中的恶意代码。
本次分析就结束了,统计了下花费的时间,确实缩短了分析样本的时间,也解决了定位具体问题的目的,希望这类解决问题的思路能真的授人以渔。
平时只有晚上的时候才有时间整理思绪写东西,纯爱好写作与分享,如果觉得内容有用可以分享或者转发,不胜感激。有喜欢讨论或交流网络安全相关主题或恶意文件相关主题等不吹水的内容的读者可以加入交流群,该群无门槛免费永不收费,如遇到需要验证可以先联系我后邀请进群。