2015年Flare-On逆向挑战赛:技术解析与实战技巧

本文深入解析2015年Flare-On逆向挑战赛中的四个关键技术挑战,涵盖Android ARM反混淆、.NET反混淆、动态指令计数破解及RC5加密图像解密,提供实用工具和实战方法。

Flare-On逆向挑战赛2015

今年夏天,FireEye的FLARE团队举办了第二届年度Flare-On挑战赛,针对逆向工程师、恶意软件分析师和安全专业人员。总共有十一个挑战,每个挑战使用不同的反逆向技术,且格式各异。例如,挑战范围从简单的密码破解到内核驱动程序,再到图像隐写术。

本博客文章将重点介绍十一个挑战中的四个(具体为6、7、9和11),这些是我们认为最有趣的,以及一些更有用的工具和材料,有助于应对未来的类似挑战。

挑战六

摘要: 挑战六是一个混淆的Android应用破解程序,接收并验证用户输入。

使用技术: 远程Android调试,IDAPython

这一关的新颖之处在于它不是Windows二进制文件(大多数挑战针对Windows平台;显然是在寻找一些Windows逆向工程师;]),并且需要ARM逆向知识。

这一关的核心是包含密钥检查算法的ARM共享对象库。在备用Android恶意软件指定手机或模拟器上启动应用,我们看到这个屏幕:

尝试输入“password”进行猜测。没有成功。

在IDA中打开它(如果你先运行它而没有先打开IDA…你和很多人一样),我们看到库的重要部分是compare函数。

向后追踪这个compare,我们找到生成预期输入值的函数。我们只需要静态逆向这个函数。这个解密函数的主要部分是对存储在二进制文件中的加密密码进行因式分解。

这个函数的逻辑可以与加密字符串一起移植到Python中。使用IDAPython从二进制文件中提取必要的数据使这个过程容易得多。对于从未使用过IDAPython的人,脚本如下所示。

主要IDAPython脚本

上述逻辑通过静态逆向从混淆的二进制文件中提取出来。IDAPython帮助从应用中提取出正确的数据段。

IDAPython脚本转储素数索引映射

IDAPython脚本转储“rounds”

运行最终的Python脚本解密字符串,打印出预期的密码。

Should_have_g0ne_to_tashi_$tation@flare-on.com

旁注

除了静态逆向,还可以使用gdbserver.py进行远程调试,附加到手机上运行的应用或附加到模拟的Android服务器。

然后可以在compare处设置断点,并从调试器中读取解密后的标志。为此,提取Android apk,设置Android调试环境,并在调用共享的混淆对象时中断。

网上有一些很好的资源展示如何在Android上设置远程gdb环境。具体来说,一些有用的资源可以在本文底部找到。

挑战七:YUSoMeta

摘要: 挑战7是一个混淆的.NET应用程序,验证用户提供的密码。

使用技术: .NET反混淆,Windbg特殊断点

挑战7,YUSoMeta,是一个.NET便携式可执行格式应用程序。像所有优秀的逆向工程师一样,我们将.NET应用程序加载到IDA Pro中。

瞥一眼函数窗口,揭示了相当多命名奇特的方法。许多类和类字段名称不仅包含ASCII字符(如“正常”.NET应用程序所示)。这表明存在混淆。

在十六进制编辑器中打开应用程序(我们特别选择HxD),我们找到一个有趣的字符串:“Powered by SmartAssembly 6.9.0.114”。

SmartAssembly是.NET应用程序的混淆器(很像Trail of Bits的MAST)。幸运的是,de4dot是一个反混淆SmartAssembly保护应用程序的工具。反混淆后,诸如.NET Reflector之类的工具可以将公共中间语言(CIL)程序集反编译回C#。使用这个,我们找到一个密码验证函数。

挑战捕获用户输入获得的密码,并将其与通过一系列有些复杂的操作生成的预期密码进行比较。获取预期密码的最简单方法是使用Windbg。

首先,我们通过加载SOS调试扩展来设置Windbg,以检查托管程序(即.NET应用程序)。

在Windbg中

其次,我们需要设置符号路径以获取调试符号。

在Windbg中

之后,我们在字符串相等比较函数System.String.op_Equality in mscorlib.dll上设置断点。注意:我们运行!name2ee两次,因为!name2ee在第一次发出时总是失败。

在Windbg中

中断后,我们使用!dumpstackobjects检查堆栈对象。用于提取密钥的密码应该在.NET堆栈上。

在Windbg中

挑战九:you_are_very_good_at_this

摘要: 挑战9是一个混淆的命令行密码检查应用程序

使用技术: Intel PIN, Windbg, Python, IDA Pro

挑战9,you_are_very_good_at_this,是一个x86便携式可执行命令行应用程序,接受一个参数并根据预期密码验证它——一个基本的破解程序。

像所有优秀的逆向工程师一样,我们立即在IDA Pro中打开应用程序,这揭示了一堵巨大的混淆代码墙——显然动态分析是要走的路。

对我们来说,有两种明确的方法来解决这个挑战。第一种使用pintool,第二种使用Windbg。

第一种解决方案:Pin

我们知道破解程序以某种方式检查命令行输入,通过大量操作逐个字符进行。幸运的是,我们不需要知道更多。

使用一个简单的指令计数Pintool(来自Pin教程的inscount0.cpp完美工作),我们可以计算执行以检查输入的指令数,并确定它是在第一个字符失败还是在第n个字符失败。这使我们能够逐字节暴力破解密码。

很明显,在第二种情况下执行了更多指令,其中第n个字符不正确,并且exit()直到程序执行的后期才被调用。我们可以利用这些知识来确定前n-1个字符是正确的输入。

使用Python,我们编写pintool脚本,为密码的第一个字符使用每个可能的可打印字符,给出二进制文件执行的指令计数。

Python伪代码

这样做,所有输入都给我们相同的指令计数结果,除了包含正确第一个字符的输入。这是因为正确字符是唯一通过应用程序验证器的字符。当这种情况发生时,二进制文件执行了否则不会运行的额外指令。

现在我们知道一个字符,我们在脚本中添加一个for循环来检查异常值,并对密码的每个字符做同样的事情…成功泄露密码!

去年,Flare-on挑战6也可以用完全相同的方式解决,感谢@gaasedelen对此的详细记录。

第二种解决方案:Windbg/Python/IDA Pro

以传统方式解决这个挑战,我们启动WinDbg并在kernel32!ReadFile上设置断点。我们追踪kernel32!ReadFile调用者,并通过在IDA Pro中交叉分析手动反混淆密码检查循环。

密码检查循环使用CMPXCHG指令比较用户提供的密码字符和预期密码。

我们确定感兴趣的寄存器是AL和BL寄存器。追踪感兴趣寄存器的数据流揭示AL寄存器遇到一些转换,作为CL和AH的函数,但最终源自用户提供的缓冲区。这意味着BL寄存器包含预期密码的转换字符。

幸运的是,我们能够在密码验证循环中的指令处精确设置断点,并提取必要的寄存器值(即BL、CL和AH寄存器)以解码实际密码。

在Windbg中

为了解码预期密码,我们为每个“字符轮”获取打印的BL、CL和AH寄存器值,并实现一个Python函数来反转在AL上完成的XOR/ROL转换。

Python伪代码

我们通过连接每个“字符轮”的ror_xor输出来挖掘密钥。

挑战十一:CryptoGraph

摘要: 挑战十一是一个使用rc5算法和混淆密钥的加密jpeg图像。结果是一个坚实的“逆向”挑战。

使用技术: RC5加密算法,Windbg,资源段提取

最终挑战。挑战十一,CryptoGraph.exe,是一个命令行二进制文件,当没有传递参数时,在系统上创建一个垃圾jpeg文件。仔细看,我们看到二进制文件确实接受一个命令行参数。然而,当传递任何数字时,二进制文件“永远”循环。

在IDA Pro中打开二进制文件,我们假设标志会以某种方式出现在正确创建的图像中。这意味着我们开始通过从“WriteFile”向上追踪调用来进行逆向。

向上几个函数,我们意识到资源#124被加载、解密并保存为此图像文件。

解密算法通过Google容易识别为RC5。第一个结果是卡巴斯基关于Equation Group及其使用RC5/6的报告。

现在我们需要的是RC5解密密钥。不幸的是,密钥长度为16字节,不能轻易暴力破解。然而,进一步逆向,我们意识到密钥是两个不同RC5解密阶段的结果。

第一次解密使用一个介于0x0和0xf之间的随机索引字节进行索引。这创建一个8字节密钥。

然后这个密钥用于另一个RC5解密,它实际上在偏移处获取加密源(Resource_122),该偏移是相同随机索引字节的函数。第二阶段,仅解密16字节,即加密jpeg Resource_124所需的16字节RC5密钥。

显示不同解密阶段的图表

在Windbg中中断,我们意识到Resource_121的解密是导致程序似乎永远循环的原因。事实上,从0x0运行到0x20的循环每次迭代执行时间呈指数级增长。

给定RC5密钥长度和用于索引到解密的Resource_121的算法,该算法给我们这个RC5密钥,我们确定只有资源的一个部分是必要的。

索引算法

仅解密Resource_121的相关位显著减少执行时间。

索引算法,不完全确定性的,最多可以索引到解密资源的前784字节。

因为每个循环解密48字节(硬编码作为传递给解密函数的参数),我们需要让主解密循环运行超过0x10次迭代,然后跳出函数。

用于计算所需循环的数学

使用Windbg,我们在循环处中断以在0x10次迭代后停止。这意味着只有部分密钥Resource_121将被解密,但幸运的是,这是8字节密钥唯一需要的部分。

最后需要暴力破解的是一个介于0x0和0xf之间的单字节值,它影响索引算法。这个字节影响先前讨论的8字节密钥的生成,以及索引到Resource_122,从其中解密16字节密钥。

Python-Windbg伪代码

在Windbg中编写脚本(完整脚本在这里),我们让二进制文件运行0xf次;每次在0x10次迭代后停止循环。

在第0x9次迭代(魔法索引字节),正确解密的图像保存到文件,标志可以读出:]。

FIN

感谢FireEye的FLARE团队提出这些挑战,并成功迫使

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计