Flare-On逆向挑战2015
今年夏天,FireEye的FLARE团队举办了第二届年度Flare-On挑战赛,面向逆向工程师、恶意软件分析人员和安全专业人员。总共包含11个挑战,每个挑战都采用不同的反逆向技术且格式各异。例如,挑战范围从简单的密码破解题到内核驱动程序,再到图像隐写术。
本文将重点介绍11个挑战中我们认为最有趣的四个挑战(具体为第6、7、9和11题),以及一些对未来类似挑战更有用的工具和材料。
挑战六
摘要:挑战六是一个经过混淆的Android应用破解题,接收并验证用户输入
使用技术:远程Android调试、IDAPython
这个关卡的新颖之处在于它不是Windows二进制文件(大多数挑战针对Windows平台;显然是在寻找Windows逆向工程师;]),并且需要ARM逆向知识。
这个关卡的核心是包含密钥检查算法的ARM共享对象库。在备用Android恶意软件专用手机或模拟器上启动应用后,我们看到这个界面:
尝试输入"password"碰碰运气。没有成功。
在IDA中打开它(如果你先运行了而没有先分析…你和很多人一样),我们看到库的重要部分是比对部分。
向后追踪这个比对,我们找到了生成预期输入值的函数。我们只需要静态逆向这个函数。这个解密函数的主要部分是对存储在二进制文件中的加密密码进行因式分解。
这个函数的逻辑可以与加密字符串一起移植到Python中。使用IDAPython从二进制文件中提取必要数据使这个过程变得容易得多。对于从未使用过IDAPython的人,脚本如下所示。
主要IDAPython脚本
上述逻辑通过静态逆向从混淆的二进制文件中提取出来。IDAPython帮助从应用中提取出正确的数据段。
IDAPython脚本转储素数索引映射
IDAPython脚本转储"轮数"
运行最终的Python脚本解密字符串,打印出预期密码。
Should_have_g0ne_to_tashi_$tation@flare-on.com
其他方法
除了静态逆向,还可以使用gdbserver.py进行远程调试,附加到手机上运行的应用或附加到模拟的Android服务器。
然后可以在比对处设置断点,从调试器中读取解密后的标志。为此,提取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完美工作),我们可以计算执行检查输入的指令数,并确定它是在第1个字符失败还是在第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次迭代(魔法索引字节),正确解密的图像保存到文件,标志可以读出:]。
结语
感谢FireEye的FLARE团队提出这些挑战,并成功迫使我打开Windows虚拟机并学习一些新的逆向工具。希望明年的挑战同样有趣、晦涩,也许更难一点。
参考资料、指南和工具
我们发现对挑战有用的东西。
Windbg:设置符号路径 Intel PIN:教程 Android远程调试:如何设置gdb服务器 RC5加密算法:卡巴斯基关于Equation组的报告
如果你喜欢这篇文章,分享它: Twitter LinkedIn GitHub Mastodon Hacker News