Frida 复制任意类型对象数组
需求是,第一次hook到一个方法的参数,类型是任意类型的数组([Ljava.security.cert.X509Certificate;
);第二次hook到一个方法,要将之前hook到的数组赋给其参数。
复制参数可以使用Java.retain(obj),但由于Frida不认为对象数组是java.lang.Object对象(参考https://bbs.pediy.com/thread-261052-1.htm),所以对整个[Ljava.security.cert.X509Certificate;
调用Java.retain是不行的,报错如下:
{‘type’: ‘error’, ‘description’: ‘TypeError: not a function’, ‘stack’: ‘TypeError: not a function\n at retain (frida/node_modules/frida-java-bridge/lib/class-factory.js:123)\n at retain (frida/node_modules/frida-java-bridge/index.js:267)\n at (/script1.js:79)\n at apply (native)\n at ne (frida/node_modules/frida-java-bridge/lib/class-factory.js:620)\n at (frida/node_modules/frida-java-bridge/lib/class-factory.js:598)’, ‘fileName’: ‘frida/node_modules/frida-java-bridge/lib/class-factory.js’, ‘lineNumber’: 123, ‘columnNumber’: 1}
取而代之,可以对数组的每个元素进行Java.retain,再将它们组装回数组。
代码如下:
jscode = """
setImmediate(function() {
Java.perform(function () {
// 主动加载apk
var DEXCL = null
var DEXFactory = null
function loadAPK(path){
var ActivityThread = Java.use("android.app.ActivityThread");
var app = ActivityThread.currentApplication();
Java.classFactory.cacheDir = "/data/data/" + app.getPackageName() + "/cache";
Java.classFactory.codeCacheDir = "/data/data/" + app.getPackageName() + "/code_cache";
var DexClassLoader = Java.use("dalvik.system.DexClassLoader");
DEXCL = DexClassLoader.$new(path, Java.classFactory.codeCacheDir, null, DexClassLoader.getSystemClassLoader());
DEXFactory = Java.ClassFactory.get(DEXCL);
DEXFactory.cacheDir = "/data/data/" + app.getPackageName() + "/cache";
DEXFactory.codeCacheDir = "/data/data/" + app.getPackageName() + "/code_cache";
}
loadAPK('/data/user_de/0/com.google.android.gms/app_chimera/m/00000009/CronetDynamite.apk');
Java.enumerateClassLoaders({
onMatch: function (loader) {
try {
// cronet apk contains class: org.chromium.net.AndroidNetworkLibrary
if (loader.findClass("org.chromium.net.AndroidNetworkLibrary")) {
// should be: /data/user_de/0/com.google.android.gms/app_chimera/m/00000009/CronetDynamite.apk
if (loader.toString().indexOf("CronetDynamite") != -1) {
Java.classFactory.loader = loader;
send("loader: " + loader);
// console.log(loader);
}
}
} catch (error) {
}
}, onComplete: function () {
}
});
Java.deoptimizeEverything();
// android.net.http.X509TrustManagerExtensions.checkServerTrusted(java.security.cert.X509Certificate[], java.lang.String, java.lang.String) : java.util.List
var class_name = 'android.net.http.X509TrustManagerExtensions';
// var DymClass = Java.use(class_name);
var DymClass = DEXFactory.use(class_name);
var time = 0;
var ArrayX509Certificate = Java.array("Ljava.security.cert.X509Certificate;",[]);
var tempArg0;
var tempArg1;
var tempArg2;
DymClass.checkServerTrusted.overload('[Ljava.security.cert.X509Certificate;', 'java.lang.String', 'java.lang.String').implementation = function (arg1,arg2,arg3)
{
var bt = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new());
if(bt.indexOf("dh.a") != -1 && arg3.indexOf("www.googleapis.com") != -1) {
// if (1) {
send('hooked');
console.log("Backtrace:" + bt);
send('orin arg1 : ' + arg1);
send('orin arg2 : ' + arg2);
send('orin arg3 : ' + arg3);
// 1st time
if(time==0){
send('1st time ');
time = time + 1;
var retval = this.checkServerTrusted(arg1,arg2,arg3);
// store arg1
send('orin arg1[0] : ' + arg1[0]);
send('orin arg1[1] : ' + arg1[1]);
send('orin arg1[2] : ' + arg1[2]);
tempArg0 = Java.retain(arg1[0]);
tempArg1 = Java.retain(arg1[1]);
tempArg2 = Java.retain(arg1[2]);
return retval;
}
// 2nd time
if (time==1) {
send('2nd time ');
ArrayX509Certificate = [tempArg0,tempArg1,tempArg2];
send('new arg1 : ' + ArrayX509Certificate);
return this.checkServerTrusted(ArrayX509Certificate,arg2,arg3);
}
}
else{
return this.checkServerTrusted(arg1,arg2,arg3);
}
}
});
});
function byteToString(arr) {
if(typeof arr === 'string') {
return arr;
}
var str = '',
_arr = arr;
for(var i = 0; i < _arr.length; i++) {
var one = _arr[i].toString(2),
v = one.match(/^1+?(?=0)/);
if(v && one.length == 8) {
var bytesLength = v[0].length;
var store = _arr[i].toString(2).slice(7 - bytesLength);
for(var st = 1; st < bytesLength; st++) {
store += _arr[st + i].toString(2).slice(2);
}
str += String.fromCharCode(parseInt(store, 2));
i += bytesLength - 1;
} else {
str += String.fromCharCode(_arr[i]);
}
}
return str;
}
function utf8ByteToUnicodeStr(utf8Bytes){
var unicodeStr ="";
for (var pos = 0; pos < utf8Bytes.length;){
var flag= utf8Bytes[pos];
var unicode = 0 ;
if ((flag >>>7) === 0 ) {
unicodeStr+= String.fromCharCode(utf8Bytes[pos]);
pos += 1;
} else if ((flag &0xFC) === 0xFC ){
unicode = (utf8Bytes[pos] & 0x3) << 30;
unicode |= (utf8Bytes[pos+1] & 0x3F) << 24;
unicode |= (utf8Bytes[pos+2] & 0x3F) << 18;
unicode |= (utf8Bytes[pos+3] & 0x3F) << 12;
unicode |= (utf8Bytes[pos+4] & 0x3F) << 6;
unicode |= (utf8Bytes[pos+5] & 0x3F);
unicodeStr+= String.fromCharCode(unicode) ;
pos += 6;
}else if ((flag &0xF8) === 0xF8 ){
unicode = (utf8Bytes[pos] & 0x7) << 24;
unicode |= (utf8Bytes[pos+1] & 0x3F) << 18;
unicode |= (utf8Bytes[pos+2] & 0x3F) << 12;
unicode |= (utf8Bytes[pos+3] & 0x3F) << 6;
unicode |= (utf8Bytes[pos+4] & 0x3F);
unicodeStr+= String.fromCharCode(unicode) ;
pos += 5;
} else if ((flag &0xF0) === 0xF0 ){
unicode = (utf8Bytes[pos] & 0xF) << 18;
unicode |= (utf8Bytes[pos+1] & 0x3F) << 12;
unicode |= (utf8Bytes[pos+2] & 0x3F) << 6;
unicode |= (utf8Bytes[pos+3] & 0x3F);
unicodeStr+= String.fromCharCode(unicode) ;
pos += 4;
} else if ((flag &0xE0) === 0xE0 ){
unicode = (utf8Bytes[pos] & 0x1F) << 12;;
unicode |= (utf8Bytes[pos+1] & 0x3F) << 6;
unicode |= (utf8Bytes[pos+2] & 0x3F);
unicodeStr+= String.fromCharCode(unicode) ;
pos += 3;
} else if ((flag &0xC0) === 0xC0 ){ //110
unicode = (utf8Bytes[pos] & 0x3F) << 6;
unicode |= (utf8Bytes[pos+1] & 0x3F);
unicodeStr+= String.fromCharCode(unicode) ;
pos += 2;
} else{
unicodeStr+= String.fromCharCode(utf8Bytes[pos]);
pos += 1;
}
}
return unicodeStr;
}
"""