准备工作

安装

1
2
pip install frida
pip install frida-tools

编译器*

编写frida用webstorm,下载一下,然后想要运行Js脚本的话还得下载一个nodejs

在对应项目目录下cmd然后输入

npm i @types/frida-gum

作用就是可以提示代码

firda server

想要将写好的frida脚本注入到进程中,就得用

frida server通过电脑链接手机,意味着电脑得开着

frida injectroot权限下,手机自己就直接注入了

frida gadget非root权限下手机自己注入

以上三个文件在frida github上下载

下载版本要与frida对应,下载之后解压出来的文件推送到手机的data/local/tmp目录下,然后赋予权限即可使用

1
2
3
4
5
6
7
8
9
10
11
12
C:\Users\13621>adb push C:\Users\13621\Desktop\frida-server-16.6.6-android-arm64 /data/local/tmp/fsarm64
C:\Users\13621\Desktop\frida-server-16.6.6-android-arm64: ...le pushed, 0 skipped. 65.0 MB/s (56875320 bytes in 0.835s)

C:\Users\13621>adb shell
crosshatch:/ $ su
crosshatch:/ # cd data/local/tmp
crosshatch:/data/local/tmp # ls
dalvik-cache fsarm64 lldb-server start_lldb_server.sh uidump.xml
crosshatch:/data/local/tmp # chmod +x fsarm64
crosshatch:/data/local/tmp # ./
dalvik-cache/ fsarm64 lldb-server start_lldb_server.sh uidump.xml
crosshatch:/data/local/tmp # ./fsarm64

ps:后台别关

运行命令

在ws终端中运行文件

附加

frida -UF -l example.js

打开

frida -U -f 包名 -l example.js

包名位置

有对应输出就是ok了

代码

打印函数

输出函数一般都用的是

console.log(“hello world”);

框架

1
2
3
4
5
6
7
Java.perform(function() {
// 关键点1:确保包名和类名完全匹配
var MainActivity = Java.use('包名');
MainActivity.onCreate.implementation=function (a) {
this.onCreate(a);
};
});

java.use(a)

a是要寻找的类名,该函数用于寻找类名

var a=java.use(a);

imlepmentation

在上一步得到类名之后,类名.方法名.imlepmentation = function(){ }

imlepmantation的作用是修改函数,也就是要hook了,function里面就是要修改的

ps:因为此代码是hook钩取所以咱们知道运行时候环境会处于原先的位置,所以

this.方法名,就是原来方法的意思

1
2
3
4
5
6
7
8
java.perform(function(){
var a=java.use(a);
a.b.implementation=function(){

this.b; //运行原本方法

}
});

frida版本问题

frida版本过高可能会导致一些软件不兼容,hook不进去

这边最推荐frida 16.0.19

pip install frida==16.0.19

记得把frida server也替换了

java.cast(a,b)

a是要转换的,b是转换变成什么类型,返回值是转换成功返回的类型值

神器,基本用法在课里学了,这里记实例和技巧

学到这里有点浮躁,听一首纯音乐慢慢来

找了三个题来进行frida的强化训练,训练最终目标学会反root检测和frida.so层和.so的调试

三个题分别是 RCTF2022-CheckYourKey VNCTF2025-ezCompose SWPUCTF2025-test

Root权限检测

基本frida框架

1
2
3
4
5
6
7
Java.perform(function() {
// 关键点1:确保包名和类名完全匹配
var MainActivity = Java.use('包名');
MainActivity.onCreate.implementation=function (a) {
this.onCreate(a);
};
});

这三个题无论哪个用这个代码frida最后都会程序闪退,提示”Root Detected”

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
//test
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
ActivityMainBinding inflate = ActivityMainBinding.inflate(getLayoutInflater());
this.binding = inflate;
setContentView(inflate.getRoot());
if (ContextCompat.checkSelfPermission(this, "android.permission.WRITE_EXTERNAL_STORAGE") != 0) {
ActivityCompat.requestPermissions(this, new String[]{"android.permission.WRITE_EXTERNAL_STORAGE"}, 1);
}
}
//ezCompose
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
androidx.activity.compose.ComponentActivity.setContent$default(this, null, ComposableLambdaKt.composableLambdaInstance(2046968156, true, new Function2<Composer, Integer, Unit>() { // from class: com.atri.ezcompose.MainActivity$onCreate$1
@Override // kotlin.jvm.functions.Function2
public /* bridge */ /* synthetic */ Unit invoke(Composer composer, Integer num) {
invoke(composer, num.intValue());
return Unit.INSTANCE;
}
//CheckYourKey
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding inflate = ActivityMainBinding.inflate(getLayoutInflater());
this.binding = inflate;
setContentView(inflate.getRoot());
context = this;
Button checkButton = (Button) findViewById(C0589R.C0592id.checkButton);
final EditText inputEditText = (EditText) findViewById(C0589R.C0592id.inputEditText);
checkButton.setOnClickListener(new View.OnClickListener() {

三段截取onCreate开头最后我还找了一个正常可以frida的onCreate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView(C0890R.layout.activity_main);
checkSecutity();
final EditText editText = (EditText) findViewById(C0890R.C0893id.usernameEditText);
final EditText editText2 = (EditText) findViewById(C0890R.C0893id.passwordEditText);
Button button = (Button) findViewById(C0890R.C0893id.loginButton);
String str = DecStr.get("Exv3nhr5BNW0axn3aNz/DNv9C3q0wxj/Exe=");
Intrinsics.checkNotNullExpressionValue(str, "get(...)");
final Class<?> cls = getClass(str);
if (cls == null) {
Toast.makeText(this, "Error: Program load failure", 0).show();
finish();
System.exit(-1);
throw new RuntimeException("System.exit returned normally, while it was supposed to halt JVM.");
}
button.setOnClickListener(new View.OnClickListener() { // from class: com.nctf.simplelogin.MainActivity$$ExternalSyntheticLambda0
@Override // android.view.View.OnClickListener
public final void onClick(View view) {
MainActivity.onCreate$lambda$0(editText, editText2, this, cls, view);
}
});
}

SWPU Android03-截获onclick函数

一开始用的frdia

失败

1
2
3
4
5
6
7
8
9
Java.perform(function() {
// 关键点1:确保包名和类名完全匹配
var MainActivity = Java.use('com.example.wllm_yyds.MainActivity');
MainActivity.onClick.implementation=function (a) {
var ret = onClick(a);
console.log(this.input);
return ret;
};
});

gpt改良1版

获取所有类名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Java.perform(function () {
// 列出匹配的已加载类(如果不确定包名)
var matches = Java.enumerateLoadedClassesSync().filter(function(c){ return c.indexOf('wllm_yyds') !== -1; });
console.log('matched classes count = ' + matches.length);
matches.forEach(function(c){ console.log(c); });

// 如果确认包名类名
try {
var C = Java.use('com.example.wllm_yyds.MainActivity');
console.log('Got MainActivity:', C);
// 列出声明的方法(反射)
var methods = C.class.getDeclaredMethods();
for (var i = 0; i < methods.length; i++) {
console.log(methods[i].toString());
}
} catch (err) {
console.log('Java.use error:', err);
}
});

获取MainActivity的所有方法名,gpt给定最终版

hook进onClick代码

1
2
3
4
5
6
7
8
9
Java.perform(function () {
// Hook 匿名内部类的 onClick 方法
var BtnClick = Java.use('com.example.wllm_yyds.MainActivity$1');
BtnClick.onClick.implementation = function (v) {
console.log('按钮被点击啦!this =', this);
// 可以在这里调用原实现
this.onClick(v);
};
});

点击事件是用 Lambda 表达式(匿名内部类) 或 setOnClickListener 注册的。 这类点击事件最终会生成匿名内部类(比如 MainActivity$1、MainActivity$$ExternalSyntheticLambda0), 这些类里才有 onClick(android.view.View)。

hook到main getid

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
Java.perform(function () {
var MainActivity = Java.use("com.example.wllm_yyds.MainActivity");
var InnerClass = Java.use("com.example.wllm_yyds.MainActivity$1");

// Hook onClick
InnerClass.onClick.implementation = function (v) {
console.log("按钮被点击啦!");

// 获取外部类 MainActivity 的实例(匿名内部类都有一个 this$0 指向外部类)
var mainThis = this.this$0.value;
console.log("外部类实例:", mainThis);

// 获取 text 对象
var textView = mainThis.text.value;
console.log("TextView 对象:", textView);

// 如果 textView 已经赋值,就能直接取字符串
if (textView !== null) {
var str = textView.getText().toString();
console.log("当前输入内容:", str);
} else {
console.log("textView 为空,尝试主动 findViewById 获取");
var R$id = Java.use("com.example.wllm_yyds.R$id");
var id = R$id.editTextText3.value;
var textView2 = Java.cast(mainThis.findViewById(id), Java.use("android.widget.TextView"));
var str2 = textView2.getText().toString();
console.log("主动获取输入内容:", str2);
}

// 调用原逻辑
this.onClick(v);
};
});

SWPU Android02-frida

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Java.perform(function() {
// 关键点1:确保包名和类名完全匹配
var MainActivity2 = Java.use('com.example.wlllm01.MainActivity');
var MainActivity3 = Java.perform('com.example.wlllm01.MainActivity$1')
MainActivity2.cipher.implementation=function () {
console.log('hello');
var ret = this.cipher();
console.log(ret);
return ret;
};
MainActivity3.onClick.implementation = function(v){
console.log('click');
return this.onClick(v);

}
});

这个失败了

主动调用frida

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Java.perform(function () {
Java.choose("com.example.wlllm01.MainActivity", {
onMatch: function (instance) {
try {
var cipher = instance.cipher();
console.log("[*] getiv() via existing instance →", cipher);
} catch (e) {
console.log("[!] 调用已存 Activity 实例失败:", e);
}
},
onComplete: function () {

}
});
});

frida -U -F com.example.wlllm01 -l .\ctf2.js

启动指令换一个