0x01 Frida安装&环境

Frida安装

可以参考objection使用那节,里面有提到Frida的安装。

Frida代码编写环境

在系统里装上这个npm包,可以在任意工程中获得frida的代码提示、补全和API查看

npm i -g @type/frida-gum

新建一个文件测试,发现就可以成功查看提示、补全等操作了:

image.png

Frida常用命令

image.png

1、查看版本

frida --version

2、查看安卓进程pid

frida-ps -U

3、端口转发

1) 第一个tcp:电脑端口

2)第二个tcp:手机端口

将PC端的27042端口收到的数据,转发给到手机中27042端口

adb forward tcp:27042 tcp:27042

4、注入JS脚本

1) -U 后(包名|进程pid| -F 注入最前端的APP)

2) -l 后(文件路径)

frida -U com.android.xxx -l D:\NodeProjects\Demo\Hook.js

5、hook启动前app

1) -U -f 后(包名)

2)-l 后(文件路径)

frida -U -f com.xxx.xxx -l D:\NodeProjects\Demo\Hook.js --no-pause

6、APP挂起的方式创建进程

1)-U -f 后(包名)

2) -l 后(文件路径)

提示 Spawned com.xiaojianbang.app. Use %resume to let the main thread start executing!

就可以执行注入等等,%resume 恢复进程

frida -U -f com.xxx.xxx -l D:\NodeProjects\Demo\Hook.js

7、恢复挂起的进程

%resume

0x02 Frida常用API

1. Frida访问方法及变量

  • 方法调用
    • 静态方法使用Java.use获得后直接调用
    • 非静态方法需要使用Java.choose查找到类实例后进行调用
    • 构造方法为$init
    • 对于内部类,通过类名$内部类名去use或者choose
  • 静态/非静态变量
    • 设置成员变量的值,写法是[field_name].value = [value],其他方面和函数一样。
    • 如果有一个成员变量和成员函数的名字相同,则在其前面加一个_,如_[field_name].value = [value]

静态方法调用Java.use

function main() {
Java.perform(function () {
// 先查找HookedObject类,然后hook其的stringTwo方法
Java.use("com.forgotten.fridatestapp.HookedObject").stringTwo.implementation = function (arg) {
// this为当前实例,获得原方法执行的结果
var result = this.stringTwo(arg);
// 打印参数和原方法结果
console.log("stringTwo arg,result: ", arg, result);
// 对方法的结果进行修改(相当于重写了该方法)
return Java.use("java.lang.String").$new("hhello");
};

// hook addNumber方法 function参数列表可以什么都不填
Java.use("com.forgotten.fridatestapp.HookedObject").addNumber.overload("int", "int").implementation = function () {
// 内置有变量[argument],为方法的参数列表
for (var i = 0; i < arguments.length; i++) {
console.log("addNumber arguments[" + i + "]=" + arguments[i]);
}
var result = this.addNumber(arguments[0], arguments[1]);
console.log("addNumber arg,result: ", arguments, result);
return 99;
};
});
}
// Frida一附加上,就执行函数
setImmediate(main);

非静态方法调用Java.choose

function invoke() {
Java.perform(function () {
// 在内存中搜索类的实例
Java.choose("com.forgotten.fridatestapp.HookedObject", {
// 如果匹配上执行回调,参数为类实例
onMatch: function (instance) {
console.log("Found `HookedObject` instance:", instance);
// 打印私有成员变量的值,需要[field].value
console.log("instance.score =", instance.score.value);
// 打印静态成员变量的值
console.log("HookedObject.msg =", Java.use("com.forgotten.fridatestapp.HookedObject").msg.value);
// 修改成员变量的值
instance.score.value = Java.use("java.lang.Integer").parseInt("-900");
// 打印修改之后的值
console.log("instance.score =", instance.score.value);
// 与方法同名的成员变量,需前面加_
console.log("instance.stringTwo =", instance._stringTwo.value);
},
// 搜索完成执行回调
onComplete: function () {
console.log("Found Completed");
},
});
});
}
// Frida附加上后延迟5秒执行,期间由于逻辑问题需要主动点击按钮来实例化对象
setTimeout(invoke, 5000);

静态变量修改

function staticField(){
// 定义一个名为 staticField 的函数,没有参数。

Java.perform(function(){
// Java.perform 是一个 Frida API,用于确保在当前线程上执行以下代码块。
// 这通常用于确保代码在 Java 虚拟机上下文中执行。

var divscale = Java.use("com.example.junior.util.Arith").DEF_DIV_SCALE.value;
// 使用 Java.use 方法获取对类 com.example.junior.util.Arith 的引用,
// 然后访问静态字段 DEF_DIV_SCALE 的值,并将其赋给变量 divscale。

console.log("divscale1 is =>", divscale);
// 使用 console.log 打印原始的 DEF_DIV_SCALE 字段的值。

Java.use("com.example.junior.util.Arith").DEF_DIV_SCALE.value = 20;
// 再次使用 Java.use 方法访问类 com.example.junior.util.Arith,
// 并将静态字段 DEF_DIV_SCALE 的值修改为 20。

divscale = Java.use("com.example.junior.util.Arith").DEF_DIV_SCALE.value;
// 重新读取修改后的 DEF_DIV_SCALE 字段的值,并更新变量 divscale。

console.log("divscale2 is =>", divscale);
// 再次打印修改后的 DEF_DIV_SCALE 字段的值,以验证是否成功修改。
})
}

非静态函数中的变量修改

function dynamicField(){
// 定义一个名为 dynamicField 的函数,没有参数。

Java.perform(function(){
// Java.perform 是一个 Frida API,用于确保在当前线程上执行以下代码块。
// 这通常用于确保代码在 Java 虚拟机上下文中执行。

Java.choose("com.example.junior.CalculatorActivity", {
// Java.choose 是一个 Frida API,用于枚举当前堆上所有属于指定类的实例。

onMatch: function(instance){
// 当找到一个匹配的实例时,会调用此函数。
// 参数 instance 是当前找到的类的实例。

console.log("found instance =>", instance);
// 打印找到的实例,以便调试或确认。

console.log("instance showText is =>", instance.showText.value);
// 打印实例中 showText 字段的当前值。

instance.showText.value = "123";
// 修改实例中 showText 字段的值为 "123"。
// 这假设 showText 是一个可以访问且可修改的字段。
},

onComplete: function(){
// 当所有实例都已经被枚举后,会调用此函数。

console.log('Search complete');
// 打印一条消息,表明搜索过程已经完成。
}
})
})
}

Hook构造函数$init

// We need to replace .$init() instead of .$new(), since .$new() = .alloc() + .init()
Java.perform(function () {
var money = Java.use('com.xx.app.Money');
money.$init.implementation = function (a, b) {
console.log("构造函数Hook中...", a, b);
return this.$init(a, b);
}

// hook 构造方法 $init
var MoneyClass = Java.use("com.kevin.app.Money");
MoneyClass.$init.overload().implementation = function () {
console.log("hook Money $init");
this.$init();
}

var StringClass = Java.use("java.lang.String");
var MoneyClass = Java.use("com.xiaojianbang.app.Money");
MoneyClass.$init.overload('java.lang.String', 'int').implementation = function (x, y) {
console.log('hook Money init');
var myX = StringClass.new("Hello World!");
var myY = 9999;
this.$init(myX, myY);
}
});

有参构造主动调用

// Utils.test(new Money(200,"美元"))
// 这行是一个注释,表示一个可能的调用场景,即调用 Utils 类的 test 方法,并传入一个 Money 类的实例。

Java.perform(function () {
// Java.perform 是一个 Frida API,用于确保在当前线程上执行以下代码块。
// 这通常用于确保代码在 Java 虚拟机上下文中执行。

var money = Java.use('com.xx.app.Money') // 如果要自定义实例化就要获取并重写
// 获取 Money 类的引用,这样就可以在后续代码中使用它来创建新的实例。

var utils = Java.use('com.xx.app.Utils');
// 获取 Utils 类的引用。

utils.test.overload("com.xx.app.Money").implementation = function (obj) {
// 使用 Frida 的 overload 方法指定要挂钩的 test 方法的具体重载版本,
// 这里指定的是接受一个 com.xx.app.Money 类型参数的重载。
// 然后将该方法的实现替换为一个新的函数。

var myBytes = StringClass.$new("Hello World").getBytes();
// 创建一个新的 String 实例,内容为 "Hello World",并获取其字节表示。
// 注意:这里 StringClass 应该是 Java 类 java.lang.String 的引用,
// 但是在代码中没有看到 StringClass 的定义,可能是一个疏忽。

// 重新实例化 $new()
var mon = money.$new(999,'我的天')
// 使用 $new 方法创建一个新的 Money 实例,参数分别是 999 和 "我的天"。

return mon.getInfo(); // 根据需求return
// 调用新创建的 Money 实例的 getInfo 方法,并返回其结果。
// 这意味着原始的 test 方法的行为被修改,现在它将返回新创建的 Money 实例的信息。
}
});

2. 枚举类&方法&接口enumerateLoadedClasses

枚举所有的类并定位类代码示例

setTimeout(function (){
Java.perform(function (){
console.log("n[*] enumerating classes...");
//Java对象的API enumerateLoadedClasses
Java.enumerateLoadedClasses({
//该回调函数中的_className参数就是类的名称,每次回调时都会返回一个类的名称
onMatch: function(_className){
//在这里将其输出
console.log("[*] found instance of '"+_className+"'");

//如果只需要打印出com.roysue包下所有类把这段注释即可,想打印其他的替换掉indexOf中参数即可定位到~
//if(_className.toString().indexOf("com.roysue")!=-1)
//{
// console.log("[*] found instance of '"+_className+"'");
//}
},
onComplete: function(){
//会在枚举类结束之后回调一次此函数
console.log("[*] class enuemration complete");
}
});
});
});

枚举类的所有方法并定位方法代码

//Hook类的所有方法
Java.perform(function(){
var md5 = Java.use("com.xxx.xxx.xxx");
var methods = md5.class.getDeclaredMethods();
for(var j = 0; j < methods.length; j++){
var methodName = methods[j].getName();
console.log(methodName);


}
});

function hook_overload_5() {
//确认Java运行时环境是否可用,如果可用,执行以下操作。
if(Java.available) {
Java.perform(function () {
//枚举com.roysue.roysueapplication.User$clz类中的所有方法。
var a = enumMethods("com.roysue.roysueapplication.User$clz")
a.forEach(function(s) {
//将这些方法名打印到控制台
console.log(s);
});
});
}
}

枚举接口实现

function searchInterface(){
// 定义一个名为 searchInterface 的函数。

Java.perform(function(){
// Java.perform 确保接下来的代码在 Java 的上下文中执行。

Java.enumerateLoadedClasses({
// Java.enumerateLoadedClasses 用于枚举当前加载的所有 Java 类。

onComplete: function(){},
// 当所有类都枚举完成后,会调用此函数。这里没有实现任何操作。

onMatch: function(name, handle){
// 当找到一个匹配的类时,会调用此函数。
// 参数 name 是类的全名,handle 是类的内部句柄。

if (name.indexOf("com.r0ysue.a0526printout") > -1) { // 使用包名进行过滤
// 如果类名包含 "com.r0ysue.a0526printout",则执行以下代码。

console.log("find class");
// 打印一条消息表示找到一个类。

var targetClass = Java.use(name);
// 使用 Java.use 获取类的引用。

var interfaceList = targetClass.class.getInterfaces(); // 使用反射获取类实现的接口数组
// 使用 getInterfaces 方法获取类实现的所有接口的列表。

if (interfaceList.length > 0) {
// 如果类实现了至少一个接口,则执行以下代码。

console.log(name) // 打印类名
// 打印类名。

for (var i in interfaceList) {
// 遍历接口列表。

console.log("\t", interfaceList[i].toString()); // 直接打印接口名称
// 打印每个接口的名称。使用 toString 方法获取接口的字符串表示。
}
}
}
}
})
})

3. 不可见函数名hook

当方法名被混淆时֏,打印出来所有的类名%D6%8F,用编码后的字符串hook

Java.perform(
function x() {
// 定义目标类
var targetClass = "com.example.hooktest.MainActivity";
var hookCls = Java.use(targetClass);
// 获得目标类的所有方法
var methods = hookCls.class.getDeclaredMethods();
// 遍历所有方法名
for (var i in methods) {
console.log(methods[i].toString());
console.log(encodeURIComponent(methods[i].toString().replace(/^.*?\.([^\s\.\(\)]+)\(.*?$/, "$1")));
}
// 如果有等于不可见字符类的
hookCls[decodeURIComponent("%D6%8F")]
.implementation = function (x) {
console.log("original call: fun(" + x + ")");
var result = this[decodeURIComponent("%D6%8F")](900);
return result;
}
}
)

4. 编写自定义类Java.registerClass

Java.perform(function () {
// 新建类实现的接口,先获取其类
var face = Java.use("com.forgotten.fridatestapp.construct.CheerInterface");
// 创建一个类
var beer = Java.registerClass({
// 类的名称,小写就可
name: "com.forgotten.fridatestapp.beer",
// 实现的接口数组,多个来写[a, b]
implements: [face],
// 类中含有的方法
methods: {
cheer: function () {
console.log("Cheer!!!");
},
},
/**
* 其余的可写属性:`super` 父类;`protocols` 该类遵循的协议数组?
*/
});
console.log("beer:", beer);
// 调用一下自己编写的类的方法
beer.$new().cheer();
});

5. Frida Hook重载overload

function hookdecodeimgkey() {
Java.perform(function () {
var base64 = Java.use("android.util.Base64")
Java.use("com.ilulutv.fulao2.other.i.b").b.overload('[B', '[B', 'java.lang.String').implementation = function (key, iv, image) {
var result = this.b(key, iv, image);
console.log("key", base64.encodeToString(key, 0));
console.log("iv", base64.encodeToString(iv, 0));
return result;
}

var UtilsClass = Java.use("com.kevin.app.Utils");
// 重载无参方法
UtilsClass.test.overload().implementation = function () {
console.log("hook overload no args");
return this.test();
}

// 重载有参方法 - 基础数据类型
UtilsClass.test.overload('int').implementation = function(num){
console.log("hook overload int args");
var myNum = 9999;
var oriResult = this.test(num);
console.log("oriResult is :" + oriResult);
return this.test(myNum);
}

// 重载有参方法 - 引用数据类型
UtilsClass.test.overload('com.kevin.app.Money').implementation = function(money){
console.log("hook Money args");
return this.test(money);
}

// hook 指定方法的所有重载
var ClassName = Java.use("com.xiaojianbang.app.Utils");
var overloadsLength = ClassName.test.overloads.length;
for (var i = 0; i < overloadsLength; i++){
ClassName.test.overloads[i].implementation = function () {
// 遍历打印 arguments
for (var a = 0; a < arguments.length; a++){
console.log(a + " : " + arguments[a]);
}
// 调用原方法
return this.test.apply(this,arguments);
}
}
})
}

6. Hook内部类&匿名类&枚举类

内部类$

function main(){
Java.perfor(function(){
// hook 内部类
// 内部类使用$进行分隔 不使用.
var InnerClass = Java.use("com.xiaojianbang.app.Money$innerClass");
// 重写内部类的 $init 方法
InnerClass.$init.overload("java.lang.String","int").implementation = function(x,y){
console.log("x: ",x);
console.log("y: ",y);
this.$init(x,y);
}
})
}

setImmediate(main)

匿名类$1

// 接口, 抽象类, 不可以被new
// 接口, 抽象类 要使用必须要实例化, 实例化不是通过new, 而是通过实现接口方法, 继承抽象类等方式
// new __接口__{} 可以理解成 new 了一个实现接口的匿名类, 在匿名类的内部(花括号内),实现了这个接口

function main(){
Java.perform(function(){
// hook 匿名类
// 匿名类在 smail中以 $1, $2 等方式存在, 需要通过 java 行号去 smail 找到准确的匿名类名称
var NiMingClass = Java.use("com.xiaojianbang.app.MainActivity$1");
NiMingClass.getInfo.implementation = function (){
return "kevin change 匿名类";
}
})
}

setImmediate(main)

枚举类

function enumPrint(){
Java.perform(function(){
Java.choose("com.r0ysue.a0526printout.Signal",{
onComplete: function(){},
onMatch: function(instance){
console.log('find it ,',instance);
console.log(instance.class.getName());
}
})
})
}

7. Hook动态加载类enumerateClassLoaders

对于调用时动态加载的类,遍历ClassLoader然后找到能加载该类的ClassLoader;然后将Frida的默认classloader为设置为找到的这个ClassLoader;再使用Java.use来加载该类

// 枚举内存中的 类加载器
Java.enumerateClassLoaders({
onMatch:function(loader){
try{
// 如果找到的类加载器 能加载的类有[class_name]
if(loader.findClass("[class_name]")){
console.log("Successfully found loader")
console.log(loader);
// 设置 java默认的classloader
Java.classFactory.loader = loader ;
}
}
catch(error){
console.log("find error:" + error)
}
},
onComplete: function () {
console.log("End")
}
})
// 再 使用该类
Java.use("[class_name]")

2和7的区别

Java.enumerateClassLoadersJava.enumerateLoadedClasses的区别:

Java.enumerateClassLoaders

  • 目的:这个函数用于枚举所有当前活跃的 Java 类加载器(ClassLoaders)。
  • 返回值:它返回一个包含类加载器实例的数组。
  • 使用场景:当你需要获取对特定类加载器的引用,以进一步操作类加载器(例如,查找它加载的类、定义新类或查找资源)时,这个函数非常有用。
Java.perform(function () {
Java.enumerateClassLoaders({
onMatch: function (loader) {
console.log(loader);
// 可以使用 loader.findClass(name) 来查找类,或者使用其他类加载器相关的方法。
},
onComplete: function () {
console.log('Enumerated all class loaders.');
}
});
});

Java.enumerateLoadedClasses

  • 目的:这个函数用于枚举由所有活跃的类加载器加载的所有 Java 类。
  • 返回值:它不会直接返回类实例的数组,而是通过回调函数 onMatch 提供类名。
  • 使用场景:当你需要获取当前运行时环境中所有加载的类的列表时,这个函数非常有用。
Java.perform(function () {
Java.enumerateLoadedClasses({
onMatch: function (className) {
console.log(className);
// 可以使用 Java.use(className) 来获取类的引用。
},
onComplete: function () {
console.log('Enumerated all loaded classes.');
}
});
});

主要区别

  • 操作对象:enumerateClassLoaders 操作的是类加载器实例,而 enumerateLoadedClasses 操作的是由这些类加载器加载的类。
  • 使用方式:enumerateClassLoaders 通常用于当你需要更细粒度的控制,比如加载自定义类或访问特定类加载器的资源时。enumerateLoadedClasses 则用于当你需要列出所有已加载的类时。
  • 返回值:enumerateClassLoaders 返回类加载器实例,enumerateLoadedClasses 则通过回调函数提供类名。

在许多情况下,你可能需要结合使用这两个函数,首先枚举类加载器,然后对每个类加载器使用 findClass 方法来查找特定的类或枚举它们加载的所有类。

8. Frida打印栈回溯

Exception类抛出异常

function printStack(name) {
Java.perform(function () {
var Exception = Java.use("java.lang.Exception");
var ins = Exception.$new("Exception");
var straces = ins.getStackTrace();
if (straces != undefined && straces != null) {
var strace = straces.toString();
var replaceStr = strace.replace(/,/g, "\\n");
console.log("=============================" + name + " Stack strat=======================");
console.log(replaceStr);
console.log("=============================" + name + " Stack end=======================\r\n");
Exception.$dispose();
}
});
}

Log类Throwable()&Exception

function printStacks(name){
console.log("====== printStacks start ====== " + name + "==============================")

// sample 1
var throwable = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new());
console.log(throwable);

// sample 2
var exception = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new());
console.log(exception);

console.log("====== printStacks end ======== " + name + "==============================")
}

这块也可以针对特定函数,来打印它的调用栈。

这块对 HookObject类中的 stringTwo函数进行hook:

image.png

function main(){
Java.perform(function(){
HookedObject = Java.use("om.forgotten.fridatestapp.HookedObject");
HookedObject.stringTwo.implementation = function(arg){
var result = stringTwo(arg);

//方法一
var Exception = Java.use("java.lang.Exception"); //这块定义一下异常类
var e = Exception.$new("StackTrace"); //捕获异常类的构造函数
var straces = e.getMessage(); //打印构造函数中初始化的参数,返回值是一个数组
console.log(straces.toString())

//方法二
var Log = Java.use("android.util.Log"); //定义一下log类
var Throwable = java.use("java.lang.Throwable"); //定义异常类Throwable
var t = Throwable.$new(); //捕获异常类的构造函数
console.log("StackTrace2",Log.getStackTraceString(t)); //打印堆栈

//方法三
var Log = Java.use("android.util.Log"); //定义一下log类
var Exception = Java.use("java.lang.Exception"); //这块定义一下异常类
var e = Exception.$new("StackTrace"); //捕获异常类的构造函数
console.log("StackTrace3",Log.getStackTraceString(e)); //打印堆栈


return result;
}

})

}

setImmediate(main);

9. 请求调用栈

var class_Socket = Java.use("java.net.Socket");
class_Socket.getOutputStream.overload().implementation = function(){
send("getOutputSteam");
var result = this.getOutputStream();
var bt = Java.use("android.util.Log").getStackTraceString(
Java.use("java.lang.Exception").$new();
)
console.log("Backtrace:" + bt);
send(result);
return result;
}

10. 常见算法hook

Java.perform(function () {
var secretKeySpec = Java.use('javax.crypto.spec.SecretKeySpec');
secretKeySpec.$init.overload('[B', 'java.lang.String').implementation = function (a, b) {
showStacks();
var result = this.$init(a, b);
send("======================================");
send("算法名:" + b + "|Dec密钥:" + bytesToString(a));
send("算法名:" + b + "|Hex密钥:" + bytesToHex(a));
return result;
}
var mac = Java.use('javax.crypto.Mac');
mac.getInstance.overload('java.lang.String').implementation = function (a) {
showStacks();
var result = this.getInstance(a);
send("======================================");
send("算法名:" + a);
return result;
}
mac.update.overload('[B').implementation = function (a) {
showStacks();
this.update(a);
send("======================================");
send("update:" + bytesToString(a))
}
mac.update.overload('[B', 'int', 'int').implementation = function (a, b, c) {
showStacks();
this.update(a, b, c)
send("======================================");
send("update:" + bytesToString(a) + "|" + b + "|" + c);
}
mac.doFinal.overload().implementation = function () {
showStacks();
var result = this.doFinal();
send("======================================");
send("doFinal结果:" + bytesToHex(result));
send("doFinal结果:" + bytesToBase64(result));
return result;
}
mac.doFinal.overload('[B').implementation = function (a) {
showStacks();
var result = this.doFinal(a);
send("======================================");
send("doFinal参数:" + bytesToString(a));
send("doFinal结果:" + bytesToHex(result));
send("doFinal结果:" + bytesToBase64(result));
return result;
}
var md = Java.use('java.security.MessageDigest');
md.getInstance.overload('java.lang.String', 'java.lang.String').implementation = function (a, b) {
showStacks();
send("======================================");
send("算法名:" + a);
return this.getInstance(a, b);
}
md.getInstance.overload('java.lang.String').implementation = function (a) {
showStacks();
send("======================================");
send("算法名:" + a);
return this.getInstance(a);
}
md.update.overload('[B').implementation = function (a) {
showStacks();
send("======================================");
send("update:" + bytesToString(a))
return this.update(a);
}
md.update.overload('[B', 'int', 'int').implementation = function (a, b, c) {
showStacks();
send("======================================");
send("update:" + bytesToString(a) + "|" + b + "|" + c);
return this.update(a, b, c);
}
md.digest.overload().implementation = function () {
showStacks();
send("======================================");
var result = this.digest();
send("digest结果:" + bytesToHex(result));
send("digest结果:" + bytesToBase64(result));
return result;
}
md.digest.overload('[B').implementation = function (a) {
showStacks();
send("======================================");
send("digest参数:" + bytesToString(a));
var result = this.digest(a);
send("digest结果:" + bytesToHex(result));
send("digest结果:" + bytesToBase64(result));
return result;
}
var ivParameterSpec = Java.use('javax.crypto.spec.IvParameterSpec');
ivParameterSpec.$init.overload('[B').implementation = function (a) {
showStacks();
var result = this.$init(a);
send("======================================");
send("iv向量:" + bytesToString(a));
send("iv向量:" + bytesToHex(a));
return result;
}
var cipher = Java.use('javax.crypto.Cipher');
cipher.getInstance.overload('java.lang.String').implementation = function (a) {
showStacks();
var result = this.getInstance(a);
send("======================================");
send("模式填充:" + a);
return result;
}
cipher.update.overload('[B').implementation = function (a) {
showStacks();
var result = this.update(a);
send("======================================");
send("update:" + bytesToString(a));
return result;
}
cipher.update.overload('[B', 'int', 'int').implementation = function (a, b, c) {
showStacks();
var result = this.update(a, b, c);
send("======================================");
send("update:" + bytesToString(a) + "|" + b + "|" + c);
return result;
}
cipher.doFinal.overload().implementation = function () {
showStacks();
var result = this.doFinal();
send("======================================");
send("doFinal结果:" + bytesToHex(result));
send("doFinal结果:" + bytesToBase64(result));
return result;
}
cipher.doFinal.overload('[B').implementation = function (a) {
showStacks();
var result = this.doFinal(a);
send("======================================");
send("doFinal参数:" + bytesToString(a));
send("doFinal结果:" + bytesToHex(result));
send("doFinal结果:" + bytesToBase64(result));
return result;
}
var x509EncodedKeySpec = Java.use('java.security.spec.X509EncodedKeySpec');
x509EncodedKeySpec.$init.overload('[B').implementation = function (a) {
showStacks();
var result = this.$init(a);
send("======================================");
send("RSA密钥:" + bytesToBase64(a));
return result;
}
var rSAPublicKeySpec = Java.use('java.security.spec.RSAPublicKeySpec');
rSAPublicKeySpec.$init.overload('java.math.BigInteger', 'java.math.BigInteger').implementation = function (a, b) {
showStacks();
var result = this.$init(a, b);
send("======================================");
//send("RSA密钥:" + bytesToBase64(a));
send("RSA密钥N:" + a.toString(16));
send("RSA密钥E:" + b.toString(16));
return result;
}
});

11. 常见转换模板

//工具相关函数
var base64EncodeChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/',
base64DecodeChars = new Array((-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), 62, (-1), (-1), (-1), 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, (-1), (-1), (-1), (-1), (-1), (-1), (-1), 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, (-1), (-1), (-1), (-1), (-1), (-1), 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, (-1), (-1), (-1), (-1), (-1));

function stringToBase64(e) {
var r, a, c, h, o, t;
for (c = e.length, a = 0, r = ''; a < c;) {
if (h = 255 & e.charCodeAt(a++), a == c) {
r += base64EncodeChars.charAt(h >> 2),
r += base64EncodeChars.charAt((3 & h) << 4),
r += '==';
break
}
if (o = e.charCodeAt(a++), a == c) {
r += base64EncodeChars.charAt(h >> 2),
r += base64EncodeChars.charAt((3 & h) << 4 | (240 & o) >> 4),
r += base64EncodeChars.charAt((15 & o) << 2),
r += '=';
break
}
t = e.charCodeAt(a++),
r += base64EncodeChars.charAt(h >> 2),
r += base64EncodeChars.charAt((3 & h) << 4 | (240 & o) >> 4),
r += base64EncodeChars.charAt((15 & o) << 2 | (192 & t) >> 6),
r += base64EncodeChars.charAt(63 & t)
}
return r
}

function base64ToString(e) {
var r, a, c, h, o, t, d;
for (t = e.length, o = 0, d = ''; o < t;) {
do
r = base64DecodeChars[255 & e.charCodeAt(o++)];
while (o < t && r == -1);
if (r == -1)
break;
do
a = base64DecodeChars[255 & e.charCodeAt(o++)];
while (o < t && a == -1);
if (a == -1)
break;
d += String.fromCharCode(r << 2 | (48 & a) >> 4);
do {
if (c = 255 & e.charCodeAt(o++), 61 == c)
return d;
c = base64DecodeChars[c]
} while (o < t && c == -1);
if (c == -1)
break;
d += String.fromCharCode((15 & a) << 4 | (60 & c) >> 2);
do {
if (h = 255 & e.charCodeAt(o++), 61 == h)
return d;
h = base64DecodeChars[h]
} while (o < t && h == -1);
if (h == -1)
break;
d += String.fromCharCode((3 & c) << 6 | h)
}
return d
}

function hexToBase64(str) {
return base64Encode(String.fromCharCode.apply(null, str.replace(/\r|\n/g, "").replace(/([\da-fA-F]{2}) ?/g, "0x$1 ").replace(/ +$/, "").split(" ")));
}

function base64ToHex(str) {
for (var i = 0, bin = base64Decode(str.replace(/[ \r\n]+$/, "")), hex = []; i < bin.length; ++i) {
var tmp = bin.charCodeAt(i).toString(16);
if (tmp.length === 1)
tmp = "0" + tmp;
hex[hex.length] = tmp;
}
return hex.join("");
}

function hexToBytes(str) {
var pos = 0;
var len = str.length;
if (len % 2 != 0) {
return null;
}
len /= 2;
var hexA = new Array();
for (var i = 0; i < len; i++) {
var s = str.substr(pos, 2);
var v = parseInt(s, 16);
hexA.push(v);
pos += 2;
}
return hexA;
}

function bytesToHex(arr) {
var str = '';
var k, j;
for (var i = 0; i < arr.length; i++) {
k = arr[i];
j = k;
if (k < 0) {
j = k + 256;
}
if (j < 16) {
str += "0";
}
str += j.toString(16);
}
return str;
}

function stringToHex(str) {
var val = "";
for (var i = 0; i < str.length; i++) {
if (val == "")
val = str.charCodeAt(i).toString(16);
else
val += str.charCodeAt(i).toString(16);
}
return val
}

function stringToBytes(str) {
var ch, st, re = [];
for (var i = 0; i < str.length; i++) {
ch = str.charCodeAt(i);
st = [];
do {
st.push(ch & 0xFF);
ch = ch >> 8;
}
while (ch);
re = re.concat(st.reverse());
}
return re;
}

//将byte[]转成String的方法
function bytesToString(arr) {
var str = '';
arr = new Uint8Array(arr);
for (var i in arr) {
str += String.fromCharCode(arr[i]);
}
return str;
}

function bytesToBase64(e) {
var r, a, c, h, o, t;
for (c = e.length, a = 0, r = ''; a < c;) {
if (h = 255 & e[a++], a == c) {
r += base64EncodeChars.charAt(h >> 2),
r += base64EncodeChars.charAt((3 & h) << 4),
r += '==';
break
}
if (o = e[a++], a == c) {
r += base64EncodeChars.charAt(h >> 2),
r += base64EncodeChars.charAt((3 & h) << 4 | (240 & o) >> 4),
r += base64EncodeChars.charAt((15 & o) << 2),
r += '=';
break
}
t = e[a++],
r += base64EncodeChars.charAt(h >> 2),
r += base64EncodeChars.charAt((3 & h) << 4 | (240 & o) >> 4),
r += base64EncodeChars.charAt((15 & o) << 2 | (192 & t) >> 6),
r += base64EncodeChars.charAt(63 & t)
}
return r
}

function base64ToBytes(e) {
var r, a, c, h, o, t, d;
for (t = e.length, o = 0, d = []; o < t;) {
do
r = base64DecodeChars[255 & e.charCodeAt(o++)];
while (o < t && r == -1);
if (r == -1)
break;
do
a = base64DecodeChars[255 & e.charCodeAt(o++)];
while (o < t && a == -1);
if (a == -1)
break;
d.push(r << 2 | (48 & a) >> 4);
do {
if (c = 255 & e.charCodeAt(o++), 61 == c)
return d;
c = base64DecodeChars[c]
} while (o < t && c == -1);
if (c == -1)
break;
d.push((15 & a) << 4 | (60 & c) >> 2);
do {
if (h = 255 & e.charCodeAt(o++), 61 == h)
return d;
h = base64DecodeChars[h]
} while (o < t && h == -1);
if (h == -1)
break;
d.push((3 & c) << 6 | h)
}
return d
}



// 打印log
function showStacks() {
Java.perform(function () {
send(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new()));
});
}

// 字节数组转hex字符串
function bytesToHex(arr) {
var str = "";
for (var i = 0; i < arr.length; i++) {
var tmp = arr[i];
if (tmp < 0) {
tmp = (255 + tmp + 1).toString(16);
} else {
tmp = tmp.toString(16);
}
if (tmp.length == 1) {
tmp = "0" + tmp;
}
str += tmp;
}
return str;
}

function bytesToBase64(arr) {
var str = "";
for (var i = 0; i < arr.length; i++) {
var tmp = arr[i];
if (tmp < 0) {
tmp = (255 + tmp + 1).toString(16);
} else {
tmp = tmp.toString(16);
}
if (tmp.length == 1) {
tmp = "0" + tmp;
}
str += tmp;
}
return str;
}

function bytesToString(arr) {
var str = "";
for (var i = 0; i < arr.length; i++) {
var tmp = arr[i];
if (tmp < 0) {
tmp = (255 + tmp + 1).toString(16);
} else {
tmp = tmp.toString(16);
}
if (tmp.length == 1) {
tmp = "0" + tmp;
}
str += tmp;
}
return str;
}
function byteToHexString(uint8arr) {
if (!uint8arr) {
return '';
}
var hexStr = '';
for (var i = 0; i < uint8arr.length; i++) {
var hex = (uint8arr[i] & 0xff).toString(16);
hex = (hex.length === 1) ? '0' + hex : hex;
hexStr += hex;
}

return hexStr.toUpperCase();
}


function stringToUint8Array(str){
var arr = [];
for (var i = 0, j = str.length; i < j; ++i) {
arr.push(str.charCodeAt(i));
}

var tmpUint8Array = new Uint8Array(arr);
return tmpUint8Array
}

function str2arraybffer(str) {
var buf = new ArrayBuffer(str.length * 2); // 每个字符占用2个字节
var bufView = new Uint16Array(buf);
for (var i = 0, strLen = str.length; i < strLen; i++) {
bufView[i] = str.charCodeAt(i);
}
return buf;
}

function printBytes(b){

var hexstr = "";
for (var i=0; i< b.length; i++)
{
var uByte = (b[i]>>>0)&0xff;
var n = uByte.toString(16);
hexstr += "0x" + ("00" + n).slice(-2)+", ";
}

return hexstr;
}

//字节数组转十六进制字符串,对负值填坑
// 二进制数据(包括内存地址)在计算机中一般以16进制的方式表示
function Bytes2HexString(arrBytes) {
var str = "";
for (var i = 0; i < arrBytes.length; i++) {
var tmp;
var num=arrBytes[i];
if (num < 0) {
//此处填坑,当byte因为符合位导致数值为负时候,需要对数据进行处理
tmp =(255+num+1).toString(16);
} else {
tmp = num.toString(16);
}
if (tmp.length == 1) {
tmp = "0" + tmp;
}
str += tmp;
}
return str;
}

// 会转成有符号的数字 JSON.stringify(bytes)
function HexString2Bytes(str) {
var pos = 0;
var len = str.length;
if (len % 2 != 0) {
return null;
}
len /= 2;
var arrBytes = new Array();
for (var i = 0; i < len; i++) {
var s = str.substr(pos, 2);
var v = parseInt(s, 16);
// 转成有符号的 10进制
if (v > 127) { v = v - 256 }
// end
arrBytes.push(v);
pos += 2;
}
return arrBytes;
}

function jstring2Str(jstring) {
var ret;
Java.perform(function() {
var String = Java.use("java.lang.String");
ret = Java.cast(jstring, String);
});
return ret;
}

function jbyteArray2Array(jbyteArray) {
var ret;
Java.perform(function() {
var b = Java.use('[B');
var buffer = Java.cast(jbyteArray, b);
ret = Java.array('byte', buffer);
});
return ret;
}

function getParamType(obj) {
return obj == null ? String(obj) : Object.prototype.toString.call(obj).replace(/\[object\s+(\w+)\]/i, "$1") || "object";
}

0x03 Frida RPC

RPC主动调用

rpc.exports导出名不可以有大写字母或者下划线

function invoke() {
Java.perform(function () {
// 搜索HookedObject类实例
Java.choose("com.forgotten.fridatestapp.HookedObject", {
onMatch: function (instance) {
console.log("found HookedObject:", instance);
// 查找到实例后主动调用方法
console.log("ho.getPasswd():", instance.getPasswd("123 ABC"));
},
onComplete: function () {
console.log("HookedObject: search complete.");
},
});
});
}
/* 测试函数 */
function test() {
console.log("I'm Frida_rpc.js!");
}
/* 导出函数列表(可供py调用的) py函数映射和实际函数名 */
rpc.exports = {
invokefunc: invoke,
testfunc: test,
};
import time
import frida

## handler | script脚本信息交互函数
def my_message_handler(message,payload):
print(message)
print(payload)

# 通过Usb连接设备
# device = frida.get_usb_device()

# 通过ip:port 连接设备
device = frida.get_device_manager().add_remote_device("192.168.0.104:8888")
################ 通过spawn方式启动 ###########################
pid = device.spawn(["com.forgotten.fridatestapp"])
device.resume(pid)
time.sleep(1)
session = device.attach(pid)
##### <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
# session = device.attach("com.forgotten.fridatestapp")
################ 通过attach现有进程方式启动 ###################
with open("./frida_rpc.js") as f:
# 创建一个新脚本
script = session.create_script(f.read())
# 加载信息交互handler函数
script.on("message",my_message_handler)
# 加载脚本
script.load()

command = ""
while True:
command = input("Enter Command(y/t/n): ")
if command=="y":
script.exports.invokefunc()
elif command=="t":
script.exports.testfunc()
elif command=="n":
break

RPC动态修改

Java.perform(function () {
Java.use("com.forgotten.fridatestapp.HookedObject").getPasswd.implementation = function () {
// 需要发送给python的字符串:由函数的参数和结果拼接而成
var string_to_send = arguments[0] + ":" + this.getPasswd(arguments[0]);
var string_to_recv;
// 发送到python程序
send(string_to_send);
// 同时调用.wait()来 阻塞运行,等待接收消息
recv(function (received_json_objection) {
// 接收来的json字符串
console.log("recv in js:",JSON.stringify(received_json_objection))
// 打印json的`my_data`,json串来自python
string_to_recv = received_json_objection.my_data;
console.log("string_to_recv:", string_to_recv);
}).wait();
// 将接收到的字符串当作被hook函数的结果返回回去
var result = Java.use("java.lang.String").$new(string_to_recv);
return result;
};
});
import time
import frida


## handler | script脚本信息交互函数
def my_message_handler(message, payload):
print(message) # 打印得到的信息
print(payload) # 输出的为`none`?
# 如果`type`字段为"send" 则是js发来的消息
if message["type"] == "send":
# 打印json的`payload`内容(js发送过来的内容)
print(message["payload"])
# 向script发送消息,格式为字典
script.post({"my_data": "Hello"})


# 通过Usb连接设备
# device = frida.get_usb_device()

# 通过ip:port 连接设备
device = frida.get_device_manager().add_remote_device("192.168.0.104:8888")
################ 通过spawn方式启动 ###########################
pid = device.spawn(["com.forgotten.fridatestapp"])
device.resume(pid)
time.sleep(1)
session = device.attach(pid)
##### <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
# session = device.attach("com.forgotten.fridatestapp")
################ 通过attach现有进程方式启动 ###################
with open("./frida_rpc.js") as f:
# 创建一个新脚本
script = session.create_script(f.read())
# 加载信息交互handler函数
script.on("message", my_message_handler)
# 加载脚本
script.load()

command = ""
while True:
command = input("Enter `n` for leave: ")
if command == "n":
break