安卓逆向-frida hook Native层
字数 1318 2025-10-01 14:05:44

Frida Hook Native层技术详解

一、Native方法与JNI基础

1.1 Native方法定义

在Java中声明native方法时,使用native关键字表明该方法由其他语言(通常是C/C++)实现:

public native int cmpstr(String str);

1.2 JNI函数命名规范

JNI函数的命名遵循特定格式:Java_包名_类名_方法名
示例:Java_com_ad2001_frida0x8_MainActivity_cmpstr

二、Frida Hook Native层技术

2.1 基础函数Hook(Frida 0x8)

场景:通过校验取决于native层的字符串比较函数

Hook策略:拦截strcmp函数获取中间参数

function hook() {
  // 查找strcmp函数地址
  var target = Module.findExportByName("libc.so", "strcmp");
  console.log("strcmp地址:", target);
  
  // 拦截函数调用
  Interceptor.attach(target, {
    onEnter: function(args) {
      // 读取第一个参数(输入字符串)
      var input = Memory.readUtf8String(args[0]);
      
      // 添加过滤条件避免过多输出
      if (input.includes("aaa")) {
        // 读取并打印第二个参数
        console.log("strcmp参数2:", Memory.readUtf8String(args[1]));
      }
    },
    onLeave: function(retval) {
      // 可在此处修改返回值
    }
  });
}

关键点

  • Module.findExportByName() 用于查找导出函数
  • Memory.readUtf8String() 读取UTF-8格式字符串
  • Interceptor.attach() 拦截函数执行
  • 地址一次查找,多次调用拦截

2.2 修改返回值(Frida 0x9)

场景:只需返回特定值(1337)即可通过验证

public native int check_flag();

Hook策略:直接修改函数返回值

var target = Module.findExportByName("libnative.so", "Java_com_example_check_flag");
Interceptor.attach(target, {
  onLeave: function(retval) {
    // 修改返回值为1337
    retval.replace(1337);
  }
});

2.3 主动调用Native函数(Frida 0xA)

场景:发现敏感函数get_flag()未被主动调用

两种调用方式

方式一:导出函数的主动调用

// 查找导出函数地址
var target = Module.findExportByName("libnative.so", "get_flag");

// 创建NativeFunction对象
const get_flag = new NativeFunction(target, 'void', ['int', 'int']);

// 主动调用
get_flag(1, 2);

方式二:未导出函数的主动调用

// 获取SO基址
var base = Module.findBaseAddress("libnative.so");

// 计算函数地址(基址+偏移)
var offset = 0x1234;
var target = base.add(offset);

// 创建NativeFunction并调用
const get_flag = new NativeFunction(target, 'void', ['int', 'int']);
get_flag(1, 2);

关键区别

  • 导出函数:可直接通过名称查找地址
  • 未导出函数:需要SO基址+偏移地址计算

2.4 修改汇编代码(Frida 0xB)

场景:条件跳转永远不执行核心逻辑

解决方案:使用NOP指令替换条件跳转

// 获取目标地址
var target = Module.findExportByName("libnative.so", "target_function");

// 准备NOP指令(ARM64为4字节)
var nop = [0x1F, 0x20, 0x03, 0xD5];

// 写入NOP指令替换原指令
Memory.writeByteArray(target, nop);

技术细节

  • ARM64架构中指令长度固定为4字节
  • NOP指令同样为4字节,可完全替换条件跳转指令
  • 修改后需重新反编译查看伪代码变化

2.5 Hook构造函数与参数修改(Frida 0x7)

场景:需要修改Checker构造函数的参数

class Checker {
    public Checker(int num1, int num2) {
        // 构造函数逻辑
    }
}

Hook策略:拦截构造函数并修改参数

// 拦截构造函数
Java.perform(function() {
  var Checker = Java.use("com.example.Checker");
  
  Checker.$init.implementation = function(num1, num2) {
    // 修改参数值
    num1 = 600;
    num2 = 600;
    
    // 调用原构造函数
    this.$init(num1, num2);
  };
});

三、关键技术总结

3.1 地址查找方法

  1. 导出函数Module.findExportByName("lib.so", "函数名")
  2. 未导出函数Module.findBaseAddress("lib.so").add(偏移)

3.2 函数拦截与修改

  • 参数读取Memory.readUtf8String()Memory.readInt()
  • 返回值修改retval.replace(新值)
  • 主动调用new NativeFunction()

3.3 汇编级修改

  • 指令替换Memory.writeByteArray(地址, 新指令)
  • ARM64特性:固定4字节指令长度
  • 常用替换:条件跳转 → NOP指令

3.4 应用场景对应方案

  1. 获取中间参数:Hook基础库函数(strcmp等)
  2. 修改验证结果:直接修改返回值
  3. 触发未调用函数:主动调用Native函数
  4. 绕过条件检查:修改汇编代码
  5. 修改对象属性:Hook构造函数

四、完整示例模板

function main() {
  Java.perform(function() {
    // 1. Hook Native函数示例
    var target = Module.findExportByName("libc.so", "strcmp");
    Interceptor.attach(target, {
      onEnter: function(args) {
        console.log("参数1:", Memory.readUtf8String(args[0]));
        console.log("参数2:", Memory.readUtf8String(args[1]));
      }
    });
    
    // 2. 主动调用示例
    var funcAddr = Module.findExportByName("libnative.so", "target_func");
    const nativeFunc = new NativeFunction(funcAddr, 'int', ['int', 'string']);
    var result = nativeFunc(123, "test");
    
    // 3. 修改汇编示例
    var patchAddr = Module.findBaseAddress("libnative.so").add(0x1234);
    Memory.writeByteArray(patchAddr, [0x1F, 0x20, 0x03, 0xD5]);
  });
}

setImmediate(main);

通过掌握这些技术,可以有效地分析和修改Android应用的Native层逻辑,实现各种逆向工程需求。

Frida Hook Native层技术详解 一、Native方法与JNI基础 1.1 Native方法定义 在Java中声明native方法时,使用 native 关键字表明该方法由其他语言(通常是C/C++)实现: 1.2 JNI函数命名规范 JNI函数的命名遵循特定格式: Java_包名_类名_方法名 示例: Java_com_ad2001_frida0x8_MainActivity_cmpstr 二、Frida Hook Native层技术 2.1 基础函数Hook(Frida 0x8) 场景 :通过校验取决于native层的字符串比较函数 Hook策略 :拦截strcmp函数获取中间参数 关键点 : Module.findExportByName() 用于查找导出函数 Memory.readUtf8String() 读取UTF-8格式字符串 Interceptor.attach() 拦截函数执行 地址一次查找,多次调用拦截 2.2 修改返回值(Frida 0x9) 场景 :只需返回特定值(1337)即可通过验证 Hook策略 :直接修改函数返回值 2.3 主动调用Native函数(Frida 0xA) 场景 :发现敏感函数 get_flag() 未被主动调用 两种调用方式 : 方式一:导出函数的主动调用 方式二:未导出函数的主动调用 关键区别 : 导出函数:可直接通过名称查找地址 未导出函数:需要SO基址+偏移地址计算 2.4 修改汇编代码(Frida 0xB) 场景 :条件跳转永远不执行核心逻辑 解决方案 :使用NOP指令替换条件跳转 技术细节 : ARM64架构中指令长度固定为4字节 NOP指令同样为4字节,可完全替换条件跳转指令 修改后需重新反编译查看伪代码变化 2.5 Hook构造函数与参数修改(Frida 0x7) 场景 :需要修改Checker构造函数的参数 Hook策略 :拦截构造函数并修改参数 三、关键技术总结 3.1 地址查找方法 导出函数 : Module.findExportByName("lib.so", "函数名") 未导出函数 : Module.findBaseAddress("lib.so").add(偏移) 3.2 函数拦截与修改 参数读取 : Memory.readUtf8String() 、 Memory.readInt() 返回值修改 : retval.replace(新值) 主动调用 : new NativeFunction() 3.3 汇编级修改 指令替换 : Memory.writeByteArray(地址, 新指令) ARM64特性 :固定4字节指令长度 常用替换 :条件跳转 → NOP指令 3.4 应用场景对应方案 获取中间参数 :Hook基础库函数(strcmp等) 修改验证结果 :直接修改返回值 触发未调用函数 :主动调用Native函数 绕过条件检查 :修改汇编代码 修改对象属性 :Hook构造函数 四、完整示例模板 通过掌握这些技术,可以有效地分析和修改Android应用的Native层逻辑,实现各种逆向工程需求。