自定义证书的 客户端校验 和 服务端校验. [客户端证书校验源码和证明] pinner + host + 证书 [okhttp的单向校验-代码混淆解读]

关于一些方法

1,  okhttp的一些特征, 找 Frida特征的时候, 被那么多的人拔掉底裤分析.那么分析 被混淆的 retrofio 和 okhttp3的时候,同样适用, 昨天这几天把混淆的 okhttp3的源码,特别证书校验的部分, 搞明白, 跨版本的逻辑小小改变是有的, 但是大体逻辑不会变化太多.  如果在2024年常用的库的分析 , okhttp3的跨大版本的更新. 知道变化的趋势.

2, 在看视频时候,一定要知道,特别原理分析时候, 调用栈, 是哪个包,哪个函数被hook, 重点照顾它,熟悉它,   因为被混淆的函数和没有被混淆的,逻辑是不会变得. 做笔记时候, 给出 hook的具体命令,和脚本路径. 可以溯源.

3, 多次观看,多次实践, >= 5次左右, 任何细节,绝不放过. 哪怕一个字一个字打出来!

4, 抓包的设计,    UDP, TCP, QUIC协议, websockt协议, 双向证书校验.

5, Frida 反调试, so层面反调试, 反hook,  修改So的逻辑, dump出修复, Native层的 模拟执行. 非常多的知识点.

6, 调用栈分成,  调用第三方库的某个具体函数, 是由1-屏幕事件, 2-调用,界面元素, 3-app内部路由, 4-底层功能模块这样的路径. [顶部是末端的函数调用的终点,具体功能的实践者]

如果遇到混淆, SSLSession 作为第二参数, 寻找,到hook,  =====>因为系统包, 他混淆不了.!!! 所以是这样. 搜索  SSLSession 作为第二参数出现在 ,

7, 校验完成了,没有问题了,才会发送.---> 请求.

你提到的三个步骤(证书校验、主机校验、pinner公钥校验)是客户端对服务器的验证过程,属于 单向认证

=============================================

在哪里执行 的这个三个校验-->

okhttp3.internal.connection.RealCoonnection. 类别中的  connectTLS方法.
1+++++> 证书  [checkerverTrusted做证书校验]
2+++++>HOST
3+++++>Pinner [CertificatePinnder . check这个对象]

 

类名和方法名字会变,   但是代码的执行过程是不会变的.

客户端校验的顺序分别是:

- 第1步:调用证书校验--服务端[证书]--> 合法性

- 第2步:主机校验--->是否域名匹配

- 第3步:pinner公钥校验,这个校验过程本质上是调用`CertificatePinner`类中的`check`方法--> 服务端证书公钥sha256-->是否本地一致.不被监听

=========================================================

-1,  证书校验:验证服务端证书的合法性(证书链、有效期、吊销状态)。

-2, 主机校验:验证服务端证书中的域名是否与客户端请求的主机名匹配。

-3,  Pinner 公钥校验:验证服务端证书的公钥 SHA-256 是否与本地预置的公钥哈希值一致。

 

服务端证书

1, 生成 key (RSA 私要)

openssl genrsa -out server/server-key.key 2048  // 无加密

2, 生成服务端证书请求文件,

openssl req -new -out server/server-req.csr -key server/server-key.key

3, 生成 服务端证书

// openssl 3.x
openssl x509 -req -in server/server-req.csr -out server/server-cert.cer -signkey server/server-key.key -days 3650

客户端证书

1, 生成客户端 私密钥

openssl genrsa -out client/client-key.key 2048  // 无加密

2, 生成客户端证书请求文件

openssl req -new -out client/client-req.csr -key client/client-key.key //

3, 生成客户端证书

// openssl 3.x
openssl x509 -req -in client/client-req.csr -out client/client-cert.cer -signkey client/client-key.key -days 3650

 

生成客户端带密码的p12证书(可集成在安卓中来实现服务端校验)

openssl pkcs12 -export -clcerts -in client/client-cert.cer -inkey client/client-key.key -out client/client.p12

 

Pinner混淆_找到connectTls

可以通过hook系统方法 + 输入调用栈,定位 `RealConnection类的connectTls方法`。

Java.perform(function () {
    var NativeSsl = Java.use('com.android.org.conscrypt.NativeSsl');
    NativeSsl.doHandshake.overload('java.io.FileDescriptor', 'int').implementation = function (a, b) {
        console.log("参数:", a, b);
        console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
        return this.doHandshake(a, b);
    };
});

// frida -D 10.10.10.200:5555 -f cn.ticktick.task -l '/home/calleng/p9/Mikrom2.0/JS_editor/OkHttp3__处理混淆证书的方法_先寻找到TLS.js' 

 

Pinner调用栈_被混淆

[MI 8::cn.ticktick.task ]-> 参数: java.io.FileDescriptor@ad68484 40000
java.lang.Throwable
        at com.android.org.conscrypt.NativeSsl.doHandshake(Native Method)
        at com.android.org.conscrypt.ConscryptFileDescriptorSocket.startHandshake(ConscryptFileDescriptorSocket.java:226)
        at uk.c.f(RealConnection.java:27)  // ====>  第三方包开始 被混淆 
        at uk.c.c(RealConnection.java:22)
        at uk.f.d(StreamAllocation.java:95)
        at uk.f.e(StreamAllocation.java:1)
        at uk.a.a(ConnectInterceptor.java:13)
        at vk.f.b(RealInterceptorChain.java:11)
        at tk.b.a(CacheInterceptor.java:105)
        at vk.f.b(RealInterceptorChain.java:11)
        at vk.a.a(BridgeInterceptor.java:37)
        at vk.f.b(RealInterceptorChain.java:11)
        at vk.i.a(RetryAndFollowUpInterceptor.java:12)
        at vk.f.b(RealInterceptorChain.java:11)
        at vk.f.a(RealInterceptorChain.java:1)
        at bl.a.a(HttpLoggingInterceptor.java:4)
        at vk.f.b(RealInterceptorChain.java:11)
        at s9.b.a(HttpResponseInterceptor.kt:8)
        at vk.f.b(RealInterceptorChain.java:11)
        at s9.a.a(HttpRequestInterceptor.kt:34)
        at vk.f.b(RealInterceptorChain.java:11)
        at vk.f.a(RealInterceptorChain.java:1)
        at rk.y.c(RealCall.java:23)
        at rk.y.b(RealCall.java:16)
        at tl.p.execute(OkHttpCall.java:18)
        at r9.a.execute(EasyCall.kt:1)
        at r9.a.c(EasyCall.kt:1)
        at com.ticktick.task.utils.DataTracker$Companion$uploadLogTask$1.doInBackground(DataTracker.kt:19)
        at com.ticktick.task.utils.DataTracker$Companion$uploadLogTask$1.doInBackground(DataTracker.kt:1)
        at lf.m.run(TickTickSingleTask.kt:2)
        at java.lang.Thread.run(Thread.java:919)

 

Pinner调用栈_无混淆[OK3_connectTls]

无论在混淆了怎样, 在证书校验环节, 第一个一定是 [RealConnection.connectTls] 这个类

 

// frida -D 10.10.10.200:5555 -f net.demo2.netd3_ca -l '/home/calleng/p9/Mikrom2.0/JS_editor/OkHttp3__处理混淆证书的方法_先寻找到TLS.js'
证书校验_调用栈          
    at net.demo2.netd3_ca.MainActivity$2.checkServerTrusted(MainActivity.java:89)
    at com.android.org.conscrypt.Platform.checkServerTrusted(Platform.java:259)
    at com.android.org.conscrypt.ConscryptEngine.verifyCertificateChain(ConscryptEngine.java:1638)
    at com.android.org.conscrypt.NativeCrypto.ENGINE_SSL_read_direct(Native Method)
    at com.android.org.conscrypt.NativeSsl.readDirectByteBuffer(NativeSsl.java:569)
    at com.android.org.conscrypt.ConscryptEngine.readPlaintextDataDirect(ConscryptEngine.java:1095)
    at com.android.org.conscrypt.ConscryptEngine.readPlaintextData(ConscryptEngine.java:1079)
    at com.android.org.conscrypt.ConscryptEngine.unwrap(ConscryptEngine.java:876)
    at com.android.org.conscrypt.ConscryptEngine.unwrap(ConscryptEngine.java:747)
    at com.android.org.conscrypt.ConscryptEngine.unwrap(ConscryptEngine.java:712)
    at com.android.org.conscrypt.ConscryptEngineSocket$SSLInputStream.processDataFromSocket(ConscryptEngineSocket.java:858)
    at com.android.org.conscrypt.ConscryptEngineSocket$SSLInputStream.-$$Nest$mprocessDataFromSocket(Unknown Source:0)
    at com.android.org.conscrypt.ConscryptEngineSocket.doHandshake(ConscryptEngineSocket.java:241)
    at com.android.org.conscrypt.ConscryptEngineSocket.startHandshake(ConscryptEngineSocket.java:220)
    // =================================> 系统包过了直接就是 第三方 okhttp的包===============> 
    at okhttp3.internal.connection.RealConnection.connectTls(RealConnection.kt:379)
    at okhttp3.internal.connection.RealConnection.establishProtocol(RealConnection.kt:337)
    at okhttp3.internal.connection.RealConnection.connect(RealConnection.kt:209)
    at okhttp3.internal.connection.ExchangeFinder.findConnection(ExchangeFinder.kt:226)
    at okhttp3.internal.connection.ExchangeFinder.findHealthyConnection(ExchangeFinder.kt:106)
    at okhttp3.internal.connection.ExchangeFinder.find(ExchangeFinder.kt:74)
    at okhttp3.internal.connection.RealCall.initExchange$okhttp(RealCall.kt:255)
    at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.kt:32)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
    at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.kt:95)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
    at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.kt:83)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
    at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.kt:76)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
    at okhttp3.internal.connection.RealCall.getResponseWithInterceptorChain$okhttp(RealCall.kt:201)
    at okhttp3.internal.connection.RealCall.execute(RealCall.kt:154)
    at net.demo2.netd3_ca.MainActivity$3.run(MainActivity.java:123)

PINNer源码__被混淆

package uk;

/* compiled from: RealConnection.java */
/* loaded from: classes3.dex */
public final class c extends g.c {

    public final void f(b bVar, int i10, rk.d dVar, n nVar) throws IOException {
        SSLSocket sSLSocket;
        x xVar = x.HTTP_1_1;
        rk.a aVar = this.f30516c.f28457a;
        if (aVar.f28363i == null) {
            List<x> list = aVar.f28359e;
            x xVar2 = x.H2_PRIOR_KNOWLEDGE;
        }
        nVar.getClass();
        rk.a aVar2 = this.f30516c.f28457a;
        SSLSocketFactory sSLSocketFactory = aVar2.f28363i;
        try {
            try {
                Socket socket = this.f30517d;  // =========> 1, 触发证书
                s sVar = aVar2.f28355a;
                sSLSocket = (SSLSocket) sSLSocketFactory.createSocket(socket, sVar.f28521d, sVar.f28522e, true);
                try {
                    i a10 = bVar.a(sSLSocket);
                    if (a10.f28463b) {
                        yk.f.f33077a.f(sSLSocket, aVar2.f28355a.f28521d, aVar2.f28359e);
                    }
                    sSLSocket.startHandshake();
                    SSLSession session = sSLSocket.getSession();
                    q a11 = q.a(session);
                    if (!aVar2.f28364j.verify(aVar2.f28355a.f28521d, session)) { // ==========> 2, 触发主机 host 校验.
                        List<Certificate> list2 = a11.f28513c;
                        if (!list2.isEmpty()) {
                            X509Certificate x509Certificate = (X509Certificate) list2.get(0);
                            throw new SSLPeerUnverifiedException("Hostname " + aVar2.f28355a.f28521d + " not verified:\n    certificate: " + rk.f.b(x509Certificate) + "\n    DN: " + x509Certificate.getSubjectDN().getName() + "\n    subjectAltNames: " + al.d.a(x509Certificate));
                        }
                        throw new SSLPeerUnverifiedException("Hostname " + aVar2.f28355a.f28521d + " not verified (no certificates)");
                    }
                    aVar2.f28365k.a(aVar2.f28355a.f28521d, a11.f28513c);  // ===============> 3, 触发 pinner 公钥校验 
                    String i11 = a10.f28463b ? yk.f.f33077a.i(sSLSocket) : null;
                    this.f30518e = sSLSocket;
                    this.f30522i = new cl.r(o.d(sSLSocket));
                    this.f30523j = new cl.q(o.b(this.f30518e));
                    this.f30519f = a11;
                    if (i11 != null) { 
                        xVar = x.a(i11);
                    }
                    this.f30520g = xVar;
                    yk.f.f33077a.a(sSLSocket);
                    if (this.f30520g == x.HTTP_2) {
                        j(i10);
                    }
                }
            }
        }
    }
}

 

 

Pinner源码__无混淆

客户端校验顺序是__证书-->HOST-->Piner:

Package RealConnection ;

@Throws(IOException::class)
private fun connectTls(connectionSpecSelector: ConnectionSpecSelector) {
    val address = route.address  // 获取路由地址信息
    val sslSocketFactory = address.sslSocketFactory  // 获取SSL Socket工厂
    var success = false  // 标记是否成功建立连接
    var sslSocket: SSLSocket? = null  // 初始化SSL Socket
    try {
        // 创建SSL Socket包装器
        sslSocket = sslSocketFactory!!.createSocket(
            rawSocket, address.url.host, address.url.port, true /* autoClose */) as SSLSocket

        // 配置Socket的加密套件、TLS版本和扩展
        val connectionSpec = connectionSpecSelector.configureSecureSocket(sslSocket)
        if (connectionSpec.supportsTlsExtensions) {
            Platform.get().configureTlsExtensions(sslSocket, address.url.host, address.protocols)
        }

         // ============================ 1, 证书校验===============================================
         // 强制进行TLS握手,可能会抛出异常
        sslSocket.startHandshake/*1, 证书校验*/()
        // 阻塞直到会话建立
        val sslSocketSession = sslSocket.session
        val unverifiedHandshake = sslSocketSession.handshake()

        // ============================ 2, 主机校验===============================================
        // 验证Socket的证书是否对目标主机有效
        if (!address.hostnameVerifier!!.verify/*2, 主机校验*/(address.url.host, sslSocketSession)) {
            val peerCertificates = unverifiedHandshake.peerCertificates
            if (peerCertificates.isNotEmpty()) {
                val cert = peerCertificates[0] as X509Certificate
                throw SSLPeerUnverifiedException("""
                    |Hostname ${address.url.host} not verified:
                    |    certificate: ${CertificatePinner.pin(cert)}
                    |    DN: ${cert.subjectDN.name}
                    |    subjectAltNames: ${OkHostnameVerifier.allSubjectAltNames(cert)}
                    """.trimMargin())
            } else {
                throw SSLPeerUnverifiedException(
                    "Hostname ${address.url.host} not verified (no certificates)")
            }
        }

        val certificatePinner = address.certificatePinner!!

        // 创建握手对象,包含TLS版本、加密套件和本地证书
        handshake = Handshake(unverifiedHandshake.tlsVersion, unverifiedHandshake.cipherSuite,
            unverifiedHandshake.localCertificates) {
            certificatePinner.certificateChainCleaner!!.clean(unverifiedHandshake.peerCertificates,
                address.url.host)
        }

        // ============================ 3, pinner公钥校验===============================================
        // 检查证书固定器是否满足所呈现的证书
        certificatePinner.check/*3, pinner公钥校验*/(address.url.host) {
            handshake!!.peerCertificates.map { it as X509Certificate }
        }

        // 成功!保存握手信息和ALPN协议
        val maybeProtocol = if (connectionSpec.supportsTlsExtensions) {
            Platform.get().getSelectedProtocol(sslSocket)
        } else {
            null
        }
        socket = sslSocket  // 保存SSL Socket
        source = sslSocket.source().buffer()  // 获取输入流并缓冲
        sink = sslSocket.sink().buffer()  // 获取输出流并缓冲
        protocol = if (maybeProtocol != null) Protocol.get(maybeProtocol) else Protocol.HTTP_1_1  // 设置协议
        success = true  // 标记连接成功
    } finally {
        if (sslSocket != null) {
            Platform.get().afterHandshake(sslSocket)  // 握手后处理
        }
        if (!success) {
            sslSocket?.closeQuietly()  // 如果连接失败,静默关闭Socket
        }
    }
}

HOST源码__无混淆

Package RealConnection

;@Throws(IOException::class)
private fun connectTls(connectionSpecSelector: ConnectionSpecSelector) {
    val address = route.address  // 获取路由地址信息   // 
// ====================> 003, address 上面有一个 route.address 
// ====================> 004, route 怎么来的,就是调用当前对象中,封装的一个字段,就好比,RealConnection是一个类,而这个类里面定义了一个 route字段, 而这个 route字段可能在内部类初始化,
// ====================> 005,  或者调用某个方法的时候, 有人给他赋值了,如果有人给他赋值,得到对象点,点 address 可以得到 address. 如果能够得到,那么调用 address.hostnameVeryify.那么就可以将这个对对象找到,找到之后,你输出这个对象,他到底在哪个方法里面,
// ====================> 006,   或者,在哪里定义的.或者通过JasonStringiFy,可以知道他的位置.知道他的位置,找他的 Veryify就非常好找, 
    val sslSocketFactory = address.sslSocketFactory  // 获取SSL Socket工厂
    var success = false  // 标记是否成功建立连接
    var sslSocket: SSLSocket? = null  // 初始化SSL Socket
    try {
        // 创建SSL Socket包装器
        sslSocket = sslSocketFactory!!.createSocket(
            rawSocket, address.url.host, address.url.port, true /* autoClose */) as SSLSocket

        // 配置Socket的加密套件、TLS版本和扩展
        val connectionSpec = connectionSpecSelector.configureSecureSocket(sslSocket)
        if (connectionSpec.supportsTlsExtensions) {
            Platform.get().configureTlsExtensions(sslSocket, address.url.host, address.protocols)
        }

         // ============================ 1, 证书校验===============================================
         // 强制进行TLS握手,可能会抛出异常
        sslSocket.startHandshake/*1, 证书校验*/()
        // 阻塞直到会话建立
        val sslSocketSession = sslSocket.session
        val unverifiedHandshake = sslSocketSession.handshake()

        // ============================ 2, 主机校验===============================================
        // 验证Socket的证书是否对目标主机有效
//=================> 001, 你创建的那个hostName Verify的那个对象.========!address.hostnameVerifier!!=通过这个对象, 你传入到里面.================> 
        if (!address.hostnameVerifier/*传入的对象*/!!.verify/*调用他的verify方法*/(address.url.host, sslSocketSession)) {  // 根据他的返回值, 如果有,  就再去做对应的校验.
// ==============> 002, 如果你知道, Address是什么,那么你可以得到,address.hostnameVerifier.就可以得到那个对象了,所以你可以找address, =====> 寻找address 是怎么来的,  address 上面有一个 route.address
            val peerCertificates = unverifiedHandshake.peerCertificates
            if (peerCertificates.isNotEmpty()) {
                val cert = peerCertificates[0] as X509Certificate
                throw SSLPeerUnverifiedException("""
                    |Hostname ${address.url.host} not verified:
                    |    certificate: ${CertificatePinner.pin(cert)}
                    |    DN: ${cert.subjectDN.name}
                    |    subjectAltNames: ${OkHostnameVerifier.allSubjectAltNames(cert)}
                    """.trimMargin())
            } else {
                throw SSLPeerUnverifiedException(
                    "Hostname ${address.url.host} not verified (no certificates)")
            }
        }

        val certificatePinner = address.certificatePinner!!

        // 创建握手对象,包含TLS版本、加密套件和本地证书
        handshake = Handshake(unverifiedHandshake.tlsVersion, unverifiedHandshake.cipherSuite,
            unverifiedHandshake.localCertificates) {
            certificatePinner.certificateChainCleaner!!.clean(unverifiedHandshake.peerCertificates,
                address.url.host)
        }

        // ============================ 3, pinner公钥校验===============================================
        // 检查证书固定器是否满足所呈现的证书
        certificatePinner.check/*3, pinner公钥校验*/(address.url.host) {
            handshake!!.peerCertificates.map { it as X509Certificate }
        }

        // 成功!保存握手信息和ALPN协议
        val maybeProtocol = if (connectionSpec.supportsTlsExtensions) {
            Platform.get().getSelectedProtocol(sslSocket)
        } else {
            null
        }
        socket = sslSocket  // 保存SSL Socket
        source = sslSocket.source().buffer()  // 获取输入流并缓冲
        sink = sslSocket.sink().buffer()  // 获取输出流并缓冲
        protocol = if (maybeProtocol != null) Protocol.get(maybeProtocol) else Protocol.HTTP_1_1  // 设置协议
        success = true  // 标记连接成功
    } finally {
        if (sslSocket != null) {
            Platform.get().afterHandshake(sslSocket)  // 握手后处理
        }
        if (!success) {
            sslSocket?.closeQuietly()  // 如果连接失败,静默关闭Socket
        }
    }
}

 

 

对于证书校验---> 这不是第三方包,没有机会进行混淆, 那么 就可以, 如果 ,  com.android.org.conscrypt.Platform.checkServerTrusted的这个方法,如果执行后报错, 代表方法publickey 校验不通过, 那么 直接hook他, 不让他执行, 就代表不报错, ====> 返回为空. 不久可以了吗?

====> 好像这个方法的定义, 你只能写在你的业务逻辑代码里面,  如果在业务代码里面, 可能在任意的包, 任意的类的里面, 是不是在main Activity 下面放着的.

======> 其他的 app 可能放在很多的地方.

=====>> 无论在哪里都会别系统的方法进行调用.  [系统的方法怎么调用的它, 或者让他不调用,就可以了.] 系统方法的源码,就是在这里.

=========> 证书验证系统包不执行即可绕过

证书校验源码分析

// https://github.com/google/conscrypt/blob/86ff4e3fd4b6b3bb76a7ec0e91290384401ccbf3/android/src/main/java/org/conscrypt/Platform.java#L396
// org.conscrypt.Platform.checkServerTrusted
    @SuppressLint("NewApi") // AbstractConscryptSocket defines getHandshakeSession()
    public static void org.conscrypt.Platform.checkServerTrusted(X509TrustManager tm/*TM这个参数,你开发中声明的对象*/, X509Certificate[] chain,
            String authType, AbstractConscryptSocket socket) throws CertificateException {
        if (!checkTrusted("checkServerTrusted", tm, chain, authType, Socket.class, socket)
                && !checkTrusted("checkServerTrusted", tm, chain, authType, String.class,
                           socket.getHandshakeSession().getPeerHost())) {
            tm.checkServerTrusted/*调用的就是咱们业务代码的那个方法校验的位置*/(chain, authType); // 调用过程中,如果报错那么,就会整体报错==> 就会校验失败.-->如果没有报错,那么就正常通过了.
        }
    }

 

客户端已知server的public_Key[保存在本地]校验服务器的Key[固定][无混淆][绕过脚本]

Java.perform(function () {
    var Platform = Java.use('com.android.org.conscrypt.Platform');
    Platform.checkServerTrusted.overload('javax.net.ssl.X509TrustManager', '[Ljava.security.cert.X509Certificate;', 'java.lang.String', 'com.android.org.conscrypt.AbstractConscryptSocket').implementation = function (x509tm, chain, authType, socket) {
        console.log('\n[+] checkServer  ',x509tm,JSON.stringify(x509tm) ); //==============> 通过这里面的 ,JSON.stringify(x509tm 就可以输出,到底是谁在调用我这里的方法.

        //return this.checkServerTrusted(x509tm, chain, authType, socket);
    };
});

// frida -U -f 包名 -l 6.hook_check.js --no-pause
// frida -UF -l 6.hook_check.js

// frida -D 10.10.10.200:5555  -F  -l '/home/calleng/p9/Mikrom2.0/JS_editor/OkHttp3_阻止证书验证_绕过SSL+TLS验证逻辑[固定].js'
// frida -D 10.10.10.200:5555  -f net.demo2.netd3_ca  -l '/home/calleng/p9/Mikrom2.0/JS_editor/OkHttp3_阻止证书验证_绕过SSL+TLS验证逻辑[固定].js'

// 验证本地的证书 --> 和服务器的证书  的  publickey. 的hash --base64 对比, 对比 , 是否一致-----> 一致则通过 (单向验证 证书......)

 

 

 

HOST校验的脚本=>无混淆

Java.perform(function () {

    function getFieldValue(obj, fieldName) {        // ====================> 010,  反射方法一  getFieldValue 
        var cls = obj.getClass();                  // ==================> 012, 先把当前的类找到, 再把当前的类中,定义的field,  
        var field = cls.getDeclaredField(fieldName);   // ===============> 013, 根据他的字段名,找到他的字段.
        field.setAccessible(true);   // ===========> 013 , 让他这个字段可以被访问 
        var name = field.getName();     // ========> 014, 再获取到这个字段的名字.
        var value = field.get(obj);      // ========> 015, obj 当前的对象, 当这个对象的字段值拿到,
        return value;
    }

    function getMethodValue(obj, methodName) {         // ====================> 011,  反射方法二  getMethodValue
        var res;
        var cls = obj.getClass();
        var methods = cls.getDeclaredMethods();

        methods.forEach(function (method) {
            var method_name = method.getName();
            console.log(method_name, method);
            if (method_name === methodName) {
                method.setAccessible(true);
                res = method;
                return;
            }
        })
        return res;
    }

    var RealConnection = Java.use('okhttp3.internal.connection.RealConnection'); // =============> 007, 对于正常没有混淆的就是先找到,connectTls,
    RealConnection.connectTls.implementation = function (connectionSpecSelector) {
        var route = getFieldValue(this, "route"); // this.route         // ===========> 008 ,  调用了 当前的 this.route, ========> 012, 如果你有一个对象this,我想取得这个对象的字段route,那么把this传递到这里, 相当于 this.route.把当前对象中的field拿到, 基于反射的方式去做的.
        var address = getFieldValue(route, 'address');                 // ============> 009,   又调用  route.address., ====== > ,016, 点address 就得到 address,  
        var hostnameVerifier = getFieldValue(address, 'hostnameVerifier'); //  ================> 017 , 类似 address.hostnameVerifier 得到.
        console.log('\n[+] hostnameVerifier', hostnameVerifier);  // =======> 018 , 直接获取他的所有的字段, 就把所有的都得到了. 
        /*
        try {
            var route = getFieldValue(this, "route");        //  -=============> 019,   如果你非常想要去得到,调用他里面的方法,, 通过反射去调用他里面的方法.根据他找到这里面的方法.找到方法,
            var address = getFieldValue(route, 'address');
            var func = getMethodValue(address, "hostnameVerifier");
            console.log('\n[+] addhostnameVerifierress', func.invoke(address, null));   // ==========> 020, 通过反射执行方法,需要,func.invoke(address, null) 再把当前的对象传递进来. 基于这种方式去运行. 
        } catch (e) {
            console.log(e);
        }
        */
        return this.connectTls(connectionSpecSelector);
    };
});

// frida -U -f 包名 -l 7.hook_verify.js
// frida -UF -l 7.hook_verify.js
// frida -U -f cn.ticktick.task -l 7.hook_verify.js

HOST校验的源码分析==>被混淆

package uk;

/* compiled from: RealConnection.java */
public final class c extends g.c {

public final void f(b bVar, int i10, rk.d dVar, n nVar) throws IOException {
        SSLSocket sSLSocket;
        x xVar = x.HTTP_1_1;
        rk.a aVar = this.f30516c.f28457a;
        nVar.getClass();
        rk.a aVar2 = this.f30516c.f28457a;
        SSLSocketFactory sSLSocketFactory = aVar2.f28363i;
        try {
            try {
                Socket socket = this.f30517d;
                s sVar = aVar2.f28355a;
                sSLSocket = (SSLSocket) sSLSocketFactory.createSocket(socket, sVar.f28521d, sVar.f28522e, true);
                try {
                    i a10 = bVar.a(sSLSocket);
                    if (a10.f28463b) {
                        yk.f.f33077a.f(sSLSocket, aVar2.f28355a.f28521d, aVar2.f28359e);
                    }
                    sSLSocket.startHandshake();  // ============> 001, 触发证书
                    SSLSession session = sSLSocket.getSession();
                    q a11 = q.a(session);
                    if (!aVar2.f28364j.verify(aVar2.f28355a.f28521d, session)) {  // ===============> 002, 触发主机校验....
                        List<Certificate> list2 = a11.f28513c;
                        if (!list2.isEmpty()) {
                            X509Certificate x509Certificate = (X509Certificate) list2.get(0);
                            throw new SSLPeerUnverifiedException("Hostname " + aVar2.f28355a.f28521d + " not verified:\n    certificate: " + rk.f.b(x509Certificate) + "\n    DN: " + x509Certificate.getSubjectDN().getName() + "\n    subjectAltNames: " + al.d.a(x509Certificate));
                        }
                        throw new SSLPeerUnverifiedException("Hostname " + aVar2.f28355a.f28521d + " not verified (no certificates)");
                    }
                    aVar2.f28365k.a(aVar2.f28355a.f28521d, a11.f28513c);  // ===================> 003, 触发 pinner 公钥校验..
                    String i11 = a10.f28463b ? yk.f.f33077a.i(sSLSocket) : null;
                    this.f30518e = sSLSocket;
                    this.f30522i = new cl.r(o.d(sSLSocket));
                    this.f30523j = new cl.q(o.b(this.f30518e));
                    this.f30519f = a11;
                    this.f30520g = xVar;
                    yk.f.f33077a.a(sSLSocket);
                } 
            } 
        }
    }
}

HOST校验的脚本=>被混淆

Java.perform(function () {

    function getFieldValue(obj, fieldName) {
        var cls = obj.getClass();
        var field = cls.getDeclaredField(fieldName);
        field.setAccessible(true);
        var name = field.getName();
        var value = field.get(obj);
        return value;
    }

    function getMethodValue(obj, methodName) {
        var res;
        var cls = obj.getClass();
        var methods = cls.getDeclaredMethods();

        methods.forEach(function (method) {
            var method_name = method.getName();
            console.log(method_name, method);
            if (method_name === methodName) {
                method.setAccessible(true);
                res = method;
                return;
            }
        })
        return res;
    }

    var RealConnection = Java.use('uk.c');
    RealConnection.f.implementation = function (a, b, c, d) {
        try {
            console.log("===============");
            var route = getFieldValue(this, "c");  // ===============>021 , this点c 得到 route.
            console.log('route=', route);
            var address = getFieldValue(route, 'a'); // =================> 022, this点a 得到
            console.log('address=', address);
            var hostnameVerifier = getFieldValue(address, 'hostnameVerifier');  // =====================> 023, address 点 hostnameVerifier获取到.
            console.log('hostnameVerifier=', hostnameVerifier);
            console.log('\n[+] hostnameVerifier', hostnameVerifier);   // ========================> 024, 直接输出....找到之后, 一个个对比去看.....
        } catch (e) {
            console.log(e);
        }
        return this.f(a, b, c, d);
    };
});

// frida -U -f 包名 -l 7.hook_verify.js --no-pause
// frida -UF -l 7.hook_verify.js

// frida -U -f cn.ticktick.task -l 7.hook_verify.js --no-pause
// frida -D 10.10.10.200:5555  -f cn.ticktick.task  -l '/home/calleng/p9/Mikrom2.0/JS_editor/OkHttp3_RealConnection_connectTls_HOST校验_混淆.js'

 

HOST 校验 调用栈

主机校验是在:okhttp3.internal.connection.RealConnection类中的connectTls方法中触发的。

host校验__调用栈 java.lang.Throwable
    at net.demo2.netd8_bks.MainActivity$3.verify(MainActivity.java:84)
    at okhttp3.internal.connection.RealConnection.connectTls(RealConnection.kt:385)
    at okhttp3.internal.connection.RealConnection.establishProtocol(RealConnection.kt:337)
    at okhttp3.internal.connection.RealConnection.connect(RealConnection.kt:209)
    at okhttp3.internal.connection.ExchangeFinder.findConnection(ExchangeFinder.kt:226)
    at okhttp3.internal.connection.ExchangeFinder.findHealthyConnection(ExchangeFinder.kt:106)
    at okhttp3.internal.connection.ExchangeFinder.find(ExchangeFinder.kt:74)
    at okhttp3.internal.connection.RealCall.initExchange$okhttp(RealCall.kt:255)
    at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.kt:32)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
    at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.kt:95)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
    at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.kt:83)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
    at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.kt:76)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
    at okhttp3.internal.connection.RealCall.getResponseWithInterceptorChain$okhttp(RealCall.kt:201)
    at okhttp3.internal.connection.RealCall.execute(RealCall.kt:154)
    at net.demo2.netd8_bks.MainActivity$4.run(MainActivity.java:110)

=================

最后的类名 和方法名会变, 但是代码的执行过程是不会变的.

需要和被混淆的证书进行对比, 一对比,就知道哪个混淆的类到底是什么了.

找到他的调用栈, 他的上一级,就是   at okhttp3.internal.connection.RealConnection.connectTls(RealConnection.kt).

这个可以当成永恒不变,直接固定使用的脚本,

需要 RealConnection.connectTls 的固定的脚本

Java.perform(function () {
    var NativeSsl = Java.use('com.android.org.conscrypt.NativeSsl');
    NativeSsl.doHandshake.overload('java.io.FileDescriptor', 'int').implementation = function (a, b) {
        console.log("参数:", a, b);
        console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
        return this.doHandshake(a, b);
    };
});

// frida -UF -l 1.hook_check.js

 

 

 

 

通过反射执行方法,需要, func.invoke 去执行. 再把当前的对象传递进来. 基于这种方式去运行.  唯平会,还是 其他, so调用时候,使用的是反射的方法.

 

===> 如果再次遇到混淆该怎么办? 首先要找到  connectTLS 方法. 由这里展开,一个一个去看.

然后再去寻找 pinner校验.

通过两个系统的包的hook 找到那里的三个位置.!!!  pinner  + 证书 +  HOST [这么三个位置]

 

 

最后 --- > 一个拓展,确实让我开了眼界.

一个安卓运用的封装的过程. // 需要详细的解答.

day37--扩展-Pinnder校验---> sha256加密值是怎么来的.java

如果要明白 sha256的值到底是多少? 我想在源码中找到.---->定位,我看一下它!

核心一句话---> 探求他的原理

CertificatePinner pinner = new CertificatePinner.Build().add(CA_DOMAIN/*这是域名*/, CA_PUBLIC_KEY/*这是sha256的值*/).build();

 

public final class CertificatePinner {
  public static final CertificatePinner DEFAULT = new Builder().build();

  private final Set<Pin> pins;
  private final @Nullable CertificateChainCleaner certificateChainCleaner;

  CertificatePinner(Set<Pin> pins, @Nullable CertificateChainCleaner certificateChainCleaner) {
    this.pins = pins;
    this.certificateChainCleaner = certificateChainCleaner;
  }

  @Override public boolean equals(@Nullable Object other) {
    if (other == this) return true;
    return other instanceof CertificatePinner
        && (Objects.equals(certificateChainCleaner,
        ((CertificatePinner) other).certificateChainCleaner)
        && pins.equals(((CertificatePinner) other).pins));
  }

  @Override public int hashCode() {   //都属于 CertificatePinner()  这个类中的方法.
    int result = Objects.hashCode(certificateChainCleaner);
    result = 31 * result + pins.hashCode();
    return result;
  }

   public static final class Builder {  // 这里面是类嵌套类[builder中嵌套一个builder() ].....
    private final List<Pin> pins = new ArrayList<>(); // 这里理解为 一个 [] 列表, 列表中是空的. [ Pin对象(域名, sha256)] //相当于封装了的 你的域名 和 sha256 .

    public Builder add(String pattern, String... pins/*把域名, sha256的值 放到这里.*/) {  // 如果这里加入..... 那么这里会去循环这里的列表, ===> 这里点点点,就像python加了 一个 星号 *args, 一个道理 .[会转换为一个元组, 放入我们sha256的值]
      for (String pin : pins) {
        this.pins.add(new Pin(pattern, pin)); // 将你的 域名和 sha256的值 当作, 传递给 new pin() 这个对象, 相当于打包,  相当于放到上面的列表里面去了,
      }                 // 因为上面会执行 new Pin() 这是一个参数(pattern),这是一个参数(pin),  ----->  对应的都是 builder 你手工添加的参数, 你可以找到pin这个类, 去hook他的构造方法, [那么域名是什么,sha256的值是什么都可以拿到!!]

      return this;
    }

    public CertificatePinner build() {
      return new CertificatePinner(new LinkedHashSet<>(pins), null);
    } // Builder()了之后, 又返回了 一个  CertificatePinner() 对象. 实例化了一个对象,LinkedHashSet(可以理解为一个集合) <===相当于所有的pins对象序列就放进来了.有实例化一个对象返回回去.
  }

    // 也就是说 , 这里不仅有一个  Builder(), 还嵌套了一个  Pin的这个类,  原本你进行实例化的时候,你调用了他里面的, 什么的构造方法.  和类同名的构造方法.  域名和sha256的值.
    class Pin(pattern: String, pin: String) {
    /** A hostname like `example.com` or a pattern like `*.example.com` (canonical form). */
    val pattern: String

    /** Either `sha1` or `sha256`. */
    val hashAlgorithm: String

    /** The hash of the pinned certificate using [hashAlgorithm]. */
    val hash: ByteString

    init {
      require((pattern.startsWith("*.") && pattern.indexOf("*", 1) == -1) ||
          (pattern.startsWith("**.") && pattern.indexOf("*", 2) == -1) ||
          pattern.indexOf("*") == -1) {
        "Unexpected pattern: $pattern"
      }

      this.pattern =
        pattern.toCanonicalHost() ?: throw IllegalArgumentException("Invalid pattern: $pattern")

      when {
        pin.startsWith("sha1/") -> {
          this.hashAlgorithm = "sha1"
          this.hash = pin.substring("sha1/".length).decodeBase64() ?: throw IllegalArgumentException("Invalid pin hash: $pin")
        }
        pin.startsWith("sha256/") -> {
          this.hashAlgorithm = "sha256"
          this.hash = pin.substring("sha256/".length).decodeBase64() ?: throw IllegalArgumentException("Invalid pin hash: $pin")
        }
        else -> throw IllegalArgumentException("pins must start with 'sha256/' or 'sha1/': $pin")
      }
    }

    fun matchesHostname(hostname: String): Boolean {
      return when {
        pattern.startsWith("**.") -> {
          // With ** empty prefixes match so exclude the dot from regionMatches().
          val suffixLength = pattern.length - 3
          val prefixLength = hostname.length - suffixLength
          hostname.regionMatches(hostname.length - suffixLength, pattern, 3, suffixLength) &&
              (prefixLength == 0 || hostname[prefixLength - 1] == '.')
        }
        pattern.startsWith("*.") -> {
          // With * there must be a prefix so include the dot in regionMatches().
          val suffixLength = pattern.length - 1
          val prefixLength = hostname.length - suffixLength
          hostname.regionMatches(hostname.length - suffixLength, pattern, 1, suffixLength) &&
              hostname.lastIndexOf('.', prefixLength - 1) == -1
        }
        else -> hostname == pattern
      }
    }

    fun matchesCertificate(certificate: X509Certificate): Boolean {
        return when (hashAlgorithm) {
          "sha256" -> hash == certificate.sha256Hash()
          "sha1" -> hash == certificate.sha1Hash()
          else -> false
        }
    }

    override fun toString(): String = "$hashAlgorithm/${hash.base64()}"

    override fun equals(other: Any?): Boolean {
      if (this === other) return true
      if (other !is Pin) return false

      if (pattern != other.pattern) return false
      if (hashAlgorithm != other.hashAlgorithm) return false
      if (hash != other.hash) return false

      return true
    }

    override fun hashCode(): Int {
      var result = pattern.hashCode()
      result = 31 * result + hashAlgorithm.hashCode()
      result = 31 * result + hash.hashCode()
      return result
    }
  }
  
}

CertificatePinner pinner = new CertificatePinner.Build().add(CA_DOMAIN/*这是域名*/, CA_PUBLIC_KEY/*这是sha256的值*/).build();  // 相当于这里把对象仍给了 Builder对象, .


// 那么, 如果我们知道 pin() , 我们可以去找, CertificatePinner(). 下面有一个 Pin(pattern: String, pin: String),  构造方法, 我只要能够hook,  就能把他所有传递进来的,  可以知道所有sha256的值,就可以知道他是什么了,
// 由于他是一个第三方的包,  如果他被混淆了, 你要知道, CertificatePinner(). 和 Pin() 被混淆后叫做什么,  再去处理一些构造方法.  如果他们被混淆了,我们就使用 ,  connectTLS 去寻找.可以知道证书类是什么 ? 
 
 

 

hook混淆后的脚本

hook 这个 okhttp3.certificatePinner.Pin.init().通过hook这个构造方法, 取他的第二个参数.

/*Java.perform(function (){
    var fa = Java.use('rk.f$a');  // 这个定位类似  rk= okhttp3 , f= CertificatePinner , $a = $ pin
    fa.$init.implementation = function(a,b) { // $init, 就是 pin的构造的初始方法. [所以全路径就是] rk.f.pin.init() = okhttp3.certificatePinner.Pin.init().通过hook这个构造方法,就能得到所有!!
        console.log('[+] pin ',a,b);
        console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
        return this.$init(a,b);
    };
})*/
// 以上是 hook 混淆后的 okhttp3的包.....
// 下面是 正常的 hook 的包

Java.perform(function (){
    var fa = Java.use('okhttp3.CertificatePinner$Pin');  // 这个定位类似  rk= okhttp3 , f= CertificatePinner , $a = $ pin
    fa.$init.implementation = function(a,b) { // $init, 就是 pin的构造的初始方法. [所以全路径就是] rk.f.pin.init() = okhttp3.certificatePinner.Pin.init().通过hook这个构造方法,就能得到所有!!
        console.log('[+] pin ',a,b);
        console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
        return this.$init(a,b);
    };
})





// frida -D 10.10.10.200:5555  -f com.example.netdemo1_calleng -l '/home/calleng/p9/Mikrom2.0/JS_editor/OkHttp3_CertificatePinner_Pin()_INIT().js'

// frida -U -f cn.ticktick.task -l 5.hook_pin.js --no-pause

// 这里就处理了两个调用栈,
//  frida -D 10.10.10.200:5555  -f cn.ticktick.task -l '/home/calleng/p9/Mikrom2.0/JS_editor/OkHttp3_CertificatePinner_Pin()_INIT().js'

// [MI 8::cn.ticktick.task ]-> [+] pin  *.dida365.com sha256/dkRZkKeSEnco4v9fsHuuTBvujK1RJ2l1dtOUOUs0Pu0=    // 这前面是他的域名, 后面是他的sha256的值.
//
// java.lang.Throwable
//         at rk.f$a.<init>(Native Method)           // 这是原始调用的位置
//         at q9.b$b.invoke(ApiFactoryBase.kt:25)   // 通过调用栈,就找到了这里,  证书的位置
//         at si.i.getValue(LazyJVM.kt:5)
//         at q9.b.b(ApiFactoryBase.kt:1)
//         at eb.d.b(SyncInitManager.kt:2)
//         at eb.d.a(SyncInitManager.kt:3)
//         at com.ticktick.task.TickTickApplicationBase.onCreate(TickTickApplicationBase.java:48)
//         at cn.ticktick.task.TickTickApplication.onCreate(TickTickApplication.java:1)
//         at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1190)
//         at android.app.ActivityThread.handleBindApplication(ActivityThread.java:6582)
//         at android.app.ActivityThread.handleBindApplication(Native Method)
//         at android.app.ActivityThread.access$1400(ActivityThread.java:224)
//         at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1887)
//         at android.os.Handler.dispatchMessage(Handler.java:107)
//         at android.os.Looper.loop(Looper.java:224)
//         at android.app.ActivityThread.main(ActivityThread.java:7562)
//         at java.lang.reflect.Method.invoke(Native Method)
//         at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:539)
//         at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:950)
//
// [+] pin  *.ticktick.com sha256/I7TYVHP5UJmUvGnTiAQOuUSuTgRuE5fLkn9UwFd1mlc=
//
// java.lang.Throwable
//         at rk.f$a.<init>(Native Method)
//         at q9.b$b.invoke(ApiFactoryBase.kt:25)
//         at si.i.getValue(LazyJVM.kt:5)
//         at q9.b.b(ApiFactoryBase.kt:1)
//         at eb.d.b(SyncInitManager.kt:2)
//         at eb.d.a(SyncInitManager.kt:3)
//         at com.ticktick.task.TickTickApplicationBase.onCreate(TickTickApplicationBase.java:48)
//         at cn.ticktick.task.TickTickApplication.onCreate(TickTickApplication.java:1)
//         at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1190)
//         at android.app.ActivityThread.handleBindApplication(ActivityThread.java:6582)
//         at android.app.ActivityThread.handleBindApplication(Native Method)
//         at android.app.ActivityThread.access$1400(ActivityThread.java:224)
//         at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1887)
//         at android.os.Handler.dispatchMessage(Handler.java:107)
//         at android.os.Looper.loop(Looper.java:224)
//         at android.app.ActivityThread.main(ActivityThread.java:7562)
//         at java.lang.reflect.Method.invoke(Native Method)
//         at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:539)
//         at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:950)
//
// [MI 8::cn.ticktick.task ]->

// 将 dida365 二进制的格式转化为 pem 格式===> 
//  openssl x509 -inform der -in '/home/calleng/p9/Mikrom2.0/season5/day37 抓包-代码混淆/相关资源/apks/滴答清单v6.3.3.0/assets/dida365'  -out '/home/calleng/p9/Mikrom2.0/season5/day37 抓包-代码混淆/相关资源/apks/滴答清单v6.3.3.0/assets/dida365_decrypted.pem'

// cat '/home/calleng/p9/Mikrom2.0/season5/day37 抓包-代码混淆/相关资源/apks/滴答清单v6.3.3.0/assets/dida365_decrypted.pem'
// -----BEGIN CERTIFICATE-----
// MIIHATCCBemgAwIBAgIQDsJpKRnU1DKYuWWXMuSpkzANBgkqhkiG9w0BAQsFADBe
// MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
// d3cuZGlnaWNlcnQuY29tMR0wGwYDVQQDExRHZW9UcnVzdCBSU0EgQ0EgMjAxODAe
// Fw0xODA1MTkwMDAwMDBaFw0yMDA4MjEwMDAwMDBaMHAxCzAJBgNVBAYTAkNOMREw
// DwYDVQQHEwhIYW5nemhvdTE2MDQGA1UEChMtSGFuZ3pob3UgU3VpYmlqaSBOZXR3
// b3JrIFRlY2hub2xvZ3kgQ28uLCBMdGQuMRYwFAYDVQQDDA0qLmRpZGEzNjUuY29t
// MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvVVphVmpOIRTERuNKzgS
// 1Af8U0vbjsCQPaY9jjgbBhruirezBLCvnm/tLZVcDUATUwaXwEVWfPpcgHUEv1MA
// r6Q0yFEEFzi6EBLzxS9RUgiFUEiHHFMMbGbJspAN/pBbtxCJfCzgTGauxXcExKa9
// OXP3WeTn/3qs/CxvWpLGWEKsJWsa5MxWkTMHpo5/U5A+2o0Jsidv0M3Bxb2cBlSR
// msfse4sjq+gOpiozTs28HZ2L+gl2S2/X2N8s7K/hPfphVid/K6m2T0NU45zJXhNu
// OKJFTIROz6Tr1VzIF+By1xPrWrQUwAHglnMNk07s562xKDFoHwvarSTqHU1NbGkc
// 8QIDAQABo4IDpzCCA6MwHwYDVR0jBBgwFoAUkFj/sJx1qFFUd7Ht8qNDFjiebMUw
// HQYDVR0OBBYEFHzrXAS6ZV5BQbz9G/Ltv07Alo9uMCUGA1UdEQQeMByCDSouZGlk
// YTM2NS5jb22CC2RpZGEzNjUuY29tMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAU
// BggrBgEFBQcDAQYIKwYBBQUHAwIwPgYDVR0fBDcwNTAzoDGgL4YtaHR0cDovL2Nk
// cC5nZW90cnVzdC5jb20vR2VvVHJ1c3RSU0FDQTIwMTguY3JsMEwGA1UdIARFMEMw
// NwYJYIZIAYb9bAEBMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8vd3d3LmRpZ2ljZXJ0
// LmNvbS9DUFMwCAYGZ4EMAQICMHUGCCsGAQUFBwEBBGkwZzAmBggrBgEFBQcwAYYa
// aHR0cDovL3N0YXR1cy5nZW90cnVzdC5jb20wPQYIKwYBBQUHMAKGMWh0dHA6Ly9j
// YWNlcnRzLmdlb3RydXN0LmNvbS9HZW9UcnVzdFJTQUNBMjAxOC5jcnQwCQYDVR0T
// BAIwADCCAfkGCisGAQQB1nkCBAIEggHpBIIB5QHjAHYApLkJkLQYWBSHuxOizGdw
// Cjw1mAT5G9+443fNDsgN3BAAAAFjdvkAXQAABAMARzBFAiEAw8aYX3QiQZJinWvb
// hJXkvzeJU9UOdn9PBXaJ0nFN6fcCIGmJQitD/tXh9+dSU7YFm/jjbP1dV6CHRkcc
// VRdm9sDYAHcAb1N2rDHwMRnYmQCkURX/dxUcEdkCwQApBo2yCJo32RMAAAFjdvkC
// IQAABAMASDBGAiEAshIfpa3kfKyA1ju0dmIBOXn2BcXnV0iALZlsNtNvGx0CIQDg
// nv9fDo9Pkf3JcC95zPcyKtHaFZQqk4rVIWvbJDVBSwB3ALvZ37wfinG1k5Qjl6qS
// e0c4V5UKq1LoGpCWZDaOHtGFAAABY3b5ASEAAAQDAEgwRgIhAM+s9b+x06jbflp/
// BRVwcO7k20iUjYe5GdedIT0zqIFXAiEAon4rZojnrUugrCGMXQaZwpPVyCspRJH/
// IaEE5k4nOVkAdwBVgdTCFpA2AUrqC5tXPFPwwOQ4eHAlCBcvo6odBxPTDAAAAWN2
// +QQpAAAEAwBIMEYCIQCXQnD10xgYIOnRMeP4FRVOKyiA1fmVR/44h5rqLI3aJgIh
// AM2PGZ8+9j0mb7f8AcFPepv3yGEcqJ6PZmKmvgh1ib0dMA0GCSqGSIb3DQEBCwUA
// A4IBAQAqk4eZmfhRozptuO59VxCcsWSUpmGD/jZPQp+IDCs3HHZ8mYtP6PEXhmO8
// m7fW6t7rfdEqtpcwRBVf/eErIBUyVPuSRidtQJKpQgkeevUACf+wTv5zxjcORbvx
// eXXZ+cJ9rvKqTT6utZpIKygi1BQ2SsYSaIynQAg3qPVUiO7UgUbGDafqg9lqXsGC
// RvsVg4WNGpyt+gBMmOG58MTRza1uN1HXqNX/K2wKlyRYr9RI2YcGiBeOjiSErgDA
// Bi1atK5nSF0XmBg9Q3R4/CY+y8sBoo6pmSowTyI/jA29AO5qS94piYPhtN4qMDjg
// B+X3ZxnAnSJ/myEgKKaElS5ZWBsl
// -----END CERTIFICATE-----

// openssl x509 -in '/home/calleng/p9/Mikrom2.0/season5/day37 抓包-代码混淆/相关资源/apks/滴答清单v6.3.3.0/assets/dida365_decrypted.pem'  -pubkey -noout  | openssl rsa -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
// writing RSA key
// dkRZkKeSEnco4v9fsHuuTBvujK1RJ2l1dtOUOUs0Pu0=

// 刚好和上面处理的保持一致.

 

 

最后一次hook调用栈的位置的分析,加载证书的生成的位置

通过上面的调用栈,找到了这里.

// java.lang.Throwable

// at rk.f$a.<init>(Native Method) // 这是原始调用的位置

// at q9.b$b.invoke(ApiFactoryBase.kt:25) // 通过调用栈,就找到了这里, 证书的位置

/* compiled from: ApiFactoryBase.kt */
    /* renamed from: q9.b$b, reason: collision with other inner class name */
    /* loaded from: classes2.dex */
    public static final class C0321b extends i implements fj.a<w> {
        public C0321b() {
            super(0);
        }

        @Override // fj.a
        public w invoke() {
            SSLContext.getInstance("TLS").init(null, null, null);
            w.b bVar = new w.b();
            b.this.getClass();
            TimeUnit timeUnit = TimeUnit.SECONDS;
            bVar.f28603x = sk.c.d(SpeechConstant.NET_TIMEOUT, 15L, timeUnit);
            b.this.getClass();
            bVar.f28604y = sk.c.d(SpeechConstant.NET_TIMEOUT, 40L, timeUnit);
            b.this.getClass();
            bVar.f28605z = sk.c.d(SpeechConstant.NET_TIMEOUT, 40L, timeUnit);
            b bVar2 = b.this;
            bVar2.getClass();
            CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
            ArrayList arrayList = new ArrayList();
            Iterator<Map.Entry<String, String>> it = bVar2.f27230b.entrySet().iterator(); // 03, 这里就是一个不断的循环.......
            while (true) {  // 02, 这里的就开始了一个循环, 
                if (it.hasNext()) {
                    Map.Entry<String, String> next = it.next();
                    String key = next.getKey();      // 04, 循环到这里不断获取到 key....
                    String value = next.getValue();   // 05, 相应的得到 value
                    String[] strArr = new String[1];
                    t.l(certificateFactory, "cf");
                    Context context = bVar2.f27231c;
                    if (context != null) {
                        InputStream open = context.getAssets().open(value);  // 06, 然后 open 打开, 调用一个, 调用 getAssets() 调用一个资源文件.读取到它. 读取到他之后,再去读取到它后面相应的内容.调取到相应的资源文件.
                        try {
                            byte[] encoded = certificateFactory.generateCertificate(open).getPublicKey().getEncoded();
                            String Q = t.Q("sha256/", Base64.encodeToString(h.B(Arrays.copyOf(encoded, encoded.length)).M().Y(), 0));  // 01, 这里就是sha256 加上后面的这些的操作.// 07, 转换为相应的值和对象... =======> 08, 接下来就 Hook Assets 是什么了.
                            i4.c.w(open, null);
                            strArr[0] = Q;
                            if (key == null) {
                                throw new NullPointerException("pattern == null");
                            }
                            for (int i10 = 0; i10 < 1; i10++) {
                                arrayList.add(new f.a(key, strArr[i10]));  // 00, 这里就是他的上面---> 通过调用栈 找到这里.
                            }
                        } finally {
                        }
                    } else {
                        t.W(com.umeng.analytics.pro.d.R);
                        throw null;
                    }
                } else {
                    bVar.f28594o = new rk.f(new LinkedHashSet(arrayList), null);
                    s9.a aVar = b.this.f27233e;
                    if (aVar != null) {
                        bVar.f28584e.add(aVar);
                        s9.b bVar3 = b.this.f27232d;
                        if (bVar3 != null) {
                            bVar.f28584e.add(bVar3);
                            b bVar4 = b.this;
                            m mVar = bVar4.f27235g;
                            if (mVar != null) {
                                bVar.f28598s = mVar;
                            }
                            bl.a aVar2 = new bl.a();
                            aVar2.f4106c = bVar4.f27229a ? 4 : 1;
                            bVar.f28584e.add(aVar2);
                            w wVar = new w(bVar);
                            l lVar = wVar.f28554a;
                            lVar.getClass();
                            synchronized (lVar) {
                                lVar.f28498a = 10;
                            }
                            lVar.b();
                            return wVar;
                        }
                        t.W("responseInterceptor");
                        throw null;
                    }
                    t.W("httpsRequestInterceptor");
                    throw null;
                }
            }
        }
    }