Android App动态侵入之Frida基础用法(附截取App输入的账户密码实例)

127 阅读8分钟

前言

之所以写一篇这个内容,也是最近一个客户那边遇到了类似的安全检查没有通过,所以从基础的内容开始分享一下相关的知识。

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的服务端程序,下载安装也比较简单,一共三步:

第一步 打开官方下载界面

github.com/frida/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的用法有很多,大家感兴趣的可以进一步研究研究,也欢迎留言、私信交流~