抓包检测绕过分析

常见的抓包检测

  1. 正常抓包不走代理
HttpsURLConnection设置不走代理
okhttp3设置不走代理
  1. 代理检测与VPN检测
Hook设置代理、在模拟器中可以使用httpv7来抓包、使用VPN抓包
  1. 单向验证与双向验证
单向验证:客户端检测服务器 (比如客户端去检测服务器的证书也就是公钥,通过比对公钥来查看是否有抓包工具的出现(因为中间人抓包工具没有对应网页的私钥,所以要给客户端签发的证书也就是自己有私钥的伪造的公钥,才能进行数据交互之间的解密。)
抓包解密SSL流量,需要伪造证书
通常使用抓包工具的根证书,来签发一个服务器实体证书,这时可以证书检测 
双向验证:
客户端校验服务器证书:
    通常利用系统相关函数来校验证书,这时可以通过Hook相关系统函数来绕过
    X509TrustManager	HostnameVerifier	okhttp3证书锁定	   okhttp3证书校验
服务器也会去检测客户端,    

  1. HOOK抓包
利用HOOK函数去实现抓取系统函数来实现抓包
不用需理会各种抓包检测
抓到的可能没有抓包工具全,会被Hook检测

代理检测:

代理检测需要设置代理,函数里面通过System.setProperty来实现设置代理,而代理检测则是通过

System.getProperty()

VPN检测:

常见的VPN检测代码:

  1. java.net.NetworkInterface.getName()
public static void getNetworkName() {
        try {
            Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
            int count = 0;
            while (networkInterfaces.hasMoreElements()) {//使用 while 循环遍历每一个网络接口。hasMoreElements() 方法检查是否还有网络接口可以处理,nextElement() 方法返回下一个网络接口对象。
                NetworkInterface next = networkInterfaces.nextElement();
                logOutPut("getName获得网络设备名称=" + next.getName());
                logOutPut("getDisplayName获得网络设备显示名称=" + next.getDisplayName());
                logOutPut("getIndex获得网络接口的索引=" + next.getIndex());
                logOutPut("isUp是否已经开启并运行=" + next.isUp());
                logOutPut("isBoopback是否为回调接口=" + next.isLoopback());
                logOutPut("**********************" + count++);
            }
        } catch (SocketException e) {
            e.printStackTrace();
        }
    // "getName获得网络设备名称=tun0" <----- 可能出现的VPN情况 这样就是有问题的
    //正常的是 ---> "getName获得网络设备名称=rmnet_data0-rmnet_data999"

这里检测的函数也就是NetworkInterface类对象的getName()方法,所以要过检测就直接去HOOK这里的函数,然后判断是否有tun的字眼,有就返回别的

function hook_vpn(){
       Java.perform(function() {
           var NetworkInterface = Java.use("java.net.NetworkInterface");
           NetworkInterface.getName.implementation = function() {
               var name = this.getName();
               console.log("name: " + name);
               if(name == "tun0" || name == "ppp0"){
                   return "rmnet_data0";
               }else {
                   return name;
               }
           }
       })
    }
  1. android.net.ConnectivityManager.getNetworkCapabilities

检测代码:

    public void networkCheck() {
        try {
            ConnectivityManager connectivityManager = (ConnectivityManager) getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);//获得网络连接管理器
            Network network = connectivityManager.getActiveNetwork();//getActiveNetwork() 方法返回当前设备上正在使用的网络对象 Network,表示当前活动的网络连接。可能返回 null,这表示当前没有活跃网络连接。
            NetworkCapabilities networkCapabilities = connectivityManager.getNetworkCapabilities(network);
            Log.i("TAG", "networkCapabilities -> " + networkCapabilities.toString());//getNetworkCapabilities(Network network) 方法根据网络对象获取 NetworkCapabilities 实例,这个实例包含了当前网络的各种特性,比如网络类型(WiFi、移动网络等)、连接速度、传输能力等。
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
//检测到VPN的字眼:networkCapabilities -> [ Transports: WIFI|VPN Capabilities: INTERNET&NOT_RESTRICTED&TRUSTED&VALIDATED&NOT_ROAMING&FOREGROUND&NOT_CONGESTED&NOT_SUSPENDED LinkUpBandwidth>=1048576Kbps LinkDnBandwidth>=1048576Kbps]

当检测到提前的网络连接时,会出现添加vpn的字眼,具体是在toString()的地方进行的添加的

    public @NonNull String toString() {
        final StringBuilder sb = new StringBuilder("[");
        if (0 != mTransportTypes) {
            sb.append(" Transports: ");
            appendStringRepresentationOfBitMaskToStringBuilder(sb, mTransportTypes,
                    NetworkCapabilities::transportNameOf, "|");
        }

具体也就是在这里添加上的VPN的字眼的,所以我们可以直接去HOOK这里

NetworkCapabilities.appendStringRepresentationOfBitMaskToStringBuilder.implementation = function (sb, bitMask, nameFetcher, separator) {
    if (bitMask == 18) {
        console.log("bitMask", bitMask);
        sb.append("WIFI");
    }else {
        console.log(sb, bitMask);
        this.appendStringRepresentationOfBitMaskToStringBuilder(sb, bitMask, nameFetcher, separator);
    }
}

HttpsURLConnection的GET和POST请求发送

package com.chen_chen_chen.myapplication;

import android.text.TextUtils;
import android.util.Base64;
import android.util.Log;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Proxy;
import java.net.URL;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPublicKey;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

public class HttpUtil {
    public static void Post()
    {
        new Thread(){
            public void run(){
                String str =getHttpsURLConnection("POST","https://baidu.com/","user");
                if (str!=null) Log.d("HTTP input:", str);
                else Log.d("HTTP input:", "error");
            }
        }.start();
    }
    private static Context getSSLContext() {

        SSLContext sslContext = null;
        try {
            sslContext = SSLContext.getInstance("TLS");
            sslContext.init(null, null, null);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return sslContext;
    }

    public static String getHttpsURLConnection(String method, String url, String outputStr) {
        //System.setProperty("https.proxyHost", "192.168.10.1");
        //System.setProperty("https.proxyPort", "8888");
        //Proxy.NO_PROXY
        //Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("192.168.10.1", 8888));
        try {
            SSLContext sslContext = getSSLContext();
            if (sslContext != null) {
                URL u = new URL(url);
                HttpsURLConnection conn = (HttpsURLConnection) u.openConnection(Proxy.NO_PROXY);
                conn.setRequestMethod("GET");
                conn.setDoInput(true);//输出
                conn.setUseCaches(false);
                conn.setConnectTimeout(30000);

                if(method.equals("POST")){
                    conn.setRequestMethod("POST");
                    conn.setDoOutput(true);//输入
                    conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
                }

                if (null != outputStr) {
                    OutputStream outputStream = conn.getOutputStream();
                    outputStream.write(outputStr.getBytes("UTF-8"));
                    outputStream.close();
                }

                conn.connect();

                InputStream inputStream = conn.getInputStream();//获取输出流
                InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");
                BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
                String str = null;
                StringBuffer buffer = new StringBuffer();
                while ((str = bufferedReader.readLine()) != null) {
                    buffer.append(str);
                }
                bufferedReader.close();
                inputStreamReader.close();
                inputStream.close();
                conn.disconnect();
                return buffer.toString();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

HttpsURLConnection框架代码获取GET和POST请求

从上面得知,我们利用HttpsURLConeection申请的get和post请求的结果是通过这一部分来确定和指定的初始化数据

        if (sslContext != null) {
            URL u = new URL(url);
            HttpsURLConnection conn = (HttpsURLConnection) u.openConnection(Proxy.NO_PROXY);
            conn.setRequestMethod("GET");
            conn.setDoInput(true);//输出
            conn.setUseCaches(false);
            conn.setConnectTimeout(30000);

            if(method.equals("POST")){
                conn.setRequestMethod("POST");
                conn.setDoOutput(true);//输入
                conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
            }                
            if (null != outputStr) {
                    OutputStream outputStream = conn.getOutputStream();
                    outputStream.write(outputStr.getBytes("UTF-8"));
                    outputStream.close();
                }

                conn.connect();

其中主要是通过的conn对象也就是HttpsURLConnection类对象来进行初始化的,其中包含了请求方式conn.setRequestMethod("GET"),以及请求的输入,这里的输入流应该是同步的,先获取输入流对象,然后直接写入就可以写好请求包了

我们获取抓包请求和响应其实也就是通过getOutputStream()以及getInputStream()来获取的,所以其实可以去HOOK这些函数来实现我们的框架下的抓包HOOK

getOutputStream()是在com.android.okhttp.internal.huc.HttpsURLConnectionImpl
javax.net.ssl.HttpsURLConnection
java.net.URLConnection   

我们可以通过objection来实现快速的定位,其实发现这个函数是在com.android.okhttp.internal.huc.HttpsURLConnectionImpl类下真正实现的

HttpsURLConnection的证书检测

首先是利用HttpsURLConnection的框架提交POST或者Get请求时初始化证书检测

SSLContext sslContext = getSSLContext();
if (sslContext != null) {
    URL u = new URL(url);
    HttpsURLConnection conn = (HttpsURLConnection) u.openConnection();
    conn.setSSLSocketFactory(sslContext.getSocketFactory());
    conn.setRequestMethod(method);
    conn.setDoInput(true); // 允许输入流
    conn.setUseCaches(false);
    conn.setConnectTimeout(30000);
    conn.setReadTimeout(30000);
    conn.setSSLSocketFactory(sslContext.getSocketFactory());//这里就是初始化证书检测的地方
    conn.setHostnameVerifier(DO_NOT_VERIFY);

conn.setSSLSocketFactory(sslContext.getSocketFactory()假如这里的 HttpsURLConnection的对象里面没有去单独设置证书初始化的话,那么这里就是利用的默认的证书检测,全部的证书都能过

百度证书:
-----BEGIN CERTIFICATE-----
MIIJ7DCCCNSgAwIBAgIMTkADpl62gfh/S9jrMA0GCSqGSIb3DQEBCwUAMFAxCzAJ
BgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMSYwJAYDVQQDEx1H
bG9iYWxTaWduIFJTQSBPViBTU0wgQ0EgMjAxODAeFw0yNDA3MDgwMTQxMDJaFw0y
NTA4MDkwMTQxMDFaMIGAMQswCQYDVQQGEwJDTjEQMA4GA1UECBMHYmVpamluZzEQ
MA4GA1UEBxMHYmVpamluZzE5MDcGA1UEChMwQmVpamluZyBCYWlkdSBOZXRjb20g
U2NpZW5jZSBUZWNobm9sb2d5IENvLiwgTHRkMRIwEAYDVQQDEwliYWlkdS5jb20w
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC1wFMskJ2dseOqoHptNwot
FOhdBERsZ4VQnRNKXEEXMQEfgbNtScQ+C/Z+IpRAt1EObhYlifn74kt2nTsCQLng
jfQkRVBuO/6PNGKdlCYGBeGqAL7xR+LOyHnpH9mwCBJc+WVt2zYM9I1clpXCJa+I
tsq6qpb1AGoQxRDZ2n4K8Gd61wgNCPHDHc/Lk9NPJoUBMvYWvEe5lKhHsJtWtHe4
QC3y58Vi+r5R0PWn2hyTBr9fCo58p/stDiRqp9Irtmi95YhwkNkmgwpMB8RhcGoN
h+Uw5TkPZVj4AVaoPT1ED/GMKZev0+ypmp0+nmjVg2x7yUfLUfp3X7oBdI4TS2hv
AgMBAAGjggaTMIIGjzAOBgNVHQ8BAf8EBAMCBaAwDAYDVR0TAQH/BAIwADCBjgYI
KwYBBQUHAQEEgYEwfzBEBggrBgEFBQcwAoY4aHR0cDovL3NlY3VyZS5nbG9iYWxz
aWduLmNvbS9jYWNlcnQvZ3Nyc2FvdnNzbGNhMjAxOC5jcnQwNwYIKwYBBQUHMAGG
K2h0dHA6Ly9vY3NwLmdsb2JhbHNpZ24uY29tL2dzcnNhb3Zzc2xjYTIwMTgwVgYD
VR0gBE8wTTBBBgkrBgEEAaAyARQwNDAyBggrBgEFBQcCARYmaHR0cHM6Ly93d3cu
Z2xvYmFsc2lnbi5jb20vcmVwb3NpdG9yeS8wCAYGZ4EMAQICMD8GA1UdHwQ4MDYw
NKAyoDCGLmh0dHA6Ly9jcmwuZ2xvYmFsc2lnbi5jb20vZ3Nyc2FvdnNzbGNhMjAx
OC5jcmwwggNhBgNVHREEggNYMIIDVIIJYmFpZHUuY29tggxiYWlmdWJhby5jb22C
DHd3dy5iYWlkdS5jboIQd3d3LmJhaWR1LmNvbS5jboIPbWN0LnkubnVvbWkuY29t
ggthcG9sbG8uYXV0b4IGZHd6LmNuggsqLmJhaWR1LmNvbYIOKi5iYWlmdWJhby5j
b22CESouYmFpZHVzdGF0aWMuY29tgg4qLmJkc3RhdGljLmNvbYILKi5iZGltZy5j
b22CDCouaGFvMTIzLmNvbYILKi5udW9taS5jb22CDSouY2h1YW5rZS5jb22CDSou
dHJ1c3Rnby5jb22CDyouYmNlLmJhaWR1LmNvbYIQKi5leXVuLmJhaWR1LmNvbYIP
Ki5tYXAuYmFpZHUuY29tgg8qLm1iZC5iYWlkdS5jb22CESouZmFueWkuYmFpZHUu
Y29tgg4qLmJhaWR1YmNlLmNvbYIMKi5taXBjZG4uY29tghAqLm5ld3MuYmFpZHUu
Y29tgg4qLmJhaWR1cGNzLmNvbYIMKi5haXBhZ2UuY29tggsqLmFpcGFnZS5jboIN
Ki5iY2Vob3N0LmNvbYIQKi5zYWZlLmJhaWR1LmNvbYIOKi5pbS5iYWlkdS5jb22C
EiouYmFpZHVjb250ZW50LmNvbYILKi5kbG5lbC5jb22CCyouZGxuZWwub3JnghIq
LmR1ZXJvcy5iYWlkdS5jb22CDiouc3UuYmFpZHUuY29tgggqLjkxLmNvbYISKi5o
YW8xMjMuYmFpZHUuY29tgg0qLmFwb2xsby5hdXRvghIqLnh1ZXNodS5iYWlkdS5j
b22CESouYmouYmFpZHViY2UuY29tghEqLmd6LmJhaWR1YmNlLmNvbYIOKi5zbWFy
dGFwcHMuY26CDSouYmR0anJjdi5jb22CDCouaGFvMjIyLmNvbYIMKi5oYW9rYW4u
Y29tgg8qLnBhZS5iYWlkdS5jb22CESoudmQuYmRzdGF0aWMuY29tghEqLmNsb3Vk
LmJhaWR1LmNvbYISY2xpY2suaG0uYmFpZHUuY29tghBsb2cuaG0uYmFpZHUuY29t
ghBjbS5wb3MuYmFpZHUuY29tghB3bi5wb3MuYmFpZHUuY29tghR1cGRhdGUucGFu
LmJhaWR1LmNvbTAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwHwYDVR0j
BBgwFoAU+O9/8s14Z6jeb48kjYjxhwMCs+swHQYDVR0OBBYEFK3KAFTK2OWUto+D
2ieAKE5ZJDsYMIIBfwYKKwYBBAHWeQIEAgSCAW8EggFrAWkAdgCvGBoo1oyj4KmK
TJxnqwn4u7wiuq68sTijoZ3T+bYDDQAAAZCQAGzzAAAEAwBHMEUCIFwF5Jc+zyIF
Gnpxchz9fY1qzlqg/oVrs2nnuxcpBuuIAiEAu3scD6u51VOP/9aMSqR2yKHZLbHw
Fos9U7AzSdLIZa8AdgAS8U40vVNyTIQGGcOPP3oT+Oe1YoeInG0wBYTr5YYmOgAA
AZCQAG3iAAAEAwBHMEUCIBBYQ6NP7VUDgfktWRg5QxT23QAbTqYovtV2D9O8Qc0T
AiEA2P7+44EvQ5adwL1y56oyxv/m+Gujeia7wpo7+Xbhv6MAdwAN4fIwK9MNwUBi
EgnqVS78R3R8sdfpMO8OQh60fk6qNAAAAZCQAGy+AAAEAwBIMEYCIQDU7Hxtx4c9
p9Jd+cr+DCMtyRYSc0b8cktCcbMmtDE9ygIhAIpJd4yb7jtxnaEC8oLWDushbK1v
0BIuZu6YrQvsf1nQMA0GCSqGSIb3DQEBCwUAA4IBAQCh9DfewC012/+fHZpmSpCn
y+h3/+ClAZ8cJVO+LCmYz9r6bkyhcFquJ5qUpyoW8AYtU0oUFlqH6zLIyujW+7lq
wFxB6NsXKKdwBKmMbmnZr2Fca5f+TtwD/GDJgG/egr7fI1u8194j9KEl8cK8Fujm
+UsoWklEzd1It9xkLazJR/6SwbhSR4k610pvj8rQrS4wAewuYFDaDOfqsHtDIsx1
tZfIfoB/O1wGWZQJU2M9wC8uYq0jQ2Q0MQJXuyJz04MFiGrPAS1Uk8mWd8M+3p65
Xy4iAf8uWzs1M+fcwBE8BNBghkQgE+FSUsldm+5ZBCazU0joJswzldWisXMLTagI
-----END CERTIFICATE-----
X509TrustManager trustManager = new X509TrustManager() {

    @ Override
    public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {

        if (chain == null) {
            throw new IllegalArgumentException("checkServerTrusted: X509Certificate array is null");
        }
        //检测是否为空
        if (!(chain.length > 0)) {
            throw new IllegalArgumentException("checkServerTrusted: X509Certificate is empty");
        }
        //检测是否长度负数
        if (!(!TextUtils.isEmpty(authType) && authType.toUpperCase().contains("RSA"))) {
            throw new CertificateException("checkServerTrusted: AuthType is not RSA");
        }
        Log.d("xiaojianbang","authType: " + authType);
        X509Certificate cf = chain[0];
        //获取证书
        RSAPublicKey pubkey = (RSAPublicKey)cf.getPublicKey();
        String encoded = Base64.encodeToString(pubkey.getEncoded(),0);
		//得到的证书进行base64编码
        CertificateFactory finalcf = CertificateFactory.getInstance("X.509");
        X509Certificate PUB_KEY = (X509Certificate)finalcf.generateCertificate(new ByteArrayInputStream(certificate.getBytes()));
        String realPubKey = Base64.encodeToString(PUB_KEY.getPublicKey().getEncoded(),0);
		//这里是去获取的真实的服务器证书同时进行base64编码
        cf.checkValidity();
        Log.d("xiaojianbang", "IssuerDN: " + cf.getIssuerDN().toString());
        Log.d("xiaojianbang", "SubjectDN: " + cf.getSubjectDN().toString());
        Log.d("xiaojianbang", "证书版本: "+ cf.getVersion());
		
        final boolean expected = realPubKey.equalsIgnoreCase(encoded);
        if (!expected) {
            throw new CertificateException("checkServerTrusted: got error public key: " + encoded);
        }
        Log.d("xiaojianbang","证书公钥验证正确");
		//证书比对
    }

    @Override
    public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {

    }

    @ Override
    public X509Certificate[] getAcceptedIssuers() {
        return new X509Certificate[0];
    }
};

这里是对于我们证书检测的单独设置的位置,其中包含了证书链chain的检测以及证书的获取和公钥证书的比对(细节进行了批注)

同样的conn.setHostnameVerifier(DO_NOT_VERIFY);这个函数也设置了一个检测点HostnameVerifier类下面的verify函数也要返回true才行

HostnameVerifier DO_NOT_VERIFY = new HostnameVerifier() {
    @ Override
    public boolean verify(String hostname, SSLSession session) {
        Log.d("xiaojianbang", hostname);
        return true;
    }
};//注意在session里面同样可以获取证书进行比对

绕过

所以我们可以直接来看看之前过HttpURLConnection的抓包检测的代码了

	var HttpsURLConnection = Java.use("com.android.okhttp.internal.huc.HttpsURLConnectionImpl");
	HttpsURLConnection.setSSLSocketFactory.implementation = function(SSLSocketFactory) {
		quiet_send("HttpsURLConnection.setSSLSocketFactory invoked");
	};
	HttpsURLConnection.setHostnameVerifier.implementation = function(hostnameVerifier) {
		quiet_send("HttpsURLConnection.setHostnameVerifier invoked");
	};
		var RequestParams = Java.use('org.xutils.http.RequestParams');
		RequestParams.setSslSocketFactory.implementation = function(sslSocketFactory) {
			sslSocketFactory = EmptySSLFactory;
			return null;
		}
		RequestParams.setHostnameVerifier.implementation = function(hostnameVerifier) {
			hostnameVerifier = TrustHostnameVerifier.$new();
			return null;
		}
//直接去HOOK的setSSLSocketFactory和setHostnameVerifier 

至于这里其实是过不了检测的,因为这个走的是com.android.okhttp.internal.huc.HttpsURLConnectionImpl类下的setSslSocketFactory和setHostnameVerifier,所以过不了

okhttps提交POST和GET请求

GET请求提交的过程:

public class okHttp3Utils {

    public static OkHttpClient client = new OkHttpClient.Builder().build();//直接构造一个客户端请求对象

    public static void POST() {
        new Thread() {
            public void run() {
//                OkHttpClient client = MySSLSocketFactory.createClient();
                Request request = new Request.Builder()//创建请求对象
                        .url("https://www.baidu.com/")
                        .get()
                        .addHeader(
                                "User-Agent",
                                "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 UBrowser/6.2.4098.3 Safari/537.36"
                        )
                        .build();
                try {
                    Response response = client.newCall(request).execute();//这里是提交过程中的重点:cilent.newCall()
                    Log.d("chen_chen_chen", "response: " + response.body().string());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }.start();
    }
}

Post请求包发送:

                FormBody builder = new FormBody.Builder().add("请求键", "请求值").add("请求键1", "请求值1").build();
//                OkHttpClient client = MySSLSocketFactory.createClient();
                Request request = new Request.Builder()
                        .url("https://www.baidu.com/")
                        .post(builder)

也就是在这里多了这些东西

LoggingInterceptor拦截器

这个函数是加载在请求之前和响应之后的函数,和HOOK一样,都可以获取过程中的信息

class LoggingInterceptor implements Interceptor{
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        long time1 = System.nanoTime();
        Log.d("chen_chen_chen",String.format("Sending request %s on %s %n %s",request.url(),chain.connection(),request.headers()));
        //请求前
        Response response = chain.proceed(request);
        long time2 = System.nanoTime();
        Log.d("chen_chen_chen",String.format("Sending response %s on %s %n %s",response.request().url(),(time2-time1)/1e6d,response.headers()));
        return response;
        //响应后
    }
}

/*Sending request https://www.baidu.com/ on null 
                                                                                                     User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 UBrowser/6.2.4098.3 Safari/537.36
2024-10-31 16:52:40.388 18715-18888 chen_chen_chen          com.chen_chen_chen.myapplication     D  Sending response https://www.baidu.com/ on 29636.590765 
*/

loggingInterceptor拦截器

package com.chen_chen_chen.myapplication;
import android.util.Log;

import java.io.EOFException;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.concurrent.TimeUnit;

import okhttp3.Connection;
import okhttp3.Headers;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okhttp3.internal.http.HttpHeaders;
import okio.Buffer;
import okio.BufferedSource;
import okio.GzipSource;
import okhttp3.Interceptor;

public class okhttp3Logging implements Interceptor {
    private static final String TAG = "okhttpGET";

    private static final Charset UTF8 = Charset.forName("UTF-8");

    @Override public Response intercept(Chain chain) throws IOException {

        Request request = chain.request();

        RequestBody requestBody = request.body();
        boolean hasRequestBody = requestBody != null;

        Connection connection = chain.connection();
        String requestStartMessage = "--> "
                + request.method()
                + ' ' + request.url();
        Log.e(TAG, requestStartMessage);

        if (hasRequestBody) {
            // Request body headers are only present when installed as a network interceptor. Force
            // them to be included (when available) so there values are known.
            if (requestBody.contentType() != null) {
                Log.e(TAG, "Content-Type: " + requestBody.contentType());
            }
            if (requestBody.contentLength() != -1) {
                Log.e(TAG, "Content-Length: " + requestBody.contentLength());
            }
        }

        Headers headers = request.headers();
        for (int i = 0, count = headers.size(); i < count; i++) {
            String name = headers.name(i);
            // Skip headers from the request body as they are explicitly logged above.
            if (!"Content-Type".equalsIgnoreCase(name) && !"Content-Length".equalsIgnoreCase(name)) {
                Log.e(TAG, name + ": " + headers.value(i));
            }
        }

        if (!hasRequestBody) {
            Log.e(TAG, "--> END " + request.method());
        } else if (bodyHasUnknownEncoding(request.headers())) {
            Log.e(TAG, "--> END " + request.method() + " (encoded body omitted)");
        } else {
            Buffer buffer = new Buffer();
            requestBody.writeTo(buffer);

            Charset charset = UTF8;
            MediaType contentType = requestBody.contentType();
            if (contentType != null) {
                charset = contentType.charset(UTF8);
            }

            Log.e(TAG, "");
            if (isPlaintext(buffer)) {
                Log.e(TAG, buffer.readString(charset));
                Log.e(TAG, "--> END " + request.method()
                        + " (" + requestBody.contentLength() + "-byte body)");
            } else {
                Log.e(TAG, "--> END " + request.method() + " (binary "
                        + requestBody.contentLength() + "-byte body omitted)");
            }
        }


        long startNs = System.nanoTime();
        Response response;
        try {
            response = chain.proceed(request);
        } catch (Exception e) {
            Log.e(TAG, "<-- HTTP FAILED: " + e);
            throw e;
        }
        long tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs);

        ResponseBody responseBody = response.body();
        long contentLength = responseBody.contentLength();
        String bodySize = contentLength != -1 ? contentLength + "-byte" : "unknown-length";
        Log.e(TAG, "<-- "
                + response.code()
                + (response.message().isEmpty() ? "" : ' ' + response.message())
                + ' ' + response.request().url()
                + " (" + tookMs + "ms" + (", " + bodySize + " body:" + "") + ')');

        Headers myheaders = response.headers();
        for (int i = 0, count = myheaders.size(); i < count; i++) {
            Log.e(TAG, myheaders.name(i) + ": " + myheaders.value(i));
        }

        if (!HttpHeaders.hasBody(response)) {
            Log.e(TAG, "<-- END HTTP");
        } else if (bodyHasUnknownEncoding(response.headers())) {
            Log.e(TAG, "<-- END HTTP (encoded body omitted)");
        } else {
            BufferedSource source = responseBody.source();
            source.request(Long.MAX_VALUE); // Buffer the entire body.
            Buffer buffer = source.buffer();

            Long gzippedLength = null;
            if ("gzip".equalsIgnoreCase(myheaders.get("Content-Encoding"))) {
                gzippedLength = buffer.size();
                GzipSource gzippedResponseBody = null;
                try {
                    gzippedResponseBody = new GzipSource(buffer.clone());
                    buffer = new Buffer();
                    buffer.writeAll(gzippedResponseBody);
                } finally {
                    if (gzippedResponseBody != null) {
                        gzippedResponseBody.close();
                    }
                }
            }

            Charset charset = UTF8;
            MediaType contentType = responseBody.contentType();
            if (contentType != null) {
                charset = contentType.charset(UTF8);
            }

            if (!isPlaintext(buffer)) {
                Log.e(TAG, "");
                Log.e(TAG, "<-- END HTTP (binary " + buffer.size() + "-byte body omitted)");
                return response;
            }

            if (contentLength != 0) {
                Log.e(TAG, "");
                Log.e(TAG, buffer.clone().readString(charset));
            }

            if (gzippedLength != null) {
                Log.e(TAG, "<-- END HTTP (" + buffer.size() + "-byte, "
                        + gzippedLength + "-gzipped-byte body)");
            } else {
                Log.e(TAG, "<-- END HTTP (" + buffer.size() + "-byte body)");
            }
        }

        return response;
    }

    /**
     * Returns true if the body in question probably contains human readable text. Uses a small sample
     * of code points to detect unicode control characters commonly used in binary file signatures.
     */
    static boolean isPlaintext(Buffer buffer) {
        try {
            Buffer prefix = new Buffer();
            long byteCount = buffer.size() < 64 ? buffer.size() : 64;
            buffer.copyTo(prefix, 0, byteCount);
            for (int i = 0; i < 16; i++) {
                if (prefix.exhausted()) {
                    break;
                }
                int codePoint = prefix.readUtf8CodePoint();
                if (Character.isISOControl(codePoint) && !Character.isWhitespace(codePoint)) {
                    return false;
                }
            }
            return true;
        } catch (EOFException e) {
            return false; // Truncated UTF-8 sequence.
        }
    }

    private boolean bodyHasUnknownEncoding(Headers myheader s) {
        String contentEncoding = myheaders.get("Content-Encoding");
        return contentEncoding != null
                && !contentEncoding.equalsIgnoreCase("identity")
                && !contentEncoding.equalsIgnoreCase("gzip");
    }
}

OKhttp3

证书检测:

首先是设置证书的函数:

public static OkHttpClient client = new OkHttpClient.Builder()
        .sslSocketFactory(createSSLSocketFactory(new ByteArrayInputStream(certificate.getBytes())),trustManager)
        .hostnameVerifier(new TrustAllHostnameVerifier())
        .certificatePinner(CPinner)
        .build();

这几个OkHttpClient类下的方法都会使得证书检测到,同时这里的sslSocketFactory也就是在设置对应的证书的位置了,也是算法进行之后直接去比对证书的位置,

certificatePinner(CPinner)这里的方法其实是对于证书进行SHA1或者是SHA256的算法进行一个计算之后的证书校验,比对的是算法之后的SHA值的校验

hostnameVerifier(new TrustAllHostnameVerifier())这里通过也可以进行证书的校验,因为其获取的参数可以得到证书的值

绕过sslSocketFactory:

   .sslSocketFactory(createSSLSocketFactory(new ByteArrayInputStream(certificate.getBytes())),trustManager)

这里同时是去HOOK对应类下的sslContext对象的init()这里规定了对于init的TrustManager的位置

private static SSLSocketFactory newSslSocketFactory(X509TrustManager trustManager) {
  try {
    SSLContext sslContext = Platform.get().getSSLContext();
    sslContext.init(null, new TrustManager[] { trustManager }, null);
    return sslContext.getSocketFactory();
  } catch (GeneralSecurityException e) {
    throw new AssertionError("No System TLS", e); // The system has no TLS. Just give up.
  }
}
HOOK:
自定义TrustManager

		TrustManager = Java.registerClass({//创建自定义的 TrustManager
			name: 'org.wooyun.TrustManager',
			implements: [X509TrustManager],
			methods: {
				checkClientTrusted: function(chain, authType) {},//checkClientTrusted 和 checkServerTrusted 方法被重写以不执行任何检查。
				checkServerTrusted: function(chain, authType) {},
				getAcceptedIssuers: function() {
					// var certs = [X509Certificate.$new()];
					// return certs;
					return [];
				}
			}
		});


var SSLContext_init = SSLContext.init.overload(
		'[Ljavax.net.ssl.KeyManager;', '[Ljavax.net.ssl.TrustManager;', 'java.security.SecureRandom');

	// Override the init method, specifying our new TrustManager
	SSLContext_init.implementation = function(keyManager, trustManager, secureRandom) {

		quiet_send('Overriding SSLContext.init() with the custom TrustManager');

		SSLContext_init.call(this, null, TrustManagers, null);
	};

上面通过去HOOK对应的SSLContext初始化的位置,实现了对应的自写的TrustManager来接受所以的证书

绕过 .hostnameVerifier(new TrustAllHostnameVerifier())

这里的证书检测也很简单了,不过需要返回的是对应的类对象,这里我们采用了自写的类对象进行赋值

		var OkHttpClient$Builder = Java.use('okhttp3.OkHttpClient$Builder');
		quiet_send('OkHttpClient$Builder Found');
		console.log("hostnameVerifier", OkHttpClient$Builder.hostnameVerifier);
		OkHttpClient$Builder.hostnameVerifier.implementation = function () {
			quiet_send('OkHttpClient$Builder hostnameVerifier() called. Not throwing an exception.');
			return this;
		}
		var myHostnameVerifier = Java.registerClass({
			name: 'com.chenchenchen.MyHostnameVerifier',
			implements: [HostnameVerifier],
			methods: {
				verify: function (hostname, session) {
					return true;
				}
			}
		});
		var OkHttpClient = Java.use('okhttp3.OkHttpClient');
		OkHttpClient.hostnameVerifier.implementation = function () {
			quiet_send('OkHttpClient hostnameVerifier() called. Not throwing an exception.');
			return myHostnameVerifier.$new();
		}

绕过 .certificatePinner(CPinner)

这里的绕过也是一样的,在CertificatePinner类下的check方法去比对了得到的公钥和证书SHA值的对比,我们HOOK这里去直接绕过

		var CertificatePinner = Java.use('okhttp3.CertificatePinner');
		quiet_send('OkHTTP 3.x Found');
		CertificatePinner.check.overload('java.lang.String', 'java.util.List').implementation = function() {
			quiet_send('OkHTTP 3.x check() called. Not throwing an exception.');
		}

混淆之后的HOOK定位

在混淆之后我们要去定位这些证书校验的函数怎么去找?

首先是 .certificatePinner(CPinner):

这里是在对应check()方法内部的findMatchingPins(),会去比对的是假如的pins的是否匹配,所以我们可以去HOOK对应的系列函数来实现打印堆栈的情况,最后实现函数定位

这里的系统函数ArrayList的add()方法,去直接HOOK,打印堆栈

List<Pin> findMatchingPins(String hostname) {
  List<Pin> result = Collections.emptyList();
  for (Pin pin : pins) {
    if (pin.matches(hostname)) {
      if (result.isEmpty()) result = new ArrayList<>();
      result.add(pin);
    }
  }
  return result;
}

MessageDigest.digest check函数的SHA算法

在这里的SHA算法之后的证书比对之前,肯定会去调用对应的SHA算法的加密,所以

private ByteString digest(String algorithm) {
  try {
    return ByteString.of(MessageDigest.getInstance(algorithm).digest(data));
  } catch (NoSuchAlgorithmException e) {
    throw new AssertionError(e);
  }
}

也可以去HOOK这里的MessageDigest.digest

网络无法访问会抛出错误,直接Hook错误比如 javax.net.ssl.SSLHandshakeException.$init

这里会报错,那么直接去HOOK对应的提示错误的地方

OKhttps3算法源码分析

okhttps3的算法源码的分析在现在貌似都被全部的Native化了,进不了Java层的函数题内部,大概的内容是Interceptor的处理,同时是对于Socket进行了connect,这里的Socket包含了java.net.Socket 包下面的socket 以com.android.org.conscrypt.Java8FileDescriptorSocket下的 sslSocket两种Socket,而且我们则是要去查看的在数据传输过程中的sslSocket的加密数据。

最终可以定位到com.android.org.conscrypt.Java8FileDescriptorSocket的getOutputStream的位置,这里是对于请求的进入的位置

最终可以定位到的位置是/src/main/java/org/conscrypt/[NativeSsl.java] 下的 write函数

406    void write(FileDescriptor fd, byte[] buf, int offset, int len, int timeoutMillis)
407            throws IOException {
408        lock.readLock().lock();
409        try {
410            if (isClosed() || fd == null || !fd.valid()) {
411                throw new SocketException("Socket is closed");
412            }
413            NativeCrypto
414                    .SSL_write(ssl, this, fd, handshakeCallbacks, buf, offset, len, timeoutMillis);
415        } finally {
416            lock.readLock().unlock();
417        }
418    }

到了NativeCrypto类下的SSL_write函数 (这个函数被Native化了)

1088    static native void SSL_write(long ssl, NativeSsl ssl_holder, FileDescriptor fd,
1089            SSLHandshakeCallbacks shc, byte[] b, int off, int len, int writeTimeoutMillis)
1090            throws IOException;

以及可能在响应包里面的read,读取服务器的响应

391    int read(FileDescriptor fd, byte[] buf, int offset, int len, int timeoutMillis)
392            throws IOException {
393        lock.readLock().lock();
394        try {
395            if (isClosed() || fd == null || !fd.valid()) {
396                throw new SocketException("Socket is closed");
397            }
398            return NativeCrypto
399                    .SSL_read(ssl, this, fd, handshakeCallbacks, buf, offset, len, timeoutMillis);
400        } finally {
401            lock.readLock().unlock();
402        }
403    }

同时还要SSL_read函数同样Native化了

1082    static native int SSL_read(long ssl, NativeSsl ssl_holder, FileDescriptor fd, SSLHandshakeCallbacks shc,
1083            byte[] b, int off, int len, int readTimeoutMillis) throws IOException;

OKHttps3自吐算法Java层

Java.perform(function () {
var ByteString = Java.use("com.android.okhttp.okio.ByteString");
function toBase64(tag, data) {
    console.log(tag + " Base64: \n", ByteString.of(data).base64());
}
function toHex(tag, data) {
    console.log(tag + " Hex: \n", ByteString.of(data).hex());
}
function toUtf8(tag, data) {
    console.log(tag + " Utf8: \n", ByteString.of(data).utf8());
}

var NativeCrypto = Java.use("com.android.org.conscrypt.NativeCrypto");
NativeCrypto.SSL_write.implementation = function (ssl, nativeCrypto, fd, handshakeCallbacks, buf, offset, len, timeoutMillis) {
    console.log(offset, len);
    toUtf8("chen_chen SSL_write: ", buf);
    console.log("=======================================================");
    this.SSL_write(ssl, nativeCrypto, fd, handshakeCallbacks, buf, offset, len, timeoutMillis);
}

NativeCrypto.SSL_read.implementation = function (ssl, nativeCrypto, fd, handshakeCallbacks, buf, offset, len, timeoutMillis) {
    console.log(offset, len);
    toUtf8("chen_chen SSL_read", buf);
    console.log("=======================================================");
    return this.SSL_read(ssl, nativeCrypto, fd, handshakeCallbacks, buf, offset, len, timeoutMillis);
}

JNI层源码的分析:

Naitve化之后的函数名为对应的类名_函数比如SSL_write ——>在NaitveCrypto的类里——>名为 NaitveCrypto_SSL_write

由此来开始对应的源码分析过程:

/external/conscrypt/common/src/jni/main/cpp/conscrypt/native_crypto.cc

static void NativeCrypto_SSL_write
(JNIEnv* env, jclass, jlong ssl_address, CONSCRYPT_UNUSED jobject ssl_holder, jobject fdObject,
8286                                     jobject shc, jbyteArray b, jint offset, jint len,
8287                                     jint write_timeout_millis)


/external/conscrypt/common/src/jni/main/cpp/conscrypt/native_crypto.cc


static int sslWrite(JNIEnv* env, SSL* ssl, jobject fdObject, jobject shc, const char* buf, jint len,
8146                      SslError* sslError, int write_timeout_millis)


boringssl是谷歌从openssl改过来的,ssl_lib.cc会编译到libssl.so中

/external/boringssl/src/ssl/ssl_lib.cc

int SSL_write(SSL *ssl, const void *buf, int num)
int SSL_read(SSL *ssl, void *buf, int num)

以上两个函数就是r0capture的hook点


/external/boringssl/src/ssl/s3_pkt.cc
int ssl3_write_app_data(SSL *ssl, bool *out_needs_handshake, const uint8_t *in,
130                          int len)


/external/boringssl/src/ssl/s3_pkt.cc
static int do_ssl3_write(SSL *ssl, int type, const uint8_t *in, unsigned len)

在这之前,数据是明文
========================================================================================================
在这之后,数据是密文
/external/boringssl/src/ssl/s3_pkt.cc
static int ssl3_write_pending(SSL *ssl, int type, const uint8_t *in,
205                                unsigned int len)



/external/boringssl/src/ssl/ssl_buffer.cc
int ssl_write_buffer_flush(SSL *ssl) 
static int dtls_write_buffer_flush(SSL *ssl)



/external/boringssl/src/crypto/bio/bio.c
int BIO_write(BIO *bio, const void *in, int inl)
——————————————————————————————————————————————————————————————————————

libcrypto.so
/external/boringssl/src/crypto/bio/socket.c


static int sock_read(BIO *b, char *out, int outl) {
108    int ret = 0;
109  
110    if (out == NULL) {
111      return 0;
112    }
113  
114    bio_clear_socket_error();
115  #if defined(OPENSSL_WINDOWS)
116    ret = recv(b->num, out, outl, 0);
117  #else
118    ret = read(b->num, out, outl);
119  #endif
120    BIO_clear_retry_flags(b);
121    if (ret <= 0) {
122      if (bio_fd_should_retry(ret)) {
123        BIO_set_retry_read(b);
124      }
125    }
126    return ret;
127  }
128  
129  static int sock_write(BIO *b, const char *in, int inl) {
130    int ret;
131  
132    bio_clear_socket_error();
133  #if defined(OPENSSL_WINDOWS)
134    ret = send(b->num, in, inl, 0);
135  #else
136    ret = write(b->num, in, inl);
137  #endif
138    BIO_clear_retry_flags(b);
139    if (ret <= 0) {
140      if (bio_fd_should_retry(ret)) {
141        BIO_set_retry_write(b);
142      }
143    }
144    return ret;
145  }


最终交给libc.so中的 write函数

SSL自吐HOOK

通常情况下HOOK点:在加密之前可以HOOK

int SSL_write(SSL *ssl, const void *buf, int num)
int SSL_read(SSL *ssl, void *buf, int num)

这两个函数其实是在libssl.so的系统so库中,但是开发中其实可以把对应实现的函数copy下来,放到别的so中,这样就找不到了

这种情况下,我们只能去HOOK加密之后的函数,然后打印对应的堆栈信息,看走的是哪个函数

HOOK点:在加密之后可以HOOK

这时候就可以去更底层看libc.so中(打印堆栈)

ssize_t write(int fd, const void * buf, size_t count)ssize_t read(int fd, void * buf, size_t count)

自吐代码:

Java.perform(function () {

    var ByteString = Java.use("com.android.okhttp.okio.ByteString");
    function toBase64(tag, data) {
        console.log(tag + " Base64: \n", ByteString.of(data).base64());
    }
    function toHex(tag, data) {
        console.log(tag + " Hex: \n", ByteString.of(data).hex());
    }
    function toUtf8(tag, data) {
        console.log(tag + " Utf8: \n", ByteString.of(data).utf8());
    }
});

var SSL_write_addr = Module.findExportByName("libssl.so", "SSL_write");
var SSL_read_addr = Module.findExportByName("libssl.so", "SSL_read");
console.log(SSL_write_addr, SSL_read_addr);
Interceptor.attach(SSL_write_addr, {
    onEnter: function (args) {
        console.log("SSL_write_addr: ", Process.getCurrentThreadId() + '\n' +
            Thread.backtrace(this.context, Backtracer.FUZZY)
                .map(DebugSymbol.fromAddress).join('\n') + '\n');
        console.log("SSL_write arg[1]"+hexdump(args[1], {length: args[2].toInt32()}));
    }, onLeave: function (retval) {

    }
});
Interceptor.attach(SSL_read_addr, {
    onEnter: function (args) {
        this.args1 = args[1];
    }, onLeave: function (retval) {
        var nums = retval.toInt32();
        if (nums > 0) {
            console.log("SSL_read_addr: ", Process.getCurrentThreadId() + '\n' +
                Thread.backtrace(this.context, Backtracer.FUZZY)
                    .map(DebugSymbol.fromAddress).join('\n') + '\n');
            console.log("SSL_read arg[1]"+hexdump(this.args1, {length: nums}));
        }
    }
});
var write_addr = Module.findExportByName("libc.so", "write");
var read_addr = Module.findExportByName("libc.so", "read");
console.log(write_addr, read_addr);
Interceptor.attach(write_addr, {
    onEnter: function (args) {
        console.log("write_addr: ", Process.getCurrentThreadId() + '\n' +
            Thread.backtrace(this.context, Backtracer.FUZZY)
                .map(DebugSymbol.fromAddress).join('\n') + '\n');
        console.log("write arg[1]"+hexdump(args[1], {length: args[2].toInt32()}));
    }, onLeave: function (retval) {

    }
});
Interceptor.attach(read_addr, {
    onEnter: function (args) {
        this.args1 = args[1];
    }, onLeave: function (retval) {
        var nums = retval.toInt32();
        if (nums > 0) {
            console.log("read_addr: ", Process.getCurrentThreadId() + '\n' +
                Thread.backtrace(this.context, Backtracer.FUZZY)
                    .map(DebugSymbol.fromAddress).join('\n') + '\n');
            console.log("SSL_read arg[1]"+hexdump(this.args1, {length: nums}));
        }
    }
});

这里的int SSL_write(SSL *ssl, const void *buf, int num) 和 int SSL_read(SSL *ssl, void *buf, int num)的结果都是解密过程的

ssize_t write(int fd, const void * buf, size_t count) 和 ssize_t read(int fd, void * buf, size_t count)都是加密过程的

单向检测和双向检测的细节处理以及绕过

单向检测是客户端校验服务器的证书

在客户端申请了SSL/TLS连接之后,服务器会发送SSL/TLS证书给客户端,这里便是客户端去校验服务器发送的证书了,这里的校验可以通过HOOK证书派发,实现全部证书的通过。

双向检测是服务器校验客户端的证书

客户端发起连接:客户端请求与服务器建立 SSL/TLS 连接。服务器发送证书服务器首先发送其 SSL/TLS 证书给客户端。客户端验证服务器证书的有效性(如前面提到的单向验证步骤);客户端发送证书:如果服务器要求,客户端也需要发送其证书给服务器进行校验,这里就是对应的服务器校验客户端了。

双向检测绕过:

对于可能出现的双向检测,假如要去绕过服务器校验客户端,则需要去获取到对应的客户端的证书,这样的证书可能会以文件的形式存放在对应的apk的压缩文件里,也能是在内存加载,我们可以通过dump下对应的内存读取。

而在证书双向验证的时候 Keystore.load()是对应证书进行加载使用的( 通常有证书密码 )我们可以去HOOK这里的代码去实现证书的获取

public final void load(InputStream stream, char[] password)

传入的是InputStream对象(证书)和证书密码

不过也可能这里的证书为null,然后在之后去设置

keyStore.setCertificateEntry(certificateAlias, certificate);

HOOK代码:

Java.perform(function () {
var KeyStore = Java.use("java.security.KeyStore");
var str = Java.use("java.lang.String");
KeyStore.load.overload("java.io.InputStream", "[C").implementation = function (input, pwdStr) {
    if (input) {
        console.log("pwdStr: ", str.$new(pwdStr));
        var file = Java.use("java.io.File").$new("/data/data/com.xh.xinghe/xiaojianbang.p12");
        // File file = new File("/data/data/com.xh.xinghe/xiaojianbang.p12");
        var output = Java.use("java.io.FileOutputStream").$new(file);
        //FileOutputStream output = new FileOutputStream(file)
        var r, myArr = [];
        for (var i = 0; i < 1024; i++) {
            myArr[i] = 0;
        }
        var buffer = Java.array("byte", myArr);
        //数组转byte array
        while((r = input.read(buffer)) > 0) {
            output.write(buffer, 0, r);
        }
        //读证书写入文件
        console.log("save");
        output.close();
    }
    return this.load(input, pwdStr);
}});

得到的证书通过cp 到 /sdcard/下然后导入抓包工具 这样就可以实现服务器校验客户端了

posted @   fisherman-ovo  阅读(93)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
点击右上角即可分享
微信分享提示