抖音数据采集Frida教程,Frida Java Hook 详解:代码及示例(上)
短视频、直播数据实时采集接口,请查看文档: TiToData
免责申明:本文档仅供学习与参考,请勿用于非法用处!否则所有后果自负。
**前言
1.1 FRIDA SCRIPT的”hello world”
1.1.1 “hello world”脚本代码示例
1.1.2 “hello world”脚本代码示例详解
1.2 Java层拦挡一般办法
1.2.1 拦挡一般办法脚本示例
1.2.2 执行拦挡一般办法脚本示例
1.3 Java层拦挡构造函数
1.3.1 拦挡构造函数脚本代码示例
1.3.2 拦挡构造函数脚本代码示例解详解
1.4 Java层拦挡办法重载
1.4.1 拦挡办法重载脚本代码示例
1.5 Java层拦挡结构对象参数
1.5.1 拦挡结构对象参数脚本示例
1.6 Java层批改成员变量的值以及函数的返回值
1.6.1 批改成员变量的值以及函数的返回值脚本代码示例
1.6.2 批改成员变量的值以及函数的返回值之小实战
结语
咱们在这篇来深刻学习如何HOOK Java
层函数,利用于与各种不同的Java
层函数,结合实际APK
案例应用FRIDA
框架对其APP
进行附加、hook
、以及FRIDA
脚本的具体编写。
1.1 FRIDA SCRIPT的”hello world”
在本章节中,仍然会大量应用注入模式附加到一个正在运行过程程序,亦或是在APP
程序启动的时候对其APP
过程进行劫持,再在指标过程中执行咱们的js
文件代码逻辑。FRIDA
脚本就是利用FRIDA
动静插桩框架,应用FRIDA
导出的API
和办法,对内存空间里的对象办法进行监督、批改或者替换的一段代码。FRIDA
的API
是应用JavaScript
实现的,所以咱们能够充分利用JS
的匿名函数的劣势、以及大量的hook
和回调函数的API
。那么大家跟我一起来操作吧,先关上在vscode
中创立一个js文件:helloworld.js
:
1.1.1 “hello world”脚本代码示例
setTimeout(function(){
Java.perform(function(){
console.log("hello world!");
});
});
1.1.2 “hello world”脚本代码示例详解
这基本上就是一个FRIDA
版本的Hello World!
,咱们把一个匿名函数作为参数传给了setTimeout()
函数,然而函数体中的Java.perform()
这个函数自身又承受了一个匿名函数作为参数,该匿名函数中最终会调用console.log()
函数来打印一个Hello world!
字符串。咱们须要调用setTimeout()
办法因为该办法将咱们的函数注册到JavaScript
运行时中去,而后须要调用Java.perform()
办法将函数注册到Frida
的Java
运行时中去,用来执行函数中的操作,当然这里只是打了一条log
。而后咱们在手机上将frida-server
运行起来,在电脑上进行操作:
roysue@ubuntu:~$ adb shell
sailfish:/ $ su
sailfish:/ $ ./data/local/tmp/frida-server
这个时候,咱们须要再开启一个终端运行另外一条命令:frida -U com.roysue.roysueapplication -l helloworld.js
这句代码是指通过USB
连贯对Android
设施中的com.roysue.roysueapplication
过程对其附加并且注入一个helloworld.js
脚本。注入实现之后会立即执行helloworld.js
脚本所写的代码逻辑!
咱们能够看到胜利注入了脚本以及附加到本人所编写包名为:com.roysue.roysueapplication
的apk
应用程序中,并且打印了一条 hell world!
。
roysue@ubuntu:~$ frida -U -l helloworld.js com.roysue.roysueapplication
____
/ _ | Frida 12.7.24 - A world-class dynamic instrumentation toolkit
| (_| |
> _ | Commands:
/_/ |_| help -> Displays the help system
. . . . object? -> Display information about 'object'
. . . . exit/quit -> Exit
. . . .
. . . . More info at https://www.frida.re/docs/home/
Attaching...
hello world!
执行了该命令之后,FRIDA
返回一个了CLI
终端工具与咱们交互,在下面可见打印进去了一些信息,显示了咱们的FRIDA版本信息还有一个反向R的图形,往下看,须要退出的时候咱们只须要在终端输出exit
即可实现退出对APP
的附加,其后咱们看到的是Attaching...
正在对指标过程附加,当附加胜利了打印了一句hello world!
,至此,咱们的helloworld.js
通过FRIDA
的-l
命令胜利的注入到指标过程并且执行结束。学会了注入可不要快乐的太早哟~~咱们持续深刻学习HOOK Java
代码中的一般函数。
1.2 Java层拦挡一般办法
Java
层的一般办法相当常见,也是咱们要学习的根底中的根底,咱们先来看几个比拟常见的一般的办法,见下图1-1。
图1-1 JADX-GUI软件关上的反编译代码
通过图1-1咱们能看三个函数别离是构造函数a()
、一般函数a()
和b()
。诸如这种一般函数会特地多,那咱们在本小章节中尝试hook
一般函数、查看函数中的参数的具体值。
在尝试写FRIDA HOOK
脚本之前咱们先来看看须要hook
的代码吧~,Ordinary_Class
类中有四个函数,都是很一般的函数,add
函数的性能也很简略,参数a
+b
;sub函数性能是参数a
–b
;而getNumber
只返回100
;getString
办法返回了 getString()
+参数的str
。见下图1-2。
图1-2 反编译的Ordinary_Class类的代码
而后咱们再看MainActivity
中的编写的代码,通过反编译进去的代码一共有四个按钮(Button
),当btn_add
点击时会运行Ordinary_Class
类中add
办法,计算100+200
的后果,通过String.valueOf
函数把计算构造转字符串而后通过Toast
弹出信息;点击btn_sub
按钮的时候触发点击事件会运行Ordinary_Class
类中sub
办法,计算100-100
的后果,通过String.valueOf
函数把计算构造转字符串而后通过Toast
弹出信息。在MainActivity
类中的onCreate
办法中的四个按钮别离对应状况是ADD
按钮对应btn_add
点击事件,SUB
对应btn_sub
的点击事件。见下图1-3。
图1-3 MainActivity中的编写的代码
依照失常流程当咱们点击ADD
的按钮界面会弹出一条信息显示,其中的值是300
,因为咱们在ADD
的点击事件中增加了Toast
,将ADD
办法运行的后果放在Toast
参数中,通过它显示了咱们的计算结果;而SUB
函数会显示0
,见下图1-4,图1-5。
图1-4 点击ADD按钮时显示的后果
图1-5 点击SUB按钮时显示的后果
咱们当初晓得曾经晓得它的运行流程以及函数的执行后果和所填写的参数,咱们当初来正式编写一个根本的应用Frida
钩子来拦挡图1-2中add
和sub
函数的调用并且在终端显示每个函数所传入的参数、返回的值,开始写roysue_0.js
:
1.2.1 拦挡一般办法脚本示例
setTimeout(function(){
//判断是否加载了Java VM,如果没有加载则不运行上面的代码
if(Java.available) {
Java.perform(function(){
//先打印一句提醒开始hook
console.log("start hook");
//先通过Java.use函数失去Ordinary_Class类
var Ordinary_Class = Java.use("com.roysue.roysueapplication.Ordinary_Class");
//这里咱们须要进行一个NULL的判断,通常这样做会排除一些不必要的BUG
if(Ordinary_Class != undefined) {
//格局是:类名.函数名.implementation = function (a,b){
//在这里应用钩子拦挡add办法,留神办法名称和参数个数要统一,这里的a和b能够本人任意填写,
Ordinary_Class.add.implementation = function (a,b){
//在这里先失去运行的后果,因为咱们要输入这个函数的后果
var res = this.add(a,b);
//把计算的后果和参数一起输入
console.log("执行了add办法 计算result:"+res);
console.log("执行了add办法 计算参数a:"+a);
console.log("执行了add办法 计算参数b:"+b);
//返回后果,因为add函数自身是有返回值的,否则APP运行的时候会报错
return res;
}
Ordinary_Class.sub.implementation = function (a,b){
var res = this.sub(a,b);
console.log("执行了sub办法 计算result:"+res);
console.log("执行了sub办法 计算参数a:"+a);
console.log("执行了sub办法 计算参数b:"+b);
return res;
}
Ordinary_Class.getString.implementation = function (str){
var res = this.getString(str);
console.log("result:"+res);
return res;
}
} else {
console.log("Ordinary_Class: undefined");
}
console.log("hook end");
});
}
});
1.2.2 执行拦挡一般办法脚本示例
写完脚本后咱们执行:frida -U com.roysue.roysueapplication -l roysue_0.js
,当咱们执行了脚本后会进入cli
控制台与frida
交互,能够看到曾经对该app
附加并且胜利注入脚本。立即打印出了start hook
和hook end
,正是咱们刚刚所写的。再持续点击app
利用中的ADD
按钮和SUB
按钮会在终端立即输入计算的后果和参数,在这里甚至能够看到清晰,参数、返回值和盘托出。
roysue@ubuntu:~$ frida -U com.roysue.roysueapplication -l roysue_0.js
____
/ _ | Frida 12.7.24 - A world-class dynamic instrumentation toolkit
| (_| |
> _ | Commands:
/_/ |_| help -> Displays the help system
. . . . object? -> Display information about 'object'
. . . . exit/quit -> Exit
. . . .
. . . . More info at https://www.frida.re/docs/home/
Attaching...
start hook
hook end
[Google Pixel::com.roysue.roysueapplication]-> 执行了add办法 计算result:300
执行了add办法 计算参数a:100
执行了add办法 计算参数b:200
执行了sub办法 计算result:0
执行了sub办法 计算参数a:100
执行了sub办法 计算参数b:100
这样咱们就曾经胜利的打印了来了咱们想要晓得的值,每个参数的值和返回值的构造。咱们就对一般函数的钩子实现了一个基本操作,大家能够本人多多尝试对其余的一般的函数进行hook
,多多练习,那咱们这一章节就欢快的实现了~~持续深刻吧。
1.3 Java层拦挡构造函数
那咱们这章来玩如何HOOK
类的构造函数,很多时候在实例化类的霎时就会把参数传递到外部为成员变量赋值,这样一来就省的类中的成员变量一个个去赋值,在Android
逆向中,也有很多的相似的场景,利用有参构造函数实例化类并且赋值。我建设了一个class
类,类名是User
,其中代码这样写:
public class User {
public int age;
public String name;
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("User{name='");
sb.append(this.name);
sb.append('\'');
sb.append(", age=");
sb.append(this.age);
sb.append('}');
return sb.toString();
}
public User(String name2, int age2) {
this.name = name2;
this.age = age2;
}
public User() {
}
}
咱们能够看到User
类中有2个成员变量别离是age
和name
,还有2
个构造方法,别离是无参结构有有参结构。咱们当初要做的是在User
类进行有参实例化时查看所填入的参数别离是什么值。在图1-3中,能够看到btn_init
的点击事件时会对User
类进行实例化参数别离填写了roysue
和30
,而后再持续调用了toString
办法把它们组合到一起并且通过Toast
弹出信息显示值。TEST_INTI对应btn_init
的点击事件,点击时成果见下图1-6。
图1-6 点击TEST_INIT时显示的值
1.3.1 拦挡构造函数脚本代码示例
当初开始编写frida
钩子来拦挡User
类的构造函数的脚本,来打印出构造函数的参数,编写roysue_1.js
。
setTimeout(function(){
if(Java.available) {
Java.perform(function(){
console.log("start hook");
//同样的在这里先获取User类对象
var User = Java.use("com.roysue.roysueapplication.User");
if(User != undefined) {
//留神在应用钩子拦挡构造函数时须要应用到 $init 也要留神参数的个数,因为该构造函数是2个所以此处填2个参数
User.$init.implementation = function (name,age){
//这里打印成员变量name和age的在运行中被调用填写的值
console.log("name:"+name);
console.log("age:"+age);
//最终要执行本来的init办法否则运行时会报异样导致原程序无奈失常运行。
this.$init(name, age);
}
} else {
console.log("User: undefined");
}
console.log("hook end");
});
}
});
1.3.2 拦挡构造函数脚本代码示例解详解
脚本写好之后关上终端执行frida -U com.roysue.roysueapplication -l roysue_1.js
,把刚刚写的脚本注入的指标过程,而后咱们在APP
利用中按下TEST_INIT
按钮,注入的脚本会立刻拦挡构造函数并且打印2个参数的具体的值,见下图1-7。
图1-7 终端显示
打印的值就是图1-3中所填的roysue
和30
,这就阐明咱们应用FRIDA
钩子拦挡到了User
类的有参构造函数并且无效的打印了参数的值。须要留神的是在输出打印参数的值之后肯定要记得执行本来的有参构造函数,这样程序才能够失常执行。
1.4 Java层拦挡办法重载
在学习HOOK
之前,咱们先理解一下什么是办法重载,办法重载是指在同一个类内定义了多个雷同的办法名称,然而每个办法的参数类型和参数的个数都不同。在调用办法重载的函数编译器会依据所填入的参数的个数以及类型来匹配具体调用对应的函数。总结起来就是办法名称一样然而参数不一样。在逆向JAVA
代码的时候时常会遇到办法重载函数,见下图1-8。
图1-8 反编译后重载函数样本代码
在图1-8中,咱们能看到一共有三个办法重载的函数,有时候理论状况甚至更多。咱们也不要怕,撸起袖子加油干。对于这种重载函数在FRIDA
中,js
会写.overload
来拦挡办法重载函数,当咱们应用overload
关键字的时候FRIDA
会十分智能把以后所有的重载函数的所须要的类型打印进去。在理解了这个之后咱们来开始实战应用FRIDA
钩子拦挡咱们想拦挡的重载函数的脚本吧!还是之前的那个app
,还是之前的那个类,原汁原味~~,我新增了一些add
的办法,使add
办法重载,见下图1-9。
图1-9 反编译后的Ordinary_Class
中的重载函数样本代码
在图1-9中add
有三个重名的办法名称,然而参数的个数不同,在图1-3中的btn_add
点击事件会执行领有2个参数的办法重载的add
函数,当我在脚本中写Ordinary_Class.add..implementation = function (a,b)
,而后持续注入所写好的脚本,FRIDA
在终端提醒了红色的字体,一看吓一跳!但咱们认真看,它说add
是一个办法重载的函数,有三个参数不同的add
函数,让咱们写.overload(xxx)
,以辨认hook的到底是哪个add
的办法重载函数。
当咱们写了这样的js
脚本去运行的时候,frida
提醒报错了,因为有三个重载函数,我用红色的框圈出了,能够看到frida
非常的智能,三个重载的参数类型完全一致的打印进去了,当它打印进去之后咱们就能够复制它的这个智能提醒的overloadxxx
重载来批改咱们本人的脚本了,进一步欠缺咱们的脚本代码如下。
1.4.1 拦挡办法重载脚本代码示例
function hook_overload() {
if(Java.available) {
Java.perform(function () {
console.log("start hook");
var Ordinary_Class = Java.use("com.roysue.roysueapplication.Ordinary_Class");
if(Ordinary_Class != undefined) {
//要做的仅仅是将frida提醒进去的overload复制在此处
Ordinary_Class.add.overload('int', 'int').implementation = function (a,b){
var res = this.add(a,b);
console.log("result:"+res);
return res;
}
Ordinary_Class.add.overload('int', 'int', 'int').implementation = function (a,b,d){
var res = this.add(a,b,d);
console.log("result:"+res);
return res;
}
Ordinary_Class.add.overload('int', 'int', 'int', 'int').implementation = function (a,b,d,c){
var res = this.add(a,b,d,c);
console.log("result:"+res);
return res;
}
} else {
console.log("Ordinary_Class: undefined");
}
console.log("start end");
});
}
}
setImmediate(hook_overload);
批改完相应的中央之后咱们保留代码时终端会主动再次运行js中的代码,不得不说frida
太强大了~,当js
再次运行的时候咱们在app
利用中点击图1-4中ADD
按钮时会立即打印出后果,因为FRIDA
钩子曾经对该类中的所有的add
函数进行了拦挡,执行了本人所写的代码逻辑。点击成果如下图1-11。
图1-11 终端显示成果
在这一章节中咱们学会了解决办法重载的函数,咱们只有根据FRIDA的终端提醒,将智能提醒进去的代码连接到本人的代码就可能对办法重载函数进行拦挡,执行咱们本人想要执行的代码。
1.5 Java层拦挡结构对象参数
很多时候,咱们岂但要HOOK
应用钩子拦挡函数对函数的参数和返回值进行记录,而且还要本人被动调用类中的函数应用。FRIDA
中有一个new()
关键字,而这个关键字就是实例化类的重要办法。
在官网API
中有这样写道:“Java.use(ClassName):
动静获取className
的JavaScript
包装器,通过对其调用new()
来调用构造函数,能够从中实例化对象。对实例调用Dispose()
以显式清理它(或期待JavaScript
对象被垃圾收集,或脚本被卸载)。动态和非静态方法都是可用的。”,那咱们就晓得通过Java.use
获取的class
类能够调用$new()
来调用构造函数,能够从实例化对象。在图1-9中有6
个函数,理解了API
的调用之后咱们来开始动手编写咱们的js文件。(在这里我感觉大家肯定要动手做测试,入手尝试,你会发现其中的妙趣无穷!)。
1.5.1 拦挡结构对象参数脚本示例
function hook_overload_1() {
if(Java.available) {
Java.perform(function () {
console.log("start hook");
//还是先获取类
var Ordinary_Class = Java.use("com.roysue.roysueapplication.Ordinary_Class");
if(Ordinary_Class != undefined) {
//这里因为add是一个静态方法可能间接调用办法
var result = Ordinary_Class.add(100,200);
console.log("result : " +result);
//调用办法重载无压力
result = Ordinary_Class.add(100,200,300);
console.log("result : " +result);
//调用办法重载
result = Ordinary_Class.add(100,200,300,400);
console.log("result : " +result);
//调用办法重载
result = Ordinary_Class.getNumber();
console.log("result : " +result);
//调用办法重载
result = Ordinary_Class.getString(" HOOK");
console.log("result : " +result);
//在这里,应用了官网API的$new()办法来实例化类,实例化之后返回一个实例对象,通过这个实例对象来调用类中办法。
var Ordinary_Class_instance = Ordinary_Class.$new();
result = Ordinary_Class_instance.getString("Test");
console.log("instance --> result : " +result);
result = Ordinary_Class_instance.add(1,2,3,4);
console.log("instance --> result : " +result);
} else {
console.log("Ordinary_Class: undefined");
}
console.log("start end");
});
}
}
setImmediate(hook_overload_1);
当咱们执行了下面写的脚本之后终端会打印调用办法之后的后果,见下图1-12。
图1-12 终端显示调用函数的后果
因为很多时候类中的办法并不一定是动态的,所以这里提供了2
种调用办法,第一种调用形式非常的不便,不须要实例化一个对象,再通过对象调自身的办法。然而遇到了没有static
关键字的函数时只能应用第二种形式来实现办法调用,在这一章节中咱们学会了如何本人被动去调用类中的函数了~~大家也能够尝试被动调用有参的构造函数玩玩。
1.6 Java层批改成员变量的值以及函数的返回值
咱们上章学完了如何本人被动调用JAVA
层的函数了,通过上章的学习咱们的功夫又精进了一些~~,当初咱们来深刻外部批改类的对象的成员变量和返回值,打入敌人外部,进步本人的内功。当初咱们来看下图1-13。
图1-13 User类
上图中的User
类是我之前建的一个类,类中写了2个公开成员变量别离是age
是name
;还有2
个办法别离是User
的有参构造函数和一个toString
函数打印成员变量的函数。咱们要做就是在User
类实例化的时候拦挡程序并且批改掉age
和name
的值,从而改写成咱们本人须要的值再运行程序,那咱们接下开始编写JS
脚本来批改成员变量的值。
这段代码次要有的性能是:通过User.$new("roysue",29)
拿到User
类的有参数结构的实例化对象,这个恰好也是应用了上章节中学到的常识本人构建对象,这里咱们也学习了如何应用FRIDA框架通过有参构造函数实例化对象,实例化之后先是调用了类自身的toString
办法打印出未修改前的成员变量的值,打印了之后再通过User_instance.age.value = 0;
来批改对象以后的成员变量的值,能够看到批改age
批改为0
,name
批改为roysue_123
,而后再次调用toString
办法查看其成员变量的最新值是否曾经被更改。
1.6.1 批改成员变量的值以及函数的返回值脚本代码示例
function hook_overload_2() {
if(Java.available) {
Java.perform(function () {
console.log("start hook");
//拿到User类
var User = Java.use("com.roysue.roysueapplication.User");
if(User != undefined) {
//这里利用上章学到常识来本人构建一个User的有参结构的实例化对象
var User_instance = User.$new("roysue",29);
//并且调用了类中的toString()办法
var str = User_instance.toString();
//打印成员变量的值
console.log("str:"+str);
//这里获取了属性的值以及打印
console.log("User_instance.name:"+User_instance.name);
console.log("User_instance.age:"+User_instance.age);
//这里从新设置了age和name的值
User_instance.age.value = 0;
User_instance.name.value = "roysue_123";
str = User_instance.toString();
//再次打印成员变量的值
console.log("str:"+str);
} else {
console.log("User: undefined");
}
console.log("start end");
});
}
}
能够看到终端显示了本来有参构造函数的值roysue和30批改为roysue_123和0曾经胜利了,成果见下图1-14。
图1-14 终端显示批改成果
通过下面的学习,咱们学会了如何批改类的成员变量,上个例子中是应用的有参构造函数给与成员变量赋值,通常在写代码相似这种实体类会定义相干的get set
办法以及修饰符为公有权限,内部不可调用,这个时候他们可能会通过set办法来设置其值和get
办法获取成员的变量的值,这个时候咱们能够通过钩子拦挡set
和get
办法本人定义值也是能够达到批改和获取的成果。当初学完了如何批改成员变量了,那咱们接下来要学习如何批改函数的返回值,假如在逆向的过程中已知检测函数A
的后果为B
,正确后果为C
,那咱们能够强行批改函数A
的返回值,不管在函数中执行了什么与返回后果无关,咱们只有批改后果即可。
1.6.2 批改成员变量的值以及函数的返回值之小实战
我在rdinary_Class
类建设了2
个函数别离是isCheck
和isCheckResult
,假如isCheck
是一个检测办法,通过add
运行后必然结果2
,代表被检测到了,在isCheckResult
办法进行了判断调用isCheck
函数后果为2
就是谬误的,那这个时候要把isCheck
函数或者add
函数的后果强行改成不是2
之后isCheckResult
即可打印Successful
,见下图1-15。
图1-15 isCheck函数与isCheckResult函数
咱们当初要做的是使sCheckResult
函数胜利打印出"Successful"
,而不是errer
,那咱们当初开始来写js
脚本吧~~
function hook_overload_7() {
if(Java.available) {
Java.perform(function () {
console.log("start hook");
//先获取类
var Ordinary_Class = Java.use('com.roysue.roysueapplication.Ordinary_Class');
if(Ordinary_Class != undefined) {
//先调用一次必定会输入error
Ordinary_Class.isCheckResult();
//在这里重写isCheck办法将返回值强行改为123并且输入了一句Ordinary_Class: isCheck
Ordinary_Class.isCheck.implementation = function (){
console.log("Ordinary_Class: isCheck");
return 123;
}
//再调用一次isCheckResult()办法
Ordinary_Class.isCheckResult();
} else {
console.log("Ordinary_Class: undefined");
}
console.log("hook end");
});
}
}
下面这段代码的次要性能是:首先通过Java.use
获取Ordinary_Class
,因为isCheckResult()
是静态方法,能够间接调用,在这里先调用一次,因为这样比拟难看成果,第一次调用会在Android LOG
中打印errer
,之后紧接着利用FRIDA
钩子对isCheck
函数进行拦挡,改掉其返回值为123
,这样每次调用isCheck
函数时返回值都必定会是123
,再调用一次isCheckResult()
办法,isCheckResult()
办法中判断isCheck
返回值是否等于2
,因为咱们曾经重写了isCheck
函数,所以不等于2
,所以程序往下执行,会打印Successful
字符串到Android Log
中,理论运行成果见下图1-16。
图1-16 终端显示以及Android Log信息
能够清晰的看到先是打印了errer
后打印了Successful
了,这阐明咱们曾经胜利过掉isCheck
的判断了。这是一个小小的综合例子。倡议大家多多入手尝试。
结语
在这章中咱们学习了HOOK Java层的一些函数,如拦挡一般函数、构造函数、以及批改成员变量以及函数返回值。下一篇中咱们来枚举所有的类、所有的办法重载、所有的子类以及RPC近程调用Java层函数。
短视频、直播数据实时采集接口,请查看文档: TiToData
免责申明:本文档仅供学习与参考,请勿用于非法用处!否则所有后果自负。
发表回复