简介
Frida是一个基于Python和JavaScript的动态代码插桩工具,它是原生应用的 Greasemonkey,或者用更专业的术语来说,它是一个动态代码插桩工具包。它允许你将 JavaScript 代码片段或你自己的库注入到 Windows、macOS、GNU/Linux、iOS、watchOS、tvOS、Android、FreeBSD 和 QNX 平台上的原生应用中。Frida 还提供了基于 Frida API 构建的一些简单工具。这些工具可以直接使用,也可以根据你的需求进行调整,或者作为 API 使用示例。
环境
系统环境
- Python – 强烈推荐最新 3.x
- Windows, macOS, or GNU/Linux
安装命令
pip install frida
pip install frida-tools
安装测试
保存下面代码到test.py。如果是macOS或GNU/Linux系统,需要把下代码中的”notepad.exe”替换成”cat”,并且执行sudo sysctl kernel.yama.ptrace_scope=0
命令,之后使用python test.py
运行脚本。
import frida
def on_message(message, data):
print("[on_message] message:", message, "data:", data)
session = frida.attach("notepad.exe")
script = session.create_script("""
rpc.exports.enumerateModules = () => {
return Process.enumerateModules();
};
""")
script.on("message", on_message)
script.load()
print([m["name"] for m in script.exports.enumerate_modules()])
如果可以正常打印出模块名,表明你已经可以运行Frida脚本了。
代码提示
为了更方便编写 Frida 脚本,我们可以安装一个代码提示插件来辅助编程。在此之前,请确保你已经安装了 VSCode 和 Node.js。
- 在工作目录执行以下命令
git clone https://github.com/oleavr/frida-agent-example.git
cd frida-agent-example
npm install
- 可以在agent/index.ts中编写脚本,就可以看到frida脚本的代码提示了。
- 要在更改脚本时自动编译则在终端中执行
npm run watch
- 通过
frida -U -f com.example.android -l _agent.js
命令执行frida脚本
远程环境
以安卓为例,我们首先需要前往 Frida 的 GitHub Release 页面下载对应版本的 frida-server。如果不确定 Frida 版本,可以通过执行 frida --version
来查看。 步骤
- 把frida-server传输到设备的临时目录下
adb push .\frida-server-17.2.15-android-arm64 /data/local/tmp
- 修改frida-server
chmod +x ./frida-server-17.2.15-android-arm64
- 执行frida-server
./frida-server-17.2.15-android-arm64
- 测试frida-server是否正常工作,在PC中执行
frida-trace -U -i open -N com.google.android.apps.maps
指令命令,如果可以正常情况下会追踪设备中该应用中open函数调用,表示frida-server工作正常。
Frida操作模式
CLI 模式(命令行模式)
通过命令行将JS脚本注入到进程中。适用于较小的 hook 修改或简单场景,优点是便于注入操作。例如:frida -U -f [包名] -l [js脚本]
RPC 模式
借助 Python 脚本来工作,实际也是注入 JS 脚本,但通过 RPC 传输,可对复杂数据进行处理,适用于大规模调用场景。在这种模式下,需要编写 Python 脚本来与 Frida 进行交互,实现更复杂的功能。例如:
import frida
import sys
import json
def on_message(message, data):
print(json.dumps(message, indent=2))
def main():
dev = frida.get_usb_device() # android
pid = dev.spawn("com.example.myapplication")
session = dev.attach(pid)
js = open("hook.js", "r", encoding="utf8", newline="\n").read()
script = session.create_script(js)
script.on("message", on_message)
script.load()
dev.resume(pid)
sys.stdin.read()
if __name__ == "__main__":
main()
注入模式
Frida脚本编写
下面我会使用一个样本为其编写HOOK脚本来介绍frida常用API。
Java层Hook
Hook脚本模板
function main() {
Java.perform(
function() {
console.log("My Hook running...");
// Write the Hook code below
}
);
}
setImmediate(main);
示例代码
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private int foo = 100;
private String prefixTag = "Hello";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
findViewById(R.id.btnClickMe).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onClickClickMe(v);
}
});
}
public static int staticMethod(String title){
Log.e(TAG, "staticMethod " + title);
return title.length();
}
protected void onClickClickMe(View sender){
Log.e(TAG, "TagNumber: " + Integer.toString(foo) + " prefix " + prefixTag + " "+ foo("MainActivity"));
Log.e(TAG, "TagNumber: " + Integer.toString(foo) + " prefix " + prefixTag + " "+ someMethod("MainActivity"));
Log.e(TAG, "TagNumber: " + Integer.toString(foo) + " prefix " + prefixTag + " "+ someMethod("MainActivity", 65535));
InnerClass innerClass = new InnerClass("InnerClass");
Log.e(TAG, "TagNumber: " + Integer.toString(foo) + " prefix " + prefixTag + " "+ innerClass.someMethodInInnerClass("innerClass"));
}
public String foo(String arg) {
return arg + "foo(String arg)";
}
public String someMethod(String arg) {
return arg + "someMethod(String arg)";
}
public String someMethod(String arg, int number) {
return arg + Integer.toString(number) + "someMethod(String arg)" ;
}
public class InnerClass {
private String tip;
public InnerClass(String tip) {
this.tip = tip;
}
public String someMethodInInnerClass(String arg) {
return tip + ":" + arg + "someMethodInInnerClass(String arg)";
}
}
}
Hook普通方法
function hookNormalMethod() {
let targetClass = Java.use("com.example.myapplication.MainActivity");
targetClass.foo.implementation = function(str) {
console.log("[+] hookNormalMethod arg1:" + str);
let retVal = this.foo(str);
console.log("[+] hookNormalMethod retVal:" + retVal);
return retVal;
}
}
Hook重载方法
function hookOverrideMethod_string(){
let targetClass = Java.use("com.example.myapplication.MainActivity");
targetClass.someMethod.overload('java.lang.String').implementation = function(str) {
console.log("[+] hookOverrideMethod_string arg1:" + str);
let retVal = this.someMethod(str);
console.log("[+] hookOverrideMethod_string retVal:" + retVal);
return retVal;
}
}
function hookOverrideMethod_string_int(){
let targetClass = Java.use("com.example.myapplication.MainActivity");
targetClass.someMethod.overload('java.lang.String', 'int').implementation = function(str, number) {
console.log("[+] hookOverrideMethod_string_int arg1:" + str + "arg2:" + number.toString(16));
let retVal = this.someMethod(str, number);
console.log("[+] hookOverrideMethod_string_int retVal:" + retVal);
return retVal;
}
}
Hook内部类的构造方法
function hookInnerInitMethod() {
let targetClass = Java.use("com.example.myapplication.MainActivity$InnerClass");
targetClass.$init.overload('com.example.myapplication.MainActivity', 'java.lang.String').implementation = function(extthis, str) {
let myarg2 = str + "-.- "
console.log("[+] hookInnerInitMethod arg1: " + str + " myarg1: " + myarg2) ;
let retVal = this.$init(extthis, myarg2);
console.log("[+] hookInnerInitMethod retVal:" + retVal);
return retVal;
}
}
枚举类与类方法
function enumerateClassesMethod(){
console.log(`------------------enumerateLoadedClasses------------------`)
Java.enumerateLoadedClasses({
onMatch:function(name, handle){
if(name.indexOf("com.example.myapplication.MainActivity") != -1) {
console.log("targetObject: " + name);
let clazz = Java.use(name)
console.log(clazz);
let methods = clazz.class.getDeclaredMethods();
console.log(methods)
console.log(`------------------------------------`)
console.log()
}
},
onComplete:function(){}
})
console.log(`---------------------------------------------------------`)
console.log(`---------------------------------------------------------`)
console.log()
}
枚举并Hook匹配到的方法
function HookClassAllMethod(){
console.log(`------------------HookClassAllMethod------------------`)
let targetClass = Java.use("com.example.myapplication.MainActivity");
let methods = targetClass.class.getDeclaredMethods();
for(let i = 0; i < methods.length; ++i){
let name = methods[i].getName();
console.log(`hook method name: ${name}`);
for(let j = 0; j < targetClass[name].overloads.length; ++j){
targetClass[name].overloads[j].implementation = function() {
console.log(`method name: ${name}`);
for(let k=0; k<arguments.length; k++){
console.log(` args:${k} name: ${arguments[k]}`);
}
return this[name].apply(this,arguments);
}
}
}
console.log(`---------------------------------------------------------`)
console.log(`---------------------------------------------------------`)
console.log()
}
修改字段
注意:字段名与函数名相同时,需要在字段名前面加个下划线用于区分。
function modifyObjectField() {
let targetClass = Java.use("com.example.myapplication.MainActivity");
targetClass.TAG.value = "[MainActivity]";
console.log(targetClass.TAG.value);
Java.choose("com.example.myapplication.MainActivity", {
onMatch: function(obj) {
// console.log(obj)
obj._foo.value = 88;//字段名与函数名相同 前面加个下划线
obj.prefixTag.value = "Hello World ";
},
onComplete:function(){}
});
}
主调调用
如果想要创建 Java 类的实例,可以使用$new()
方法。
function invokeMethod(){
let targetClass = Java.use("com.example.myapplication.MainActivity");
let innerClass = Java.use("com.example.myapplication.MainActivity$InnerClass");
let obj = innerClass.$new();
console.log("obj: " + obj);
// static method
let retVal = targetClass.staticMethod("Hello World");
console.log("staticMethod retVal: " + retVal);
// instance method
Java.choose("com.example.myapplication.MainActivity", {
onMatch: function(obj){
let retVal = obj.foo("Hi ");
console.log("invoke foo method retVal: " + retVal);
},
onComplete: function(){
}
});
}
打印栈回溯
function printStackTracr() {
// console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
let Throwable = Java.use("java.lang.Throwable");
let obj = Throwable.$new();
let stakcTrace = obj.getStackTrace();
console.log("------------Stack Trace------------");
for(let i = 0; i < stakcTrace.length; ++i){
console.log(stakcTrace[i].toString());
}
console.log("--------------------------------------");
console.log("--------------------------------------");
console.log();
}
安全防范
Frida 框架功能强大,但它对应用安全可能造成一定影响。为应对这一问题,Virbox Protector 是一款优秀的 Android 应用加固工具,专为防止逆向分析、反编译和恶意代码注入而设计。通过多重加固手段,Virbox Protector 有效保障应用的安全性,防止黑客利用工具如 Frida 进行动态分析。它提供了 DEX 加密、字符串加密、反调试和文件校验等功能,有效防止 APK 被篡改或注入恶意代码。同时,反注入和模拟器检测功能能够阻止应用在模拟器或被 Root 的设备上运行,从源头上确保数据安全。结合签名校验和防截屏功能,Virbox Protector 进一步强化了应用的防护层级,帮助开发者应对各种逆向分析和篡改攻击,确保应用发布时的完整性与安全性。面对 Frida 等动态分析工具,Virbox Protector 提供的多重防护能够显著提升应用的抗破解能力,有效保护开发者的利益和用户的隐私。