利用FRIDA攻击Android应用程序(四)

           这篇文章详细介绍了解决OWASP的Bernhard Mueller发布的Android crackme中的UnCrackable-Level3.apk的几种方法。我们的主要目标是从一个被保护的APK中提取隐藏的字符串。

UnCrackable Level3中的安全机制

APK中实施了反破解技术,主要是为了延长逆向分析人员所需的时间。冷静一下,因为现在我们必须处理所有的保护措施。

我们在该APP中检测到以下保护措施。

Java层反调试

Java层完整性检查

Java层root检查

Native层反DBI(动态二进制插桩)

Native层反调试

Native层对Dalvik字节码的完整性检查

Native层混淆(只删除了一些符号信息并使用了一个函数来保护秘密信息)

在该APP中没有检测到以下保护措施。

Java层反DBI

Java层混淆

Native层root检查

Native层对Native代码自身的完整性检查


开始之前

首先在分析APK之前,先明确以下几点。

Android手机需要root。

在Java和Native层有反DBI,反调试,防篡改和root检查。我们不需要绕过它们,只需要提取我们需要的秘密信息。

Native层是执行重要代码的位置。不要在Dalvik字节码上纠缠。

我的解决方案只是解决这个问题的一种方式。也许很快就会出现更好更聪明的解法。


可能的解决方案

这个问题可以用很多方法解决。首先,我们需要知道应用程序到底在做什么。应用程序是通过比较用户输入和Java层与Native层的secret异或的结果来实现验证的。通过JNI将Java层的secret发送到native库后,验证在native层完成。事实上,验证是对用户输入的一个简单的strncmp的和对两个secret的xor操作。验证的伪代码如下(函数名由我给出)。

因此,我们需要提取这两个secret来确定显示成功消息的正确的用户输入。通过反编译APK,可以很简单地恢复Java层的secret。然而,native层的函数通过混淆隐藏了secret使其不容易恢复,只通过静态的方法可能相当乏味耗时。hook或符号执行可能是一个聪明的想法。为了提取这些信息,我的解决方案是通过Frida。这个工具是一个注入JavaScript探索Windows,MacOS,Linux,iOS,Android和QNX上的应用程序的框架,并且这个工具还在不断改进中。Frida用于执行动态分析,hex-rays用于反编译native层代码,BytecodeViewer(Procyon)用于反编译Java层代码。使用hex-rays是因为它的ARM代码反编译出来的结果很可靠。Radare2加上开源的反编译器也可以做得很好。

提取隐藏的secret

这篇文章的结构分为四个部分。

逆向Dalvik字节码。

逆向native层的代码。

使用Frida插桩Dalvik字节码。

使用Frida插桩native层的代码。

1.逆向Dalvik字节码(classes.dex)

首先需要解压APK得到几个文件,以便稍后进行逆向。为了做到这一点,你可以使用apktool或7zip。一旦APK被打包,下面这两个文件在这篇文章中是非常重要的。

./classes.dex包含Dalvik字节码。

./lib/arm64-v8a/libfoo.so是一个包含ARM64汇编代码的native库。在这篇文章中讨论native代码时,我们会参考这一点(如果需要,请随意使用x86/ARM32代码)。当我在Nexus5X中运行应用程序时,对应的需要逆向的是为ARM64架构编译的库。

1.png

下面显示的MainActivity的代码片段是通过反编译UnCrackable app Level3的main class获得的。有一些有趣的问题需要讨论。

(String xorkey = "pizzapizzapizzapizzapizz")中的硬编码的key。

加载native库libfoo.so和两种native方法的声明:将通过JNI调用的init()和baz()。请注意,一个方法是用xorkey初始化的。

追踪变量和类,以防在运行时检测到任何篡改。

当应用程序启动时,main activity的onCreate()方法被执行,该方法在Java层执行以下操作。

通过计算CRC校验和来验证native库的完整性。请注意,native库的签名没有用到任何加密方法。

初始化native库,并通过JNI调用将Java secret("pizzapizzapizzapizzapizz")发送到native代码。

执行root,调试和篡改检测。如果检测到任何一个,则应用程序中止。

反编译代码如下。

一旦观察到应用程序的主要流程,我们现在来描述找到的安全机制。

完整性检查:如上所述,verifyLibs在保护native库和Dalvik字节码的功能中使用了完整性检查。请注意,由于使用了较弱的CRC校验和,重新打包Dalvik字节码和native代码可能仍然可行。通过patch Dalvik字节码中的verifyLibs函数和native库中的baz函数,攻击者可以绕过所有的完整性检查,然后继续篡改app。负责验证库的函数反编译如下。

在这些完整性检查之上,我们还观察到,类IntegrityCheck还验证了应用程序没有被篡改,因此不包含可调试标志。这个类被反编译如下。

阅读ADB日志,我们还可以跟踪运行APP时执行的计算。运行时这些检查的一个例子如下。

因为我们不想patch二进制代码,所以我们不会深入这些检查。

Root检查:Java包sg.vantagepoint.util有一个称为RootDetection的类,最多可执行三次检查,以检测运行该应用程序的设备是否已经root。

checkRoot1()检查文件系统中是否存在二进制文件su。

checkRoot2()检查BUILD标签test-keys。默认情况下,来自Google的ROM是使用release-keys标签构建的。如果test-keys存在,这可能意味着在设备上构建的Android是测试版或非Google官方发布的。

checkRoot3()检查危险的root应用程序、配置文件和守护程序的存在。

负责执行root检查的Java代码如下。

2.逆向native代码(libfoo.so)

Java(Dalvik)和native代码通过JNI调用进行通信。当Java代码启动时将加载native代码,并使用包含Java密钥的一堆字节对其进行初始化。除了保护secret的函数之外,native代码不会被混淆。此外,它删除一些符号并且不是静态编译的。重要的是IDA Pro可能不会将JNI回调检测为函数。为了解决这个问题,只需转到exports窗口在导出的Java_sg_vantagepoint_uncrackable3_MainActivity_*按下P键。之后,您还需要在其函数声明处按Y键重新定义函数参数。您可以定义JNIEnv*对象以获得更好的反编译结果,如本节中所示的类C代码。

native构造函数:ELF二进制文件包含一个称为.init_array的部分,它保存了当程序启动时将执行的函数的指针。如果我们观察在其构造函数中的ARM共享对象,那么我们可以在偏移0x19cb0处看到函数指针sub_73D0:(在IDA Pro中使用快捷键ctrl+s显示sections)。

Radare2最近也支持JNI init方法的识别。感谢@pancake和@alvaro_fe,他们在radare2快速实现了支持JNI入口点。如果您正在使用radare2,只需使用命令ie即可显示入口点。

构造函数sub_73D0()执行以下操作。

①pthread_create()函数创建一个新线程执行monitor_frida_xposed函数。此函数已被重命名为这个名称,因为Frida和Xposed这两个框架不间断地被检查,以避免hook操作。

②在从Java secret初始化之前,xorkey_native的内存被清除。

③codecheck变量是确定完整性的计数器。之后,在计算native secret之前会检查它。因此,我们需要这个函数结束之后获得正确的codecheck值以进入最终的验证。

sub_73D0()(重命名为init)的反编译代码如下。

native反hook检查:monitor_frida_xposed函数执行几个安全检查,以避免人们使用DBI框架。如果我们仔细观察以下反编译代码,那么可以看到几个DBI框架被列入黑名单。这种检查在无限循环中进行一遍又一遍,如果检测到任何DBI框架,则调用goodbye函数并且应用程序崩溃。该函数的反编译代码如下。

下面显示了篡改检测的示例,其中应用程序使用信号SIGABRT(6)中止。

在DBI部分我们将通过以不同的方式插桩,了解如何绕过这些检查。使用Frida绕过反frida检查那将是最好不过了。

native反调试检查:Java_sg_vantagepoint_uncrackable3_MainActivity_init先执行anti_debug函数,如果反调试检查正确完成那么复制xorkey到全局变量中并将全局计数器codecheck递增以用来稍后检测。该变量的值在验证时需要等于2,因为这将意味着反DBI和反调试检查正确完成。这个JNI调用被反编译如下。

研究anti_debug函数得到如下所示的代码(函数名称和变量由我重新命名)。

这个crackme的作者写了一篇很棒的文章,解释了如何执行自调试技术。这利用了一个事实,即只有一个调试器可以随时附加到进程。想深入研究的话请仔细看看他的博客,因为我不会在这里重新解释。实际上,如果我们运行附带调试器的应用程序,那么我们可以看到启动了两个线程并且应用程序崩溃。

3.用Frida hook java层代码

现在,我们需要隐藏我们的手机是root过的这一事实。用Frida绕过这些检查的通常方法将是为这些功能编写hook。hook MainActivity的onCreate()的方法上时,出现了一个问题。Frida基本上无法在正确的时候截获方法onCreate()。更多信息可以在frida-Java issue #29找到。我们可以想到其它的方法来绕过这些检查。如果我们接管系统调用的exit()呢?这样做可以让我们不花时间绕过Java安全机制,并且在hook exit方法之后,我们可以继续与应用程序进行交互,就好像没有启动任何检查一样。以下hook是有效的。

一旦我们放置这个hook并启动应用程序,我们就可以输入了。然而,native层检查也需要被绕过。

4.使用Frida hook native层代码

如逆向native代码部分所示,有几个libc函数(例如strstr)执行一些关于Frida和Xposed检查。此外,该应用程序还创建线程来循环检查调试器或附加到应用程序的DBI框架。在这个阶段,我们可以考虑如何绕过这些检查。我想到了hook strstr和hook pthread_create。我们将尝试这两种方法,并将向您展示无论选择哪种方法都能达到相同的效果。请注意,在这两种情况下,应用程序都需要重启,因为Frida将代理注入到程序的地址空间中,然后才会取消附加。因此,反调试检查不是一个大问题。

解决方案1:hook strstr并禁用反frida检查

我们想干扰这一行反编译代码的行为。

为了hook这个libc函数,我们可以编写一个native hook来检查传递给该函数的字符串是否是Frida或者Xposed然后返回null指针,就像这个字符串没有被发现一样。在Frida中,我们可以使用如下所示的Interceptor附加native hook:(如果要观察整个行为,请取消最后的注释)。

下面是hook strstr之后的输出。

应用程序现在检测不到我们,我们可以在DBI阶段更进一步了。你想到下一次hook哪个函数了吗?之后,我们将hook通过strncmp和xor执行验证的函数。

解决方案2:替换native函数pthread_create并禁用安全线程

如果我们看看pthread_create的交叉引用,那么我们意识到所有的引用都是我们想要影响的回调。请参见下图。


2.png

请注意,这两个线程有一些共同点。看着它们,我们观察到第一个和第三个参数都是0,如下所示。

为了避免调用这些线程,策略如下。

①从libc函数获取native指针pthread_create。

②使用此指针创建native函数。

③定义native回调并重载此方法。

④使用Interceptor与replace模式注入。

⑤如果我们检测到pthread_create想要检测我们,那么我们将假冒回调并且将始终返回0,模拟Frida不在进程的地址空间中。

以下代码代替native功能pthread_create。

让我们运行这个脚本看看会发生什么事情。请注意,两个native调用pthread_create被hook,因此我们绕过了安全检查(init和anti_debug函数)。还要注意,我们希望在第一个和第三个参数被设置为0时避免pthread_create被调用并在应用程序中留下其它正常的线程。

或者,如果你想要更多地使用Frida的话,那么你可能会首先想要调用pthread_create观察行为。为此,您可以使用下面的hook。

Hook secret:一旦抵达这里,我们几乎准备好进行最后一步了。下一个native hook将包含拦截与用户输入进行比较的参数。在下面的C代码中,我们已经把一个函数重命名为protect_secret。这个函数在一堆经过混淆的操作之后生成secret。一旦生成了这个secret,它就在strncmp_with_xor函数中与用户输入进行比较。如果我们hook这个函数的参数呢?

验证的代码被反编译如下:(名称由我重命名)。

为了准备hook strncmp_with_xor,我们需要在反汇编代码中获得某些偏移量,还要获得libc的基址,并在运行时重新计算最终的指针。可以通过调用Interceptor来附加到native指针。请注意,使用native指针p_protect_secret的hook不需要恢复secret。因此,您可以在脚本中跳过它。

本文转载:http://bobao.360.cn/learning/detail/3930.html

原文链接:https://enovella.github.io/android/reverse/2017/05/20/android-owasp-crackmes-level-3.html






发表评论

(必填)

(必填)

(以便回访)