反编译一个app搜不到关键字

1
2
3
4
5
6
H5的app,jadx只反编译dex
字符串被加密
反射调用的相关类
app加固
动态加载的dex
热修复

Hook系统函数

不管怎么加密,反射,底层都是调用的目标类的目标函数

常用函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
java.util.Hashmap put
java.util.ArrayList add,addAll,set
java.lang.StringBuilder append
java.lang.StringBuffer append
android.text.TextUtils isEmpty
java.lang.String trim
Log
android.widget.Toast show
android.widget.EditText getText
org.json.JSONObject put
java.util.Collections sort 有的消息摘要算法需要sort方法来排序,确保密文一致
java.util.Arrays sort,toString
android.util.Base64
java.util.Base64
okio.Base64
okio.ByteString
1
2
frida -UF -l Hook.js -o result.txt
如果输出太多就存到txt里
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Java.perform ( function () {
console.log("Hooking...");
// 主动调用,打印堆栈
// 用的是android.util.Log.getStackTraceString方法Log.getStackTraceString(new Throwable())
function showStack() {
var log = Java.use("android.util.Log");
var throwable = Java.use("java.lang.Throwable")
// 静态函数直接调用
console.log(log.getStackTraceString(throwable.$new()));
}
var hashMap = Java.use("java.util.HashMap");
hashMap.put.implementation = function (a,b) {
// 不能直接打印堆栈,不然输出太多程序会崩
// 可以加判断
if (a.equals("username")) {
showStack();
console.log("hashMap.put:",a,b);
}
return this.put(a,b);
}
});

有的可能需要类型强转才能输出,比如EditText方法返回的是Editable类,这个类在原方法里是由Charsequence类强转来的,Editable类调用toString无法输出字符串,就需要强转回Charsequence类再调用toString(强转有时候不能随便转)

1
2
3
4
5
6
7
var editText = Java.use("android.widget.EditText");
editText.getText.overload().implementation = function () {
var result = this.getText();
// 第一个参数是当前对象,第二个参数是目标类
result = Java.cast(result,Java.use("java.lang.CharSequence"));
return result;
}

怎么确定类?除了看源码分析类之间的转换外,还可以先用JSON.stringify(result)来看输出,有时候输出里会有类名,然后再强转回该类

运行起Hook.js之后可以运行Java.enumerateLoadedClassesSync()来查看当前已经加载的类,会打印出类路径,因为有的类查阅的资料里加载的类路径和实际app加载的类路径不一定一样

确定目标按钮的id值,可以通过R文件去获取,因为调用findViewById(R.id.btn_login)这种形式,id是写在R文件中的,R文件的路径一般是包名.R

或者可以通过截屏功能去确定id

通过R文件的话,需要Hook内部类,因为id这些都是定义在R类的内部类中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.dodonew.online;

/* loaded from: classes.dex */
public final class R {

/* loaded from: classes.dex */
public static final class anim {
public static final int abc_fade_in = 0x7f040000;
public static final int abc_fade_out = 0x7f040001;
public static final int abc_grow_fade_in_from_bottom = 0x7f040002;
public static final int abc_popup_enter = 0x7f040003;
...
}
public static final class id {
public static final int action0 = 0x7f0d01e1;
public static final int action_bar = 0x7f0d007d;
...
}
}

frida访问内部类和内部类的属性值

1
2
R$id
R$id.btn_login.value

Hook属性值(R.id.btn_login)

1
2
3
4
5
6
7
8
9
10
var btn_login_id = Java.use("com.dudunew.online.R$id").btn_login.value;
console.log("btn_login_id:",btn_login_id);
var appCompatActivity = Java.use("android.support.v7.app.AppCompatActivity");
appCompatActivity.findViewById.implementation = function (a) {
if (a == btn_login_id){
showStack();
console.log("appCompatActivity.findViewById:" + a);
}
return this.findViewById(a);
}

还有一个问题,就是在Hook函数findViewById的时候,如果此时已经打开了app,是Hook不到的,因为findViewById在app被加载显示之前就已经执行了,所以需要指定包名去Hook

1
2
3
frida -U -f com.xxx.xxx -l hook.js -o log.txt --no-pause
-f表示由frida去主动启动app
--no-pause表示启动之后立即执行

关于Hook setOnClickListener方法

因为调用setOnClickListener方法一般是先findViewById找到控件,然后对findViewById的返回值调用setOnClickListener方法

1
2
3
4
5
private void initEvent() {
findViewById(R.id.btn_login).setOnClickListener(this);
findViewById(R.id.btn_forget_password).setOnClickListener(this);
findViewById(R.id.btn_register_now).setOnClickListener(this);
}

findViewById返回的值一般是EditText这类View类的子类,所以直接Hook View类的setOnClickListener方法即可

1
2
3
4
5
6
7
8
9
10
11
var btn_login_id = Java.use("com.dudunew.online.R$id").btn_login.value;
console.log("btn_login_id:",btn_login_id);
var view = Java.use("android.view.View");
view.setOnClickListener.implementation = function (a) {
// 因为调用setOnClickListener的对象是View类的子类,所以有getId()方法
if (this.getId() == btn_login_id){
showStack();
console.log("view.setOnClickListener is called");
}
return this.setOnClickListener(a);
}

hook StringBuildertoString方法(字符串的拼接,用+的时候其实也是走的StringBuilder)

1
2
3
4
5
6
7
8
9
var stringBuilder = Java.use("java.lang.StringBuilder");
stringBuilder.toString.implementation = function () {
var result = this.toString.apply(this, arguments);
if(result == "username=11111"){
showStacks();
console.log("stringBuilder.toString is called!", result);
}
return result;
}

Hook接口的实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var classes = Java.enumerateLoadedClassesSync();
for (const index in classes) {
let className = classes[index];
if(className.indexOf("com.xxx") === -1) continue;
let clazz = Java.use(className);
// 获取当前对象的接口
let resultArr = clazz.class.getInterfaces();
if(resultArr.length === 0) continue;
for (let i = 0; i < resultArr.length; i++) {
if(resultArr[i].toString().indexOf("com.xxx.app.TestRegisterClass") !== -1){
console.log(className, resultArr);
}
}
}

Hook抽象类的实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var classes = Java.enumerateLoadedClassesSync();
for (const index in classes) {
// 获取所有类名
let className = classes[index];
// 最好过滤一下类名,是app包名前缀的才往后执行,不然类太多了
if(className.indexOf("com.xxx") === -1) continue;
// 获取类的对象
let clazz = Java.use(className);
// 获取父类,抽象类是单继承的额,直接getSuperclass就行
let resultClass = clazz.class.getSuperclass();
if(resultClass == null) continue;
if(resultClass.toString().indexOf("com.xxx.app.TestAbstract") !== -1){
console.log(className, resultClass);
}
}