frida的⽤法--HookJava代码篇
frida是⼀款⽅便并且易⽤的跨平台Hook⼯具,使⽤它不仅可以Hook Java写的应⽤程序,⽽且还可以Hook原⽣的应⽤程序。
1. 准备
frida分客户端环境和服务端环境。在客户端我们可以编写Python代码,⽤于连接远程设备,提交要注⼊的代码到远程,接受服务端的发来的消息等。在服务端,我们需要⽤Javascript代码注⼊到⽬标进程,操作内存数据,给客户端发送消息等操作。我们也可以把客户端理解成控制端,服务端理解成被控端。
假如我们要⽤PC来对Android设备上的某个进程进⾏操作,那么PC就是客户端,⽽Android设备就是服务端。
1.1 准备frida服务端环境
本⽂,服务端在Android平台测试。服务端环境准备步骤如下:
根据⾃⼰的平台下载frida服务端并解压
执⾏以下命令将服务端推到⼿机的/data/local/tmp⽬录
adb push frida-server /data/local/tmp/frida-server
执⾏以下命令修改frida-server⽂件权限
adb shell chmod 777 /data/local/tmp/frida-server
注:Windows系统执⾏命令可以在CMD中进⾏;Linux和MacOS执⾏命令可以在终端中进⾏。adb是Android⼀个调试⼯具,具体安装⽅法不是本⽂的重点。
1.2 准备客户端环境
在PC上安装Python的运⾏环境,安装完成后执⾏下⾯的命令安装frida
pip install frida-tools
1.3 客户端命令参数
下⾯是frida客户端命令⾏的参数帮助
Usage: frida [options] target
Options:
--version show program's version number and exit
-h, --help show this help message and exit
-D ID, --device=ID connect to device with the given ID
-U, --usb connect to USB device
-R, --remote connect to remote frida-server
-H HOST, --host=HOST connect to remote frida-server on HOST
-f FILE, --file=FILE spawn FILE
-
n NAME, --attach-name=NAME
attach to NAME
-p PID, --attach-pid=PID
attach to PID
--debug enable the Node.js compatible script debugger
--enable-jit enable JIT
-l SCRIPT, --load=SCRIPT
load SCRIPT
-c CODESHARE_URI, --codeshare=CODESHARE_URI
load CODESHARE_URI
-e CODE, --eval=CODE evaluate CODE
-
q quiet mode (no prompt) and quit after -l and -e
--no-pause automatically start main thread after startup
-o LOGFILE, --output=LOGFILE
output to log file
1.3.1 将⼀个脚本注⼊到Android⽬标进程
frida -U -l myhook.xx
参数解释:
-U 指定对USB设备操作
-l 指定加载⼀个Javascript脚本
最后指定⼀个进程名,如果想指定进程pid,⽤-p选项。正在运⾏的进程可以⽤frida-ps -U命令查看
1.3.2 重启⼀个Android进程并注⼊脚本
frida -U -l myhook.js -xx --no-pause
参数解释:
-f 指定⼀个进程,重启它并注⼊脚本
--no-pause ⾃动运⾏程序
这种注⼊脚本的⽅法,常⽤于hook在App就启动期就执⾏的函数。
frida运⾏过程中,执⾏%resume重新注⼊,执⾏%reload来重新加载脚本;执⾏exit结束脚本注⼊
2. Hook Java⽅法
2.1 载⼊类
Java.use⽅法⽤于加载⼀个Java类,相当于Java中的Class.forName()。⽐如要加载⼀个String类:
var StringClass = Java.use("java.lang.String");
加载内部类:
var MyClass_InnerClass = Java.use("com.luoyesiqiu.MyClass$InnerClass");
其中InnerClass是MyClass的内部类
2.2 修改函数的实现
修改⼀个函数的实现是逆向调试中相当有⽤的。修改⼀个函数的实现后,如果这个函数被调⽤,我们的Javascript代码⾥的函数实现也会被调⽤。
2.2.1 函数参数类型表⽰
不同的参数类型都有⾃⼰的表⽰⽅法
对于基本类型,直接⽤它在Java中的表⽰⽅法就可以了,不⽤改变,例如:
int
short
char
byte
boolean
float
double
long
基本类型数组,⽤左中括号接上基本类型的缩写
基本类型缩写表⽰表:
缩写
基本类型缩写
基本类型
boolean Z
byte B
char C
double D
float F
int I
long J
short S
例如:int[]类型,在重载时要写成[I
任意类,直接写完整类名即可
例如:java.lang.String
对象数组,⽤左中括号接上完整类名再接上分号
例如:[java.lang.String;
2.2.2 带参数的构造函数
修改参数为byte[]类型的构造函数的实现
ClassName.$init.overload('[B').implementation=function(param){
//do something
}
注:ClassName是使⽤Java.use定义的类;param是可以在函数体中访问的参数
修改多参数的构造函数的实现
ClassName.$init.overload('[B','int','int').implementation=function(param1,param2,param3){
//do something
}
2.2.3 ⽆参数构造函数
ClassName.$init.overload().implementation=function(){
//do something
}
调⽤原构造函数
ClassName.$init.overload().implementation=function(){
//do something
this.$init();
//do something
}
注意:当构造函数(函数)有多种重载形式,⽐如⼀个类中有两个形式的func:void func()和void func(int),要加上overload来对函数进⾏重载,否则可以省略overload
2.2.4 ⼀般函数
修改函数名为func,参数为byte[]类型的函数的实现
ClassName.func.overload('[B').implementation=function(param){
//do something
//return ...
}
2.2.5 ⽆参数的函数
ClassName.func.overload().implementation=function(){
//do something
}
注:在修改函数实现时,如果原函数有返回值,那么我们在实现时也要返回合适的值
ClassName.func.overload().implementation=function(){
/
/do something
return this.func();
}
3. 调⽤函数
和Java⼀样,创建类实例就是调⽤构造函数,⽽在这⾥⽤$new表⽰⼀个构造函数。
var ClassName=Java.use("st.ClassName");
var instance = ClassName.$new();
实例化以后调⽤其他函数
var ClassName=Java.use("st.ClassName");
var instance = ClassName.$new();
instance.func();
4. 字段操作
字段赋值和读取要在字段名后加.value,假设有这样的⼀个类:
package com.luoyesiqiu.app;
public class Person{
private String name;
private int age;
}
写个脚本操作Person类的name字段和age字段:
var person_class = Java.use("com.luoyesiqiu.app.Person");
//实例化Person类
var person_class_instance = person_class.$new();
/
/给name字段赋值
person_class_instance.name.value = "luoyesiqiu";
//给age字段赋值
person_class_instance.age.value = 18;
//输出name字段和age字段的值
console.log("name = ",person_class_instance.name.value, "," ,"age = " ,person_class_instance.age.value);
输出:
name = luoyesiqiu , age = 18
5. 类型转换
⽤Java.cast⽅法来对⼀个对象进⾏类型转换,如将variable转换成java.lang.String:
var StringClass=Java.use("java.lang.String");
var NewTypeClass=Java.cast(variable,StringClass);
6. Java.available字段
这个字段标记Java虚拟机(例如: Dalvik 或者 ART)是否已加载, 操作Java任何东西之前,要确认这个值是否为true
7. Java.perform⽅法
Java.perform(fn)在Javascript代码成功被附加到⽬标进程时调⽤,我们核⼼的代码要在⾥⾯写。格式:
Java.perform(function(){
//
});
8. 实例讲解
有了以上的基础知识,我们就可以进⾏编写代码了
8.1 修改返回值
8.1.1 场景
假设有以下的程序,给isExcellent⽅法传⼊两个值,通过计算,返回⼀个布尔值,表⽰是否优秀。默认情况下,它是只会显⽰是否优秀:false的,因为我们默认传⼊的数很⼩:
public class MainActivity extends AppCompatActivity {
private String TAG="Crackme";
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.activity_main);
textView =findViewById(R.id.tv);
textView.setText("是否优秀:"+isExcellent(46,54));
}
private boolean isExcellent(int chinese, int math){
if( chinese + math >=180){
return true;
}
else{
return false;
}
}
}
python转java代码我们编写⼀个脚本来Hook isExcellent函数,使它返回true,显⽰为是否优秀:true 对于这种简单的场景,直接修改返回值就可以了,因为只有结果是重要的。
8.1.2 代码
想直接返回结果很简单,直接在匿名⽅法⾥return即可。
if(Java.available){
Java.perform(function(){
var MainActivity = Java.use("ackme.MainActivity");
MainActivity.isExcellent.implementation=function(){
return true;
}
});
}
将上⾯的代码保存为:exp1.js
执⾏adb shell 'su -c /data/local/tmp/frida-server'启动服务端
运⾏⽬标App
执⾏frida -U -l exp1.js ackme注⼊代码
按返回键返回桌⾯,再重新打开App,发现达到预期
在命令⾏输⼊exit,回车,停⽌注⼊代码
注:这⾥为什么要打开两次App?第⼀打开是为了让frida能够到进程,第⼆次打开是为了验证结果,即使Hook成功了,界⾯是有缓存的,并不能实时显⽰Hook结果,所以需要重新打开App
8.2 修改参数
8.2.1 场景
假设有以下场景,isExcellent除了返回是否优秀以外,⽅法的内部还把分数打印出来。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论