跳过内容

MASTG-TOOL-0001: Android 版 Frida

Frida支持通过Java API与Android Java运行时进行交互。您将能够hook和调用进程及其原生库中的Java和native函数。您的JavaScript代码段可以完全访问内存,例如,读取和/或写入任何结构化数据。

以下是Frida API提供的一些任务,这些任务与Android相关或专有

  • 实例化Java对象并调用静态和非静态类方法(Java API)。
  • 替换Java方法实现(Java API)。
  • 通过扫描Java堆枚举特定类的实时实例(Java API)。
  • 扫描进程内存中出现的字符串(Memory API)。
  • 拦截本机函数调用,以便在函数入口和出口处运行您自己的代码(Interceptor API)。

请记住,在Android上,您还可以从安装Frida时提供的内置工具中受益,包括Frida CLI (frida)、frida-psfrida-ls-devicesfrida-trace等等。

Frida经常与Xposed进行比较,但是这种比较远非公平,因为这两个框架的设计目标不同。理解这一点很重要,因为作为应用安全测试人员,您可以知道在什么情况下使用哪个框架

  • Frida是独立的,您只需要从目标Android设备上的已知位置运行frida-server二进制文件(请参阅下面的“安装Frida”)。这意味着,与Xposed相比,它没有深度安装在目标操作系统中。
  • 逆向一个应用程序是一个迭代的过程。作为前一点的结果,您在测试时获得了更短的反馈循环,因为您不需要(软)重启来应用或简单地更新您的hook。因此,在实现更永久的hook时,您可能更喜欢使用Xposed。
  • 您可以在进程运行期间的任何时候动态注入和更新您的Frida JavaScript代码(类似于iOS上的Cycript)。这样,您可以通过让Frida生成您的应用程序来执行所谓的早期检测,或者您可以选择附加到您可能已置于某种状态的正在运行的应用程序。
  • Frida能够处理Java和native代码(JNI),允许您修改它们。不幸的是,这是Xposed的一个限制,它缺乏对native代码的支持。

请注意,截至2019年初,Xposed尚不能在Android 9(API级别28)上运行。

在Android上安装Frida

为了在您的Android设备上设置Frida

除非另有说明,否则我们假设这里有一个root的设备。从Frida发布页面下载frida-server二进制文件。确保您为您的Android设备或模拟器的架构下载正确的frida-server二进制文件:x86、x86_64、arm或arm64。确保服务器版本(至少主要版本号)与您本地Frida安装的版本匹配。PyPI通常安装最新版本的Frida。如果您不确定安装了哪个版本,您可以使用Frida命令行工具进行检查

frida --version

或者您可以运行以下命令来自动检测Frida版本并下载正确的frida-server二进制文件

wget https://github.com/frida/frida/releases/download/$(frida --version)/frida-server-$(frida --version)-android-arm.xz

将frida-server复制到设备并运行它

adb push frida-server /data/local/tmp/
adb shell "chmod 755 /data/local/tmp/frida-server"
adb shell "su -c /data/local/tmp/frida-server &"

在Android上使用Frida

随着frida-server的运行,您现在应该能够使用以下命令获取正在运行的进程的列表(使用-U选项指示Frida使用连接的USB设备或模拟器)

$ frida-ps -U
  PID  Name
-----  --------------------------------------------------------------
  276  adbd
  956  android.process.media
  198  bridgemgrd
30692  com.android.chrome
30774  com.android.chrome:privileged_process0
30747  com.android.chrome:sandboxed
30834  com.android.chrome:sandboxed
 3059  com.android.nfc
 1526  com.android.phone
17104  com.android.settings
 1302  com.android.systemui
(...)

或者使用-Uai标志组合限制列表以获取当前安装在连接的USB设备(-U)上的所有应用程序(-a)(-i

$ frida-ps -Uai
  PID  Name                                      Identifier
-----  ----------------------------------------  ------------------------------
  766  Android System                            android
30692  Chrome                                    com.android.chrome
 3520  Contacts Storage                          com.android.providers.contacts
    -  Uncrackable1                              sg.vantagepoint.uncrackable1
    -  drozer Agent                              com.mwr.dz

这将显示所有应用程序的名称和标识符,如果它们当前正在运行,它还将显示它们的PID。在列表中搜索您的应用程序并记下PID或其名称/标识符。从现在开始,您将通过使用其中一个来引用您的应用程序。建议使用标识符,因为PID在每次运行应用程序时都会更改。例如,让我们以com.android.chrome为例。您现在可以在所有Frida工具上使用此字符串,例如,在Frida CLI、frida-trace或Python脚本中。

使用frida-trace追踪Native库

要追踪特定的(低级)库调用,您可以使用frida-trace命令行工具

frida-trace -U com.android.chrome -i "open"

这会在__handlers__/libc.so/open.js中生成一个小的JavaScript,Frida将其注入到进程中。该脚本跟踪libc.so中对open函数的所有调用。您可以根据您的需求使用Frida JavaScript API修改生成的脚本。

不幸的是,目前尚不支持跟踪Java类的高级方法(但将来可能会支持)。

Frida CLI和Java API

使用Frida CLI工具(frida)以交互方式使用Frida。它hook到一个进程,并为您提供一个Frida API的命令行界面。

frida -U com.android.chrome

使用-l选项,您还可以使用Frida CLI加载脚本,例如,加载myscript.js

frida -U -l myscript.js com.android.chrome

Frida还提供了一个Java API,这对于处理Android应用程序特别有用。它允许您直接使用Java类和对象。这是一个覆盖Activity类的onResume函数的脚本

Java.perform(function () {
    var Activity = Java.use("android.app.Activity");
    Activity.onResume.implementation = function () {
        console.log("[*] onResume() got called!");
        this.onResume();
    };
});

上面的脚本调用Java.perform以确保您的代码在Java VM的上下文中执行。它通过Java.use实例化android.app.Activity类的包装器,并覆盖onResume函数。新的onResume函数实现在控制台上打印一个通知,并通过在应用程序中每次恢复活动时调用this.onResume来调用原始的onResume方法。

jadx可以通过其图形代码浏览器生成Frida代码段。要使用此功能,请使用jadx-gui打开APK或DEX,浏览到目标方法,右键单击方法名称,然后选择“Copy as frida snippet (f)”。例如,使用MASTG Android UnCrackable L1

上述步骤将以下输出放在剪贴板中,然后您可以将其粘贴到JavaScript文件中并将其馈送到frida -U -l

let a = Java.use("sg.vantagepoint.a.a");
a["a"].implementation = function (bArr, bArr2) {
    console.log('a is called' + ', ' + 'bArr: ' + bArr + ', ' + 'bArr2: ' + bArr2);
    let ret = this.a(bArr, bArr2);
    console.log('a ret value is ' + ret);
    return ret;
};

上面的代码hook了sg.vantagepoint.a.a类中的a方法,并记录其输入参数和返回值。

Frida还允许您搜索和处理堆上的实例化对象。以下脚本搜索android.view.View对象的实例并调用它们的toString方法。结果将打印到控制台

setImmediate(function() {
    console.log("[*] Starting script");
    Java.perform(function () {
        Java.choose("android.view.View", {
             "onMatch":function(instance){
                  console.log("[*] Instance found: " + instance.toString());
             },
             "onComplete":function() {
                  console.log("[*] Finished heap search")
             }
        });
    });
});

输出如下所示

[*] Starting script
[*] Instance found: android.view.View{7ccea78 G.ED..... ......ID 0,0-0,0 #7f0c01fc app:id/action_bar_black_background}
[*] Instance found: android.view.View{2809551 V.ED..... ........ 0,1731-0,1731 #7f0c01ff app:id/menu_anchor_stub}
[*] Instance found: android.view.View{be471b6 G.ED..... ......I. 0,0-0,0 #7f0c01f5 app:id/location_bar_verbose_status_separator}
[*] Instance found: android.view.View{3ae0eb7 V.ED..... ........ 0,0-1080,63 #102002f android:id/statusBarBackground}
[*] Finished heap search

您还可以使用Java的反射功能。要列出android.view.View类的公共方法,您可以在Frida中为此类创建一个包装器,并从包装器的class属性中调用getMethods

Java.perform(function () {
    var view = Java.use("android.view.View");
    var methods = view.class.getMethods();
    for(var i = 0; i < methods.length; i++) {
        console.log(methods[i].toString());
    }
});

这将在终端上打印一个非常长的方法列表

public boolean android.view.View.canResolveLayoutDirection()
public boolean android.view.View.canResolveTextAlignment()
public boolean android.view.View.canResolveTextDirection()
public boolean android.view.View.canScrollHorizontally(int)
public boolean android.view.View.canScrollVertically(int)
public final void android.view.View.cancelDragAndDrop()
public void android.view.View.cancelLongPress()
public final void android.view.View.cancelPendingInputEvents()
...