Juana111 发表于 2025-3-22 15:58

安卓逆向学习4-frida编译调试、框架学习

本帖最后由 Juana111 于 2025-3-22 16:36 编辑

0x00 搞清楚Fri.da和Objection的区别
表格一 fri.da&objection

表格中跨平台支持解释:Frida的跨平台支持中提到的Windows平台的使用,这里的Windows平台使用是可以通过Frida直接对Windows系统的程序进行调试,使用macOS和Windows作为例子来说有两个连接方式:1.在Windows上运行frida-server使用macOS进行连接。
[*]运行frida-server
[*]确保 Windows 和 macOS 在同一个网络中。
[*]获取 Windows 的 IP 地址(在 Windows 上运行 ipconfig)。
[*]在 macOS 上使用 Frida 客户端连接到 Windows:
[*]frida -H <Windows IP> -n <进程名>
[*]连接成功后,在 macOS 上编写和加载 JavaScript 脚本,对 Windows 上的目标进程进行 Hook 和调试。
2.在windows上运行frida-server并调试本地进程
[*]运行frida-server
[*]frida -n <进程名>
[*]连接成功后,直接在 Windows 上编写和加载 JavaScript 脚本,对本地进程进行 Hook 和调试。

表格二

0x01 frida的JavaScript脚本
表格一中提到的javascript脚本,Frida可以通过javascript脚本hook应用程序的函数,拦截函数的输入参数、返回值以及执行流程。
1.hook函数
[*]修改函数行为(如绕过验证、修改返回值)。
[*]监控函数的调用频率和参数值。
[*]分析加密算法、网络请求等关键逻辑。
Interceptor.attach(Module.findExportByName("libc.so", "strcmp"), {onEnter: function(args) {
      console.log("strcmp called with:", args.readCString(), args.readCString());},onLeave: function(retval) {
      console.log("strcmp returned:", retval);}});
2. 动态修改内存
通过 JavaScript 脚本,Frida 可以读取和修改目标进程的内存数据。
[*]修改内存中的变量值或字符串。
[*]绕过某些内存中的校验逻辑。
[*]动态篡改应用的行为。
var ptr = Module.findBaseAddress("libnative.so").add(0x1234);Memory.writeUtf8String(ptr, "new_value");
//Module.findBaseAddress是Frida提供的一个函数 找到libnative.so库在内存中的基地址,在基地址的基础上加上偏移量0x1234,
//将字符串"new_value"写入到指定的内存地址ptr
3. 调用函数
通过 JavaScript 脚本直接调用目标应用程序中的函数
[*]触发特定功能以测试其行为。
[*]调用未导出的函数或私有方法。
var func = new NativeFunction(Module.findExportByName("libnative.so", "my_function"), 'int', ['int']);
//查找 libnative.so 库中名为 my_function 的函数的地址。Module.findExportByName根据模块名(libnative.so)和函数名(my_function)查找该函数在内存中的地址
//'int'->my_function返回一个整数, ['int']->表示 my_function 接受一个整数参数。
var result = func(123);
//调用 my_function 并传入参数 123
console.log("Function returned:", result);
4. 监控和拦截系统调用
Frida 可以监控目标应用程序与操作系统之间的交互,如文件读写、网络请求等。
[*]分析应用程序的文件操作行为。
[*]拦截网络请求,查看或修改请求数据。
var openPtr = Module.findExportByName(null, "open");
//open 是标准库函数(通常由 libc.so 提供),用于打开文件
Interceptor.attach(openPtr, {

    onEnter: function(args) {
    //onEnter:在函数调用时触发。
      console.log("Opening file:", args.readCString());
    },
    onLeave: function(retval) {
    //onLeave:在函数返回时触发
      console.log("File opened with fd:", retval);
    }
});
open函数的原型int open(const char *pathname, int flags, mode_t mode);
[*]args 是第一个参数 pathname,表示要打开的文件路径。
[*]args 是第二个参数 flags,表示打开文件的标志。
[*]args 是第三个参数 mode,表示文件的权限模式(如果创建文件)
5.动态加载和卸载模块
Frida 可以在运行时加载或卸载目标应用程序中的模块(如 SO 文件)。
[*]分析特定模块的功能。
[*]绕过某些模块的加载校验。
var module = Process.findModuleByName("libnative.so");
console.log("Module base address:", module.base);这个功能对我来说比较抽象,具体的场景是在目标应用程序在运行时加载的某些模块(插件,广告SDK,加密库),例如一些恶意软件可能会在运行时动态加载加密模块来隐藏行为。举例:某个程序加密库在加载之后解密数据,此时就要hook掉解密函数来获取解密后的内容
var module = Process.findModuleByName("libcrypto.so");
if (module) {
    var decryptFunc = Module.findExportByName("libcrypto.so", "decrypt");
    Interceptor.attach(decryptFunc, {
      onEnter: function(args) {
            console.log("Decrypting data:", args.readCString());
      },
      onLeave: function(retval) {
            console.log("Decrypted data:", retval.readCString());
      }
    });
}其他功能:应用程序支持插件或扩展功能,通过动态模块来实现,就要分析插件的导出函数和调用逻辑;检测恶意行为,检测模块的加载行为,用户不知情的情况下加载恶意模块来窃取数据;加载大量模块,导致内存占用过高或启动速度变慢的情况。6.调试和日志记录Frida 的 JavaScript 脚本可以用于动态调试和记录应用程序的运行状态。
[*]打印函数调用栈。
[*]记录关键变量的值。
[*]跟踪应用程序的执行流程。
console.log("Current thread ID:", Process.getCurrentThreadId());
console.log("Backtrace:", Thread.backtrace(this.context, Backtracer.ACCURATE));
7.绕过反调试和检测
Frida 的 JavaScript 脚本可以用于绕过应用程序中的反调试机制。
[*]修改反调试标志位。
[*]Hook 反调试函数,使其失效。
var ptracePtr = Module.findExportByName(null, "ptrace");
Interceptor.replace(ptracePtr, new NativeCallback(function() {
    console.log("ptrace called, bypassing...");
    return 0;
}, 'int', []));
8. 自动化测试和分析
Frida 的 JavaScript 脚本可以用于自动化测试和分析应用程序的行为。
[*]批量测试函数的输入输出。
[*]自动化分析应用程序的逻辑。
function testFunction(input) {
    var func = new NativeFunction(Module.findExportByName("libnative.so", "my_function"), 'int', ['int']);
    var result = func(input);
    console.log("Input:", input, "Output:", result);
}
for (var i = 0; i < 10; i++) {
    testFunction(i);
}
0x02 Hook脚本
先搞清楚的是frida的JavaScript脚本是指使用Frida的JavaScript API编写的脚本,实现的功能:hook函数、读取和修改内存、调用native函数、动态调试。
hook脚本是frida的javascript脚本的一个子集,专门hook目标函数:拦截函数调用、修改函数行为、监控函数调用。

hook脚本经典结构
Java.perform(function() {
    // 1. 获取目标类
    var TargetClass = Java.use("com.example.app.TargetClass");

    // 2. Hook 目标方法
    TargetClass.targetMethod.implementation = function(arg1, arg2) {
      // 3. 打印日志
      console.log("targetMethod called with:", arg1, arg2);

      // 4. 调用原始方法(可选)
      var result = this.targetMethod(arg1, arg2);

      // 5. 修改返回值(可选)
      return result + 1;
    };
});
1.常见用途
[*]动态调试:打印函数参数和返回值;跟踪程序的执行流程。
[*]绕过验证:修改函数的返回值,绕过登录验证、许可证检查等。
[*]分析加密算法:拦截加密函数的输入和输出,分析其逻辑。
[*]修改应用行为:替换函数的实现,改变应用的行为。
[*]数据监控:记录敏感数据(如密码、密钥等)。
2.应用
应用验证许可证是否有效
Java.perform(function() {
    var LicenseManager = Java.use("com.example.app.LicenseManager");
    LicenseManager.checkLicense.implementation = function() {
      console.log("Bypassing license check...");
      return true; // 强制返回 true
    };
});
3.hook脚本的难点

4. 部分hook脚本
4.1.枚举已加载的类
为什么要枚举已加载的类?   已加载的类通过枚举类名,结合方法名、字段名、调用关系来定位目标,动态分析应用的行为。
目标类名被混淆,就可以先枚举已经加载的类
Java.perform(function() {
    Java.enumerateLoadedClasses({onMatch: function(className) {
    //enumerateLoadedClasses获取已加载的类的名称
            console.log(className);},onComplete: function() {}});});
4.2.Hook目标方法
找到目标类com.example.app.MainActivity和方法login
Java.perform(function() {
    var MainActivity = Java.use("com.example.app.MainActivity");
    MainActivity.login.implementation = function(username, password) {
      console.log("Login called with:", username, password);
      // 修改返回值
      return true;
    };
});
4.3.监控网络请求
应用实现方式:
1.使用系统apiandroid应用使用系统api:HttpURLConnection、OkHttp、Retrofit 等ios应用使用系统api:NSURLSession、NSURLConnection 等。
2.发送请求:构建请求(url,请求头,请求体),发送请求服务器
3.接收响应,接收服务器返回的响应数据(状态码,响应头,响应体)。
Hook思路:
目标应用使用HttpURLConnection
Java.perform(function() {
    var URL = Java.use("java.net.URL");
    var HttpURLConnection = Java.use("java.net.HttpURLConnection");

    // Hook URL.openConnection
    URL.openConnection.overload().implementation = function() {
      var connection = this.openConnection();
      console.log("URL:", this.toString());

      // Hook getInputStream
      if (connection.getClass().getName().indexOf("HttpURLConnection") !== -1) {
            var httpConnection = Java.cast(connection, HttpURLConnection);
            httpConnection.getInputStream.implementation = function() {
                console.log("Request Headers:", httpConnection.getRequestProperties());
                var inputStream = this.getInputStream();
                // 读取响应数据
                var response = readInputStream(inputStream);
                console.log("Response:", response);
                return inputStream;
            };
      }

      return connection;
    };

    // 读取 InputStream 的工具函数
    function readInputStream(inputStream) {
      var BufferedReader = Java.use("java.io.BufferedReader");
      var InputStreamReader = Java.use("java.io.InputStreamReader");
      var StringBuilder = Java.use("java.lang.StringBuilder");

      var reader = BufferedReader.$new(InputStreamReader.$new(inputStream));
      var builder = StringBuilder.$new();
      var line;
      while ((line = reader.readLine()) !== null) {
            builder.append(line);
      }
      reader.close();
      return builder.toString();
    }
});
目标应用使用 OkHttp
Java.perform(function() {
    var OkHttpClient = Java.use("okhttp3.OkHttpClient");
    var Request = Java.use("okhttp3.Request");
    var Response = Java.use("okhttp3.Response");

    // Hook OkHttpClient.newCall
    OkHttpClient.newCall.implementation = function(request) {
      console.log("Request URL:", request.url().toString());
      console.log("Request Headers:", request.headers().toString());

      var response = this.newCall(request).execute();
      console.log("Response Code:", response.code());
      console.log("Response Body:", response.body().string());
      return response;
    };
});
4.4.绕过 SSL Pinning
SSL pinning (SSL 证书绑定)安全机制,防止中间人攻击,核心是将服务器的SSL\TLS证书或公钥硬编码到客户端应用中,客户端与特定的服务器通信,不是与拥有有效证书的服务器通信。
引入机制:
1.证书或公钥绑定,客户端在建立连接,检查服务器返回的证书或公钥是否与硬编码的值匹配
2.防止中间人攻击,攻击者的证书与硬编码值不匹配
实现方式:
1.证书绑定:将服务器的完整证书(通常是 DER 或 PEM 格式)硬编码到客户端应用中。
2.公钥绑定:将服务器的公钥(通常是公钥的哈希值)硬编码到客户端应用中3.证书链绑定:将服务器证书链中的某个中间证书或根证书硬编码到客户端应用中。
绕过方式:
1.修改客户端代码:反编译应用代码,删掉SSL Pinning的实现代码并修改删除
2.使用frida或者objection绕过:
Frida 使用脚本Hook SSL Pinning 相关的函数;
Java.perform(function() {
    var X509TrustManager = Java.use("javax.net.ssl.X509TrustManager");
    var TrustManager = Java.registerClass({
      name: "com.example.TrustManager",
      implements: ,
      methods: {
            checkClientTrusted: function(chain, authType) {},
            checkServerTrusted: function(chain, authType) {},
            getAcceptedIssuers: function() {
                return [];
            }
      }
    });

    var SSLContext = Java.use("javax.net.ssl.SSLContext");
    SSLContext.init.implementation = function(keyManager, trustManager, secureRandom) {
      this.init(keyManager, , secureRandom);
    };
});
使用objection命令:
android sslpinning disable
4.5.动态修改内存
var ptr = Module.findBaseAddress("libnative.so").add(0x1234);
Memory.writeUtf8String(ptr, "new_value");
4.6. 处理反调试
目标应用检测Frida,可以hook反调试函数
Java.perform(function() {
    var System = Java.use("java.lang.System");
    System.getProperty.implementation = function(key) {
      if (key === "java.vm.name") {
            return "Dalvik"; // 伪装成 Dalvik 虚拟机
      }
      return this.getProperty(key);
    };
});

AntelopeE 发表于 2025-3-22 18:26

感谢分享

HHY112 发表于 2025-3-22 19:00

大佬,有没有安卓逆向的零基础教程呀

xilou1989 发表于 2025-3-22 19:06

非常详细,学习了

Juana111 发表于 2025-3-22 19:23

HHY112 发表于 2025-3-22 19:00
大佬,有没有安卓逆向的零基础教程呀

可以看看我之前发的一些帖子 之前学习是看的是 正己 师傅的帖子和视频 推荐推荐~

xiaofeng9527 发表于 2025-3-22 21:48


非常详细,学习了

lycqol 发表于 2025-3-22 22:38

感谢分享

liyicha 发表于 2025-3-23 08:55

感谢楼主分享,已经知道怎么进行hook了

wzzjhc 发表于 2025-3-23 17:34

感谢分享 学习了

bailaoyyds 发表于 2025-3-23 20:57

谢谢分享,大佬厉害
页: [1] 2
查看完整版本: 安卓逆向学习4-frida编译调试、框架学习