静态方法和实例方法的hook

太喜欢原神辣!所以就拿newstar week2的原神题来操作一下

做例子hook了!

使用frida进行hook的时候,无论是有没有带 static 的方法都是可以进行调用的

img

那就hook这个whatme函数吧,hook出encode函数的data数据和CUSTOM_TABLE(码表)

这是一个base64加密,码表是经过rc4解密之后的结果,我们可以通过hook直接拿到码表,当然也可以通过修改rc4的返回值修改码表

1
2
3
4
5
6
7
8
9
10
11
12
Java.perform(function () {
var base64 =Java.use("android.util.Base64");
var whatme=Java.use("com.genshin.impact.whatme");
whatme.encode.implementation=function (a,b){
console.log("加密的参数是:",base64.encodeToString(a,0));
console.log("加密的码表是:",b);
var secret=this.encode(a,b);
console.log("加密之后的结果是:",secret);
return secret;

}
});

输入正确的账号,然后密码随便输入,得到的结果

img

这期间,由于加密之前的a直接输出无法输出,所以就主动调用base64编码把a编码一下就知道了,然后去解密网站解密一下就行

img

没错,我输入的是123456789,并且也得到了码表:BADCFEHGJILKNMPORQTSVUXWZYbadcfehgjilknmporqts.uxwzy1032547698/+

小故障

如果在hook的时候遇到这个问题,重启一下手机重新开始就行了

img

img

获得参数和修改返回值

然后我在中途修改了参数b,也就是码表,然后我们去修改的函数的加密过程也发生了改变

[MI 9 Transparent Edition::Genshin_impact]->

加密的参数是: MTIzNDU2Nzg5

加密的码表是: BADCFEHGJILKNMPORQTSVUXWZYbadcfehgjilknmporqts.uxwzy1032547698/+

加密的码表2是: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/

加密之后的结果是: MTIzNDU2Nzg5

另外,这个题是一个

判断加密之后的结果是否和”VVwPUWsYcXEPW0MpN35gW0FwW1RxandgJTE8”这字符串是否一样,我灵机一变,加入我把返回值改成那个字符串那对比岂不是永远成立,那岂不是随便登录了???

[MI 9 Transparent Edition::Genshin_impact]-> 加密的参数是: MTIzNDU2Nzg5

加密的码表是: BADCFEHGJILKNMPORQTSVUXWZYbadcfehgjilknmporqts.uxwzy1032547698/+ 加密的码表2是: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/ 加密之后的结果是: MTIzNDU2Nzg5 加密之后的结果2是: VVwPUWsYcXEPW0MpN35gW0FwW1RxandgJTE8 成功了,这样的话,错误的密码也可以随便成功登录了!

img

完整源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Java.perform(function () {
var base64 =Java.use("android.util.Base64");
var whatme=Java.use("com.genshin.impact.whatme");
whatme.encode.implementation=function (a,b){
console.log("加密的参数是:",base64.encodeToString(a,0));
console.log("加密的码表是:",b);
b="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
console.log("加密的码表2是:",b);
var secret=this.encode(a,b);
console.log("加密之后的结果是:",secret);
secret = "VVwPUWsYcXEPW0MpN35gW0FwW1RxandgJTE8"
console.log("加密之后的结果2是:",secret);
return secret;

}
});

以下内容由于没有很好的示例apk(我自己也不会写)所以就用xiaojianbang老师的Hookdemo来学习了使用了,xiaojianbang老师太强啦!

首先分析一下,点击test之后会干啥

img

  1. 当点击 ID 为 Test 的按钮时,执行以下操作。
  2. 调用 logOutPut 方法,将当前线程的 ID 打印输出。
  3. 创建一个名为 money 的新对象,该对象是 Money 类的实例,构造函数接受两个参数:货币类型为 “人民币”,金额为 100。
  4. 创建一个名为 wallet 的新对象,该对象是 Wallet 类的实例,构造函数接受三个参数:持有人姓名为 “xiaojianbang”,品牌为 “lv”,初始金额为 100。
  5. 创建一个名为 bankCard 的新对象,该对象是 BankCard 类的实例,构造函数接受五个参数:持卡人姓名为 “xiaojianbang”,卡号为 “123456789”,发卡行为 “CBDA”,卡类型为 1,手机号为 “15900000000”。
  6. bankCard 对象赋值给 this.bankCard
  7. 调用 wallet 对象的 addBankCard 方法,将 bankCard 添加到钱包中。
  8. 调用 wallet 对象的 deposit 方法,将 money 存入钱包。

综上所述,代码的功能是创建一个钱包对象,添加银行卡并存入一定金额的人民币。

差不多就是这样,然后现在就对下面进行hook

构造函数hook

img

在Money函数中有Money方法,这就叫构造函数,在对构造函数进行Hook的时候,要加上$init进行调用 所以就hook一下Money这个构造函数:

1
2
3
4
5
6
7
8
Java.perform(function () {
var money = Java.use("com.xiaojianbang.hook.Money");
money.$init.implementation = function (a, b)
{
console.log("money.$init param: ", a, b);
return this.$init("美元", 200);
}
});

img

得到了相应的结果,但是好像返回值有点问题

对于String的构造函数有点不同,是调用StringFactory函数

对象参数的构造与修改

img

amount是一个属性,我们可以直接通过调用来对他进行赋值

语句是:a.amount.value = 6666;】

再hook就会改变那里面的赋值

img

重载函数的hook

重载函数是指多次调用同一个函数,但是传入的参数的数量不同

需要overload函数进行传递指令类型

解决方法:可以根据报错,进行重载函数的调用

1
2
3
4
5
var utils = Java.use("com.xiaojianbang.hook.Utils");
utils.getCalc.overload('int', 'int').implementation = function (a, b) {
console.log("utils.getCalc param: ", a, b);
return this.getCalc(a, b);
}

补上一个模板(其实本质上就加了个overload进行区分罢了

主动调用

直接hook是不区分静态方法还是实例方法的,但是主动调用是区分的

静态方法

直接调用就行了

抓到类之后就直接当成本地函数就行了

假设调用一下getFlag,这里就设计一下首先是通过hook去修改一下getFlag的返回值为”WenWenJiang6666”,然后在通过主动调用去调用这个函数,去看看他的返回值是不是我自己定义的

代码:

1
2
3
4
5
6
7
8
9
Java.perform(function () {
var money = Java.use("com.xiaojianbang.hook.Money");
money.getFlag.implementation=function (){
var mm = "WenWenJiang6666";
return mm;
}
var mmm = money.getFlag()
console.log("参数是:",mmm)
});

img

成功,静态方法还是很简单的!

实例方法

Java.choose()

是frida提供的一个去内存中寻找某一对象属性的参数,并且可以进行打印或是对调用

img

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Java.perform(function () {
var money = Java.use("com.xiaojianbang.hook.Money");
money.getFlag.implementation=function (){
var mm = "WenWenJiang6666";
return mm;
}
Java.choose("com.xiaojianbang.hook.Money", {
onMatch: function (sec){
console.log(sec.amount.value);
console.log(sec.currency.value);
},
onComplete: function (){
console.log("内存中的Money对象搜索完毕");
}
});

});

当然也可以自己创建对象,自己去调用,但是没啥意义

枚举已加载的所有java类

Java.enumerateLoadedClassesSync()
console.log(Java.enumerateLoadedClassesSync().join("\n"));

可以枚举出所有的已加载的类
1.枚举的类是已经加载的类
2.出现的类也有可能Hook不到,因为有类加载器进行加载,很有可能

枚举类的所有方法

通过反射来获得类以及类里的所有字段

1
2
3
4
5
6
7
8
Java.perform(function () {

var wallet = Java.use("com.xiaojianbang.hook.Wallet");
var methods = wallet.class.getDeclaredMethod();//hook方法
var constructors = wallet.class.getDeclaredCinstructors();//构造函数
var fields = wallet.class.getDeclaredFields();//字段
var classes = wallet.class.getDeclaredClasses();//内部类信息
});
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
26
27
console.log(Java.enumerateLoadedClassesSync().join("\n"));
var wallet = Java.use("com.xiaojianbang.hook.Wallet");
var methods = wallet.class.getDeclaredMethods();
var constructors = wallet.class.getDeclaredConstructors();
var fields = wallet.class.getDeclaredFields();
var classes = wallet.class.getDeclaredClasses();

for (var i = 0; i < methods.length; i++) {
console.log(methods[i].getName());
}
console.log("============================");
for (var i = 0; i < constructors.length; i++) {
console.log(constructors[i].getName());
}
console.log("============================");
for (var i = 0; i < fields.length; i++) {
console.log(fields[i].getName());
}
console.log("============================");
for (var i = 0; i < classes.length; i++) {
console.log(classes[i].getName());
//classes[i] 这里得到的已经是类的字节码,不需要再.calss
var Wallet$InnerStructure = classes[i].getDeclaredFields();
for (var j = 0; j < Wallet$InnerStructure.length; j++) {
console.log(Wallet$InnerStructure[j].getName());
}
}

上面的方法可能太过于麻烦,所以可以直接去遍历每一个方法:

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
26
27
28
29
30
31
32
33
34
35
Java.perform(function () {
// 定义一个函数来进行方法的Hook
function hookFunc(methodName) {
console.log(methodName);
// 获取指定方法的所有重载
var overloadsArr = utils[methodName].overloads;
// 遍历每个重载
for (var j = 0; j < overloadsArr.length; j++) {
// 将方法的实现替换为自定义实现
overloadsArr[j].implementation = function () {
var params = "";
// 将方法的参数拼接成字符串
for (var k = 0; k < arguments.length; k++) {
params += arguments[k] + " ";
}
// 打印方法名和参数
console.log("utils." + methodName + " 被调用!参数为:", params);
// 使用提供的参数调用原始方法
return this[methodName].apply(this, arguments);
}
}
}

// 获取目标类(com.xiaojianbang.hook.Utils)的引用
var utils = Java.use("com.xiaojianbang.hook.Utils");
// 获取目标类的所有声明的方法
var methods = utils.class.getDeclaredMethods();
// 遍历每个方法
for (var i = 0; i < methods.length; i++) {
// 获取方法名
var methodName = methods[i].getName();
// 使用hookFunc函数对方法进行Hook
hookFunc(methodName);
}
});

这段代码在com.xiaojianbang.hook包下的Utils类上执行方法Hook。它定义了一个hookFunc函数,该函数将每个方法的实现替换为一个自定义实现,该实现会打印方法名和参数。代码获取了Utils类的引用,然后检索所有已声明的方法,并使用hookFunc函数对每个方法进行Hook。
image-20231206170154353

强的强的。算是一个全自动hook吧,挺好玩的,试试别的!

image-20231206171138162

试了试之前的CTF题目,果然可以差不多起到一个一键分析的功能,还挺好用的嘞!
(但是跟这个题目关系不大,而且这样hook也是起到了一个帮助分析的作用!)

调用自己写的函数

Java.registerClass

可以在原有的android代码的基础上进行自定义创建函数,然后自己去进行主动调用
但是需要写的是js代码
理论可行,但是没有具体的试验过,可以看一下官方文档:frida的官方JavaScriptAPI

https://frida.re/docs/javascript-api/
其实有一个更好的方法是dex注入

dex注入

使用registerClass的方式自己去写执行代码的过程实在是比较麻烦一些,可以用android studio自己随便写一个apk的程序,然后新建一个类,写上自己需要的代码,在打包成apk之后。再通过hook去主动调用就行了。

(比自己写js代码方便多了)

以上两种方法都是可以通过一些方式来调用自己写的函数来方便分析,目前觉得没太大的卵用,以后需要用的时候再补上一个自己写的demo

Java.cast转型

之前在我去主动调用原神附件的有一些参数的时候,提示了我输出object,不能直接输出出来从而看不到数值,所以今天就可以拿Java.cast来进行转化从而得到我们想要的数值
先看一下原版的(使用的是枚举类的所有方法的函数)

image-20231207123619191

whatme.encode的方法被调用了,但是参数不知道,所以就要用Java.cast转一下

image-20231207124140804

终于是没有object了

但是怎么什么也没有了????

image-20231207124309508

出了,参数出来了。没问题
问题应该是在于刚刚在计算完之后又定义了一次params

image-20231207124409676

总之还是得到了想要的base64码表。太牛了,妈妈再也不用担心我hook出参数是[object]l了

js代码:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
Java.perform(function () {
// 定义一个函数来进行方法的Hook
function hookFunc(methodName) {
console.log(methodName);
// 获取指定方法的所有重载
var overloadsArr = utils[methodName].overloads;
// 遍历每个重载
for (var j = 0; j < overloadsArr.length; j++) {
// 将方法的实现替换为自定义实现
overloadsArr[j].implementation = function () {
var params = "";
if (methodName.indexOf("encode ")!= -1){
params=Java.cast(arguments[0],Java.use("java.util.HashMap"));

}else {
for (var k = 0; k < arguments.length; k++) {
params += arguments[k] + " ";
}
// 将方法的参数拼接成字符串
}
// 打印方法名和参数
console.log("whatme." + methodName + " 被调用!参数为:", params);
// 使用提供的参数调用原始方法
return this[methodName].apply(this, arguments);
}
}
}

// 获取目标类(com.xiaojianbang.hook.Utils)的引用
var utils = Java.use("com.genshin.impact.whatme");
// 获取目标类的所有声明的方法
var methods = utils.class.getDeclaredMethods();
// 遍历每个方法
for (var i = 0; i < methods.length; i++) {
// 获取方法名
var methodName = methods[i].getName();
// 使用hookFunc函数对方法进行Hook
hookFunc(methodName);
}
});

类加载器

同样的类,用不同的类加载器去加载,得到的实际上是两个类
在我们hook的时候经常会遇到 not find xxx class的报错,这个情况大概有可能是类加载器的问题
由于软件会使用各种的classloader,所以就导致明明这个类我们能在反编译的时候看到,并且在使用枚举已加载的所有类的时候也可以明确看到我们需要加载的类:

1
2
Java.enumerateLoadedClassesSync()
console.log(Java.enumerateLoadedClassesSync().join("\n"));

但是去hook就是hook不到

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
26
27
Java.enumerateClassLoaders({ 
// 使用Java.enumerateClassLoaders函数列举所有的类加载器
onMatch: function (loader) {
// 当找到一个类加载器时,执行onMatch回调函数
try {
Java.classFactory.loader = loader;
// 将Java.classFactory.loader设置为当前找到的类加载器
var dynamic = Java.use("com.xiaojianbang.app.Dynamic");
// 使用Java.use函数获取com.xiaojianbang.app.Dynamic类的引用
console.log("dynamic: ", dynamic);
// 打印dynamic对象
//console.log(dynamic.$new().sayHello());
dynamic.sayHello.implementation = function () {
// 重写dynamic对象的sayHello方法
console.log("hook dynamic.sayHello is run!");
// 打印hook dynamic.sayHello is run!
return "hahahhahahaha";
// 返回字符串"hahahhahahha"
}
} catch (e) {
console.log(loader);
// 如果发生异常,打印当前的类加载器
}
},
onComplete: function () {}
// 当所有类加载器都被遍历完毕后执行的回调函数
});

image-20231211162908792

看到了不仅加载了全部的类加载器而且通过try catch的方法去寻找各个加载器加载的方法然后再进行hook调用和修改返回值的操作,一气呵成!

动态加载:

就是在需要的时候才会主动去加载那个类,这也是 类似于一种加密或者混淆,可以通过主动调用来进行加载,但是我到现在还没遇到过类似的apk,有空的话找一个玩玩

总结

以上就是一些常见的frida_api,在进行hook的时候可以根据上面的一些方法和代码进行调用