在安卓开发的世界里,Frida 是一个极具威力的工具,它能帮助开发者轻松实现代码插桩、动态调试等功能,但同时也可能被恶意利用,威胁应用安全。今天,就让我们深入探索 Frida 的使用技巧与防护策略,让你在开发中游刃有余,同时筑牢安全防线!
一、Frida 简介:开发者的强大助力
Frida 是一个基于 Python 和 JavaScript 的动态代码插桩工具,它支持 Windows、macOS、GNU/Linux、iOS、Android 等众多平台,能够将 JavaScript 代码片段或自定义库注入到原生应用中,实现对应用运行时的动态修改和调试。无论是快速定位问题、测试功能,还是进行安全研究,Frida 都能大显身手。
二、环境搭建:轻松上手 Frida
(一)系统环境
- 推荐使用最新版本的 Python 3.x。
- 支持 Windows、macOS 和 GNU/Linux 系统。
(二)安装步骤
- 打开终端或命令提示符,运行以下命令安装 Frida 及其工具:
pip install frida pip install frida-tools - 安装完成后,可以通过运行测试脚本验证安装是否成功。将以下代码保存为
test.py,并在对应的系统环境下运行(macOS 或 GNU/Linux 系统需将代码中的"notepad.exe"替换为"cat",并执行sudo sysctl kernel.yama.ptrace_scope=0命令):如果能正常打印出模块名,说明 Frida 已经可以正常使用啦!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 脚本,可以借助 VSCode 和 Node.js 来实现代码提示。具体操作如下:
- 在工作目录执行以下命令:
git clone https://github.com/oleavr/frida-agent-example.git cd frida-agent-example npm install - 在
agent/index.ts文件中编写脚本,即可享受代码提示带来的便利。 - 执行
npm run watch命令,实现脚本更改时的自动编译。 - 使用
frida -U -f com.example.android -l _agent.js命令运行 Frida 脚本。
四、远程环境设置:安卓设备的连接与调试
以安卓为例,要实现远程调试,需要先下载对应版本的 frida-server。具体步骤如下:
- 访问 Frida 的 GitHub Release 页面,找到并下载与本地 Frida 版本匹配的
frida-server文件。 - 使用
adb push命令将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 - 在 PC 端执行测试命令,验证
frida-server是否正常工作:
如果一切正常,即可开始远程调试安卓应用啦!frida-trace -U -i open -N com.google.android.apps.maps
五、Frida 操作模式:灵活应对不同需求
(一)CLI 模式(命令行模式)
通过命令行将 JS 脚本注入到进程中,适用于简单的 hook 修改或快速调试场景。例如:
frida -U -f [包名] -l [js脚本]
(二)RPC 模式
借助 Python 脚本实现更复杂的功能,适用于大规模调用场景。以下是一个简单的 RPC 模式示例:
import frida
import sys
import json
def on_message(message, data):
print(json.dumps(message, indent=2))
def main():
dev = frida.get_usb_device() # 连接安卓设备
pid = dev.spawn("com.example.myapplication") # 启动目标应用
session = dev.attach(pid) # 附加到应用进程
js = open("hook.js", "r", encoding="utf8", newline="\n").read() # 加载 JS 脚本
script = session.create_script(js)
script.on("message", on_message)
script.load()
dev.resume(pid) # 恢复应用运行
sys.stdin.read()
if __name__ == "__main__":
main()
六、Frida 脚本编写:实现强大功能的关键
(一)Java 层 Hook
1. Hook 脚本模板
function main() {
Java.perform(
function() {
console.log("My Hook running...");
// 在此处编写 Hook 代码
}
);
}
setImmediate(main);
2. 示例代码分析
以下是一个简单的安卓应用代码示例,我们将基于此进行 Hook 操作:
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);
setContentView(R.layout.activity_main);
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)";
}
}
}
3. 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;
}
}
4. 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;
}
}
5. 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;
}
}
6. 枚举类与类方法
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()
}
7. 枚举并 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()
}
8. 修改字段
注意:字段名与函数名相同时,需要在字段名前面加个下划线用于区分。
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(){}
});
}
9. 主调调用
如果想要创建 Java 类的实例,可以使用$new()方法。
function invokeMethod(){
let targetClass = Java.use("com.example