r0tracer介绍

项目地址

https://github.com/r0ysue/r0tracer

安卓Java层多功能追踪脚本==>相当于 精简版的Objection+Wallbreaker

功能

1、根据黑白名单批量追踪类的所有方法

hook("javax.crypto.Cipher", "$");

image-20241029105629339

2、在命中方法后打印出该类或对象的所有域值(包括参数、返回值、调用栈)

image-20241029105913147

image-20241029105943372

image-20241029110010440

3、极简的文本保存日志机制、易于搜索关键参数

4、针对加壳应用找不到类时可以切换Classloader

使用方法

1、修改r0tracer.js文件最底部处的代码,开启某一个Hook模式。

image-20241029110254227

2、推荐使用Frida14版本,并且将日志使用-o参数进行输出保存

$ frida -U -f com.r0ysue.example -l r0tracer.js  --no-pause -o saveLog5.txt

“-f”为Spawn模式,去掉”-f”为Attach模式

3、Frida版本=<12时,要加上--runtime=v8选项

$ frida -U com.r0ysue.example -l r0tracer.js  --runtime=v8 --no-pause -o saveLog6.txt

优势

  • objection增加延时spawn
  • objection增加批量hook类\方法\构造函数
  • Wallbreakerfrida14上还是一直崩
  • Wallbreaker增加hookinstancefields
  • inspectObject函数可以单独拿出去使用

源码解读

main()

修改r0tracer.js文件最底部处的代码,开启某一个Hook模式。

function main() {
Java.perform(function () {
console.Purple("r0tracer begin ... !")
//0. 增加精简模式,就是以彩虹色只显示进出函数。默认是关闭的,注释此行打开精简模式。
//isLite = true;
/*
//以下三种模式,取消注释某一行以开启
*/
//A. 简易trace单个类
traceClass("javax.crypto.Cipher")
//B. 黑白名单trace多个函数,第一个参数是白名单(包含关键字),第二个参数是黑名单(不包含的关键字)
// hook("javax.crypto.Cipher", "$");
//C. 报某个类找不到时,将某个类名填写到第三个参数,比如找不到com.roysue.check类。(前两个参数依旧是黑白名单)
// hook("com.roysue.check"," ","com.roysue.check");
})
}

三种Hook方式

  • hook已知类
traceClass("javax.crypto.Cipher")
  • hook关键字(黑白名单,前面是需要寻找的关键字,后面是忽略的关键字)
hook("javax.crypto.Cipher", "$")
  • hook关键字(黑白名单 + 第三个参数 关键类,前面时要寻找的关键字,后面时忽略的关键字)
hook("com.roysue.check"," ","com.roysue.xxx")

第三种是因为可能某个类不在当前classloader中,需要遍历所有classloader去寻找

hook&havahook

function hook(white, black, target = null) {
if (Java.available) {
Java.perform(function () {
javahook(white, black, target)
})
} else if (ObjC.available) {
ioshook(white, black)
} else {
console.log("please connect to either iOS or Android device ...")
}

}


function javahook(white, black, target = null) {
console.Red("start")
if (!(target === null)) {
console.LightGreen("Begin enumerateClassLoaders ...")
Java.enumerateClassLoaders({
onMatch: function (loader) {
try {
if (loader.findClass(target)) {
console.Red("Successfully found loader")
console.Blue(loader);
Java.classFactory.loader = loader;
console.Red("Switch Classloader Successfully ! ")
}
}
catch (error) {
console.Red(" continuing :" + error)
}
},
onComplete: function () {
console.Red("EnumerateClassloader END")
}
})
}
console.Red("Begin Search Class...")
var targetClasses = new Array();
Java.enumerateLoadedClasses({
onMatch: function (className) {
if (className.toString().toLowerCase().indexOf(white.toLowerCase()) >= 0 &&
(black == null || black == '' || className.toString().toLowerCase().indexOf(black.toLowerCase()) < 0)) {
console.Black("Found Class => " + className)
targetClasses.push(className);
traceClass(className);
}
}, onComplete: function () {
console.Black("Search Class Completed!")
}
})
var output = "On Total Tracing :" + String(targetClasses.length) + " classes :\r\n";
targetClasses.forEach(function (target) {
output = output.concat(target);
output = output.concat("\r\n")
})
console.Green(output + "Start Tracing ...")
}

1、如果填了关键方法, 回去遍历所有的classloader,并将当前使用的classloader设置为包含目标类的classloader

2、遍历这个classloader所有的类,满足黑白名单的情况下,将类名推入targetClasses

traceClass&JavaTraceClass

function traceClass(targetClass) {
if (Java.available) {
Java.perform(function () {
JavaTraceClass(targetClass)
})
} else if (ObjC.available) {
IosTraceClass(targetClass)
} else {
console.log("please connect to either iOS or Android device ...")
}
}


function JavaTraceClass(targetClass) {
//Java.use是新建一个对象哈,大家还记得么?
var hook = Java.use(targetClass);
//利用反射的方式,拿到当前类的所有方法
var methods = hook.class.getDeclaredMethods();
//建完对象之后记得将对象释放掉哈
hook.$dispose;
//将方法名保存到数组中
var parsedMethods = [];
var output = "";
output = output.concat("\tSpec: => \r\n")
methods.forEach(function (method) {
output = output.concat(method.toString())
output = output.concat("\r\n")
parsedMethods.push(method.toString().replace(targetClass + ".", "TOKEN").match(/\sTOKEN(.*)\(/)[1]);
});
//去掉一些重复的值
var Targets = uniqBy(parsedMethods, JSON.stringify);
// targets = [];
var constructors = hook.class.getDeclaredConstructors();
if (constructors.length > 0) {
constructors.forEach(function (constructor) {
output = output.concat("Tracing ", constructor.toString())
output = output.concat("\r\n")
})
Targets = Targets.concat("$init")
}
//对数组中所有的方法进行hook,
Targets.forEach(function (targetMethod) {
traceMethod(targetClass + "." + targetMethod);
});
//画个横线
for (var p = 0; p < 100; p++) {
output = output.concat("+");
}
console.Green(output);
}

这里是找到targetClass下所有的方法和构造方法,对每个方法都执行traceMethod

traceMethod

// trace单个类的所有静态和实例方法包括构造方法 trace a specific Java Method
function traceMethod(targetClassMethod) {
var delim = targetClassMethod.lastIndexOf(".");
if (delim === -1) return;
var targetClass = targetClassMethod.slice(0, delim)
var targetMethod = targetClassMethod.slice(delim + 1, targetClassMethod.length)
var hook = Java.use(targetClass);
if (!hook[targetMethod]) {
return;
}
var overloadCount = hook[targetMethod].overloads.length;
console.Red("Tracing Method : " + targetClassMethod + " [" + overloadCount + " overload(s)]");
for (var i = 0; i < overloadCount; i++) {
hook[targetMethod].overloads[i].implementation = function () {
//初始化输出
var output = "";
//画个横线
for (var p = 0; p < 100; p++) {
output = output.concat("==");
}
//域值
if (!isLite) { output = inspectObject(this, output); }
//进入函数
output = output.concat("\n*** entered " + targetClassMethod);
output = output.concat("\r\n")
// if (arguments.length) console.Black();
//参数
var retval = this[targetMethod].apply(this, arguments);
if (!isLite) {
for (var j = 0; j < arguments.length; j++) {
output = output.concat("arg[" + j + "]: " + arguments[j] + " => " + JSON.stringify(arguments[j]));
output = output.concat("\r\n")
}
//调用栈
output = output.concat(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
//返回值
output = output.concat("\nretval: " + retval + " => " + JSON.stringify(retval));
}
// inspectObject(this)
//离开函数
output = output.concat("\n*** exiting " + targetClassMethod);
//最终输出
// console.Black(output);
var r = parseInt((Math.random() * 7).toFixed(0));
var i = r;
var printOutput = null;
switch (i) {
case 1:
printOutput = console.Red;
break;
case 2:
printOutput = console.Yellow;
break;
case 3:
printOutput = console.Green;
break;
case 4:
printOutput = console.Cyan;
break;
case 5:
printOutput = console.Blue;
break;
case 6:
printOutput = console.Gray;
break;
default:
printOutput = console.Purple;
}
printOutput(output);
return retval;
}
}
}

在这里打印类的成员的值,和追踪方法的入参和出参的值,并打印出调用堆栈