APP动态分析系列 – Frida的进阶用法(上)

APP动态分析系列 - Frida的进阶用法(上)


APP动态分析系列 - Frida的进阶用法(上)

APP动态分析系列 - Frida的进阶用法(上)

大狗涉网线索分析平台是无糖信息旗下一款针对“新型电信网络诈骗犯罪”专业化、智能化、跨境化等特征,提供涉诈网站识别及监控、预警数据分析、案件研判的一体化服务平台。为公安部门在预警防范、侦查破案提供多方位、深层次、多类型的情报信息及数据支持,助力公安机关形成反网络犯罪的打击能力。










《APP动态分析系列 – Frida的基础用法(上)》和《APP动态分析系列 – Frida的基础用法(下)》两篇文章中,我们一起探讨了关于 Frida 脚本的七种基础用法,解决了在分析恶意APP样本时,仅依靠静态和逆向分析无法解决问题,需引入Frida框架才能实现的基础分析场景。


本篇文章将继续分享APP动态分析中更为复杂的两种场景,需要我们使用 Frida 的进阶用法来解决:

  • Hook native 函数:例如,某些APP可能会将加载SDK的代码写在 native 库中,以绕过静态分析的检测,在分析隐藏加载的SDK时,我们可以通过 Frida 脚本 Hook native 库中的目标函数,获取函数调用时相关参数和返回值,来分析SDK的加载过程。

  • 更改 native 函数的返回值:例如,一些APP为了提高安全性、防止被篡改,会使用 native 函数来实现权限验证的功能,在绕过权限验证时,我们需要使用 Frida 脚本更改 native 函数的返回值,观察不同权限验证结果下的行为,以便绕过验证或深入了解验证逻辑。

通常使用Frida脚本实现上述功能时,我们需要操作一些前置条件,如安装Python、Frida、安卓模拟器等环境(可参考官方文档:https://frida.re/docs/home/),而基于【大狗涉网线索分析平台】的【云真机操作台】的Frida 脚本功能,既省去了复杂的安装过程,又实现一键运行 Frida 脚本,在无糖浏览器中随时对恶意 APP 进行动态调试,大大提高了工作效率。


在这个基础上,我们还需要掌握,Frida 获取 native 函数地址、Hook native 函数、修改 native 函数返回值等进阶用法。接下来,我们将对以上两个分析场景中涉及到的Frida进阶用法进行讲解和演示。





进阶用法示例





进阶篇文章将以 Frida-lab 中的进阶题目为例,分上下两篇为大家介绍云真机操作台中 Frida 脚本的进阶用法。Frida-lab 是 Github 上的一个开源项目,是专为学习 Frida for Android 而设计的一系列挑战,包含多个 CTF 风格的 APP 样本,旨在帮助初学者掌握 Frida 及其常用的 API 基础知识。



项目链接:https://github.com/DERE-ad2001/Frida-Labs


使用工具


  • Jadx 反编译工具:jadx 是一个功能强大、使用简单的 Android 反编译利器,适合开发者在逆向工程和代码分析时使用。(官方网站:https://github.com/skylot/jadx)

  • IDA 反汇编工具:ida 是一款功能强大的反汇编工具,用于分析和逆向工程二进制文件,被广泛用于软件漏洞分析、恶意代码分析、逆向工程等领域。(官方网站:https://www.hex-rays.com/products/ida/)

  • JavaScript 脚本:我们将使用 JavaScript API 来完成 Frida 脚本的编写。(API文档地址:https://frida.re/docs/javascript-api/)(值得注意的是,Frida也支持Python。)

  • Frida 框架:本次我们使用【大狗涉网线索分析平台】-【云真机操作台】中引入的 Frida 脚本功能。



前期知识储备



  • 了解 Frida 框架的基本原理和架构。

  • 了解 Hook 技术拦截和修改函数或方法的基础知识。

  • 掌握使用 jadx 进行逆向工程的基础知识。

  • 掌握 x86/ARM64 汇编和反汇编基础知识。

  • 具备理解 Java 代码的能力。

  • 具备编写小型 JavaScript 代码片段的能力。




01




进阶用法1:Hook native函数


APP下载地址:https://github.com/DERE-ad2001/Frida-Labs/tree/main/Frida%200×8



01
了解APP样本


在下载 Challenge 0x8.apk 文件并上传至大狗云真机操作平台后,可以看到 APP 程序有一个输入框,随意输入一些字符点击按钮提交,程序会提示“TRY AGAIN ”(图1),猜测需要输入正确的字符才能获取flag。我们将 APK 放入 jadx 进行静态分析。


APP动态分析系列 - Frida的进阶用法(上)

图1. 应用程序Challenge 0x8界面


02
静态分析


使用 jadx 反编译 APP,查看反编译源代码,找到程序入口(图2)。可以看到,MainActivity 类中,有一段 native 功能声明。首先,使用 native 关键字定义了一个 cmpstr native 函数,该函数接收一个字符串类型的参数并返回一个整数。然后,在静态代码块中使用 System.loadLibrary 方法将 native 库 frida0x8 加载到内存中。声明完 native 功能之后,MainActivity类中还定义了一个 onCreate 方法。



Android NDK(Native Development Kit)允许开发人员将用C和C++ 等语言编写的原生(native)代码添加到 Android 应用程序中,这些原生代码被编译为可被 Java 代码加载的动态链接库(.so)文件 ,用于实现高性能以及与硬件直接交互等功能。

APP动态分析系列 - Frida的进阶用法(上)

图2. 程序入口


public native int cmpstr(String str);

static {
    System.loadLibrary("frida0x8");
}


onCreate方法初始化界面的布局,找到 EditText 和 Button 的引用,并设置了按钮的点击事件监听器,当按钮被点击时,会获取 EditText 中的文本,调用 cmpstr 方法进行比较。分析代码我们可以发现,只有当我们输入正确的 flag,让 cmpstr 方法返回值为1时,程序才会输出”YEY YOU GOT THE FLAG ” 提示语+输入的 flag 本身,否则就会输出”TRY AGAIN”。现在我们需要来分析 frida0x8 native 库,查看 cmpstr 函数具体是如何实现的。


public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding inflate = ActivityMainBinding.inflate(getLayoutInflater());
this.binding = inflate;
setContentView(inflate.getRoot());
this.edt = (EditText) findViewById(R.id.editTextText);
Button button = (Button) findViewById(R.id.button);
this.btn = button;
button.setOnClickListener(new View.OnClickListener() { // from class: com.ad2001.frida0x8.MainActivity.1
    @Override // android.view.View.OnClickListener
    public void onClick(View v) {
        String ip = MainActivity.this.edt.getText().toString();
        int res = MainActivity.this.cmpstr(ip);
        if (res == 1) {
            Toast.makeText(MainActivity.this"YEY YOU GOT THE FLAG " + ip, 1).show();
        } else {
            Toast.makeText(MainActivity.this"TRY AGAIN"1).show();
        }
    }
});
}


我们可以在 jadx 中看到 frida0x8 库位于 lib 目录下,分别提供了基于ARMv8-A(64位)、ARMv7-A(32位)、Intel x86、Intel x86_64 的四种 CPU 架构的动态链接库文件,以确保应用程序能够在不同类型的设备上正确运行和执行 native 代码(图3),由于多数 Android 设备使用 ARM64 架构,这里我们提取 arm64-v8a 文件夹下的 libfrida0x8.so 库文件进行分析。可以通过将 APK 文件名后缀重命名为 .zip,然后将其解压,在对应目录下提取 libfrida0x8.so 库文件(也可以使用 apktool 等工具来转储这个库)。提取出来之后需要使用 IDA 工具对 native 库文件进行逆向分析。




libfrida0x8.so库文件的命名规则中,lib表示库文件前缀,.so表示native库编译后的动态链接库扩展名。

APP动态分析系列 - Frida的进阶用法(上)

图3. frida0x8库文件


将 libfrida0x8.so 放入 IDA 中进行反汇编分析,在右侧函数名称窗口中找到 cmpstr ,该函数在反汇编代码中被命名为 Java_com_ad2001_frida0x8_MainActivity_cmpstr(附加了包名和类名),双击它IDA会显示这个函数的反汇编视图(图4)。我们可以点击选中反汇编视图中的反汇编代码,按F5键打开它的反编译窗口,得到便于理解和分析的反编译伪代码(图5)。为了更好理解 native 函数如何实现,作者在题目中给出了这个函数的源代码,让我们直接对源代码进行分析。


APP动态分析系列 - Frida的进阶用法(上)

图4. cmpstr 函数反汇编代码


APP动态分析系列 - Frida的进阶用法(上)

5. cmpstr 函数反编译伪代码


#include <jni.h>
#include <string.h>
#include <cstdio>
#include <android/log.h>

extern "C"
JNIEXPORT jint JNICALL
Java_com_ad2001_frida0x8_MainActivity_cmpstr(JNIEnv *env, jobject thiz, jstring str) 
{
    const char *inputStr = env->GetStringUTFChars(str, 0);
    const char *hardcoded = "GSJEB|OBUJWF`MBOE~";
    char password[100];

    for (int i = 0; i < strlen(hardcoded) ; i++) {

        password[i] = (char)(hardcoded[i] - 1);
    }

    password[strlen(hardcoded)] = '';
    int result = strcmp(inputStr, password);
    __android_log_print(ANDROID_LOG_DEBUG, "input ""%s",inputStr);
    __android_log_print(ANDROID_LOG_DEBUG, "Password""%s",password);
    env->ReleaseStringUTFChars(str, inputStr);

    // Returning result: 1 if equal, 0 if not equal
    return (result == 0) ? 1 : 0;
}


下面是关于这段代码的解释:

  • extern “C” JNIEXPORT jint JNICALL Java_com_ad2001_frida0x8_MainActivity_cmpstr(JNIEnv *env, jobject thiz, jstring str) {…} 声明一个名为 cmpstr 的 JNI (Java native接口) 函数,这个函数需要从 Java 代码(Java_com_ad2001_frida0x8_MainActivity_cmpstr)中调用,它需要三个参数,分别是:用于 JNI 环境的 env参数、表示 Java 对象的 thiz 参数和一个 Java 字符串 str 参数。


  • const char *inputStr = env->GetStringUTFChars(str, 0); 从 Java 字符串(jstring)中检索输入字符串,并将其转换为C语言样式字符串(const char*)。


  • const char *hardcoded = “GSJEB|OBUJWF`MBOE~”; char password[100];定义一个字符串类型的hardcoded变量保存一段硬编码的值,并声明了一个空字符串数组password。

  • for (int i = 0; i < strlen(hardcoded) ; i++) { password[i] = (char)(hardcoded[i] – 1);} 通过循环将hardcoded字符串中的每个字符减去1之后,保存在 password 数组中。


  • int result = strcmp(inputStr, password); 使用 strcmp 函数,将用户输入的字符串 inputStr 与处理后的密码 password进行比较,并将结果储存在整型变量 result 中。


  • env->ReleaseStringUTFChars(str, inputStr); 释放与输入字符串关联的资源。

  • return (result == 0) ? 1 : 0; 如果字符串相等,则返回 1,如果字符串不相等,则返回 0。


现在我们来总结一下,应用程序在 MainActivity 类中加载了 frida0x8 native 库,并声明了一个需要在这个 native 库中实现的 native 函数 cmpstr,当我们输入字符串点击提交时,onClick 方法会调用 cmpstr 函数进行比较。


cmpstr 函数被调用时,首先根据预设的 hardcoded 硬编码,获取一个新的字符串,储存在 password 变量中,然后调用 strcmp 函数比较我们输入的字符串与 password 字符串内容是否相同。如果相同 cmpstr 函数返回1,onClick 方法会将”YEY YOU GOT THE FLAG “提示语和我们输入的正确flag拼接后的消息显示在APP界面中。


要通过 cmpstr 函数的检测,我们需要知道函数中 password 变量的值。由于password 参数只在 native 库中调用的 strcmp 函数中被作为参数传递,因此我们可以通过 Frida 脚本 Hook strcmp 函数来实现。


03
编写Frida脚本


我们需要使用 Frida 框架,编写一个 JavaScript 脚本,Hook 应用程序加载的 frida0x8 native 库中 cmpstr 函数使用的 strcmp 函数,并获取函数中使用的参数 password 的值。


在基础篇中我们讲到,Hook一个函数需要知道调用这个函数的程序包名和类名。而Hook native 函数,我们需要知道这个 native 函数的地址,然后使用 Frida 的 Interceptor API 进行 Hook。



Interceptor API是Frida中一个功能强大的模块,能够帮助我们 Hook C 函数、Objective-C 方法。


Interceptor模块中Interceptor.attach()函数用于拦截函数调用,需要传递两个参数,第一个参数是要拦截的函数地址,第二个参数是包含回调函数的对象,用于定义在目标函数被调用时执行的回调函数,通常包含以下两个回调函数:

  • onEnter:在目标函数被调用之前执行的回调函数。在这个回调函数中,可以访问函数的参数,修改参数的值,记录函数调用信息等操作。

  • onLeave:在目标函数被调用之后执行的回调函数。在这个回调函数中,可以访问函数的返回值,修改返回值,记录函数执行结果等操作。

下一步我们需要解决的问题是,如何获取指定的 native 函数地址?Frida 中有很多方法可以做到,下面将列举一些 API 来实现:


在此之前,我们先了解库文件中导出函数表和导入函数表的概念。导出函数表指的是库文件提供给外部使用的函数或变量。导入表是指库文件引用的函数或变量,例如,在库文件中我们导入像 libc.so 这样的库,就可以试用诸如 strcmp 之类的标准函数。


在Frida 0x8 的这个例子中,我们分析出了 Hook 目标是 strcmp函数,所以我们可以从 libfrida0x8.so 的导入表或者 libc 的导出表中找到该函数地址(图6)。


//strcmp并不在libfrida0x8.so的导出表中
send(Module.enumerateExports("libfrida0x8.so"));

// strcmp在libfrida0x8的导入表,或libc.so的导出表中。并且下面两个值应该相等。
send(Module.findExportByName("libc.so""strcmp"));
send(Module.enumerateImports("libfrida0x8.so")[4]);


APP动态分析系列 - Frida的进阶用法(上)

6. libfrida0x8.so的导入表和ibc的导出表


接下来让我们熟悉如何使用Frida API获取库文件的导出和导入表信息。


(1)使用Frida API:Module.enumerateExports()

这个API 可以直接枚举出库文件的所有导出符号信息。让我们尝试在云真机-Frida脚本中获取libfrida0x8.so 库的所有导出符号信息(图7)。


send(Module.enumerateExports("libfrida0x8.so"))
//send方法用于向Frida服务器端发送消息或数据


APP动态分析系列 - Frida的进阶用法(上)

7. Module.enumerateExports获取所有导出符号


[
  {
    "address":"0xe6e684545864",
    "name":"Java_com_ad2001_frida0x8_MainActivity_cmpstr",
    "type":"function"
  },
  {
    "address":"0xe6e6845459d4",
    "name":"_ZN7_JNIEnv17GetStringUTFCharsEP8_jstringPh",
    "type":"function"
  },
  {
    "address":"0xe6e684545a10",
    "name":"_ZN7_JNIEnv21ReleaseStringUTFCharsEP8_jstringPKc",
    "type":"function"
  }
]


观察运行日志中输出的导出符号的地址、名称和类型。可以看到 cmpstr 函数对应的地址为”0xe6e684545864″。我们可以确认该函数在导出表中,这个结果和我们在 jadx 中分析的一致。


(2)使用Frida API:Module.getExportByName()

这个 API 用于获取指定动态链接库中指定名称的导出符号地址(如果不知道导出的符号位于哪个库中,则可以传递null)。让我们使用这个 API 来查找 cmpstr 函数的地址。可以看到两个 API 获取到的 cmpstr 函数地址是相同的(图8)。



需要注意的是:由于 Android 默认启用 ASLR 安全机制,系统启动或程序加载时随机化内存地址空间,如果你重新启动了 APP,前后两次获取到的指定函数地址会有差异。

send(Module.getExportByName("libfrida0x8.so""Java_com_ad2001_frida0x8_MainActivity_cmpstr"))


APP动态分析系列 - Frida的进阶用法(上)

8. Module.getExportByName获取指定函数地址


(3)使用Frida API:Module.findExportByName()

它与 Module.getExportByName 相同。唯一的区别是,如果找不到导出符号,Module.getExportByName 会引发异常,而 Module.findExportByName 会返回 null。让我们尝试用这个方法获取 cmpstr 函数的地址(图9)。


send(Module.findExportByName("libfrida0x8.so""Java_com_ad2001_frida0x8_MainActivity_cmpstr"))


APP动态分析系列 - Frida的进阶用法(上)

图9. Module.findExportByName获取指定函数地址


(4)使用Frida API:Module.getBaseAddress()

有时,如果上面的 API 获取不到指定函数地址,我们可以使用 Module.getBaseAddress,这个 API 返回给定模块的基地址,我们可以用它来找到 libfrida0x8.so 库的基地址。如果我们想找到一个特定函数的地址,可以在基地址的基础上添加偏移量。cmpstr 函数的偏移量,我们可以在 IDA 中查看(图10)。让我们用这种方法获取 cmpstr 的地址(图11)。


APP动态分析系列 - Frida的进阶用法(上)

图10. cmpstr函数偏移量


send(Module.getBaseAddress("libfrida0x8.so").add(0x864))


APP动态分析系列 - Frida的进阶用法(上)

图11. 使用Module.getBaseAddress获取指定函数地址


(5)使用Frida API:Module.enumerateImports()

与 Module.enumerateExports 类似,Module.enuerateImports 将为我们枚举指定模块的所有导入符号信息。现在我们获取 libfrida0x8.so 所有导入符号信息(图12)。


send(Module.enumerateImports("libfrida0x8.so"))


APP动态分析系列 - Frida的进阶用法(上)

图12. Module.enuerateImports获取所有导入符号


[
  ...
  {
    "address":"0xe6e983ecea40",
    "module":"/apex/com.android.runtime/lib64/bionic/libc.so",
    "name":"strcmp",
    "type":"function"
  },
  ...
]


在运行结果中我们可以找到 strcmp 函数,它对应的地址为”0xe6e983ecea40″。我们也可以使用索引4和存储地址的键结合 Module.enuerateImports 函数,来获取指定函数 strcmp 的地址(图13)。


send(Module.enumerateImports("libfrida0x8.so")[4]["address"])


APP动态分析系列 - Frida的进阶用法(上)

图13. Module.enuerateImports获取指定函数地址


现在我们已经掌握了如何使用 Frida 的 API 获取指定 native 函数的地址,接下来就可以使用 Interceptor API 来 Hook 指定的函数了。重申一下,我们需要 Hook cmpstr 函数中调用的 strcmp 函数,以获取 password 参数的值(这也是我们需要在程序界面输入的 flag )。需要注意的是,不同的体系结构(如x86、ARM等)在函数调用时会使用不同的调用约定和寄存器来传递参数和返回值(图14)(取自:https://syscall.sh/)。在使用 Frida 的 Interceptor API 进行函数拦截时,需要根据目标函数所在的体系结构来理解参数和返回值的传递方式,并相应地处理这些信息。此外,可以通过查看目标函数的反汇编代码来了解更多关于函数调用约定和寄存器使用的细节,以帮助正确地拦截和监控目标函数。


APP动态分析系列 - Frida的进阶用法(上)

图14. 不同架构体系调用约定


首先,我们知道像 strcmp 这种 C 语言函数会在 libc.so 库中被导出,我们使用 Module.findExportByName API 找到 strcmp 函数的地址,将它储存在 strcmp_adr 变量中,然后使用 Frida 的 Interceptor API Hook 这个函数。下一步我们要获取password参数值,我们可以在 Interceptor.attach 函数定义的 onEnter 回调函数中访问并记录函数的参数。在前面分析 IDA 反编译代码时,我们知道 strcmp 函数两个参数都是字符串指针,在 Frida 中可以通过 Memory.readUtf8String 函数来读取。



Memory.readUtf8String()是Frida中用于读取内存中UTF-8编码字符串的函数。它的作用是从指定的内存地址读取UTF-8编码的字符串,并将其转换为JavaScript中的字符串类型。

完成上述 Frida 脚本功能的代码实现如下:


var strcmp_adr =  Module.findExportByName("libc.so""strcmp");
// 使用Module.findExportByName()获取libc.so库中strcmp函数的地址。

Interceptor.attach(strcmp_adr, {
  // Hook strcmp_adr地址对应的函数,将onEnter和onLeave回调函数附加到strcmp_adr地址中

  onEnter: function (args{
    // 在strcmp函数被调用之前执行的回调函数,args是一个指针数组,提供对函数参数的访问。

    console.log("Hooking the strcmp function");
    // 进入strcmp函数时打印提示语"Hooking the strcmp function"

    var flag = Memory.readUtf8String(args[1]); 
    // 使用Memory.readUtf8String()获取password参数;
    // password参数在strcmp函数中的地址为arg[1]。

    console.log("The flag is "+ flag);
    // 打印获取到的password参数值。

  },
  onLeavefunction (retval{
    // 在strcmp函数被调用之后执行的回调函数,它提供对返回值retval的访问。
  }
});


不过,当我们在云真机操作台-Firda脚本中运行这段代码,会出现一个问题,运行日志中重复打印出多次 Hook 到的 strcmp 函数(图15),说明 Firda 脚本 Hook 了应用程序中的每个 strcmp,而我们不希望出现这种情况。在前面的分析中我们知道,strcmp 函数的第一个参数 inputStr 是我们的输入的字符串,因此,我们可以输入一个特定的字符串(例如:”Hello”),将其作为 Hook 脚本的过滤器。修改之后,最终的代码是这样:


var strcmp_adr = Module.findExportByName("libc.so""strcmp");
// 使用Module.findExportByName()获取libc.so库中strcmp函数的地址。

Interceptor.attach(strcmp_adr, {
  // Hook strcmp_adr地址对应的函数,将onEnter和onLeave回调函数附加到strcmp_adr地址中

    onEnter: function (args{
      // 在strcmp函数被调用之前执行的回调函数,args是一个指针数组,提供对函数参数的访问。

        var arg0 = Memory.readUtf8String(args[0]);
          // 使用Memory.readUtf8String()读取第一参数inputStr内容。

        var flag = Memory.readUtf8String(args[1]);
          // 使用Memory.readUtf8String()获取第二个参数password内容。

        if (arg0.includes("Hello")) {
          // 只有当第一个参数值是我们输入的特定字符串"Hello"时才执行下面的操作。

            console.log("Hookin the strcmp function");
             // 打印提示语"Hooking the strcmp function"

            console.log("Input " + arg0);
             // 打印第一个参数inputStr的值。

            console.log("The flag is "+ flag);
             // 打印第二个参数password的值。

        }
    },
    onLeavefunction (retval{
        // 在strcmp函数被调用之后执行的回调函数,它提供对返回值retval的访问。
    }
});


APP动态分析系列 - Frida的进阶用法(上)

图15. 未过滤参数Hook strcmp函数


04
运行脚本


进入云真机操作台启动APP,在Frida脚本功能区点击【新增脚本】,命名为Challenge_0x8_hook,将编写好的代码复制进来,点击【加载脚本】。脚本加载成功后,我们输入“Hello”即可触发 Hook 脚本,接着可以在运行日志中看到 password 参数也就是我们需要获取的 flag 值(图16),将它输入应用程序中进行验证,可以看到屏幕出现消息提示,说明我们输入的 flag 正确(图17)。


APP动态分析系列 - Frida的进阶用法(上)

图16. 运行脚本获取flag


APP动态分析系列 - Frida的进阶用法(上)

图17. 验证flag


05
总结



Frida Hook native 函数的脚本模板:


Interceptor.attach(targetAddress, {
  // 将回调附加到指定的函数地址,targetAddress为我们想要挂钩的native函数的地址。

  onEnter: function (args{
    // 在目标函数被调用之前执行的回调函数,提供对函数参数args的访问。

    console.log('Entering ' + functionName);
    // 根据需要修改或记录参数
  },
  onLeavefunction (retval{
    // 在目标函数被调用之后执行的回调函数,它提供对返回值retval的访问。

    console.log('Leaving ' + functionName);
    // 根据需要修改或记录参数
  }
});




02




进阶用法2:
  更改native函数的返回值


APP下载地址:https://github.com/DERE-ad2001/Frida-Labs/tree/main/Frida%200×9


01
了解APP样本


在下载 Challenge 0x9.apk 文件并上传至大狗云真机操作平台后,可以看到应用程序界面只有一个按钮,当我们点击它时,程序会提示“Try again”(图18)。这跟我们在基础篇中遇到的单击按钮的情形很类似,猜测同样需要修改某个参数的值,使之满足程序验证,从而获取 flag 。我们将 APK 放入 jadx 进行静态分析。


APP动态分析系列 - Frida的进阶用法(上)

图18.  应用程序Challenge 0x9界面


02
静态分析


使用 jadx 反编译 APP,查看反编译源代码,找到程序入口(图19)。可以看到,MainActivity 类中,同样有一段 native 功能声明,使用 native 关键字定义了一个 native 函数check_flag,这个函数不接受任何参数并返回一个整数,然后将 a0x9 库加载到程序中,System.loadLibrary 方法提示我们 a0x9 是 native 库文件,check_flag 函数在 a0x9 库中实现。同时,MainActivity 类中还定义了一个 onClick 方法来监控按钮的点击,当点击应用程序按钮时,onClick 方法会将 check_flag 函数的返回值与1337进行比较,如果它们相等,就会解密 flag 并显示在应用程序界面。否则,打印“Try again”。接下来我们需要使用 IDA 来分析 a0x9 库中的 check_flag 函数。


APP动态分析系列 - Frida的进阶用法(上)

图19. 程序入口


public native int check_flag();

static {
    System.loadLibrary("a0x9");
}


public void onClick(View v) {
if (MainActivity.this.check_flag() == 1337) {
    try {
        Cipher cipher = Cipher.getInstance("AES");
        SecretKeySpec secretKeySpec = new SecretKeySpec("3000300030003003".getBytes(), "AES");
        try {
            cipher.init(2, secretKeySpec);
            byte[] decryptedBytes = Base64.getDecoder().decode("hBCKKAqgxVhJMVTQS8JADelBUPUPyDiyO9dLSS3zho0=");
            try {
                String decrypted = new String(cipher.doFinal(decryptedBytes));
                Toast.makeText(MainActivity.this.getApplicationContext(), "You won " + decrypted, 1).show();
                return;
            } 
                ...
        }
        Toast.makeText(MainActivity.this.getApplicationContext(), "Try again"1).show();
    }


在 IDA 中加载 liba0x9.so 库(我们同样使用ARM64架构下的库文件进行分析),我们找到 check_flag 函数,附加了包名和类名后它在反汇编代码中的名称为Java_com_ad2001_a0x9_MainActivity_check_1flag,双击这个函数打开它的反汇编视图(图20),选中右侧反汇编代码,按下F5键打开它的反编译伪代码窗口(图21)。可以看到,这个函数中没有任何操作,只返回了一个长长整型的常量1。


APP动态分析系列 - Frida的进阶用法(上)

图20. 反汇编视图


APP动态分析系列 - Frida的进阶用法(上)

图21. 反编译伪代码


分析完代码我们可以知道,点击按钮时,程序会比较 check_flag 函数的返回值是否等于1337,如果等于就会解码 flag 并输出,check_flag 函数是在程序加载的 liba0x9.so native 库中实现,它只返回一个常量1。因此,想要获取 flag,我们需要 Hook native 函数 check_flag 并将它的返回值更改为1337。


03
编写Frida脚本


我们需要使用 Frida 框架,编写一个 JavaScript 脚本,Hook在应用程序加载的 liba0x9.so native 库中运行的 check_flag 函数,并将它的返回值更改为1337。


在上一个进阶用法中,我们已经学会如何使用 Frida 的 API 来获取 native 函数地址,以及如何让使用 Interceptor API 来 Hook 指定地址的 native 函数,并且我们知道可以在 Interceptor.attach 函数定义的 onLeave 回调函数中访问或修改目标函数的返回值。通过对 APK 静态分析,我们可以知道 check_flag 函数在 liba0x9.so 库中的名称是Java_com_ad2001_a0x9_MainActivity_check_1flag,同时可以在 liba0x9.so 中的导出表中找到该符号的地址。实现上述功能的脚本内容如下:


var check_flag = Module.findExportByName("liba0x9.so""Java_com_ad2001_a0x9_MainActivity_check_1flag")
// 获取liba0x9.so库中Java_com_ad2001_a0x9_MainActivity_check_1flag函数地址,并将它储存在check_flag中。

Interceptor.attach(check_flag, {
  // Hook check_flag地址对应的函数,将onEnter和onLeave回调函数附加check_flag地址中
  onEnter: function ({
    // 在check_flag函数被调用之前执行的回调函数,根据需要修改或记录参数。

  },
  onLeavefunction (retval{
    // 在check_flag函数被调用之后执行的回调函数,它提供对返回值retval的访问。

    console.log("Original return value :" + retval);
    // 打印函数原本返回值

    retval.replace(1337)  
     // 将check_flag函数返回值修改为1337。
  }
});


04
运行脚本


进入云真机操作台启动APP,在Frida脚本功能区点击【新增脚本】,命名为Challenge_0x9_hook,将编写好的代码复制进来,点击【加载脚本】。脚本运行过程中我们再次点击“CLICK ME”按钮,此时 check_flag 运行后的返回值已经被修改,成功绕过 onClick 函数中对返回值的验证,flag 被解码后输出(图22)。


APP动态分析系列 - Frida的进阶用法(上)

图22. 运行脚本点击按钮获取flag


05
总结


Frida 更改native函数返回值的脚本模板:


Interceptor.attach(targetAddress, {
  // 将回调附加到指定的函数地址,targetAddress为我们想要挂钩的native函数的地址。

  onEnter: function (args{
    // 在目标函数被调用之前执行的回调函数,提供对函数参数args的访问。

    console.log('Entering ' + functionName);
    // 根据需要修改或记录参数
  },
  onLeavefunction (retval{
    // 在目标函数被调用之后执行的回调函数,它提供对返回值retval的访问。

    console.log('Leaving ' + functionName);
    // 根据需要修改或记录参数

    retval.replace(value)  
    // 将目标函数返回值修改为value。
  }
});






结语



以上为本期分享的两个 Frida 的进阶用法,本系列文章后续还将在 Frida 的进阶用法下篇中介绍 Frida 的更多进阶用法。欢迎相关公检法技术工作者注册安装【无糖浏览器】,自行通过【大狗平台专区】的【云真机操作台】复现研究。大家还可以根据Frida-lab项目中的参考答案,尝试更多有趣的解题方法。


参考链接


[1] 进阶用法1:https://github.com/DERE-ad2001/Frida-Labs/blob/main/Frida%200×8/Solution/Solution.md

[2] 进阶用法2:https://github.com/DERE-ad2001/Frida-Labs/blob/main/Frida%200×9/Solution/Solution.md



无糖浏览器-您身边的办案助手




01

下载地址(PC端与APP同链接):

http://browser.nosugar.tech

02

邀请码:注册邀请码可从已认证通过的公安民警处获得,完成注册流程并审核通过可开通完整使用权限。

如有疑问,可以扫描下方二维码进入无糖反网络犯罪研究中心。

APP动态分析系列 - Frida的进阶用法(上)


原文始发于微信公众号(无糖反网络犯罪研究中心):APP动态分析系列 – Frida的进阶用法(上)

版权声明:admin 发表于 2024年4月18日 上午11:36。
转载请注明:APP动态分析系列 – Frida的进阶用法(上) | CTF导航

相关文章