当前位置: 首页 > news >正文

【Frida Android】基础篇11:Native层hook基础——修改原生函数的返回值

文章目录

    • 1. 基础概念
      • 1.1 原生函数(Native函数)
      • 1.2 函数返回值修改的意义
    • 2. 核心语法(修改原生函数返回值的hook)
      • 2.1 获取目标类
      • 2.2 定位目标原生函数
      • 2.3 重写函数实现
    • 3. 案例分析
      • 3.1 代码分析(MainActivity)
      • 3.2 Hook思路
      • 3.3 核心源码与脚本
        • 3.3.1 Frida Hook脚本(JS)
        • 3.3.2 注入脚本(Python)
      • 3.4 成功效果
    • 4. 技术总结

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

本章内容是对上一章学习内容的夯实,使用相似的案例进行强化记忆。

1. 基础概念

1.1 原生函数(Native函数)

原生函数指通过JNI(Java Native Interface)机制调用的、由C/C++等原生语言实现的函数。在Android开发中,这类函数通常被编译为动态链接库(.so文件),并通过System.loadLibrary()加载到Java进程中。原生函数常用于处理性能敏感、底层交互(如硬件操作)或需要保护逻辑(如加密验证)的场景,案例中的check_flag()就是典型的原生函数。

1.2 函数返回值修改的意义

修改原生函数的返回值是逆向工程和动态调试中的常用手段。当程序逻辑依赖原生函数的返回值(如验证结果、权限判断、数据校验等)时,通过hook技术强制修改返回值,可以绕过原有逻辑限制(如跳过验证、模拟成功状态),从而快速分析程序行为或实现特定功能(如案例中触发"成功"提示)。

2. 核心语法(修改原生函数返回值的hook)

使用Frida实现对原生函数返回值的修改,核心语法围绕Java层函数拦截展开,主要步骤如下:

2.1 获取目标类

通过Java.use(className)获取需要hook的类的引用,其中className为类的完整路径(包含包名)。
示例:

// 获取MainActivity类引用
const MainActivity = Java.use('com.ad2001.a0x9.MainActivity');

2.2 定位目标原生函数

直接通过类引用访问原生函数(函数名需与Java层声明一致)。原生函数在Java层的声明与普通函数一致,仅多了native关键字,Frida无需区分其实现语言,统一按Java函数处理。

源码:

public class MainActivity extends AppCompatActivity {public native int check_flag();static {System.loadLibrary("a0x9");}// ...
}

Hook代码:

// 定位check_flag()原生函数
MainActivity.check_flag

2.3 重写函数实现

通过implementation属性重写函数逻辑,在自定义实现中直接返回目标值(无需调用原函数)。
示例:

// 强制check_flag()返回1
MainActivity.check_flag.implementation = function () {return 1; // 自定义返回值
};

3. 案例分析

本章示例应用的链接:
https://pan.baidu.com/s/16EE2XE-OZS_xBRPlWUODbw?pwd=n2vb
提取码: n2vb
使用APK:Challenge 0x9.apk

3.1 代码分析(MainActivity)

在这里插入图片描述

Java层核心代码(MainActivity)

package com.ad2001.a0x9;import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import com.ad2001.a0x9.databinding.ActivityMainBinding;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;public class MainActivity extends AppCompatActivity {private ActivityMainBinding binding;Button btn;// 声明原生函数(实现位于liba0x9.so)public native int check_flag();static {System.loadLibrary("a0x9"); // 加载原生库}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);ActivityMainBinding activityMainBindingInflate = ActivityMainBinding.inflate(getLayoutInflater());this.binding = activityMainBindingInflate;setContentView(activityMainBindingInflate.getRoot());Button button = (Button) findViewById(R.id.button);this.btn = button;// 按钮点击事件:依赖check_flag()的返回值判断逻辑button.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException {if (MainActivity.this.check_flag() == 1337) { // 核心判断条件try {// AES解密逻辑(成功时执行)Cipher cipher = Cipher.getInstance("AES");SecretKeySpec secretKeySpec = new SecretKeySpec("3000300030003003".getBytes(), "AES");cipher.init(2, secretKeySpec);byte[] decryptedBytes = Base64.getDecoder().decode("hBCKKAqgxVhJMVTQS8JADelBUPUPyDiyO9dLSS3zho0=");String decrypted = new String(cipher.doFinal(decryptedBytes));Toast.makeText(MainActivity.this.getApplicationContext(), "You won " + decrypted, 1).show();return;} catch (Exception e) {throw new RuntimeException(e);}}Toast.makeText(MainActivity.this.getApplicationContext(), "Try again", 1).show();}});}
}

案例中的MainActivity是一个简单的Android页面,核心逻辑如下:

  1. 原生函数声明
    声明了native int check_flag(),该函数的实现位于liba0x9.so中(通过static代码块的System.loadLibrary("a0x9")加载)。

  2. 按钮点击事件
    点击按钮时触发onClick事件,逻辑分为两步:

    • 调用check_flag(),判断返回值是否为1337;
    • 若返回值为1337,使用AES算法解密预定义的Base64字符串(hBCKKAqgxVhJMVTQS8JADelBUPUPyDiyO9dLSS3zho0=),密钥为3000300030003003,解密成功后显示"You won + 解密结果";
    • 若返回值不是1337,显示"Try again"。
  3. 核心依赖
    程序的"成功"逻辑完全依赖check_flag()的返回值,因此只要修改该函数的返回值为1337,即可绕过原生层验证,直接触发解密流程。

3.2 Hook思路

基于上述代码分析,hook的核心目标是check_flag()强制返回1337,具体思路如下:

  1. 确定hook点
    目标函数为com.ad2001.a0x9.MainActivity类中的check_flag(),无需关心其原生实现(无论liba0x9.so中如何计算返回值,直接覆盖即可)。

  2. 编写hook脚本
    使用Frida的Java桥接API,获取MainActivity类引用,重写check_flag()的实现,固定返回1337。

  3. 注入脚本到目标进程
    通过Python脚本连接设备、启动目标应用进程(com.ad2001.a0x9)、附加进程并注入JS脚本,实现动态hook。

3.3 核心源码与脚本

3.3.1 Frida Hook脚本(JS)
import Java from 'frida-java-bridge';Java.perform(function () {// 1. 获取目标类const MainActivity = Java.use('com.ad2001.a0x9.MainActivity');// 2. 重写原生函数check_flag(),强制返回1337MainActivity.check_flag.implementation = function () {console.log("[Hook] 拦截check_flag(),返回1337");return 1337; // 覆盖原返回值};
});
3.3.2 注入脚本(Python)
import frida
import sys
import timedef on_message(message, data):if message['type'] == 'send':print(f"[Hook 日志] {message['payload']}")elif message['type'] == 'error':print(f"[错误] {message['stack']}")# 目标应用包名
PACKAGE_NAME = "com.ad2001.a0x9"def main():try:# 连接USB设备device = frida.get_usb_device(timeout=10)print(f"已连接设备:{device.name}")# 启动目标进程print(f"启动进程 {PACKAGE_NAME}...")pid = device.spawn([PACKAGE_NAME])device.resume(pid)time.sleep(2)  # 等待进程启动# 附加到进程并注入脚本process = device.attach(pid)print(f"已附加到进程 PID: {pid}")with open("./js/compiled_hook.js", "r", encoding="utf-8") as f:js_code = f.read()script = process.create_script(js_code)script.on('message', on_message)script.load()print("JS 脚本注入成功,开始监控...(按 Ctrl+C 退出)")sys.stdin.read()except frida.TimedOutError:print("未找到USB设备")except frida.ProcessNotFoundError:print(f"应用 {PACKAGE_NAME} 未安装")except FileNotFoundError:print("未找到 js 脚本,请检查路径")except Exception as e:print(f"异常:{str(e)}")finally:if 'process' in locals():process.detach()print("程序退出")if __name__ == "__main__":main()

3.4 成功效果

执行上述脚本后,点击应用按钮,check_flag()被hook强制返回1337,触发AES解密流程,成功显示flag:

在这里插入图片描述

4. 技术总结

  1. 核心原理:修改原生函数返回值的本质是通过hook技术拦截函数调用,用自定义返回值覆盖原逻辑。对于依赖返回值的程序(如验证、权限判断),该方法可快速绕过限制。

  2. Frida的优势:作为动态hook工具,Frida无需修改原程序代码或重打包,支持跨平台(Android、iOS等),且通过Java桥接API可轻松操作Java层函数(包括原生函数),降低了hook的技术门槛。

  3. 关键注意事项

    • 需准确获取类的完整路径(包名+类名)和函数名,否则无法定位目标;
    • 进程附加时机需合理(建议在进程启动初期注入,避免函数已被调用);
    • 若函数有参数,需在重写实现时保持参数列表一致(即使不使用参数)。
  4. 适用场景:适用于逆向分析中需要绕过原生层验证、调试函数依赖关系、或临时修改程序行为的场景,是移动端逆向工程的基础技能之一。

http://www.dtcms.com/a/515337.html

相关文章:

  • 什么是DNS负载均衡?提升网站稳定性与容错性的方法
  • 设计自学网站哪个好建设银行网站怎么短信转账
  • 网站如何做seo优化教程迪虎科技网站建设
  • win10底部搜索栏怎么关闭 图文详解
  • 网站备案密码是什么样的大冶建设局网站
  • python学习之进程
  • PAGE下载安装图解教程(附安装包)
  • 接口练习哈哈
  • 【原理解析】详细剖析 HAMi 在支持 NVIDIA GPU 拓扑感知调度时的具体设计与实现原理
  • Dubins曲线:最短有向路径的数学之美与工程实现
  • 解读“Time Model Statistics”中的“sql execute elapsed time”
  • 图形化设计或流程编辑软件界面组件
  • C++ char 类型深度解析:字符与字节的双重身份
  • 做网站的群html5网站正在建设中模板下载
  • 常平众展做网站长沙网上房地产官网
  • 在 Linux 中实现虚拟机管理程序级行为分析
  • Jmeter请求发送加密参数详解
  • STM32G474单片机开发入门(二十二)SHT30温湿度传感器模块实战
  • 【开题答辩实录分享】以《智能垃圾回收小程序》为例进行答辩实录分享
  • FSMC-灵活的静态存储控制器
  • 开源AI大模型AI智能名片S2B2C商城小程序在护肤品文案痛点表达中的应用与效果研究
  • 《3D端游云原生协作任务数据一致性优化实战》
  • Day8C语言前期阶段练习算法之插入排序
  • 计算机算法性能详解
  • 建设银行租房网站6网站建设的用例图
  • 国外黄冈网站推广软件有哪些网站建设销售员工作内容
  • Java 大视界 -- Java 大数据在智能医疗远程护理与患者健康管理中的应用与前景
  • 江苏省义务教育标准化建设网站北京住房和城乡建设局门户网站
  • 记2025羊城杯部分题目的解题思路
  • 198种组合算法+优化RF随机森林+SHAP分析+新数据预测!机器学习可解释分析,强烈安利,粉丝必备!