关注网络安全
和行业未来

绕过安卓系统SSL验证和证书绑定的四种方法

移动应用程序曾经毫无怨言地忽略了各种各样的SSL错误,并允许你随意地拦截和篡改它们的通信,但是这样的日子已经一去不复返了。相反,大多数现代应用程序至少会检查其收到的证书是否由一个有效的、受信任的证书颁发机构(CA)所颁发。作为渗透测试人员,我们希望让应用程序相信我们的证书是有效且可信的,这样我们就可以对其进行“中间人”(MITM)攻击,并篡改它的通信。在本篇博客中,我将介绍4种技术,你可以使用这些技术来绕过安卓系统中的SSL证书检查:

  • ·向受信任的证书存储库中添加一个自定义的证书颁发机构
  • ·用自定义的证书颁发机构的证书覆盖已打包的证书
  • ·使用Frida来钩住(hook)并绕过SSL证书检查机制
  • ·翻转自定义证书代码

在执行方面,这些方法中既有相当简单的,也有非常先进的——这篇博客将试着对每一种方法都进行探讨,而不会纠缠于特定情况的细节。

SSL“中间人”攻击——为什么?

为什么我们需要特别注意移动应用程序的SSL“中间人”攻击的条件?为了查看移动应用程序的web服务调用并使其变得模糊,我们需要使用一个拦截代理服务器,就像BurpSuite或ZAP那样。当使用代理服务器拦截SSL通信时,来自客户端的SSL连接就会在代理服务器上被终止——代理服务器发送的任何表明自己身份的证书都将使移动应用程序认为,代理服务器就是web服务端点。默认情况下,像Burp这样的工具生成的自签名证书不会有有效的信任链,而如果证书不能被验证为可信的,那么大多数移动应用程序将会终止连接,而不会连接到一个可能不安全的通道上。下面这几项技术都有一个共同的目标,那就是说服移动应用程序信任我们的拦截代理服务器所提供的证书。

第一项技术——向用户证书存储库中添加自定义证书颁发机构

避免SSL错误的最简单方法是拥有一个有效的、受信任的证书。如果你可以在设备上安装新的、受信任的证书颁发机构,这就相对容易一些了——如果操作系统信任你的证书颁发机构,它也会信任由该机构颁发的证书。

安卓系统有两个内置的证书存储库,可以跟踪操作系统所信任的证书颁发机构——一个是系统存储库(保存预先安装的证书颁发机构),另一个是用户存储库(保存用户安装的证书颁发机构)。下面是来自developer.android.com网站的观点:默认情况下,来自所有应用程序的(使用诸如TLS和HTTPS这样的协议的)安全连接都信任系统中预先安装的证书颁发机构,而针对安卓6.0(API等级23)及以下版本系统的应用程序还会信任默认情况下用户添加的证书颁发机构存储库。一个应用程序可以使用base-config配置(应用程序范围内的自定义)和domain-config配置(域范围内的自定义)对其本身的连接进行自定义设置。

这对我们来说意味着什么呢?如果我们正在尝试进行“中间人”攻击的目标应用程序适用于安卓6.0或更低版本系统的话,我们就可以直接将我们的证书颁发机构添加到用户证书颁发机构存储库中。当应用程序验证我们的自定义证书的信任链时,它将会发现我们自定义的证书颁发机构在受信任的证书库中,这样我们的证书就会得到信任了。但是,如果该应用程序适用的安卓版本高于6.0的话,那么它就不会信任用户添加的证书颁发机构存储库了。为了解决这个问题,我们可以编辑应用程序的“manifest”文件,并强制它适用安卓6.0系统。目标API的等级是由“AndroidManifest.xml”文件中的“manifest”元素的“platformBuildVersionCode”属性来指定的。

<manifestxmlns:android="http://schemas.android.com/apk/res/android"package="com.test.app"platformBuildVersionCode="25"platformBuildVersionName="7.1.1">

上述“manifest”元素的“platformBuildVersionCode”属性值为25,我们需要把它改为23。

<manifestxmlns:android="http://schemas.android.com/apk/res/android"package="com.test.app"platformBuildVersionCode="23"platformBuildVersionName="6.0">

在修改了“manifest”文件并重新打包这个应用程序后,它将会信任用户添加的证书颁发机构存储库。

另外,如果需要在特定版本的系统上运行的话,我们可以在APK的配置文件“/res/xml/network_security_config.xml”中专门定义一个信任锚点。例如,以下文件定义了一个新的受信任的证书颁发机构,它需要存储在“/res/raw/my_ca”目录下(这个例子来自https://developer.android.com/training/articles/security-config.html):

<?xml version="1.0" encoding="utf-8"?>

<network-security-config>

  <base-config>

    <trust-anchors>

      <certificatessrc="@raw/my_ca"/>

    </trust-anchors>

  </base-config>

</network-security-config>

如果应用程序只是验证所提供的证书是否有效,那么这项技术应该可以让你建立一个成功的“中间人”攻击条件。

第二项技术——用自定义证书覆盖已封装的证书

假如,你成功地将证书安装到用户添加的证书颁发机构存储库中了,应用程序也适用于安卓6.0系统,并且当你尝试浏览其他的在SSL保护下的资源时证书也会显示为有效,但是应用程序仍然会发生SSL错误,那这是怎么回事呢?其实,开发人员可能已经采取了额外的措施来限制应用程序所信任的证书颁发机构库。回想一下上一项技术,我们设置了一个自定义的信任锚点,并为一个证书提供了路径——而这也正是开发人员可以用来保护他们的应用程序不受SSL拦截的方法。

如果一个自定义的证书链正在被一个应用程序分发,那么提取APK文件并使用我们自定义的证书颁发机构覆盖系统所提供的证书颁发机构,就可以使我们用于拦截的证书得到信任。请注意,在某些情况下,可能会对信任链进行额外的验证,因此这种方法可能会产生复杂的结果。

使用APK Studio这样的工具打开APK文件,可以明显地发现与已部署应用程序捆绑在一起的证书。在上面的图片中,证书位于“assets”目录下。用我们自定义的证书颁发机构来覆盖这个非常贴切地被命名为“UniversalRootCA”的证书,应该就可以让我们骗过应用程序使之接受我们的证书了。

第三项技术——Frida钩子

如果安装你自己的证书颁发机构还不足以成功地代理SSL通信,那就说明应用程序可能正在执行某种SSL绑定或额外的SSL验证。通常,为了绕过这种类型的验证,我们需要劫持应用程序的代码,并干扰验证过程本身。这种类型的干扰过去往往被限制在被破解/越狱的手机上,但在Frida小工具的帮助下,现在可以用来控制安卓应用程序,并且可以在没有获得设备root权限的情况下取得完整的Frida功能。

如果你以前执行过移动应用程序渗透测试的话,那么你对Frida框架可能会很熟悉。全面讲解Frida的功能超出了这篇博客的范围,但在更高层次上,它是一个允许你在应用程序运行时篡改其代码的框架。通常来说,Frida将作为一个独立的程序运行在操作系统上——但这需要取得设备的root权限。为了避免这种情况,我们可以把Frida小工具注入到目标APK文件中。Frida小工具包含了Frida的大部分功能,但它被封装在一个动态库中,该库在目标应用程序运行时加载,它允许你对目标应用程序的代码进行控制和修改。

为了加载Frida小工具,我们需要提取APK文件,插入动态库,以及编辑一些smali语言的代码,这样我们的动态库就成为了应用程序启动时第一个调用的东西,然后重新打包APK文件并安装它。这整个过程都被John Kozyrakis详细地记录在这里了,你最好把这个过程手动操作一遍,以便了解这一切是如何共同工作的。不过,为了节省时间,我们还可以使用另一种工具——Objection。这个工具使整个过程自动进行,并且只需要向命令行提供目标APK文件。

C:\ >objection patchapk -s test_app.apk

No architecture specified. Determining it using `adb`...

Detected target device architecture as: armeabi-v7a

Github FridaGadget is v10.6.28, local is v10.6.13. Updating...

Downloading armeabi-v7a library to C:\.objection\android\armeabi-v7a\libfrida-gadget.so.xz...

Unpacking C:\.objection\android\armeabi-v7a\libfrida-gadget.so.xz...

Cleaning up downloaded archives...

Using Gadget version: 10.6.28

Unpacking test_app.apk

App already has android.permission.INTERNET

Reading smali from: C:\Temp\tmp8dxqks1u.apktemp\smali\com/test/app/TestMainActivity.smali

Injecting loadLibrary call at line: 10

Writing patched smali back to: C:\Temp\tmp8dxqks1u.apktemp\smali\com/test/app/TestMainActivity.smali

Creating library path: C:\Temp\tmp8dxqks1u.apktemp\lib\armeabi-v7a

Copying Frida gadget to libs path...

Rebuilding the APK with the frida-gadget loaded...

Built new APK with injected loadLibrary and frida-gadget

Signing new APK.

jar signed.

Signed the new APK

Performing zipalign

Zipaling completed

Copying final apk from C:\Users\cwass\AppData\Local\Temp\tmp8dxqks1u.apktemp.aligned.objection.apk to current directory...

Cleaning up temp files...

在此之后,在我们的工作目录上,应该就有了一个名为“test_app.objection.apk”的文件——默认情况下,objection工具会在原始APK文件名上附加一个“.objection”。我们可以像安装任何其他APK文件一样安装这个APK文件——adb install test_app.objection.apk命令应该会把它推送到我们已连接的设备上。在我们的目标设备上安装了已被objection工具修改过的APK文件之后,运行该应用程序应该会导致其启动时出现暂停。此时,我们可以连接到一个Frida服务器,该服务器应该正在对设备进行侦听。如果你更喜欢使用Frida工具的话,那就这么做:

C:\>frida-ps -U

PID  Name

----  ------

6383  Gadget

 

C:\>frida -U gadget

____

/ _| Frida 10.3.14 - A world-class dynamic instrumentation framework

| (_| |

>_| Commands:

/_/ |_| help -> Displays the help system

. . . . object? -> Display information about 'object'

. . . . exit/quit -> Exit

. . . .

. . . . More info at http://www.frida.re/docs/home/

 

[Motorola Moto G (5) Plus::gadget]-> Java.available

true

另外,Objection工具还支持与Frida侦听服务器之间的交互,这可以通过使用“explore”命令来实现:

C:\>objection explore

___||_  |_|___ ___||_|_|___ ___

| . | . || | -_|  _|_| | . |   |

|___|___|_||___|___|_||_|___|_|_|

|___|(object)inject(ion) v1.2.2

 

Runtime Mobile Exploration

by: @leonjza from @sensepost

 

[tab] for command suggestions

com.test.app on (motorola: 7.0) [usb] # android hooking search classes TrustManager

android.security.net.config.RootTrustManager

android.app.trust.ITrustManager$Stub$Proxy

android.app.trust.ITrustManager

android.security.net.config.NetworkSecurityTrustManager

android.security.net.config.RootTrustManagerFactorySpi

android.app.trust.TrustManager

android.app.trust.ITrustManager$Stub

com.android.org.conscrypt.TrustManagerImpl

com.android.org.conscrypt.TrustManagerImpl$ExtendedKeyUsagePKIXCertPathChecker

com.android.org.conscrypt.TrustManagerImpl$TrustAnchorComparator

com.android.org.conscrypt.TrustManagerFactoryImpl

javax.net.ssl.TrustManagerFactory$1

javax.net.ssl.TrustManager

javax.net.ssl.TrustManagerFactory

javax.net.ssl.X509TrustManager

javax.net.ssl.TrustManagerFactorySpi

javax.net.ssl.X509ExtendedTrustManager

[Ljavax.net.ssl.TrustManager;

此时,你应该能够从内置的绕过SSL绑定的功能中获益:

com.test.appon (motorola: 7.0) [usb] # androidsslpinningdisable

Job: 2f633f86-f252-4a57-958e-6b46ac8d69d1-Starting

[6b46ac8d69d1][android-ssl-pinning-bypass]Custom, EmptyTrustManagerready

Job: 2f633f86-f252-4a57-958e-6b46ac8d69d1Started

第四项技术——翻转自定义证书验证代码

最后,开发人员可能会选择提供他们自己的SSL库,而不是依赖于系统库来处理SSL证书验证。如果是这种情况的话,我们可能需要提取APK文件并将smali语言转换回Java语言,这样我们就可以查找负责处理证书验证的代码了。

使用“dex2jar”命令,语句如下:

C:\>d2j-dex2jar.bat "C:\test_app.apk"

dex2jar C:\test_app.apk -> .\test_app-dex2jar.jar

由此产生的.jar文件应该可以在你所喜欢的Java翻转工具(如JD-GUI)中打开了。

一旦你找到了负责证书验证的代码,你就既可以选择把它完全替换,也可以用Frida工具来钩住你想要的函数。为了避免重新构建整个应用程序,通常来说,只篡改负责证书验证的功能会更加高效。使用第三项技术中的方法可以让你对应用程序进行控制——在那里,你应该能够使用Frida命令行工具或Objection界面来钩住一个函数,你觉得哪个方法好用就用哪个。

结语

上文提到的技术应该可以让你拦截安卓系统的SSL通信了,还可以让你绕过开发人员部署的一些常规的防御手段。此外,这篇博客还简要介绍了Objection和Frida工具——绕过SSL绑定和其它防御的能力仅仅是这两种工具所提供的极多功能中的一小部分。我希望这篇博客能够帮助人们开始了解安卓系统移动应用程序安全测试中的各种技术,并且能够阐明通过多种方法绕过指定的安全机制的重要性。

 

稿源:netspi

评论 抢沙发