0x01 通杀okhttp-ssl-pinning思路

所谓通杀:就是不管有没有混淆,都可以将对应的类识别出来。。。

本质上还是hook对应的类,对应的方法。但是这些方法和类都是做了混淆处理的,怎么在所有类中识别出来特定的类和方法是通杀的关键。。。

第一次校验:X509TrustManager

OKHttpClient.Builder builder = new OKHttpClient.Builder();
builder.sslSocketFactory(factory, (X509TrustManager) trustManagers[0])

X509TrustManagerTrustManager接口的一个实现类,主要校验是通过TrustManager接口下的checkServerTrusted函数。

X509TrustManager是通过对checkServerTrusted函数进行重新实现SSLPinning

上节课的hook思路为:只要是实现了TrustManager接口的类,都hook里面的checkServerTrusted方法。

本节课换思路:Hook掉sslSocketFactory方法,将里面的factory替换我们自己的factory

  • factory是证书信任工厂
  • 可以自定义factory,让其信任所有

第二次校验:certificatePinner

CertificatePinner.Builder cerBuilder = new CertificatePinner.Builder();
//下面4条,对应 ".taobao.com 域名的公钥
cerBuilder.add("*.taobao.com",CertificatePinner.pin(cerBuilder))
cerBuilder.add("*.taobao.com", "sha256/IfXz1a0gWBA5oH+zasmRutUiyoZN3I8wLxHNQxk3NVo=")
cerBuilder.add("*.taobao.com", "sha256/IQBnNBEiFuhj+8x6X8XLgh01V9Ic5/V3IRQLNFFc7v4=")
cerBuilder.add("*.taobao.com", "sha256/K87oWBWM9UZfyddvDfoxL+8lpNyoUB2ptGtn0fv6G2Q=")

builder.cerBuilderPinner(cerBuilder.builder())

上节课思路:Hook掉CertificatePinner.Builder类的add方法,将其置空

本节课思路:同上,Hook掉cerBuilderPinner方法,将其参数cerBuilder.builder()置空

第三次校验:hostnameVerify

builder.hostnameVerifier(new HostnameVerify){
@Override
public boolean verify(String hostname, SSLSession session){ //传入两个参数,hostname:服务器域名 session:会话信息,里面会包含证书相关内容
if (hostname.equals('www.baidu.com')){ //首先判断hostname是否为www.baidu.com(这块随便写的)
try{
Certificate[] peerCertificates = session.getPeerCertificates(); //从session获取到服务器端证书相关信息
if(peerCertificates[0].getPublicKey().equals(myCertificate.getPublicKey())){ //判断服务器端证书公钥是否和本地证书公钥一样
return true; //若匹配成功则校验通过,校验失败,也就意味抓不到包
}else{
return false;
}
}catch(SSLPeerUnverifiedException e){
e.printStackTrace();
return false;
}
}
return false;
}
}

本节课思路:Hook掉hostnameVerifier方法,将里面的参数HostnameVerify替换为我们自己的

0x02 X509TrustManager实现

OKHttpClient.Builder builder = new OKHttpClient.Builder();
builder.sslSocketFactory(factory, (X509TrustManager) trustManagers[0])

本节课换思路:Hook掉sslSocketFactory方法,将里面的factory替换我们自己的factory

1、builder是OKHttpClient的一个内部类,正常情况下直接Java.use("OKHttp3.OKHttpClient$builder")即可

2、针对有混淆的情况,如何定位到OKHttpClient这个类呢???

原理:可以通过这类实现了哪些接口,这个类对应的方法有什么几个参数等等。如OKHttpClient这个类是一个public类,没有static和final;包名为OKHttp3只有一层等等

image.png

本质:我们通过这个类的相关特征,来对他进行识别!

var classNames = new Array();
//定义了三个变量,赋值为空
var OkhttpClinetClassName = ""
var CertificatePinnerClassName = ""
var perfix = ""


//定义一个数组,遍历内存中的所有类,将所有类名放入数组中。
function loadClasses(){
Java.perform(function(){
Java.enumerateClassLoaders({
onMatch:function(clasName,handle){
classNames.push(clasName)
},
onComplete:function(){
console.log("Search classes complete!")
}
})
})
}

function findOkHttpClass(){
Java.perform(function(){
var Modifier = Java.use("java.lang.reflect.Modifier") //反射框架提供的一个类,可以判断当前这个类的一些属性,如final、static等
//判读这个类是否是okhttp3.OkHttpClient
function isOkhttpClient(clasName){
if (clasName.split('.').length != 2){
return false;
}

try{
var clas = Java.use(clasName);
var interfaces = clas.class.getInterfaces(); //通过反射获取当前类的接口
var count = interfaces.length; //获取当前类的接口数量
if (count < 2){
return false;
}

var flag = false;
for (var i = 0; i < count; i++){
var interface_ = interfaces[i];
var interface_Name = interface_.getName();

if (interface_Name.indexOf("Cloneable") > 0){ //OkHttpClass实现了Cloneable接口
flag = true;
}else {
if (interface_Name.indexOf("$") <= 0){ //"$"代表内部类,不属于okhttp3的类,返回false
return false
}
}
}
if (!flag){
return false;
}

if (clas.class.getDeclaringClass().length = 0){ //判断当前类中内部类的个数,如果没有返回false
return false;
}

if (clas.class.getSuperclass().getName().indexOf("java.lang.Object") < 0){ //判断当前类是否继承了Object类,如果没有返回false)
return false;
}

}catch(e){
return false;
}
return true; //如果当前类是okhttp3.OkHttpClient类的前缀,并且实现了okhttp3.OkHttpClient接口,并且没有内部类,并且继承了Object类,那么就认为是okhttp3.OkHttpClient类
}



function isCertificatePinner(clasName,perfix){
if(!clasName.startsWith(perfix)){
return false;
}

if (clasName.indexOf('$') > 0){
return false;
}

if (clasName.split('.'.length != 2)){
return false;
}

var cls = Java.use(clasName);
if (cls.class.isInterface()){
return false;
}

if (cls.class.getInterfaces().length > 0){
return false;
}

if (cls.class.getDeclaringClass().length = 0){
return false;
}

if (cls.class.getSuperclass().getName() != "java.lang.Object"){
return false;
}

if (!Modifier.isFinal(cls.class.getModifiers())){
return false;
}

var flag=false
var methods = cls.class.getDeclaredMethods(); //获取当前类的所有方法
for (var i = 0; i < methods.length; i++){
var method = methods[i]; //获取当前方法
if (method.getParameterCount() < 1){
couninue;
}
if (method.getParameterTypes()[0].getName() != "java.security.cert.Certificate"){
flag = true;
break;
}
}
if (!flag){
return false;
}


flag=false
var filed = cls.class.getDeclaredFields(); //获取当前类的所有字段
for (var i = 0; i < filed.length; i++){
var field = filed[i]; //获取当前字段
if (field.getType().getName() == "java.util.set"){ //判断当前字段是否是set类型
flag = true;
break;
}
}
if (!flag){
return false;
}

//console.log("Find CertificatePinnerClassName: " + clasName)

return true; //如果当前类是okhttp3.CertificatePinner类的前缀,并且没有内部类,并且没有接口,并且继承了Object类,并且是final的,并且有set类型字段,并且有方法参数为Certificate类型,那么就认为是okhttp3.CertificatePinner类
}



for (var i = 0; i < classNames.length; i++){
if (isOkhttpClient(classNames[i])){
OkhttpClinetClassName = classNames[i]
console.log("Find OkhttpClinetClassName: " + OkhttpClinetClassName)
//获取OkhttpClinetClassName类的前缀,比如okhttp3.OkHttpClient,得到了okhttp3.
perfix = OkhttpClinetClassName.split('.')[0] + "."

}
}


for (var i = 0; i < classNames.length; i++){
if (isCertificatePinner(classNames[i],perfix)){
CertificatePinnerClassName = classNames[i]
//console.log("Find CertificatePinnerClassName: " + CertificatePinnerClassName)
}
}

console.error("Found class: " + classNames.length);
console.error("OkHttp's package prefix: " + perfix);
console.error("Find OkhttpClinetClassName: " + OkhttpClinetClassName);
console.error("Find CertificatePinnerClassName: " + CertificatePinnerClassName);

if (OkhttpClinetClassName == "" || CertificatePinnerClassName == "" || perfix == ""){
console.error("Can't find OkhttpClinetClassName or CertificatePinnerClassName");
return false
}
return true //如果找到了okhttp3.OkHttpClient类和okhttp3.CertificatePinner类,并且得到了前缀,那么就返回true

})
}


function main(){
loadClasses();
if (findOkHttpClass()){

}
}

setImmediate(main)

尝试在加载的类中查找OkHttpClientCertificatePinner这两个类,运行这个脚本,发现通过上面脚本可以成功找到这两个类:

image.png

0x03 完整代码&详解

var classesNames = new Array()
var OkhttpClientClassName = ""
var CertificatePinnerClassName = ""
var prefix = ""

function initConsole(){
var Color = {RESET: "\x1b[39;49;00m", Black: "0;01", Blue: "4;01", Cyan: "6;01", Gray: "7;11", "Green": "2;01", Purple: "5;01", Yellow: "3;01", Red: "1;01"}
var LightColor = {RESET: "\x1b[39;49;00m", Black: "0;11", Blue: "4;11", Cyan: "6;11", Gray: "7;01", "Green": "2;11", Purple: "5;11", Red: "1;11", Yellow: "3;11"}
var colorPrefix = '\x1b[3'
var colorSuffix = 'm'
Object.keys(Color).forEach(function(c){
if (c == "RESET")
return
console[c] = function(message){
console.log(colorPrefix + Color[c] + colorSuffix + message + Color.RESET)
}
console["Light" + c] = function(message){
console.log(colorPrefix + LightColor[c] + colorSuffix + message + Color.RESET)
}
})
}

function loadOkhttpClient(){
Java.perform(function (){
try{
Java.use("okhttp3.OkHttpClient")
}catch(e){
//console.error(e)
}
})

}

function loadClasses(){
Java.perform(function (){
Java.enumerateLoadedClasses({
onMatch: function(clsName, handle){
classesNames.push(clsName)
},
onComplete: function(){
console.Green("Search Class Completed!")
}
})
})
}

function findOkhttpClass(){
Java.perform(function (){
var Modifier = Java.use("java.lang.reflect.Modifier")
function isOkhttpClient(clsName){
if(clsName.split('.').length != 2){
return false;
}

try{
var cls = Java.use(clsName)
var interfaces = cls.class.getInterfaces()
const count = interfaces.length
if(count < 2){
return false
}
var flag = false
for(var i = 0; i < count; i++){
var interface_ = interfaces[i]
var interface_name = interface_.getName()

if(interface_name.indexOf("Cloneable") > 0){
flag = true
}else{
if(interface_name.indexOf("$") <= 0){
return false
}
}
}
if(!flag) return false;

if(cls.class.getDeclaredClasses().length < 1){
return false
}

if(cls.class.getSuperclass().getName() != 'java.lang.Object'){
return false
}

}catch(e){
return false
}
return true;
}

function isCertificatePinner(clsName,prefix){

if(!clsName.startsWith(prefix)){
return false
}

if(clsName.indexOf("$") > 0){
return false
}

if(clsName.split('.').length != 2){
return false;
}

var cls = Java.use(clsName)
if(cls.class.isInterface()){
return false
}


if(cls.class.getInterfaces().length > 0){
return false
}


if(cls.class.getDeclaredClasses().length < 1){
return false
}

if(cls.class.getSuperclass().getName() != "java.lang.Object"){
return false
}

if(!Modifier.isFinal(cls.class.getModifiers())){
return false
}
var flag = false
var methods = cls.class.getDeclaredMethods()
for(var i = 0; i < methods.length; i++){
var method = methods[i]
if(method.getParameterCount() < 1){
continue
}
if(method.getParameterTypes()[0].getName() == "java.security.cert.Certificate"){
flag = true
break
}
}
if(!flag) return false

flag = false
var fields = cls.class.getDeclaredFields()
for(var k = 0; k < fields.length; k++){
var field = fields[k];
if(field.getType().getName() == "java.util.Set"){
flag = true
break
}
}
if(!flag) return false
return true
}

for(var i = 0; i < classesNames.length; i++){
if(isOkhttpClient(classesNames[i])){
OkhttpClientClassName = classesNames[i]
var prefix = classesNames[i].split('.')[0]+'.'
}
}

for(var i = 0; i < classesNames.length; i++){
if(isCertificatePinner(classesNames[i],prefix)){
CertificatePinnerClassName = classesNames[i]
}
}

var printOut
if(OkhttpClientClassName == "" || CertificatePinnerClassName == "" || prefix == ""){
printOut = console.Red
printOut("Can't find the okhttp class")
}else{
printOut = console.Green
}
printOut("Found Class: "+classesNames.length)
printOut("Okhttp's package prefix: "+prefix)
printOut("Found the OkhttpClient: "+OkhttpClientClassName)
printOut("Found the OkhttpCertificatePinner: "+CertificatePinnerClassName)
})
}

function hook(){
Java.perform(function (){
var Modifier = Java.use("java.lang.reflect.Modifier")
//TrustAllManager
var TrustAllManagerClass = Java.registerClass({
name: "TrustAllManager",
implements:[Java.use("javax.net.ssl.X509TrustManager")],
methods: {
checkClientTrusted(chain, authType) {
console.Cyan("checkClientTrusted Called!!")
},
checkServerTrusted(chain, authType) {
console.Cyan("checkServerTrusted Called!!")
},
getAcceptedIssuers() {
return [];
},
}
})
var trustAllManagerHandle = TrustAllManagerClass.$new()

var sslContext = Java.use("javax.net.ssl.SSLContext").getInstance("TLS")
sslContext.init(null,Java.array("Ljavax.net.ssl.X509TrustManager;",[trustAllManagerHandle]),null)
var sslSocketFactory = sslContext.getSocketFactory()

//HostnameVerify
var MyHostnameVerify = Java.registerClass({
name: "MyHostnameVerify",
implements:[Java.use("javax.net.ssl.HostnameVerifier")],
methods: {
verify(hostname, session){
console.log(hostname)
return true
}
}
})
var myHostnameVerifyHandle = MyHostnameVerify.$new()

var internalOkhttpClientClasses = Java.use(OkhttpClientClassName).class.getDeclaredClasses()
internalOkhttpClientClasses.forEach(function (internalClass) {
var methods = internalClass.getDeclaredMethods()
methods.forEach(function(method) {
if(method.getParameterCount() < 1){
return
}
var firstParameterTypeName = method.getParameterTypes()[0].getName()

if(firstParameterTypeName == "javax.net.ssl.SSLSocketFactory"){
var Builder = Java.use(internalClass.getName())
var sslSocketFacotryMethodName = method.getName()
Builder[sslSocketFacotryMethodName].overloads.forEach(function(overload){
overload.implementation = function(SSLSocketFactory){
arguments[0] = sslSocketFactory
return this[sslSocketFacotryMethodName].apply(this,arguments)
}
console.Blue(sslSocketFacotryMethodName+" Hooked!")
});

}
if(firstParameterTypeName == "javax.net.ssl.HostnameVerifier"){
var Builder = Java.use(internalClass.getName())
var hostnameVerifierMethodName = method.getName()
Builder[hostnameVerifierMethodName].overloads.forEach(function(overload){
overload.implementation = function(hostnameVerifier){
arguments[0] = myHostnameVerifyHandle
return this[hostnameVerifierMethodName].apply(this,arguments)
}
console.Yellow(hostnameVerifierMethodName+" Hooked!")
});
}

if(firstParameterTypeName == CertificatePinnerClassName){
var Builder = Java.use(internalClass.getName())
var certificatePinnerMethodName = method.getName()

Builder[certificatePinnerMethodName].overloads.forEach(function(overload){
overload.implementation = function(certificatePinner){
return Java.retain(this)
}
console.Purple(certificatePinnerMethodName+" Hooked!")
});

}
})
});

var CertificatePinnerClass = Java.use(CertificatePinnerClassName)
var methods = CertificatePinnerClass.class.getDeclaredMethods()
methods.forEach(function (method){
if(method.getReturnType().getName() == 'void'){
var methodName = method.getName()
console.Cyan(methodName+" Hooked!")
CertificatePinnerClass[methodName].overloads.forEach(function (overload){
if(overload.returnType.name == 'V'){
overload.implementation = function(){
console.Cyan("certificatePinner check called!")
}
}
})
}
})
})
}

function main(){
initConsole()
loadOkhttpClient()
loadClasses()
findOkhttpClass()
hook()
}
setImmediate(main)

initConsole()-可选

function initConsole(){
var Color = {RESET: "\x1b[39;49;00m", Black: "0;01", Blue: "4;01", Cyan: "6;01", Gray: "7;11", "Green": "2;01", Purple: "5;01", Yellow: "3;01", Red: "1;01"}
var LightColor = {RESET: "\x1b[39;49;00m", Black: "0;11", Blue: "4;11", Cyan: "6;11", Gray: "7;01", "Green": "2;11", Purple: "5;11", Red: "1;11", Yellow: "3;11"}
var colorPrefix = '\x1b[3'
var colorSuffix = 'm'
Object.keys(Color).forEach(function(c){
if (c == "RESET")
return
console[c] = function(message){
console.log(colorPrefix + Color[c] + colorSuffix + message + Color.RESET)
}
console["Light" + c] = function(message){
console.log(colorPrefix + LightColor[c] + colorSuffix + message + Color.RESET)
}
})
}

这个函数initConsole的作用是扩展JavaScript的console对象,使得能够在控制台中输出带有颜色的文本。它通过定义一个Color对象和一个LightColor对象来包含ANSI转义代码,这些代码用于设置终端中的文本颜色和样式。

具体来说,这个函数做了以下几件事情:

  1. 定义了一个Color对象,包含了默认的前景色(文本颜色)和背景色,以及一些基本的文本颜色代码,如黑、蓝、绿等。
  2. 定义了一个LightColor对象,包含了与Color相同的颜色,但是这些颜色是亮色的版本。
  3. 定义了colorPrefix和colorSuffix变量,这两个变量用于构造ANSI转义代码,用于在终端中设置文本颜色。
  4. 使用Object.keys(Color)获取Color对象的所有键(颜色名),然后使用forEach遍历这些键。
  5. 对于每个颜色名(除了”RESET”),这个函数会为console对象添加两个新方法:一个用于普通颜色,一个用于亮色。例如,如果颜色名是”Red”,则添加console.Red和console.LightRed两个方法。
  6. 当这些新方法被调用时,它们会使用相应的ANSI转义代码来设置文本颜色,然后打印传入的消息,最后将颜色重置为默认。

通过调用initConsole(),你可以在控制台中使用如console.Red(“Error message”)的方式来输出红色文本,或者使用console.LightGreen(“Info message”)来输出亮绿色文本。这有助于在日志输出中区分不同的信息级别或类型。

loadOkhttpClient()

//提前加载okhttpclient,不然spawn的时候会报错
function loadOkhttpClient(){
Java.perform(function(){
try{
Java.use("Okhttps.OkhttpClient") //这块类名混淆后的话,我们可以运行代码后保存后,修改参数,重新加载
}catch(e){
console.error(e)
}

})
}

loadOKhttpClient()函数的主要作用是在开始Hook前,就加载”Okhttps.OkhttpClient”。

防止通过spawn方法加载脚本的时候,没有完全加载进去所有的类,导致脚本报错。

Java.use("Okhttps.OkhttpClient") 里面的参数,根据下面findOkhttpClass()函数的实际情况进行修改。

loadClasses()

//定义一个数组,遍历内存中的所有类,将所有类名放入数组中。
var classNames = new Array();
function loadClasses(){
Java.perform(function(){
Java.enumerateClassLoaders({
onMatch:function(clasName,handle){
classNames.push(clasName)
},
onComplete:function(){
console.log("Search classes complete!")
}
})
})
}

作用:调用fridaAPI "Java.enumerateClassLoaders" 遍历内存中所有的类,并将这些类放到数组中方便后面调用。

findOkhttpClass()-关键

找到内存中的 “OkhttpClient“ 和 “CertificatePinner“ 类。

//定义了三个变量,赋值为空
var OkhttpClinetClassName = ""
var CertificatePinnerClassName = ""
var perfix = ""

function findOkhttpClass(){
Java.perform(function (){
var Modifier = Java.use("java.lang.reflect.Modifier") //Modifier是反射框架提供的一个类,可以判断当前这个类的一些属性,如final、static等

//定义了一个函数isOkhttpClient,传入参数为类名clsName
//作用:获取到经过混淆后的OkhttpClient类名
function isOkhttpClient(clsName){
if(clsName.split('.').length != 2){ //判断类名长度必须为2
return false;
}

try{
var cls = Java.use(clsName)
var interfaces = cls.class.getInterfaces() //通过反射获取当前类的接口
const count = interfaces.length //接口数量
if(count < 2){ //OkhttpClient类继承的接口数量要求大于等于2
return false
}
var flag = false
for(var i = 0; i < count; i++){
var interface_ = interfaces[i]
var interface_name = interface_.getName() //遍历获得OkhttpClient类继承的接口

if(interface_name.indexOf("Cloneable") > 0){ //接口名称包含"Cloneable"。这块cloneable是OKhttpclient类实现的一个接口
flag = true
}else{
if(interface_name.indexOf("$") <= 0){ //判断当前接口是否是内部类,如果不是返回false
return false
}
}
}
if(!flag) return false; //如果在遍历过程中没有找到包含"Cloneable"的接口,函数返回false。

if(cls.class.getDeclaredClasses().length < 1){ //检查这个类至少包含一个内部类,如果没有返回false
return false
}

if(cls.class.getSuperclass().getName() != 'java.lang.Object'){ //因为OkhttpClient这个类的没有父类,所以默认他的父类为 Object
return false
}

}catch(e){
return false
}
return true;
}


///定义了一个函数isCertificatePinner
function isCertificatePinner(clsName,prefix){
if(!clsName.startsWith(prefix)){ //检查保密是否以prefix位前缀,若不是返回false
return false
}

if(clsName.indexOf("$") > 0){ //判断当前类是否是内部类,如果是返回false
return false
}

if(clsName.split('.').length != 2){ //判断当前类以"."分割后,长度是否为2,如果不是返回false
return false;
}

var cls = Java.use(clsName)
if(cls.class.isInterface()){ //判断当前类是否是接口,如果是返回false
return false
}


if(cls.class.getInterfaces().length > 0){ //判断当前类是否有接口,如果有返回false
return false
}


if(cls.class.getDeclaredClasses().length = 0){ //判断当前类中内部类的个数,如果没有返回false
return false
}

if(cls.class.getSuperclass().getName() != "java.lang.Object"){ //判断当前类是否继承了Object类,如果没有返回false
return false
}

if(!Modifier.isFinal(cls.class.getModifiers())){ //判断当前类是否是final修饰的,如果是返回false
return false
}
var flag = false
var methods = cls.class.getDeclaredMethods() //获取当前类的所有方法
for(var i = 0; i < methods.length; i++){
var method = methods[i]
if(method.getParameterCount() < 1){ //判断当前方法是否有参数,如果没有则continue
continue
}
if(method.getParameterTypes()[0].getName() == "java.security.cert.Certificate"){ //判断当前方法的第一个参数类型是否是Certificate类型
flag = true //如果有方法参数为Certificate类型,那么就认为是okhttp3.CertificatePinner类
break
}
}
if(!flag) return false //如果没有方法参数为Certificate类型,那么就认为不是okhttp3.CertificatePinner类

flag = false
var fields = cls.class.getDeclaredFields() //通过反射获取类的所有声明字段。这些字段包括私有、保护、默认(包)访问级别的字段,但不包括继承的字段
for(var k = 0; k < fields.length; k++){
var field = fields[k];
if(field.getType().getName() == "java.util.Set"){ //检查当前字段的类型是否是集合(java.util.Set)
flag = true
break
}
}
if(!flag) return false
return true
}

//for循环,调用isOkhttpClient方法,找到OkhttpClient这个类
for(var i = 0; i < classesNames.length; i++){
if(isOkhttpClient(classesNames[i])){
OkhttpClientClassName = classesNames[i]
//获取OkhttpClinetClassName类的前缀,比如okhttp3.OkHttpClient,得到了okhttp3.
var prefix = classesNames[i].split('.')[0]+'.'
}
}
//for循环,调用isCertificatePinner方法,找到isCertificatePinner类
for(var i = 0; i < classesNames.length; i++){
if(isCertificatePinner(classesNames[i],prefix)){
CertificatePinnerClassName = classesNames[i]
}
}

var printOut
if(OkhttpClientClassName == "" || CertificatePinnerClassName == "" || prefix == ""){ //如果没有找到okhttp3.OkHttpClient类和okhttp3.CertificatePinner类,那么就返回false
printOut = console.Red
printOut("Can't find the okhttp class")
}else{
printOut = console.Green
}
//打印相关字段,如类名前缀、OkhttpClient类、OkhttpCertificatePinner类
printOut("Found Class: "+classesNames.length)
printOut("Okhttp's package prefix: "+prefix)
printOut("Found the OkhttpClient: "+OkhttpClientClassName)
printOut("Found the OkhttpCertificatePinner: "+CertificatePinnerClassName)
})
}

1、首先,定义了一个 反射类Modifier,该类是反射框架提供的一个类,可以判断当前这个类的一些属性,如final、static等

2、定义了一个函数isOkhttpClient,传入参数为类名clsName,作用是获取到经过混淆后的OkhttpClient类名

  • 要求类名长度必须为2(正常的OkHttpClient全名为okhttp3.OkHttpClient

  • 要求实现的接口数量大于等于2(默认OkHttpClient实现了2个接口)

    public class OkHttpClient implements Cloneable, Call.Factory {
    // 类的实现
    }

    Cloneable: 这个接口来自 Java 标准库,表明 OkHttpClient 类可以被克隆。

    Call.Factory: 这个接口是 OkHttp 库的一部分,它定义了创建 Call 实例的方法。Call 对象用于发起 HTTP 请求。

  • for 循环遍历所有接口,检查是否包含 Cloneable 接口,并且确保其他接口是内部类接口(通过接口名中包含 $ 符号来判断)

  • 检查OkHttpClient 类是否至少包含一个内部类。如果没有返回false

  • 因为 OkHttpClient 是一个顶层类,其父类应该是 Object

如果上述条件都满足,就可以认为这个类是 OkhttpClient 类。

3、定义了一个函数isCertificatePinner,用来获取经过混淆后的CertificatePinner

  • 检查包名是否以prefix为前缀,若不是返回false,因为CertificatePinnerOkhttpClient都属于Okhttp3这个包名下的。
  • 判断当前类是否是内部类,CertificatePinner不是一个内部类
  • 判断当前类以”.”分割后,长度是否为2,如果不是返回false
  • 判断当前类是否是接口,如果是返回false
  • 判断当前类是否有接口,如果有返回false
  • 判断当前类中内部类的个数,如果没有返回false
  • 判断当前类是否继承了Object类,如果没有返回false,因为CertificatePinner是顶级类
  • 判断当前类是否是final修饰的,如果是返回false
  • 获取当前类中的所有方法,如果有方法参数为Certificate类型,那么就认为是okhttp3.CertificatePinner类。

hook()

这块对前面讲的三种Hook思路进行实现。

function hook(){
Java.perform(function (){
var Modifier = Java.use("java.lang.reflect.Modifier") //Modifier 反射框架提供的一个类,可以判断当前这个类的一些属性,如final、static等

//TrustAllManager:这段代码的目的是创建一个 SSL 上下文,它使用一个自定义的信任管理器来信任所有的 SSL 证书。
var TrustAllManagerClass = Java.registerClass({ //Java.registerClass是firda提供的,用于动态注册一个 Java 类
name: "TrustAllManager",
implements:[Java.use("javax.net.ssl.X509TrustManager")],
methods: {
checkClientTrusted(chain, authType) {
console.Cyan("checkClientTrusted Called!!")
},
checkServerTrusted(chain, authType) {
console.Cyan("checkServerTrusted Called!!")
},
getAcceptedIssuers() {
return [];
},
}
})
var trustAllManagerHandle = TrustAllManagerClass.$new() //创建了一个 TrustAllManager 类的实例trustAllManagerHandle

var sslContext = Java.use("javax.net.ssl.SSLContext").getInstance("TLS") //获取了一个 SSLContext 实例,用于创建 SSL 套接字和引擎
sslContext.init(null,Java.array("Ljavax.net.ssl.X509TrustManager;",[trustAllManagerHandle]),null) //初始化 SSLContext,传入我们创建的 trustAllManagerHandle 作为信任管理器。
var sslSocketFactory = sslContext.getSocketFactory() //从 SSLContext 对象中获取一个 SSLSocketFactory,这个工厂可以用来创建 SSL 套接字,这些套接字将会使用我们的自定义信任管理器,从而信任所有的 SSL 证书

//HostnameVerify:
var MyHostnameVerify = Java.registerClass({
name: "MyHostnameVerify",
implements:[Java.use("javax.net.ssl.HostnameVerifier")],
methods: {
verify(hostname, session){
console.log(hostname)
return true
}
}
})
var myHostnameVerifyHandle = MyHostnameVerify.$new()


var internalOkhttpClientClasses = Java.use(OkhttpClientClassName).class.getDeclaredClasses() //获取 OkhttpClientClassName 类声明的所有内部类
internalOkhttpClientClasses.forEach(function (internalClass) { //遍历上一步获取到的所有内部类
var methods = internalClass.getDeclaredMethods() //获取内部类的所有方法
methods.forEach(function(method) {
if(method.getParameterCount() < 1){ //检查方法是否有参数。如果没有参数,则跳过当前方法的处理
return
}
var firstParameterTypeName = method.getParameterTypes()[0].getName() //获取方法第一个参数的类型名称。

if(firstParameterTypeName == "javax.net.ssl.SSLSocketFactory"){ //检查第一个参数是否是 SSLSocketFactory 类型,如果是执行下面代码
var Builder = Java.use(internalClass.getName()) //获取okhttp.okhttpclinet$builder
var sslSocketFacotryMethodName = method.getName() //获取方法的名称
Builder[sslSocketFacotryMethodName].overloads.forEach(function(overload){ //遍历该方法的所有重载版本。
overload.implementation = function(SSLSocketFactory){
arguments[0] = sslSocketFactory
return this[sslSocketFacotryMethodName].apply(this,arguments)
}
console.Blue(sslSocketFacotryMethodName+" Hooked!")
});

}
if(firstParameterTypeName == "javax.net.ssl.HostnameVerifier"){ //检查第一个参数是否是 HostnameVerifier 类型,如果是执行下面代码。逻辑同上
var Builder = Java.use(internalClass.getName())
var hostnameVerifierMethodName = method.getName()
Builder[hostnameVerifierMethodName].overloads.forEach(function(overload){
overload.implementation = function(hostnameVerifier){
arguments[0] = myHostnameVerifyHandle
return this[hostnameVerifierMethodName].apply(this,arguments)
}
console.Yellow(hostnameVerifierMethodName+" Hooked!")
});
}

if(firstParameterTypeName == CertificatePinnerClassName){ //检查第一个参数是否是 CertificatePinnerClassName 类型
var Builder = Java.use(internalClass.getName())
var certificatePinnerMethodName = method.getName()

Builder[certificatePinnerMethodName].overloads.forEach(function(overload){
overload.implementation = function(certificatePinner){
return Java.retain(this)
}
console.Purple(certificatePinnerMethodName+" Hooked!")
});

}
})
});


//这段代码的目的是hook CertificatePinner类的所有返回类型为 void 的方法,并在这些方法被调用时打印一条消息到控制台
var CertificatePinnerClass = Java.use(CertificatePinnerClassName) //CertificatePinner类
var methods = CertificatePinnerClass.class.getDeclaredMethods() //得到CertificatePinner的所有方法
methods.forEach(function (method){ //遍历所有的方法
if(method.getReturnType().getName() == 'void'){ //判断如果方法返回类型为void
var methodName = method.getName()
console.Cyan(methodName+" Hooked!")
CertificatePinnerClass[methodName].overloads.forEach(function (overload){ //遍历当前方法的所有重载方法
if(overload.returnType.name == 'V'){
overload.implementation = function(){
console.Cyan("certificatePinner check called!")
}
}
})
}
})
})
}