【Frida Android】基础篇12:Native层hook基础——调用原生函数

文章目录

  • [1. Hook 语法](#1. Hook 语法)
  • [2. 案例解析](#2. 案例解析)
    • [2.1 源码分析](#2.1 源码分析)
    • [2.2 IDA使用步骤(定位目标函数)](#2.2 IDA使用步骤(定位目标函数))
    • [2.3 Hook脚本说明](#2.3 Hook脚本说明)
  • [3. 技术总结](#3. 技术总结)

⚠️本博文所涉安全渗透测试技术、方法及案例,仅用于网络安全技术研究与合规性交流,旨在提升读者的安全防护意识与技术能力。任何个人或组织在使用相关内容前,必须获得目标网络 / 系统所有者的明确且书面授权,严禁用于未经授权的网络探测、漏洞利用、数据获取等非法行为。

1. Hook 语法

Native层Hook是通过动态修改程序运行时行为,实现对原生函数(Native Function)的拦截、调用或修改的技术。在Frida框架中,针对Native函数的Hook核心语法如下:

  1. 模块枚举与基地址获取

    由于Native函数的实际地址 = 模块基地址 + 函数偏移量(静态分析得到),需先通过Process.enumerateModules()枚举进程加载的模块,找到目标SO库的基地址:

    javascript 复制代码
    var modules = Process.enumerateModules();
    var targetModuleBase = null;
    for (var i = 0; i < modules.length; i++) {
        if (modules[i].name === "目标库名.so") {
            targetModuleBase = modules[i].base; // 模块基地址(运行时动态分配)
            break;
        }
    }
  2. 函数地址计算

    结合静态分析得到的函数偏移量(如IDA中获取),计算函数实际运行地址:

    javascript 复制代码
    var functionOffset = 0x12345; // 静态偏移量(IDA中获取)
    var functionAddress = targetModuleBase.add(functionOffset); // 实际地址 = 基地址 + 偏移量
  3. Native函数封装

    使用NativeFunction将内存地址封装为可调用的函数,需指定返回值类型和参数类型(参考IDA伪代码的函数签名):

    javascript 复制代码
    // 示例:封装一个返回void、接收两个int参数的函数
    var targetFunction = new NativeFunction(
        functionAddress, // 函数实际地址
        'void', // 返回值类型(如'pointer'、'int'、'string'等)
        ['int', 'int'] // 参数类型列表
    );
  4. 函数调用与Hook

    直接调用封装后的函数,或通过Interceptor.attach拦截函数执行(修改参数/返回值):

    javascript 复制代码
    // 调用函数
    targetFunction(1, 2); // 传入符合类型的参数
    
    // 拦截函数(示例)
    Interceptor.attach(functionAddress, {
        onEnter: function(args) {
            console.log("函数被调用,参数1:" + args[0].toInt32());
        },
        onLeave: function(retval) {
            console.log("函数返回值:" + retval.toInt32());
        }
    });

2. 案例解析

本章示例应用的链接:

https://pan.baidu.com/s/16EE2XE-OZS_xBRPlWUODbw?pwd=n2vb

提取码: n2vb

使用APK:Challenge 0xA.apk

2.1 源码分析

目标APK的核心逻辑通过Java层与Native层交互实现,使用JADX反编译后关键代码如下:

java 复制代码
public final class MainActivity extends AppCompatActivity {
    private ActivityMainBinding binding;

    public final native String stringFromJNI(); // 声明Native方法

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 布局初始化逻辑...
        binding.sampleText.setText(stringFromJNI()); // 调用Native方法并显示结果
    }

    static {
        System.loadLibrary("frida0xa"); // 加载Native库libfrida0xa.so
    }
}

分析可知:

  • 应用启动时加载libfrida0xa.so库;
  • 通过stringFromJNI()调用Native层代码,最终显示Hello Hackers
  • 目标是找到并调用库中隐藏的"获取Flag"函数(静态分析发现未被主动调用)。

2.2 IDA使用步骤(定位目标函数)

IDA工具获取参考:https://blog.csdn.net/qq_40037555/article/details/153777782

  1. 获取SO文件

    将APK修改为ZIP格式并解压,在lib目录下可看到不同CPU架构的子目录(如arm64-v8ax86_64等),每个目录下包含对应架构的libfrida0xa.so

  2. 确定设备架构

    通过ADB命令获取运行设备的CPU架构,用于选择匹配的SO文件:

    shell 复制代码
    adb shell getprop ro.product.cpu.abi

    本例中模拟器架构为x86_64,因此选择x86_64/libfrida0xa.so

  3. IDA打开SO文件

    启动IDA Pro,选择"File- Open" -> 打开x86_64/libfrida0xa.so,默认加载即可(本地需要 python3 环境)。左侧"Functions"窗口会显示库中所有函数:

  4. 查找目标函数

    • 在"Functions"窗口找到stringFromJNI,查看伪代码确认其功能为返回Hello Hackers,无Flag相关逻辑;

    • 继续查找发现一个"获取Flag"的函数(名为get_flag),点击该函数查看伪代码,可以看到在if ( a2 + a1 == 3 )即传入的2个参数和为3时,通过日志_android_log_print输出解密后的Flag;

    • 记录该函数的偏移量 (静态地址),本例中为0x206B0

  5. 确认基地址

    IDA中默认基地址可通过"View -> Open subviews -> Segments"查看,x86_64架构默认基地址通常为0x0,因此函数静态地址 = 基地址 + 偏移量 = 0x206B0

2.3 Hook脚本说明

脚本核心目标是找到libfrida0xa.so的运行时基地址,计算get_flag函数的实际地址并主动调用,从而触发Flag输出:

javascript 复制代码
function hook() {
    // 枚举模块,获取libfrida0xa.so的基地址(运行时动态分配)
    var modules = Process.enumerateModules();
    var libnative_addr = null;
    for (var i = 0; i < modules.length; i++) {
        if (modules[i].name === "libfrida0xa.so") {
            libnative_addr = modules[i].base;
            break;
        }
    }
    
    if (!libnative_addr) {
        console.log("未能找到 libfrida0xa.so 模块");
        return;
    }
    
    // 计算get_flag函数的实际地址(基地址 + 静态偏移量0x206B0)
    var adr = libnative_addr.add(0x206B0);
    var get_flag_ptr = new NativePointer(adr);
    
    // 封装函数:返回值为void,参数为两个int(根据IDA伪代码的函数签名)
    const get_flag = new NativeFunction(get_flag_ptr, 'void', ['int', 'int']);
    
    console.log("get_flag function address is =>", adr);
    
    // 调用函数,传入任意符合条件(参数和为3)的参数
    get_flag(1, 2);
}

hook();

这里单独说明下,伪代码中get_flag的返回值为unsigned __int64(64 位无符号整数),但上面 Frida 脚本中声明为'void'(无返回值)。

这一差异不影响函数调用成功的原因是:

  • Frida 的NativeFunction对返回值的处理是 "按需解析":如果声明为'void',则会忽略函数的实际返回值,但不会阻止函数本身的执行。
  • 目标函数get_flag的核心逻辑(解密 Flag 并通过__android_log_print输出)不依赖返回值,只要参数正确,函数内部逻辑就能正常执行,因此即使返回值类型声明不匹配,也能成功触发 Flag 输出。

脚本执行后,通过ADB查看日志即可获取Flag:

shell 复制代码
adb logcat -c  # 清除旧日志
adb logcat > log.txt  # 保存新日志(执行脚本后查看)

日志中Debug级别输出包含Flag:Decrypted Flag: FRIDA{DONT_CALL_ME}

3. 技术总结

Native层Hook的核心流程与关键要点如下:

  1. 核心流程
    • 静态分析:通过JADX解析Java层代码,定位加载的SO库及关键Native方法;
    • 逆向SO库:使用IDA分析SO文件,找到目标函数(如隐藏的Flag获取函数)并记录偏移量;
    • 动态Hook:通过Frida脚本枚举模块获取基地址,计算函数实际地址,封装并调用函数;
    • 结果验证:通过ADB日志或其他方式获取Hook后的输出。
  2. 关键工具
    • JADX:反编译APK,分析Java层与Native层的交互逻辑;
    • IDA Pro:逆向SO文件,查看函数列表、伪代码及偏移量。
  3. 注意事项
    • 函数地址计算:需区分静态偏移量(IDA中获取)和运行时基地址(Frida动态获取),实际地址 = 基地址 + 偏移量;
    • 函数签名匹配:NativeFunction的返回值类型和参数类型必须与IDA伪代码一致,否则会导致调用异常;
    • 架构匹配:需选择与目标设备CPU架构一致的SO文件进行分析(如x86_64、arm64-v8a)。

通过上述步骤,可实现对Native层函数的精准Hook,进而分析或修改程序的原生逻辑。

相关推荐
Cathyqiii4 小时前
Diffusion-TS:一种基于季节性-趋势分解与重构引导的可解释时间序列扩散模型
人工智能·神经网络·1024程序员节
存储国产化前线4 小时前
从浪涌防护到系统可控,天硕工业级SSD重构工业存储安全体系
ssd·固态硬盘·1024程序员节·工业级固态硬盘
瑞禧生物ruixibio4 小时前
4-ARM-PEG-Alkene(2)/Biotin(2),四臂聚乙二醇-烯烃/生物素多功能支链分子
1024程序员节
焦点链创研究所4 小时前
BUYCOIN:以社区共治重构加密交易版图,定义交易所3.0时代
1024程序员节
DO_Community4 小时前
DigitalOcean Gradient™ 平台上线 fal 四款多模态 AI 模型:快速生成图像与音频
1024程序员节
2501_916008895 小时前
用多工具组合把 iOS 混淆做成可复用的工程能力(iOS混淆|IPA加固|无源码混淆|Ipa Guard|Swift Shield)
android·开发语言·ios·小程序·uni-app·iphone·swift
胎粉仔5 小时前
Swift 初阶 —— inout 参数 & 数据独占问题
开发语言·ios·swift·1024程序员节
MeowKnight9585 小时前
【C】使用C语言举例说明逻辑运算符的短路特性
c语言·1024程序员节
阿金要当大魔王~~5 小时前
uniapp 页面标签 传值 ————— uniapp 定义 接口
前端·javascript·uni-app·1024程序员节