前言
- 本文非全原创内容,部分内容摘自GitHub@luoyesiqiu
- 目前知识点还不全,深入学习实战后会对本文进行知识点补全
- 本文仅用于技术学习与交流,作为个人的笔记摘录
环境配置
- 对于环境配置这边就不进行赘述,网上大把的教程
- 如果你的设备root了,推荐直接使用
jshook
这个插件,会更加方便
Hook Java方法
载入类
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的内部类
修改函数的实现
修改一个函数的实现是逆向调试中相当有用的。修改一个函数的实现后,如果这个函数被调用,我们的Javascript代码里的函数实现也会被调用。
函数参数类型表示
不同的参数类型都有自己的表示方法
- 对于基本类型,直接用它在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;
带参数的构造函数
修改参数为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
}
无参数构造函数
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
一般函数
修改函数名为func,参数为byte[]类型的函数的实现
ClassName.func.overload('[B').implementation=function(param){
//do something
//return ...
}
无参数的函数
ClassName.func.overload().implementation=function(){
//do something
}
注: 在修改函数实现时,如果原函数有返回值,那么我们在实现时也要返回合适的值
ClassName.func.overload().implementation=function(){
//do something
return this.func();
}
调用函数
和Java一样,创建类实例就是调用构造函数,而在这里用$new
表示一个构造函数。
var ClassName=Java.use("com.luoye.test.ClassName");
var instance = ClassName.$new();
实例化以后调用其他函数
var ClassName=Java.use("com.luoye.test.ClassName");
var instance = ClassName.$new();
instance.func();
字段操作
字段赋值和读取要在字段名后加.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
类型转换
用Java.cast
方法来对一个对象进行类型转换,如将variable
转换成java.lang.String
:
var StringClass=Java.use("java.lang.String");
var NewTypeClass=Java.cast(variable,StringClass);
Java.available字段
这个字段标记Java虚拟机(例如: Dalvik 或者 ART)是否已加载, 操作Java任何东西之前,要确认这个值是否为true
Java.perform方法
Java.perform(fn)在Javascript代码成功被附加到目标进程时调用,我们核心的代码要在里面写。格式:
Java.perform(function(){
//do something...
});
实例讲解
有了以上的基础知识,我们就可以进行编写代码了
修改返回值
场景
假设有以下的程序,给isExcellent方法传入两个值,通过计算,返回一个布尔值,表示是否优秀。默认情况下,它是只会显示是否优秀:false
的,因为我们默认传入的数很小:
public class MainActivity extends AppCompatActivity {
private String TAG="Crackme";
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(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;
}
}
}
我们编写一个脚本来Hook isExcellent函数,使它返回true,显示为是否优秀:true
对于这种简单的场景,直接修改返回值就可以了,因为只有结果是重要的。
代码
想直接返回结果很简单,直接在匿名方法里return即可。
if(Java.available){
Java.perform(function(){
var MainActivity = Java.use("com.luoyesiqiu.crackme.MainActivity");
MainActivity.isExcellent.implementation=function(){
return true;
}
});
}
修改参数
场景
假设有以下场景,isExcellent除了返回是否优秀以外,方法的内部还把分数打印出来。
public class MainActivity extends AppCompatActivity {
private String TAG="Crackme";
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView =findViewById(R.id.tv);
textView.append("是否优秀:"+isExcellent(46,54)+"\n");
}
private boolean isExcellent(int chinese, int math){
textView.append("语文+数学总分:"+(chinese+math)+"\n");
if( chinese + math >=180){
return true;
}
else{
return false;
}
}
}
这种情况下我们不可能只返回是否优秀吧,显示的总分很低,但是却返回优秀,是很尴尬的...所以我们要修改isExcellent方法的参数,使其通过计算打印和返回合理的值。
代码
if(Java.available){
Java.perform(function(){
var MainActivity = Java.use("com.luoyesiqiu.crackme.MainActivity");
MainActivity.isExcellent.overload("int","int").implementation=function(chinese,math){
return this.isExcellent(95,96);
}
});
}
上面的代码,通过overload方法重载参数,修改isExcellent方法实现,并在实现函数里调用原来的方法,得到新的返回值