安卓 Frida 的使用与安全指南

简介

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。

  1. 在工作目录执行以下命令
    • git clone https://github.com/oleavr/frida-agent-example.git
    • cd frida-agent-example
    • npm install
  2. 可以在agent/index.ts中编写脚本,就可以看到frida脚本的代码提示了。
  3. 要在更改脚本时自动编译则在终端中执行 npm run watch
  4. 通过frida -U -f com.example.android -l _agent.js命令执行frida脚本

远程环境

以安卓为例,我们首先需要前往 Frida 的 GitHub Release 页面下载对应版本的 frida-server。如果不确定 Frida 版本,可以通过执行 frida --version 来查看。 步骤

  1. 把frida-server传输到设备的临时目录下 adb push .\frida-server-17.2.15-android-arm64 /data/local/tmp
  2. 修改frida-server chmod +x ./frida-server-17.2.15-android-arm64
  3. 执行frida-server ./frida-server-17.2.15-android-arm64
  4. 测试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 提供的多重防护能够显著提升应用的抗破解能力,有效保护开发者的利益和用户的隐私。

滚动至顶部
售前客服
周末值班
电话

电话

13910187371