Andorid 绕过 SSL Pinning 抓 https 报文

SSL pinning

SSL Pinning是一种防止中间人攻击的技术,主要机制是在客户端发起请求–>收到服务器发来的证书进行校验,如果收到的证书不被客户端信任,就直接断开连接不继续请求。可以发现中间人攻击的要点是伪造了一个假的服务端证书给了客户端,客户端误以为真。解决思路就是,客户端也预置一份服务端的证书,比较一下就知道真假了。

SSL-pinning有两种方式:证书锁定(Certificate Pinning) 和公钥锁定( Public Key Pinning)。

证书锁定

需要在客户端代码内置仅接受指定域名的证书,而不接受操作系统或浏览器内置的CA根证书对应的任何证书,通过这种授权方式,保障了APP与服务端通信的唯一性和安全性,因此客户端与服务端(例如API网关)之间的通信是可以保证绝对安全。但是CA签发证书都存在有效期问题,缺点是在证书续期后需要将证书重新内置到APP中。

公钥锁定

提取证书中的公钥并内置到客户端中,通过与服务器对比公钥值来验证连接的正确性。制作证书密钥时,公钥在证书的续期前后都可以保持不变(即密钥对不变),所以可以避免证书有效期问题,一般推荐这种做法。

(此小节内容摘抄自互联网)

总体思路

使用 mitmproxy https://github.com/mitmproxy/mitmproxy 进行抓包,使用 frida 绕过 SSL pinning, frida 的安装和使用这里就不再详述了,可以参考其他资料。

安装 mitmproxy

参考 https://docs.mitmproxy.org/stable/overview-installation/ 文档

可以直接下载 Linux binary: https://snapshots.mitmproxy.org/7.0.2/mitmproxy-7.0.2-linux.tar.gz, 或者使用 pip 进行安装 https://pypi.org/project/mitmproxy/
执行命令 ~/.local/bin/pip3 install mitmproxy --user

安装成功之后,有三个程序可以使用: mitmproxymitmdumpmitmweb

设置代理

在主机上执行下面几行命令设置代理

mitmweb -p 8080
adb shell settings put global http_proxy 127.0.0.1:8888
adb reverse tcp:8888 tcp:8080

mitmweb -p 8080 在本机起 8080 代理,在 Android 上设置 http 全局代理 127.0.0.1:8888, 最后将 Android 的 8888 端口转发到本机 8080 端口

设置 CA

https://docs.mitmproxy.org/stable/concepts-certificates/

The first time mitmproxy is run, it creates the keys for a certificate authority (CA) in the config directory (~/.mitmproxy by default).
Filename Contents
mitmproxy-ca.pem The certificate and the private key in PEM format.
mitmproxy-ca-cert.pem The certificate in PEM format. Use this to distribute on most non-Windows platforms.
mitmproxy-ca-cert.p12 The certificate in PKCS12 format. For use on Windows.
mitmproxy-ca-cert.cer Same file as .pem, but with an extension expected by some Android devices.

我们是 Android 应该使用 mitmproxy-ca-cert.cer,在 Android 系统安装的话,需要点击 设置 -〉安全 -〉 加密与凭证 -〉安装证书 -〉CA 证书

使用 frida 绕过 SSL pinning

使用 frida 脚本首先需要将 mitmproxy-ca-cert.cer 上传到 /data/local/tmp/cert-der.crt

使用脚本 https://codeshare.frida.re/@pcipolloni/universal-android-ssl-pinning-bypass-with-frida/

/*
   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);

执行下面命令,绕过 SSL pinning

frida -U --codeshare pcipolloni/universal-android-ssl-pinning-bypass-with-frida -F

     ____
    / _  |   Frida 14.2.18 - A world-class dynamic instrumentation toolkit
   | (_| |
    > _  |   Commands:
   /_/ |_|       help      -> Displays the help system
   . . . .       object?   -> Display information about 'object'
   . . . .       exit/quit -> Exit
   . . . .
   . . . .   More info at https://frida.re/docs/home/

[.] Cert Pinning Bypass/Re-Pinning
[+] Loading our CA...
[o] Our CA Info: O=mitmproxy, CN=mitmproxy
[+] Creating a KeyStore for our CA...
[+] Creating a TrustManager that trusts the CA in our KeyStore...
[+] Our TrustManager is ready...
[+] Hijacking SSLContext methods now...
[-] Waiting for the app to invoke SSLContext.init()...
[Pixel 2::智能生活]-> exit

其中 -F 参数 attach to frontmost application 不用指定 pid 或者包名,非常方便。

使用 mitmweb 查看报文

执行 mitmweb -p 8080 后可以用浏览器访问 http://127.0.0.1:8081/ 查看报文,如果需要共享报文数据可以使用
mitmweb 界面提供的 save 功能,会保存成一个 flow 文件,后面使用 mitmweb 界面提供的 open 打开报文文件即可展示报文详细信息。

参考资料

https://shunix.com/ssl-pinning/


Andorid 绕过 SSL Pinning 抓 https 报文
https://usmacd.com/cn/Android_SSL_Pinning/
作者
henices
发布于
2023年9月6日
许可协议