Android 逆向/反编译/Hook修改应用行为 基础实现

news2025/1/4 19:56:13

前言:本文通过一个简单的情景案例实现安卓逆向的基本操作

一、情景描述

本文通过一个简单的情景案例来实现安卓逆向的基本操作。在这个案例中所使用的项目程序是我自己的Demo程序,不会造成任何的财产侵害,本文仅作为日常记录及案例分享。
实现步骤大致如下
反编译APK获取源码 → 源码分析 → 目标设备运行Frida服务(需要root权限) → 使用Python编写hook注入程序 / 编写hook脚本 → 执行程序

二、前置准备

整个流程中需要做的准备工作大致有以下几点:

1. 反编译工具

当前流行的反编译工具有许多种,例如:APKTool、JADX、JD-GUI 等等。反编译的主要目的是查看app的源码分析app的运作流程从而快速制定出所需的hook脚本,因此挑选一款适合自己的即可。不过需要注意的是:并非所有的apk包都能够反编译成功,虽然技术上大多数 APK 文件都能被反编译,但反编译的难度和完整性可能会因应用的保护措施而有所不同。具体来说,有些 APK 文件通过加固、混淆、加密等手段提高了反编译的难度,甚至可能在某些情况下完全避免了反编译的效果,由于我的案例使用的是自己的源码所以不需要考虑这一步,那就以 JADX 作为案例吧。

安装JADX

直接到github仓库下载打好的包即可,可以直接下载带UI的版本,简单易用,不过要注意系统已经有了JRE环境,如果没有的话也可以下载下方的带jre版本 https://github.com/skylot/jadx/releases

反编译APK包

直接把文件拖拽到框内即可

2. Python环境

需要用到 Python 进行脚本程序编写及运行
Python 环境安装:直接官网下载傻瓜式一键安装即可 https://www.python.org/downloads/
PyCharm 编辑器安装:同上,下载社区版(Community)就够了 https://www.jetbrains.com.cn/en-us/pycharm/download/?section=windows

3. 安装 Frida

Frida 有两部分,frida-tools以python程序为载体运行在本地并执行hook脚本,frida-server则需要在目标设备端运行并进行动态分析和hook操作
安装 frida-tools:安装frida-tools需要用到pip因此先确保python环境安装成功并设置好环境变量。在命令提示符中直接输入以下指令等待安装完成即可:pip install frida-tools
下载完成后输入指令判断是否安装成功:frida --version
如果成功返回了版本号则表示安装成功了,记住这个版本号后续还要用到

下载 frida-server:直接到github仓库下载打好的包即可 https://github.com/frida/frida/releases
注意下载的版本要frida-tools相匹配,另外还需要注意的是server的系统平台和cpu架构版本也要选对,他的命名规则是:frida-server-版本号-支持系统-CPU架构.扩展名,我需要在用到Android系统上使用因此我把对应的所有android server包都下载下来

4. 具有 root 权限的设备

由于现在的厂商真机获取 root 权限越来越困难,使用真机调试的成本较高因此选择模拟器作为android端设备载体,我在案例中使用的是夜神模拟器,直接官网安装即可 https://www.yeshen.com/ 安装完成后在设置中开启root权限即可

三、运行 hook 程序,修改应用行为

1. 启动 frida-server

启动frida-server前要先将server文件导入目标设备的 /data/local/tmp/ 目录中,执行命令修改可执行权限并运行,这里需要注意的一点是要确保目标设备的cpu架构与server文件相对应,夜神模拟器的cpu架构是x86_64因此我们选用这个即可

解压server文件

选择之前下载好的server压缩包文件并解压

导入/启动 server 文件

直接在路径栏中输入"cmd"回车进入当前命令提示符

依次执行以下指令:
输入 "adb devices" 指令查询当前已连接设备确定模拟器设备已经连接成功
输入 "adb push frida-server-16.5.7-android-x86_64 /data/local/tmp/" 指令将 frida-server 导入tmp目录,注意这里的要根据你的server文件名修改一下
等待导入完成,输入 "adb shell" 进入设备指令操作
输入 "su" 切换root权限
输入 "cd /data/local/tmp/" 进入tmp目录
输入 "ls -l" 查询当前目录下的文件信息,此时我们可以看到刚才导入进去的server文件,他的特征为rw-并没有可执行权限
输入 "chmod 777 frida-server-16.5.7-android-x86_64" 设置用户权限开启可执行
再次输入 "ls -l" 查询当前目录下的文件信息,此时我们可以看到文件特征变为rwx,可以执行
输入 "./frida-server-16.5.7-android-x86_64" 启动 frida-server,启动后光标换行没有任何提升,表示已经启动成功 server 正在运行中,注意不要关闭提示符窗口

2. 验证 frida 连接

打开 PyCharm,新建py文件,导入frida包,根据app包名启动指定进程,这一步先做连接验证,不注入hook脚本

import frida
import sys


def on_message(message, data):
    """
    回调函数,处理 Frida 发回的消息。
    """
    if message['type'] == 'send':
        print(f"[+] {message['payload']}")
    elif message['type'] == 'error':
        print(f"[!] {message['stack']}")

def main():
    print("执行任务")
    try:
        print(f"[!] {frida.__version__}")
        # 连接到 Android 设备
        device = frida.get_usb_device(timeout=5)

        # 查找目标进程
        pid = device.spawn(["com.lxt.single_module"]) # 注意,这里要改为你需要hook的程序包名
        session = device.attach(pid)

        # 加载 Hook 脚本
        # script = session.create_script(HOOK_SCRIPT_1)
        # script.on('message', on_message)  # 设置消息回调
        # script.load()

        # 恢复进程
        device.resume(pid)
        print("[*] Hook 脚本已加载,应用正在运行...")

        # 保持脚本运行状态
        sys.stdin.read()
    except Exception as e:
        print(f"[!] 错误: {e}")

if __name__ == "__main__":
    main()

运行程序观察日志及模拟器,程序运行之后模拟器会自动调起对应的程序,如果日志中没有报错或退出则表示连接成功

3. hook 改变方法执行效果

改变一个简单的方法返回数据

假设我在 MainActivity 中有个简单的方法,该方法固定返回一个boolean类型数据"true"

fun testBool():Boolean{return true}

设置某个测试按键的点击事件为log这个方法返回的结果

viewBinding.test5.onClick {
    Log.e(TAG, "click result:${testBool()}")
}

点击结果

那么这时我们可以编写一个简单的hook脚本去注入修改这个方法的返回数据

创建Java.perform方法并在作用域中实现需要执行的脚本细节,Java.perform 是 Frida 提供的一个用于在 Java 虚拟机中执行代码的方法。它确保在 Java 环境完全加载并且可以安全地访问 Java 类和方法后执行给定的回调函数。所有的 Java hooking 操作都需要在这个回调内进行

Java.perform(function() {

}

我们需要hook的方法是在MainActivity中,因此我们要做的第一个操作就是获取到这个对象的引用,我们可以使用Java.use()方法来获取。通过Java.use()拿到的引用你可以访问该类的方法和字段或者修改这些方法的实现,但前提是必须要知道这个需要加载的类的完整路径,这个路径我们可以通过反编译的源码中获取

var MainActivity = Java.use("com.lxt.single_module.Activity.MainActivity");

拿到 MainActivity 中 testBool 方法的引用,强制其返回一个"false"

MainActivity.testBool.implementation = function() {
    console.log("testBool 被 Hook,强制返回 false");
    // 强制返回 false
    return false;
};

完整代码

import frida
import sys

HOOK_SCRIPT_1 = """
Java.perform(function() {
    var MainActivity = Java.use("com.lxt.single_module.Activity.MainActivity");
    
    // Hook MainActivity 中的 testBool 方法
    MainActivity.testBool.implementation = function() {
        console.log("testBool 被 Hook,强制返回 false");
        
        // 强制返回 false
        return false;
    };
    
});
"""

# Python 代码
def on_message(message, data):
    """
    回调函数,处理 Frida 发回的消息。
    """
    if message['type'] == 'send':
        print(f"[+] {message['payload']}")
    elif message['type'] == 'error':
        print(f"[!] {message['stack']}")

def main():
    print("执行任务1")
    try:
        print(f"[!] {frida.__version__}")
        # 连接到 Android 设备
        device = frida.get_usb_device(timeout=5)

        # 查找目标进程
        pid = device.spawn(["com.lxt.single_module"])
        session = device.attach(pid)

        # 加载 Hook 脚本
        script = session.create_script(HOOK_SCRIPT_1)
        script.on('message', on_message)  # 设置消息回调
        script.load()

        # 恢复进程
        device.resume(pid)
        print("[*] Hook 脚本已加载,应用正在运行...")

        # 保持脚本运行状态
        sys.stdin.read()
    except Exception as e:
        print(f"[!] 错误: {e}")


if __name__ == "__main__":
    main()

执行程序注入hook脚本,此时再次点击测试按键就会触发hook方法
观察日志hook方法被触发,app中的testBool方法返回结果为false


改变应用行为

这里我们用一个相对较接近实际应用的情景作为案例吧
假设:我们需要在MainActivity中请求某个api再根据api的结果决定是否需要跳转到另一个指定页面
如果:我们需要绕过这个api,让程序无需根据api的请求结果而必定跳转到这个指定的页面
那么:我们可以直接hook这个请求api的方法让它必定返回一个可以让页面跳转的结果

代码情景
点击测试按键后使用ViewModel调取请求方法,再根据请求方法返回的结果判断是否需要执行页面跳转

viewBinding.test4.onClick {
    lifecycleScope.launch {
        var login_result = viewModel.testLogin()
        Log.e(TAG,"request result:${login_result}")
        if(login_result){
            Log.e(TAG, "登陆成功")
            startActivity(Intent(this@MainActivity, SucceedActivity::class.java))
        }else{
            Log.e(TAG, "登陆失败")
        }
    }
}

正常执行结果

根据方法的执行流程去编写 hook 脚本

在这个流程中最为关键的一步就是需要根据ViewModel请求方法的结果判断是否需要进行页面跳转,那么我们只要在ViewModel对象初始化之后拿到它的引用再hook它的请求方法,让这个方法的返回结果必定为true就好了
在我的Activity框架中ViewModel是在名为"initData"的抽象方法中初始化,并且在子类实现时必须要调用super.initData()在基类中执行反射自动初始化

override fun initData() {
    super.initData()  // 在父类初始化 viewModel
    // 执行其它任务
    rxPermissions = RxPermissions(this)
    check_permission()
    registerScreenListen()
}

由此可知:我们只需要获取到MainActivity的initData方法引用,并在执行它原本的super.initData()方法之后拿到已经初始化的ViewModel对象引用并修改请求api的方法让它必定返回一个true

拿到initData方法引用并执行它的的super方法

MainActivity.initData.implementation = function() {
    console.log("initData 被 Hook");
    // 调用原始的 initData 方法
    this.initData();
};

在执行完super方法后通过 Java.choose() 方法来获取已实例化的ViewModel对象,Java.choose 方法可以用于遍历已加载的类的所有实例,并执行指定的回调函数。

Java.choose("com.lxt.single_module.Activity.MainActivity", {
    onMatch: function(instance) {
        var viewModel = instance.viewModel.value;
    },
    onComplete: function() {}
});

获取到ViewModel对象之后修改 testLogin 方法为必定返回 true

if (viewModel) {
    console.log("成功获取到 viewModel");
    // 检查 viewModel 是否为 MainVM 的实例
    if (viewModel.$className === "com.lxt.single_module.ViewModel.MainVM") {
        console.log("viewModel 是 MainVM 的实例");
        // Hook testLogin 方法
        MainVM.testLogin.implementation = function() {
            console.log("testLogin 被 Hook,返回 true");
            return Java.use("java.lang.Boolean").valueOf(true);;
        };
    } else {
    console.log("viewModel 不是 MainVM 的实例");
    }
} else {
    console.log("viewModel 为 null");
}

完整代码

import frida
import sys

HOOK_SCRIPT_1 = """
Java.perform(function() {

    var MainActivity = Java.use("com.lxt.single_module.Activity.MainActivity");
    var MainVM = Java.use("com.lxt.single_module.ViewModel.MainVM");  // 请确保这是正确的包名和类名
    
    MainActivity.initData.implementation = function() {
        console.log("initData 被 Hook");
        
        // 调用原始的 initData 方法
        this.initData();
        
        Java.choose("com.lxt.single_module.Activity.MainActivity", {
                onMatch: function(instance) {
                    var viewModel = instance.viewModel.value;
                    if (viewModel) {
                        console.log("成功获取到 viewModel");
                        
                        // 检查 viewModel 是否为 MainVM 的实例
                        if (viewModel.$className === "com.lxt.single_module.ViewModel.MainVM") {
                            console.log("viewModel 是 MainVM 的实例");
                            
                            // Hook testLogin 方法
                            MainVM.testLogin.implementation = function() {
                                console.log("testLogin 被 Hook,返回 true");
                                return Java.use("java.lang.Boolean").valueOf(true);;
                            };
                        } else {
                            console.log("viewModel 不是 MainVM 的实例");
                        }
                    } else {
                        console.log("viewModel 为 null");
                    }
                },
                onComplete: function() {}
            });
    };
    
    // Hook MainActivity 中的 testBool 方法
    MainActivity.testBool.implementation = function() {
        console.log("testBool 被 Hook,强制返回 false");
        // 强制返回 false
        return false;
    };
    
});
"""

# Python 代码
def on_message(message, data):
    """
    回调函数,处理 Frida 发回的消息。
    """
    if message['type'] == 'send':
        print(f"[+] {message['payload']}")
    elif message['type'] == 'error':
        print(f"[!] {message['stack']}")

def main():
    print("执行任务")
    try:
        print(f"[!] {frida.__version__}")
        # 连接到 Android 设备
        device = frida.get_usb_device(timeout=5)

        # 查找目标进程
        pid = device.spawn(["com.lxt.single_module"])
        session = device.attach(pid)

        # 加载 Hook 脚本
        script = session.create_script(HOOK_SCRIPT_1)
        script.on('message', on_message)  # 设置消息回调
        script.load()

        # 恢复进程
        device.resume(pid)
        print("[*] Hook 脚本已加载,应用正在运行...")

        # 保持脚本运行状态
        sys.stdin.read()
    except Exception as e:
        print(f"[!] 错误: {e}")


if __name__ == "__main__":
    main()

再次运行脚本并点击测试按键

触发脚本,绕过api请求步骤执行页面跳转

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2257388.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

从0开始深度学习(35)——YOLO V5原理详解

以YOLO V5s为例,介绍YOLO V5的网络结构,以及其中具体的功能模块 1 YOLO V5的整体网络结构 YOLO V5网络结构分为四个部分: 输入端: 输入端负责对输入图像进行预处理,包括数据增强、锚框计算等。骨干网络(Ba…

云计算vspere 安装过程

1 材料的准备 1 安装虚拟机 vmware workstation 2 安装esxi 主机 3 在esxi 主机上安装windows 2018 dns 服务器 4 在虚拟机上安装windows 2018 服务器 6 安装vcenter 5 登入界面测试 这里讲一下,由于部署vspere 需要在windows 2012 服务器上部…

微信小程序中使用miniprogram-sm-crypto实现SM4加密攻略

在微信小程序开发过程中,数据安全至关重要。本文将为大家介绍如何在微信小程序中使用miniprogram-sm-crypto插件进行SM4加密,确保数据传输的安全性。 一、SM4加密简介 SM4是一种对称加密算法,由国家密码管理局发布,适用于商密领…

算法-字符串-43.字符串相乘

一、题目 二、思路解析 1.思路: 1.双重for循环,倒序依次相乘 2.在倒序处理进位问题 3.最后返回参数的类型是string,用StringBuilder拼接,再转换为字符串 2.常用方法: 1.equals,比较对象内容是否一致 "0".eq…

vscode鼠标右键跳转到定义只能跳转到头文件

在使用Visual Studio Code进行C语言编程开发时,C/C插件出错,鼠标右键只能跳转到头文件,不能跳转到源代码中函数原型定义的地方。 解决办法 打开C/C拓展设置页面,点击卸载右边的小箭头,点击安装特定版本 安装老版本的C…

AI 数字人模型 Hallo2:让图片开口说话,一键修复模糊人脸

Hallo2 是由复旦大学 (Fudan University)、百度公司 (Baidu Inc) 和南京大学 (Nanjing University) 于 2024 年联合开发的一项先进技术,旨在生成长时间、高质量的唇形视频。该技术在原有的 Hallo 模型基础上进行了多项创新和改进,使其能够应对长时间视频…

模型训练数据-MinerU一款Pdf转Markdown软件

模型训练数据-MinerU一款Pdf转Markdown软件-说明 简介: MinerU是什么 MinerU是上海人工智能实验室OpenDataLab团队推出的开源智能数据提取工具,专注于复杂PDF文档的高效解析与提取。MinerU能将包含图片、公式、表格等元素的多模态PDF文档转化为易于分析…

Spring07——AOP通知以及几个相关案例

切入点表达式 注意,不是参数,是参数类型 可以使用通配符描述切入点,快速描述 ■ *:单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的通配符出现 execution(public∗com.itheima.∗.UserServi…

【ETCD】【源码阅读】configurePeerListeners() 函数解析

configurePeerListeners 是 ETCD 的一个核心函数,用于为集群中节点之间的通信配置监听器(Peer Listener)。这些监听器主要负责 Raft 协议的消息传递、日志复制等功能。函数返回一个包含所有监听器的列表。 函数签名 func configurePeerList…

Kafka怎么发送JAVA对象并在消费者端解析出JAVA对象--示例

1、在pom.xml中加入依赖 <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-stream-kafka</artifactId><version>3.1.6</version></dependency> 2、配置application.yml 加入Kafk…

为了安全,自己搭建KMS,成功激活Office2010

在本篇文章中&#xff0c;将全过程描述Office Professional Plus 2010 With SP1 VOL从下载到自建KMS服务器再到激活的过程。本文展示的是64位版本&#xff0c;32位版本的方法类似。 特别注意&#xff1a;KMS激活仅限于VOL 版本&#xff0c;其他的零售版无法激活&#xff01;&am…

Unity 基于Collider 组件在3D 物体表面放置3D 物体

实现 从鼠标点击的屏幕位置发送射线&#xff0c;以射线监测点击到的物体&#xff0c;根据点击物体的法线向量调整放置物体的位置及朝向。 Ray ray Camera.main.ScreenPointToRay(Input.mousePosition); if (Physics.Raycast(ray, out RaycastHit hit, 100)) {obj.transform.…

【RDMA】RDMA read和write编程实例(verbs API)

WRITE|READ编程&#xff08;RDMA read and write with IB verbs&#xff09; &#xff08;本文讲解的示例代码在&#xff1a;RDMA read and write with IB verbs | The Geek in the Corner&#xff09; 将 RDMA 与verbs一起使用非常简单&#xff1a;首先注册内存块&#xff0c…

HTML5教程-表格宽度设置,最大宽度,自动宽度

HTML表格宽度 参考&#xff1a;html table width HTML表格是网页设计中常用的元素之一&#xff0c;可以用来展示数据、创建布局等。表格的宽度是一个重要的参数&#xff0c;可以通过不同的方式来设置表格的宽度&#xff0c;本文将详细介绍HTML表格宽度的不同设置方式和示例代…

2024年12月9日Github流行趋势

项目名称&#xff1a;ollama / ollama 项目维护者&#xff1a;mxyng, jmorganca, dhiltgen, BruceMacD, technovangelist等项目介绍&#xff1a;快速上手使用Llama 3.2、Mistral、Gemma 2及其他大型语言模型。项目star数&#xff1a;101,591项目fork数&#xff1a;8,117 项目名…

IntelliJ+SpringBoot项目实战(29)--如何将Beetl的模板文件放在独立的文件目录

在实际的项目开发中&#xff0c;为了方便前端人员调试页面&#xff0c;所以有必要将Beetl的模板文件放在独立的目录下&#xff0c;方便前端人员维护&#xff0c;而不是打包到项目的jar包中&#xff0c;如果打包到项目的jar包中还有另外的问题&#xff0c;就是一改动页面就要重新…

在Ubuntu上使用IntelliJ IDEA:开启你的Java开发之旅!

你好&#xff0c;年轻的学徒&#xff01;&#x1f9d1;‍&#x1f4bb; 是时候踏上进入Java开发世界的史诗之旅了&#xff0c;我们的得力助手将是强大的IntelliJ IDEA。准备好了吗&#xff1f;出发吧&#xff01; 在我们开始之前&#xff0c;我们需要下载这个工具。但是&#…

GWAS分析先做后学

大家好&#xff0c;我是邓飞。 GWAS分析是生物信息和统计学的交叉学科&#xff0c;上可以学习编程&#xff0c;下可以学习统计。对于Linux系统&#xff0c;R语言&#xff0c;作图&#xff0c;统计学&#xff0c;机器学习等方向&#xff0c;都是一个极好的入门项目。生物信息如…

LeetCode—189. 轮转数组(中等)

题目描述&#xff1a; 给定一个整数数组 nums&#xff0c;将数组中的元素向右轮转 k 个位置&#xff0c;其中 k 是非负数。 示例1&#xff1a; 输入: nums [1,2,3,4,5,6,7], k 3输出:[5,6,7,1,2,3,4] 解释: 向右轮转 1 步:[7,1,2,3,4,5,6] 向右轮转 2 步:[6,7,1,2,3,4,5] 向…

ModelScope-Agent(1): 基于开源大语言模型的可定制Agent系统

目录 简介快速入门 简介 github地址 快速入门 看前两篇&#xff0c;调用千问API和天气API # 选用RolePlay 配置agent from modelscope_agent.agents.role_play import RolePlay # NOQArole_template 你扮演一个天气预报助手&#xff0c;你需要查询相应地区的天气&#x…