自定义证书的 客户端校验 和 服务端校验. [客户端证书校验源码和证明] pinner + host + 证书 [okhttp的单向校验-代码混淆解读]
- iOS破解
- 2025-01-20
- 66热度
- 0评论
关于一些方法
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;
}
}
}
}
