一、背景

安卓安全测试中,其中一个维度测试就是服务端业务逻辑安全性测试,主要通过抓包实现。
其实说白了就是中间人攻击,装着要测试APP的安卓手机发包给我们电脑的burpsuite,我们可以使用bp截包改包的内容,再发给服务端,服务端收到请求后响应,把返回包发给电脑的bp,bp最后发给我们手机上的app。
随着移动端安全逐渐加强,现在越来越多的app已经无法抓到包,或者提示网络相关错误。其实根本原因在于客户端发包时对于服务端的ssl证书进行了校验。

二、客户端证书处理逻辑分类

客户端关于证书的处理逻辑,按照安全等级可做如下分类:
在这里插入图片描述
本文所述的使用frida绕过ssl pinning即通过hook注入方式篡改锁定逻辑,可破解后三种情况。

三、ssl pinning原理

证书锁定(SSL/TLS Pinning)即将服务器提供的SSL/TLS证书内置到APP客户端中,当客户端发请求时,通过比对内置的证书和服务器端证书的内容,以确定这个连接的合法性。
所以,ssl pinning需要开发人员将APP代码内置仅接受指定CA或域名的证书,而不接受操作系统或浏览器内置的CA根证书对应的任何证书,通过这种授权方式,保障了APP与服务端通信的唯一性和安全性。但是CA签发证书都存在有效期问题,所以缺点是在证书续期后需要将证书重新内置到APP中。

四、frida绕过安卓ssl pinning

那么,假设一个APP使用了ssl pinning,不信任任何系统和用户证书,整个通信过程就真的安全了吗,安全测试人员就完全没办法使用bp抓包来测试服务端业务逻辑安全了吗。
答案当然是否定的,我们可以使用VirtualXposed+JustTrustMe在安卓手机不需root条件下,禁用客户端的SSL 证书检查,但是这种方法现在对很多APP也不奏效了。
因此,本文推荐使用frida绕过安卓APP的ssl pinning机制。我们想要实现的效果就是绕过安卓APP的ssl pinning机制,把包发给我们电脑的bp(说白了就是要实现对APP的中间人攻击),以进行服务端安全测试。

1. 绕过原理

  1. 从安卓手机中加载我们自己的 CA证书
  2. 创建包含我们信任CA证书的KeyStore
  3. 创建TrustManager,让它信任我们KeyStore中的CA证书

当安卓APP初始化SSLContext时,我们使用frida劫持SSLContext.init方法,使用我们自己创建的TrustManager , 把它作为实参传入SSLContext.init方法的第二个参数( SSLContext.init(KeyManager,TrustManager,SecuRandom) 。这样我们就使APP信任我们的CA了。
以上逻辑都是通过一个js注入脚本实现的。

2. 使用工具

Frida

Frida就是一个让你可以注入脚本到APP中的工具,从而修改APP的行为,并实时的进行动态测试。
github链接:https://github.com/frida/frida
本文基于python 3.7版本(推荐使用python3版本运行frida),frida 12.9.4版本。

root设备/模拟器

这边推荐大家某宝买个谷歌nexus安卓手机,可以很方便刷机、root设备和进行安全测试,而且也不贵,就两三百,由于需要注入脚本,所以手机必须是root过的。
模拟器可使用genymotion。
本文是基于nexus 6p安卓机,可root。

adb

adb工具对于移动端安全测试还是很重要的,建议大家装个android studio安卓开发环境,装完后自带adb。

注入脚本

注入脚本我们使用某国外大神写的基于js的注入脚本,地址在:https://techblog.mediaservice.net/wp-content/uploads/2017/07/frida-android-repinning_sa-1.js
在这里插入图片描述

3. 绕过过程

0x01 安装Frida

Frida需要在手机上安装服务端,电脑上安装客户端。

  1. 电脑安装客户端,使用阿里源加速
pip3 install Frida -i https://mirrors.aliyun.com/pypi/simple/
pip3 install frida-tools -i https://mirrors.aliyun.com/pypi/simple/
  1. 手机usb连接电脑,adb连接成功后输入adb shell getprop ro.product.cpu.abi,查看手机arch 版本,然后去https://github.com/frida/frida/releases下载对应的frida 服务端版本到电脑上
    在这里插入图片描述
    在这里插入图片描述
  2. 在电脑上解压.xz结尾的压缩文件,得到可执行文件
  3. 通过adb命令将刚刚解压可执行的文件push到安卓指定目录:adb push C:\xxx\frida-server /data/local/tmp
  4. adb shell chmod 777 /data/local/tmp/frida-server给可执行文件权限
0x02 准备burpsuite证书

为了能拦截流量,frida要访问我们的Burpsuite CA证书,因此通过bp下载其证书,并将其重命名为cert-der.crt,使用adb push C:\xxx\cert-der.crt /data/local/tmp命令同样将其push到手机相同目录

0x03 准备注入脚本

将以下代码保存为在电脑上,命名为fridascript.js

/* 

   Android SSL Re-pinning frida script v0.2 030417-pier

$ adb push burpca-cert-der.crt /data/local/tmp/cert-der.crt

   $ frida -U -f it.app.mobile -l frida-android-repinning.js --no-pause

https://techblog.mediaservice.net/2017/07/universal-android-ssl-pinning-bypass-with-frida/

   

   UPDATE 20191605: Fixed undeclared var. Thanks to @oleavr and @ehsanpc9999 !

*/

setTimeout(function(){

    Java.perform(function (){

     console.log("");

     console.log("[.] Cert Pinning Bypass/Re-Pinning");

var CertificateFactory = Java.use("java.security.cert.CertificateFactory");

     var FileInputStream = Java.use("java.io.FileInputStream");

     var BufferedInputStream = Java.use("java.io.BufferedInputStream");

     var X509Certificate = Java.use("java.security.cert.X509Certificate");

     var KeyStore = Java.use("java.security.KeyStore");

     var TrustManagerFactory = Java.use("javax.net.ssl.TrustManagerFactory");

     var SSLContext = Java.use("javax.net.ssl.SSLContext");

// Load CAs from an InputStream

     console.log("[+] Loading our CA...")

     var cf = CertificateFactory.getInstance("X.509");

     

     try {

      var fileInputStream = FileInputStream.$new("/data/local/tmp/cert-der.crt");

     }

     catch(err) {

      console.log("[o] " + err);

     }

     

     var bufferedInputStream = BufferedInputStream.$new(fileInputStream);

    var ca = cf.generateCertificate(bufferedInputStream);

     bufferedInputStream.close();

var certInfo = Java.cast(ca, X509Certificate);

     console.log("[o] Our CA Info: " + certInfo.getSubjectDN());

// Create a KeyStore containing our trusted CAs

     console.log("[+] Creating a KeyStore for our CA...");

     var keyStoreType = KeyStore.getDefaultType();

     var keyStore = KeyStore.getInstance(keyStoreType);

     keyStore.load(null, null);

     keyStore.setCertificateEntry("ca", ca);

     

     // Create a TrustManager that trusts the CAs in our KeyStore

     console.log("[+] Creating a TrustManager that trusts the CA in our KeyStore...");

     var tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();

     var tmf = TrustManagerFactory.getInstance(tmfAlgorithm);

     tmf.init(keyStore);

     console.log("[+] Our TrustManager is ready...");

console.log("[+] Hijacking SSLContext methods now...")

     console.log("[-] Waiting for the app to invoke SSLContext.init()...")

SSLContext.init.overload("[Ljavax.net.ssl.KeyManager;", "[Ljavax.net.ssl.TrustManager;", "java.security.SecureRandom").implementation = function(a,b,c) {

      console.log("[o] App invoked javax.net.ssl.SSLContext.init...");

      SSLContext.init.overload("[Ljavax.net.ssl.KeyManager;", "[Ljavax.net.ssl.TrustManager;", "java.security.SecureRandom").call(this, a, tmf.getTrustManagers(), c);

      console.log("[+] SSLContext initialized with our custom TrustManager!");

     }

    });

},0);
0x04 利用脚本注入
  1. 在手机上运行Frida服务端
adb shell
su
/data/local/tmp/frida-server &

在这里插入图片描述
2. 通过ps命令找到你要注入的正在运行APP的package name,如果知道的话,也不用事先找
在这里插入图片描述
3. 最后运行frida -U -f com.xxx.xxx-l C:\xxx\fridascript.js --no-paus 命令,将脚本注入到原生应用程序中。手机别忘了设置代理,指向电脑的bp监听端口,bp即可截取到通信过程的包
在这里插入图片描述

五、后记

  1. 是否能绕过所有APP的ssl pinning机制
    安全其实是木桶原理,一些银行类APP,安全防护机制比较健全,会进行注入的检测,所以无法使用Frida进行注入。但是本文叙述的方法适用大部分APP。
  2. 是否能抓到所有通信包
    需要看具体APP业务逻辑,我能确认的是现在有一些APP的重要接口或者新接口会走socket通信,目前对于socket通信的包,还没有比较好的截取办法。
Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐