前言
之所以写一篇这个内容,也是最近一个客户那边遇到了类似的安全检查没有通过,所以从基础的内容开始分享一下相关的知识。
Android App动态侵入是什么
Android App 动态注入,本质是 在 App 运行时(而非编译期 / 安装期),将外部代码(如 dex、so 库、Java 类)或资源注入到目标进程中并执行 的技术。核心目的是在不修改目标 App 原始代码、不重新打包安装的前提下,干预其运行逻辑(如 hook 方法、新增功能、修改数据)。
它的核心逻辑是利用 Android 系统的进程机制(如 zygote 进程孵化、ClassLoader 类加载机制)和 Linux 底层特性(如 ptrace 调试、dlopen 动态链接),打破目标 App 的沙箱隔离,让外部代码被目标进程 “主动加载” 并执行。
Frida是什么
Frida是一款基于Python+Java的hook框架,可以运行在Android、ios、linux、win等平台用于进行动态二进制插桩技术。
简单来说它是一款功能强大的动态插桩工具,可以让你在一个运行中的APP你插入自定义的JavaScript代码,来监控、修改应用的一些信息和行为。
Frida的优势
Frida作为一款跨平台、轻量级的动态插桩工具,其核心优势是:
- 跨平台支持
- 轻量
- 无需源码
- 无需重新打包应用
- 实时注入
Frida的适用场景
想一想,原本一款APP在启动后,倒计时5s要进入自己的主页,然后你通过插桩技术改变了它的这个行为,打开到了一个你想展示的页面或者网页,这听上去是不是还不错!
其实Frida动态插桩技术的适用场景是非常多的,这里就简单总结几个场景:
-
逆向工程与APP的逻辑分析
- 函数调用跟踪,通过编写脚本分析出app内部的调用逻辑
- 数据与行为的篡改和验证,可以通过脚本获取应用内部的一些数据、方法的入参、出参以及篡改其执行行为等
- 协议分析,拦截一些网络请求等直接进行分析,无需额外工具就可以实现
- 辅助脱壳,现在的应用基本都加固了,所以在一定程度上是可以利用frida进行脱壳处理的
-
安全测试与漏洞挖掘
- 漏洞验证,这里可以利用frida快速进行一些常见或者可能的漏洞验证
- 加密/签名绕过,绕过App的一些加密限制,直接跳过执行后续逻辑等
- 隐私合规性检测,可以直接检测App是否违规手机用户隐私信息,特别是现在APP的一些权限状态以及在未同意时是否有相关的调用等
- Native层的漏洞挖掘,这里hook一些原生的库的关键函数来执行和检测一些风险漏洞等。
-
开发调试与问题定位
这里就不再过多描述了,开发者用Frida可以快速调试多种问题,而无需修改源码、重新编译打包运行等,特别是针对一些已经上线发布的版本。
- 线上问题排查
- 第三方SDK调试
- 性能分析
-
破解和逆向辅助,这里更多是内部测试以及灰色场景使用了
-
热修复&动态调试验证
Frida的限制
这里的限制更多是偏向Android App的角度,不适用以下场景:
- 没有root/越狱环境的高版本系统
- 强防护的APP
- 需要长期修改逻辑并持久化生效的场景
- 高并发和高性能场景
Frida安装
frida依赖Python3.7+版本,Python的安装这里就不再赘述,不清楚可以看我历史相关文章分享。 注:后文以windows系统和Android APP为例进行分享。
Frida客户端工具安装
使用以下命令安装frida客户端的核心库和工具:
# 安装命令
pip install frida frida-tools
验证安装结果,也是后续下载对应服务端的依据:
# 验证安装版本
frida --verison
下载Frida服务端
Frida需要在Android设备上运行一个服务进程,用于与电脑端进行通信,所以这里需要Frida的服务端程序,下载安装也比较简单,一共三步:
第一步 打开官方下载界面
【图片】
第二步 选择对应版本下载
这里需要结合我们客户端的版本号,以及设备CPU架构信息选择对应的版本进行下载,我这里演示用的是雷电模拟器,所以选择x86_64的架构。注意一定不要选择错误了,因为版本确实很多。
注:Android 设备 CPU 架构(常见arm、arm64、x86、x86_64),可通过adb shell getprop ro.product.cpu.abi查询设备架构(如arm64-v8a对应arm64)
【图片】
第三步 解压.xz文件
将这里下载到的server文件解压到本地等待使用,解压名称较长,这里重命名为frida-server,也方便后续操作。
Android设备配置
设备依赖
- root权限,这里的Android设备前文也提到了,需要是已经root或者可以获取root权限的设备。
- 开启USB调试,打开设备的开发者选项,能够让电脑能够连接设备
部署frida-server到设备
第一步 推送文件
电脑连接设备后,将下载解压得到的文件推送到设备上,使用以下命令即可:
adb push 解压得到的文件 /data/local/tmp/frida-server
第二步 赋予执行权限
使用以下命令给我们的server程序赋予执行权限:
adb shell su -c "chmod 755 /data/local/tmp/frida-server"
# 或者
1、adb shell
2、su
3、chmod 755 /data/local/tmp/frida-server
第三步 启动服务
运行以下命令启动设备上的frida-server服务,便于后续电脑端连接通信:
adb shell su -c "/data/local/tmp/frida-server &"
# 或者
1、adb shell
2、su
3、/data/local/tmp/frida-server
以上操作后,可以通过下方命令查看服务是否正常启动:
ps -ef | grep frida-server
【图片】
Frida动态注入
经过上述客户端和服务端的配置安装,已经支持我们进行动态注入了,主要过程分以下几步:
- 1、获取目标应用信息
- 2、获取需要hook的业务代码
- 3、编写hook脚本
- 4、执行注入
- 5、观察效果按需调整脚本
后续就以一个简单的Android demo来演示一下hook的基础用法。
实例目标
通过firda注入一个hook脚本,读取到界面输入的密码信息。
第一步 获取应用信息
由于这里的demo是我们自己写的,所以基本的应用信息我们就有了,这一步主要是获取应用的包名,demo的包名为com.example.fridademo。
第二步 获取需要hook的业务代码
同样的这里我们有源码,就不再逆向应用获取了:
package com.example.fridademo;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.widget.Button;
import android.widget.Toast;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.AppCompatEditText;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
public class LoginActivity extends AppCompatActivity {
private AppCompatEditText editAccount;
private AppCompatEditText editPassword;
private Button btnLogin;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_login);
btnLogin = findViewById(R.id.btn_login);
editAccount = findViewById(R.id.edit_account);
editPassword = findViewById(R.id.edit_passwd);
btnLogin.setOnClickListener(v -> {
goLoging(editAccount.getText().toString(), editPassword.getText().toString());
});
}
private void goLoging(String account, String passwd) {
if("yinian".equals(account) && "123456".equals(passwd)){
Log.d("LoginActivity", "goLoging: 登录成功");
Toast.makeText(LoginActivity.this, "登录成功", Toast.LENGTH_SHORT).show();
}else{
Log.d("LoginActivity", "goLoging: 登录失败");
Toast.makeText(LoginActivity.this, "登录失败", Toast.LENGTH_SHORT).show();
}
}
}
【图片】
我们的目标代码就是goLoging方法的账号和密码参数。
第三步 编写hook脚本
这是根据Android demo代码写的一个简单hook,由于知道源码,所以hook就朝着目标写就成,如果你不知道源码,那就得想办法找到代码逻辑或者对应的方法、变量等等。
Java.perform(function() {
// 定义获取当前时间的函数,用于日志
function getCurrentTime() {
var date = new Date();
return "[" + date.getHours() + ":" + date.getMinutes() + ":" + date.getSeconds() + "]";
}
// 定位LoginActivity类
var LoginActivity = Java.use('com.example.fridademo.LoginActivity');
console.log("" + getCurrentTime() + " [*] 开始hook LoginActivity类的goLoging方法");
// hook goLoging方法,注意新的签名是(String, String)
LoginActivity.goLoging.implementation = function(account, passwd) {
console.log("\n" + getCurrentTime() + " [+] ======================");
console.log(getCurrentTime() + " [+] 检测到goLoging方法被调用");
try {
// 直接从方法参数中获取明文账号密码
// 注意参数名:第一个参数是账号,第二个参数是密码
// 打印获取到的账号密码
console.log(getCurrentTime() + " [+] ====== 登录凭证 ======");
console.log(getCurrentTime() + " [+] 账户名: " + account);
console.log(getCurrentTime() + " [+] 密码: " + passwd);
console.log(getCurrentTime() + " [+] ======================");
} catch (e) {
console.log(getCurrentTime() + " [-] 获取明文账号密码失败: " + e.message);
}finally{
// 调用原始方法并传递参数
return this.goLoging(account, passwd);
}
};
console.log(getCurrentTime() + " [*] Hook安装完成,等待goLoging方法被调用...");
});
第四步 执行注入
在命令行中输入以下命令,将脚本注入目标应用即可:
frida -U -f "com.example.fridademo" -l .\hook.js
第五步 观察效果
以下就是运行效果和hook注入和成功截取到的效果,在实际的目标应用注入过程中,大概率是需要很多次调试的,所以找对目标代码、编写正确的hook脚本就非常重要了。
【图片】
最后
Frida的用法有很多,大家感兴趣的可以进一步研究研究,也欢迎留言、私信交流~