目录
- 一、背景
- 二、客户端证书处理逻辑分类
- 三、ssl pinning原理
- 四、frida绕过安卓ssl pinning
- 1. 绕过原理
- 2. 使用工具
- Frida
- root设备/模拟器
- adb
- 注入脚本
- 3. 绕过过程
- 0x01 安装Frida
- 0x02 准备burpsuite证书
- 0x03 准备注入脚本
- 0x04 利用脚本注入
- 五、后记
一、背景
安卓安全测试中,其中一个维度测试就是服务端业务逻辑安全性测试,主要通过抓包实现。
其实说白了就是中间人攻击,装着要测试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. 绕过原理
- 从安卓手机中加载我们自己的 CA证书
- 创建包含我们信任CA证书的KeyStore
- 创建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需要在手机上安装服务端,电脑上安装客户端。
- 电脑安装客户端,使用阿里源加速
pip3 install Frida -i https://mirrors.aliyun.com/pypi/simple/
pip3 install frida-tools -i https://mirrors.aliyun.com/pypi/simple/
- 手机usb连接电脑,adb连接成功后输入
adb shell getprop ro.product.cpu.abi
,查看手机arch 版本,然后去https://github.com/frida/frida/releases下载对应的frida 服务端版本到电脑上 - 在电脑上解压.xz结尾的压缩文件,得到可执行文件
- 通过adb命令将刚刚解压可执行的文件push到安卓指定目录:adb push C:\xxx\frida-server /data/local/tmp
-
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 利用脚本注入
- 在手机上运行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即可截取到通信过程的包
五、后记
- 是否能绕过所有APP的ssl pinning机制
安全其实是木桶原理,一些银行类APP,安全防护机制比较健全,会进行注入的检测,所以无法使用Frida进行注入。但是本文叙述的方法适用大部分APP。 - 是否能抓到所有通信包
需要看具体APP业务逻辑,我能确认的是现在有一些APP的重要接口或者新接口会走socket通信,目前对于socket通信的包,还没有比较好的截取办法。