【翻译】使用 AFL++ Frida 模式进行 Android 灰盒模糊测试
字数 1040 2025-08-22 12:23:00

使用 AFL++ Frida 模式进行 Android 灰盒模糊测试教学文档

1. 概述

本教学文档详细介绍如何使用 AFL++ 及其 Frida 模式对 Android 应用程序进行灰盒模糊测试。内容包括环境搭建、工具编译、目标分析、线束开发以及实际模糊测试过程。

2. 环境准备

2.1 硬件要求

  • 测试设备:已 root 的 Android 设备(示例使用 Samsung Galaxy A32,SM-A325F)
  • 开发机:Ubuntu 22.04 x86_64

2.2 软件要求

  • AFL++ 4.06c
  • Frida GumJs devkit 16.0.13
  • Android standalone NDK r25c
  • Android 12 (API 31) aarch64

2.3 系统配置

# 安装基础工具
apt update
apt install cmake curl unzip xxd

3. AFL++ 编译与部署

3.1 下载源码

cd /opt
curl https://codeload.github.com/AFLplusplus/AFLplusplus/zip/refs/tags/4.06c --output 4.06c.zip
unzip 4.06c.zip

# 下载Android NDK
curl https://dl.google.com/android/repository/android-ndk-r25c-linux.zip --output ndk.zip
unzip ndk.zip

3.2 获取CMake配置

cd AFLplusplus-4.06c
curl https://raw.githubusercontent.com/quarkslab/android-fuzzing/main/AFLplusplus/CMakeLists.txt --output CMakeLists.txt

3.3 编译AFL++

mkdir build && cd build
cmake -DANDROID_PLATFORM=31 \
      -DCMAKE_TOOLCHAIN_FILE=/opt/android-ndk-r25c/build/cmake/android.toolchain.cmake \
      -DANDROID_ABI=arm64-v8a ..
make

3.4 部署到设备

adb push afl-fuzz afl-frida-trace.so /data/local/tmp

4. 目标应用分析

4.1 目标函数

示例应用包含三个测试场景:

  1. 标准本机函数:fuzzMe
  2. 弱链接JNI函数:Java_qb_blogfuzz_NativeHelper_fuzzMeArray
  3. 强链接JNI函数:Java_qb_blogfuzz_NativeHelper_fuzzMeWrapper

4.2 关键函数代码

void fuzzMe(const jbyte *buffer, jsize length) {
    void (*crashMe)() = nullptr;
    if (length < 16) return;
    
    if (buffer[0] == 'Q')
    if (buffer[1] == 'u')
    if (buffer[2] == 'a')
    if (buffer[3] == 'r')
    if (buffer[4] == 'k')
    if (buffer[5] == 's')
    if (buffer[6] == 'l')
    if (buffer[7] == '4')
    if (buffer[8] == 'b')
    if (buffer[9] == 'f')
    if (buffer[10] == 'u')
    if (buffer[11] == 'z')
    if (buffer[12] == 'z')
    if (buffer[13] == 'M')
    if (buffer[14] == 'e')
    if (buffer[15] == '!')
        crashMe();
}

5. 标准本机函数模糊测试

5.1 线束开发

关键函数fuzz_one_input

void fuzz_one_input(const uint8_t *buffer, size_t length) {
    fuzzMe((const jbyte *)buffer, (jsize)length);
}

5.2 Frida配置脚本 (afl.js)

const pStartAddr = DebugSymbol.fromName("fuzz_one_input").address;
Afl.setPersistentAddress(pStartAddr);
Afl.setEntryPoint(pStartAddr);

const cm = new CModule(`
#include <string.h>
#include <gum/gumdefs.h>

#define BUF_LEN 256

void afl_persistent_hook(GumCpuContext *regs, uint8_t *input_buf, uint32_t input_buf_len) {
    uint32_t length = (input_buf_len > BUF_LEN) ? BUF_LEN : input_buf_len;
    memcpy((void *)regs->x[0], input_buf, length);
    regs->x[1] = length;
}
`, {
    memcpy: Module.getExportByName(null, "memcpy")
});

Afl.setPersistentHook(cm.afl_persistent_hook);

5.3 设备端准备

# 设置CPU性能模式
cd /sys/devices/system/cpu
echo performance | tee cpu*/cpufreq/scaling_governor

# 准备测试目录和样本
cd /data/local/tmp
mkdir in out
dd if=/dev/urandom of=in/sample.bin bs=1 count=16

5.4 启动模糊测试

./afl-fuzz -O -G 256 -i in -o out ./fuzz

6. JNI函数模糊测试

6.1 JNI环境初始化

typedef struct JavaContext {
    JavaVM *vm;
    JNIEnv *env;
    struct JniInvocationImpl *invoc;
} JavaCTX;

int init_java_env(JavaCTX *ctx, char **jvm_options, uint8_t jvm_nb_options) {
    // 详细实现参考原文
    // 关键步骤包括:
    // 1. 加载libandroid_runtime.so
    // 2. 获取JniInvocationCreate和JniInvocationInit函数
    // 3. 调用JNI_CreateJavaVM
    // 4. 注册框架本地方法
}

6.2 JNI函数线束

void fuzz_one_input(const uint8_t *buffer, size_t length) {
    jbyteArray jBuffer = (*ctx.env)->NewByteArray(ctx.env, length);
    (*ctx.env)->SetByteArrayRegion(ctx.env, jBuffer, 0, length, (const jbyte *)buffer);
    Java_qb_blogfuzz_NativeHelper_fuzzMeArray(ctx.env, NULL, jBuffer);
    (*ctx.env)->DeleteLocalRef(ctx.env, jBuffer);
}

6.3 Frida配置增强

const MODULE_WHITELIST = [
    "fuzz",
    "libblogfuzz.so",
];

Module.load("libandroid_runtime.so");
new ModuleMap().values().forEach(m => {
    if (!MODULE_WHITELIST.includes(m.name)) {
        Afl.print(`Exclude: ${m.base} - ${m.base.add(m.size)} ${m.name}`);
        Afl.addExcludedRange(m.base, m.size);
    }
});

Afl.setPersistentCount(10000);

7. 结果分析

7.1 崩溃验证

xxd out/default/crashes/id*
00000000: 5175 6172 6b73 6c34 6266 757a 7a4d 6521  Quarksl4bfuzzMe!

8. 性能优化建议

  1. 限制输入大小:使用-G参数限制最大输入大小
  2. 模块排除:通过Frida脚本排除不相关模块的检测
  3. 持久性计数:适当设置Afl.setPersistentCount
  4. 白名单机制:只检测目标模块

9. 替代方案

  1. fpicker-aflpp-android:在应用上下文中进行模糊测试
  2. AFL++ with QEMU模式:另一种Android模糊测试解决方案

10. 结论

AFL++ Frida模式为Android灰盒模糊测试提供了:

  • 灵活的配置选项
  • 相对简单的实现方式
  • 对JNI函数的良好支持
  • 通过覆盖率反馈提高效率

注意:目标与Java元素的交互越多,性能影响越大,需要针对具体场景进行优化。

使用 AFL++ Frida 模式进行 Android 灰盒模糊测试教学文档 1. 概述 本教学文档详细介绍如何使用 AFL++ 及其 Frida 模式对 Android 应用程序进行灰盒模糊测试。内容包括环境搭建、工具编译、目标分析、线束开发以及实际模糊测试过程。 2. 环境准备 2.1 硬件要求 测试设备:已 root 的 Android 设备(示例使用 Samsung Galaxy A32,SM-A325F) 开发机:Ubuntu 22.04 x86_ 64 2.2 软件要求 AFL++ 4.06c Frida GumJs devkit 16.0.13 Android standalone NDK r25c Android 12 (API 31) aarch64 2.3 系统配置 3. AFL++ 编译与部署 3.1 下载源码 3.2 获取CMake配置 3.3 编译AFL++ 3.4 部署到设备 4. 目标应用分析 4.1 目标函数 示例应用包含三个测试场景: 标准本机函数: fuzzMe 弱链接JNI函数: Java_qb_blogfuzz_NativeHelper_fuzzMeArray 强链接JNI函数: Java_qb_blogfuzz_NativeHelper_fuzzMeWrapper 4.2 关键函数代码 5. 标准本机函数模糊测试 5.1 线束开发 关键函数 fuzz_one_input : 5.2 Frida配置脚本 (afl.js) 5.3 设备端准备 5.4 启动模糊测试 6. JNI函数模糊测试 6.1 JNI环境初始化 6.2 JNI函数线束 6.3 Frida配置增强 7. 结果分析 7.1 崩溃验证 8. 性能优化建议 限制输入大小:使用 -G 参数限制最大输入大小 模块排除:通过Frida脚本排除不相关模块的检测 持久性计数:适当设置 Afl.setPersistentCount 白名单机制:只检测目标模块 9. 替代方案 fpicker-aflpp-android :在应用上下文中进行模糊测试 AFL++ with QEMU模式:另一种Android模糊测试解决方案 10. 结论 AFL++ Frida模式为Android灰盒模糊测试提供了: 灵活的配置选项 相对简单的实现方式 对JNI函数的良好支持 通过覆盖率反馈提高效率 注意:目标与Java元素的交互越多,性能影响越大,需要针对具体场景进行优化。