利用Frida绕过Android App(apk)的SSL Pinning

2023-03-09 httpsSSL 证书JavaScriptAndroid

0x00 前言

做app测试过程中,使用burp无法抓到数据包或提示网络错误可能是因为app启用了ssl pinning,刚好最近接触到apk就是这种情况,于是便有了本文。

0x01 ssl pinning原理

ssl pinning即证书锁定,将服务器提供的ssl/tls证书内置到移动端开发的app客户端中,当客户端发起请求时,通过比对内置的证书和服务器端证书的内容,以确定这个连接的合法性。

0x02 环境

win10 安卓模拟器(夜神模拟器) burp frida(python 3.7) adb工具 apk(途牛apk)

0x03 利用frida绕过ssl pinning

绕过原理: 客户端请求时会将内置的证书与服务端的证书做一次性校验,通过hook的方式将此次校验的结果返回true或者干脆不让其做校验即可以绕过。当安卓app初始化sslcontext时,我们使用frida劫持sslcontext.init方法,使用我们自己创建的trustmanager , 把它作为实参传入sslcontext.init方法的第二个参数( sslcontext.init(keymanager,trustmanager,securandom) 。这样就能使得app信任我们的ca,以上操作都是通过一个js脚本注入实现的。

1. 利用adb连接安卓模拟器

这里我们使用的是夜神模拟器,它默认使用android 5版本的,就是因为这个点,这里踩坑踩了很久,夜神的android版本低会导致下面在启用frida-server时报错,因此这里需要使用android 7以上。 android低版本报错如下

夜神模拟器创建高版本android 7并启动

利用adb连接模拟器,夜神模拟器adb连接默认是连接本机的62001端口,可是这里因为是新建的可能端口会有所变化,不过也还是在62001附近,可以使用

netstat -ano

查看一下本机端口,我这里是62041端口,如下所示代表连接成功

adb connect 127.0.0.1:62025 #连接设备
adb devices #查看设备    

2. 设置burp证书

先在burp里设置本机代理

访问代理地址并下载burp证书

将下载的burp证书导入到模拟器中/data/local/tmp目录下,并重命名为cert-der.crt(此名称在接下来的fridascript.js脚本中使用,如果该名字命名为其他则脚本中相对应的地方也需要进行替换)

adb push cacert.der /data/local/tmp/cert-der.crt

3. 模拟器设置代理

在安卓模拟器设置->wlan选择对应网络设置代理

4. frida设置

python frida包安装

win10需安装python3.7环境,终端安装frida和frida-tools

pip3 install frida 
pip3 install frida-tools

frida-server设置

查看安卓设备的arch版本并下载对应的frida-server包

adb shell getprop ro.product.cpu.abi

这里是x86的因此下载frida-server-12.9.7-android-x86.xz

下载完成以后解压重命名文件为frida-server并将其上传到安卓模拟器的/data/local/tmp/目录下

adb push frida-server /data/local/tmp

设置frida-server权限

adb shell chmod 777 /data/local/tmp/frida-server

运行frida-server

# 进入模拟器:/data/local/tmp/目录运行
cd /data/local/tmp/
./frida-server
# 直接运行
adb shell /data/local/tmp/frida-server &

如果运行正常则不会有任何输出

在模拟器里运行途牛旅游app,并使用frida-ps -u命令列出设备上运行的服务,找到途牛应用程序的包名

下载注入脚本,保存为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);

脚本里的30行其中对应的就是burp证书的信息 将fridascript.js注入到目标应用程序中

frida -u -f com.tuniu.app.ui -l c:\users\xxx\desktop\fridascript.js --no-pause

-f选项表示强制启动一个应用程序,-l选项表示加载指定脚本,–no-pause选项表示不中断应用程序的启动,如下所示代表运行成功

一旦frida服务运行成功,目标程序的流量都将被burp拦截

0x04 参考

https://xz.aliyun.com/t/6102
https://blog.csdn.net/w1590191166/article/details/106308028

原文地址:https://www.codenong.com/cs106650473/

上一篇:C/C++ Qt TabWidget 实现多窗体创建

下一篇:C/C++ Qt StatusBar 底部状态栏应用