第四章:Unidbg原理与环境搭建

第四章:Unidbg原理与环境搭建

本章字数:约28000字 阅读时间:约90分钟 难度等级:★★★★★

声明:本文中的公司名称、包名、API地址、密钥等均已脱敏处理。文中的"梦想世界"、"dreamworld"等均为虚构名称,与任何真实公司无关。


引言

在前三章的探索中,我们尝试了几乎所有常规的逆向手段:

  • 网络抓包被代理检测绕过
  • Frida注入导致APP崩溃
  • Xposed框架被检测到
  • 模拟器环境被识别
  • APK修改被签名校验拦截

就在我几乎要放弃的时候,一个偶然的发现改变了一切------Unidbg

这是一个能在PC上直接运行Android Native库的神器,它让我们可以绑过APP的所有检测机制,直接调用SO库中的签名函数。

本章将深入讲解Unidbg的原理、环境搭建,以及如何用它来突破梦想世界APP的安全防护。


4.1 什么是Unidbg?

4.1.1 Unidbg简介

Unidbg是由中国开发者zhkl0228开发的一个开源项目,它基于Unicorn引擎实现了Android和iOS的Native库模拟执行。

bash 复制代码
┌─────────────────────────────────────────────────────────────────┐
│                    Unidbg 项目概览                               │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  项目地址: https://github.com/zhkl0228/unidbg                   │
│  开发语言: Java                                                  │
│  核心引擎: Unicorn (CPU模拟器)                                   │
│  支持架构: ARM32, ARM64                                          │
│  支持平台: Android, iOS                                          │
│  开源协议: Apache 2.0                                            │
│                                                                  │
│  主要功能:                                                       │
│  ├── 模拟执行ARM/ARM64指令                                       │
│  ├── 模拟Android/iOS系统调用                                     │
│  ├── 模拟JNI环境                                                 │
│  ├── 支持动态调试                                                │
│  └── 支持Hook和Trace                                             │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

简单来说:Unidbg可以让你在PC上直接运行Android的SO库,而不需要真实的Android设备或模拟器。

4.1.2 为什么Unidbg能绕过检测?

这是最关键的问题。让我们对比一下传统方法和Unidbg的区别:

传统方法的执行流程

scss 复制代码
┌─────────────────────────────────────────────────────────────────┐
│                    传统方法执行流程                              │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  1. 启动APP                                                      │
│     ↓                                                            │
│  2. Application.onCreate() 执行                                  │
│     ↓                                                            │
│  3. 加载SO库 → .init_array 执行 → 反调试检测启动                │
│     ↓                                                            │
│  4. JNI_OnLoad() 执行 → 更多检测代码                            │
│     ↓                                                            │
│  5. Activity启动 → 环境检测                                      │
│     ↓                                                            │
│  6. 检测到Frida/Root/模拟器 → APP崩溃                           │
│                                                                  │
│  问题: 检测代码在我们能干预之前就已经执行了!                    │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

Unidbg的执行流程

复制代码
┌─────────────────────────────────────────────────────────────────┐
│                    Unidbg执行流程                                │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  1. 在PC上创建模拟的Android环境                                  │
│     ↓                                                            │
│  2. 只加载目标SO库(不启动APP)                                  │
│     ↓                                                            │
│  3. 可选择性地执行JNI_OnLoad(或跳过)                           │
│     ↓                                                            │
│  4. 直接调用目标函数(如签名函数)                               │
│     ↓                                                            │
│  5. 获取返回结果                                                 │
│                                                                  │
│  优势:                                                           │
│  ✓ 不运行完整APP,大部分检测代码不会执行                        │
│  ✓ 完全可控的环境,可以Hook任意函数                             │
│  ✓ 可以伪造任何系统调用的返回值                                 │
│  ✓ 没有真实的Frida进程,检测无从下手                            │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

核心原理:Unidbg创建了一个"干净"的虚拟环境,这个环境中:

  • 没有Frida进程
  • 没有Xposed框架
  • 没有Root权限
  • 不是模拟器

因为这些东西根本就不存在于Unidbg的虚拟世界中!

4.1.3 Unidbg的技术架构

scss 复制代码
┌─────────────────────────────────────────────────────────────────┐
│                    Unidbg技术架构                                │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                    用户代码 (Java)                       │    │
│  │  - 创建模拟器实例                                        │    │
│  │  - 加载SO库                                              │    │
│  │  - 调用JNI方法                                           │    │
│  └─────────────────────────────────────────────────────────┘    │
│                            ↓                                     │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                    Unidbg框架层                          │    │
│  │  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐      │    │
│  │  │  DalvikVM   │  │   Memory    │  │   Syscall   │      │    │
│  │  │  (JNI模拟)  │  │  (内存管理) │  │  (系统调用) │      │    │
│  │  └─────────────┘  └─────────────┘  └─────────────┘      │    │
│  └─────────────────────────────────────────────────────────┘    │
│                            ↓                                     │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                    Unicorn引擎                           │    │
│  │  - ARM/ARM64指令模拟                                     │    │
│  │  - 寄存器管理                                            │    │
│  │  - 内存访问模拟                                          │    │
│  └─────────────────────────────────────────────────────────┘    │
│                            ↓                                     │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                    Capstone反汇编                        │    │
│  │  - 指令解析                                              │    │
│  │  - 调试支持                                              │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

4.2 Unicorn引擎深度解析

4.2.1 什么是Unicorn?

Unicorn是一个轻量级的多平台、多架构CPU模拟器框架,它是Unidbg的核心引擎。

复制代码
┌─────────────────────────────────────────────────────────────────┐
│                    Unicorn引擎特性                               │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  支持的CPU架构:                                                  │
│  ├── ARM (32位)                                                  │
│  ├── ARM64 (AArch64)                                             │
│  ├── x86                                                         │
│  ├── x86-64                                                      │
│  ├── MIPS                                                        │
│  ├── SPARC                                                       │
│  └── M68K                                                        │
│                                                                  │
│  核心功能:                                                       │
│  ├── 指令级模拟执行                                              │
│  ├── 内存映射和管理                                              │
│  ├── 寄存器读写                                                  │
│  ├── Hook机制(代码、内存、中断)                                │
│  └── 多种编程语言绑定                                            │
│                                                                  │
│  语言绑定:                                                       │
│  Python, Java, Go, Ruby, Rust, Haskell, .NET, ...               │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

4.2.2 Unicorn的工作原理

Unicorn基于QEMU的CPU模拟代码,但去掉了QEMU中与设备模拟相关的部分,只保留了纯CPU模拟功能。

python 复制代码
# Unicorn基本使用示例(Python)
from unicorn import *
from unicorn.arm64_const import *

# ARM64机器码: mov x0, #0x1234
CODE = b"\x80\x46\x82\xd2"

# 初始化ARM64模拟器
mu = Uc(UC_ARCH_ARM64, UC_MODE_ARM)

# 映射内存
ADDRESS = 0x10000
mu.mem_map(ADDRESS, 2 * 1024 * 1024)

# 写入代码
mu.mem_write(ADDRESS, CODE)

# 执行
mu.emu_start(ADDRESS, ADDRESS + len(CODE))

# 读取结果
x0 = mu.reg_read(UC_ARM64_REG_X0)
print(f"X0 = 0x{x0:x}")  # 输出: X0 = 0x1234

4.2.3 为什么选择Unicorn?

特性 Unicorn QEMU 真实设备
启动速度 毫秒级 秒级 分钟级
资源占用
可控性 完全可控 部分可控 有限
Hook能力 指令级 有限 需要工具
调试能力 需要工具
环境隔离 完全隔离 部分隔离 无隔离

4.3 Unidbg环境搭建

4.3.1 开发环境准备

首先,我们需要准备开发环境:

java 复制代码
┌─────────────────────────────────────────────────────────────────┐
│                    开发环境要求                                  │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  必需组件:                                                       │
│  ├── JDK 8+ (推荐JDK 11)                                        │
│  ├── Maven 3.6+                                                  │
│  ├── IDE (IntelliJ IDEA推荐)                                    │
│  └── Git                                                         │
│                                                                  │
│  可选组件:                                                       │
│  ├── IDA Pro (用于分析SO库)                                     │
│  ├── Ghidra (免费的反汇编工具)                                  │
│  └── jadx (用于反编译APK)                                       │
│                                                                  │
│  操作系统:                                                       │
│  ├── macOS (推荐)                                               │
│  ├── Linux                                                       │
│  └── Windows                                                     │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

安装JDK

bash 复制代码
# macOS (使用Homebrew)
brew install openjdk@11

# 设置环境变量
export JAVA_HOME=/usr/local/opt/openjdk@11
export PATH=$JAVA_HOME/bin:$PATH

# 验证安装
java -version
# openjdk version "11.0.x" ...

安装Maven

bash 复制代码
# macOS
brew install maven

# 验证安装
mvn -version
# Apache Maven 3.8.x ...

4.3.2 创建Maven项目

创建一个新的Maven项目来使用Unidbg:

bash 复制代码
# 创建项目目录
mkdir dw_mall_security_chain
cd dw_mall_security_chain

# 创建Maven标准目录结构
mkdir -p src/main/java/com/dreamworld
mkdir -p src/main/resources

pom.xml配置

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    
    <groupId>com.dreamworld</groupId>
    <artifactId>security-chain</artifactId>
    <version>1.0.0</version>
    <packaging>jar</packaging>
    
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    
    <!-- JitPack仓库(Unidbg托管在这里) -->
    <repositories>
        <repository>
            <id>jitpack.io</id>
            <url>https://jitpack.io</url>
        </repository>
    </repositories>
    
    <dependencies>
        <!-- Unidbg核心依赖 -->
        <dependency>
            <groupId>com.github.zhkl0228</groupId>
            <artifactId>unidbg-android</artifactId>
            <version>0.9.8</version>
        </dependency>
        
        <!-- HTTP客户端 -->
        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
            <version>4.9.3</version>
        </dependency>
        
        <!-- JSON处理 -->
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.8.9</version>
        </dependency>
        
        <!-- 日志 -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <version>1.7.32</version>
        </dependency>
    </dependencies>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>8</source>
                    <target>8</target>
                </configuration>
            </plugin>
            
            <!-- 可执行JAR打包 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>3.2.4</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <transformers>
                                <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                    <mainClass>com.dreamworld.Main</mainClass>
                                </transformer>
                            </transformers>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

4.3.3 准备资源文件

要使用Unidbg模拟执行SO库,我们需要准备以下资源:

bash 复制代码
┌─────────────────────────────────────────────────────────────────┐
│                    所需资源文件                                  │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  必需文件:                                                       │
│  ├── APK文件                                                     │
│  │   └── android-dreamworld-arm64-v8a-prod-v8.x.x.apk           │
│  │       (用于提供APK上下文,如签名信息)                        │
│  │                                                               │
│  ├── 目标SO库                                                    │
│  │   └── libSecurityCore.so                                      │
│  │       (包含签名函数的核心库)                                  │
│  │                                                               │
│  └── 依赖SO库                                                    │
│      └── libc++_shared.so                                        │
│          (C++标准库,很多SO库都依赖它)                          │
│                                                                  │
│  文件来源:                                                       │
│  - APK: 从应用商店下载或从设备提取                              │
│  - SO库: 从APK中解压 (lib/arm64-v8a/ 目录)                      │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

从APK中提取SO库

bash 复制代码
# 解压APK(APK本质上是ZIP文件)
unzip android-dreamworld-arm64-v8a-prod-v8.x.x.apk -d apk_extracted

# 查看lib目录结构
ls -la apk_extracted/lib/
# arm64-v8a/  (64位ARM)
# armeabi-v7a/ (32位ARM,可能没有)

# 复制需要的SO库
mkdir -p unilib
cp apk_extracted/lib/arm64-v8a/libSecurityCore.so unilib/
cp apk_extracted/lib/arm64-v8a/libc++_shared.so unilib/

4.3.4 项目目录结构

最终的项目结构如下:

bash 复制代码
dw_mall_security_chain/
├── pom.xml                              # Maven配置
├── unilib/                              # SO库目录
│   ├── libSecurityCore.so               # 目标SO库
│   └── libc++_shared.so                 # C++标准库
├── apk/                                 # APK目录
│   └── android-dreamworld-v8.x.x.apk    # 原始APK
└── src/main/java/com/dreamworld/
    ├── Main.java                        # 主入口
    ├── unidbg/
    │   └── UnidbgJNIWrapper.java        # Unidbg封装
    ├── security/
    │   └── SecurityStub.java            # 安全接口封装
    ├── network/
    │   └── ApiClient.java               # API客户端
    └── utils/
        └── LogUtils.java                # 日志工具

4.4 Unidbg核心API详解

4.4.1 创建模拟器实例

Unidbg提供了两种模拟器:32位和64位。根据目标SO库的架构选择:

java 复制代码
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;

// 创建64位ARM模拟器(ARM64/AArch64)
AndroidEmulator emulator = AndroidEmulatorBuilder.for64Bit()
    .setProcessName("com.dreamworld.app")  // 设置进程名
    .build();

// 或者创建32位ARM模拟器
// AndroidEmulator emulator = AndroidEmulatorBuilder.for32Bit().build();

模拟器配置选项

java 复制代码
AndroidEmulator emulator = AndroidEmulatorBuilder.for64Bit()
    .setProcessName("com.dreamworld.app")     // 进程名(影响/proc/self/cmdline)
    .addBackendFactory(new Unicorn2Factory(true))  // 使用Unicorn2后端
    .build();

// 获取内存管理器
Memory memory = emulator.getMemory();

// 设置库解析器(指定Android API级别)
memory.setLibraryResolver(new AndroidResolver(23));  // Android 6.0

4.4.2 创建DalvikVM

DalvikVM是Unidbg模拟的Android虚拟机,用于处理JNI调用:

java 复制代码
import com.github.unidbg.linux.android.dvm.VM;
import com.github.unidbg.linux.android.dvm.DalvikModule;

// 创建DalvikVM,传入APK文件
File apkFile = new File("apk/android-dreamworld-v8.x.x.apk");
VM vm = emulator.createDalvikVM(apkFile);

// 设置JNI回调处理器
vm.setJni(this);  // this需要实现Jni接口或继承AbstractJni

// 设置是否输出详细日志
vm.setVerbose(false);  // 生产环境建议关闭

为什么需要APK文件?

APK文件提供了以下信息:

  1. 签名信息:某些SO库会验证APK签名
  2. 包名 :用于getPackageName()等JNI调用
  3. 资源文件:某些SO库可能读取assets目录

4.4.3 加载SO库

java 复制代码
import com.github.unidbg.Module;

// 加载依赖库(如果有的话)
File libcxx = new File("unilib/libc++_shared.so");
if (libcxx.exists()) {
    vm.loadLibrary(libcxx, false);  // false表示不调用JNI_OnLoad
}

// 加载目标SO库
File targetLib = new File("unilib/libSecurityCore.so");
DalvikModule dm = vm.loadLibrary(targetLib, false);

// 获取Module对象(用于后续操作)
Module module = dm.getModule();

// 调用JNI_OnLoad(动态注册JNI方法)
dm.callJNI_OnLoad(emulator);

关于JNI_OnLoad

JNI_OnLoad是SO库的入口函数,通常在这里进行:

  • JNI方法的动态注册
  • 全局变量初始化
  • 某些检测代码的执行
c 复制代码
// JNI_OnLoad的典型实现
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
    JNIEnv *env;
    if ((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_6) != JNI_OK) {
        return -1;
    }
    
    // 动态注册JNI方法
    JNINativeMethod methods[] = {
        {"getPriId", "()Ljava/lang/String;", (void*)native_getPriId},
        {"rsaSign", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;", (void*)native_rsaSign},
        // ...
    };
    
    jclass clazz = (*env)->FindClass(env, "com/dreamworld/secutil/JNIWrapper");
    (*env)->RegisterNatives(env, clazz, methods, sizeof(methods)/sizeof(methods[0]));
    
    return JNI_VERSION_1_6;
}

4.4.4 解析JNI类

java 复制代码
import com.github.unidbg.linux.android.dvm.DvmClass;

// 解析JNI包装类
// 这个类名需要与SO库中注册的类名一致
DvmClass jniWrapperClass = vm.resolveClass("com/dreamworld/secutil/JNIWrapper");

如何找到正确的类名?

  1. 反编译APK:使用jadx查看Java代码中的native方法声明
  2. 分析SO库:使用IDA Pro查看JNI_OnLoad中的RegisterNatives调用
  3. 查看日志:开启Unidbg的verbose模式,观察类加载日志

4.4.5 调用JNI方法

这是最关键的部分------调用SO库中的JNI方法:

java 复制代码
import com.github.unidbg.linux.android.dvm.StringObject;
import com.github.unidbg.linux.android.dvm.DvmObject;

// 方法签名格式: 方法名(参数类型)返回类型
// 例如: getPriId()Ljava/lang/String;

// 调用无参数方法
StringObject result = jniWrapperClass.callStaticJniMethodObject(
    emulator, 
    "oGetPriId2b3c4d5e6f7a8b9c0d1e2f3a4b()Ljava/lang/String;"  // 混淆后的方法名
);
String priId = result.getValue();

// 调用带参数的方法
StringObject signResult = jniWrapperClass.callStaticJniMethodObject(
    emulator,
    "oRsaSign3c4d5e6f7a8b9c0d1e2f3a4b5c(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;",
    new StringObject(vm, ""),           // 第一个参数
    new StringObject(vm, "data_to_sign") // 第二个参数
);
String signature = signResult.getValue();

JNI类型签名对照表

Java类型 JNI签名 示例
void V ()V
boolean Z ()Z
byte B ()B
char C ()C
short S ()S
int I ()I
long J ()J
float F ()F
double D ()D
String Ljava/lang/String; ()Ljava/lang/String;
Object Ljava/lang/Object; ()Ljava/lang/Object;
int[] [I ()[I
String[] [Ljava/lang/String; ()[Ljava/lang/String;

4.5 实战:加载梦想世界APP的SO库

4.5.1 分析目标SO库

在开始编码之前,我们需要先分析目标SO库的结构。

使用IDA Pro打开libSecurityCore.so

复制代码
┌─────────────────────────────────────────────────────────────────┐
│                    libSecurityCore.so 分析结果                   │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  文件信息:                                                       │
│  ├── 架构: ARM64 (AArch64)                                       │
│  ├── 大小: 约2.5MB                                               │
│  └── 类型: 共享库 (.so)                                          │
│                                                                  │
│  导出函数:                                                       │
│  ├── JNI_OnLoad                    → 动态注册入口                │
│  └── Java_com_dreamworld_*         → 静态注册的JNI方法(如果有) │
│                                                                  │
│  关键发现:                                                       │
│  ├── 使用动态注册(RegisterNatives)                             │
│  ├── 方法名经过混淆(如oGetPriId2b3c4d5e6f7a8b9c0d1e2f3a4b)      │
│  ├── 包含反调试代码(在.init_array中)                          │
│  └── 依赖libc++_shared.so                                        │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

4.5.2 找到JNI方法签名

通过反编译APK,我们找到了JNI方法的声明:

java 复制代码
// 反编译得到的JNIWrapper类
package com.dreamworld.secutil;

public class JNIWrapper {
    static {
        System.loadLibrary("SecurityCore");
    }
    
    // 获取设备密钥ID
    public static native String oGetPriId2b3c4d5e6f7a8b9c0d1e2f3a4b();
    
    // RSA签名
    public static native String oRsaSign3c4d5e6f7a8b9c0d1e2f3a4b5c(String prefix, String data);
    
    // 获取密钥对
    public static native String oGetKeyPair6f7a8b9c0d1e2f3a4b5c6d();
    
    // 设置环境
    public static native void oSetEnv1a2b3c4d5e6f7a8b9c0d1e2f3a4(String env);
    
    // 解密密钥
    public static native String[] oFormatDK4d5e6f7a8b9c0d1e2f3a4b5c(
        String tempAesKey, String tempIv, String aesKey, String hmacKey);
    
    // HMAC签名
    public static native String oHmacSign5e6f7a8b9c0d1e2f3a4b5c6d(String key, String data);
    
    // RSA验签
    public static native boolean oRsaVerify7a8b9c0d1e2f3a4b5c6d7e(
        String publicKey, String signature, String data, String padding, String hash);
    
    // 加密
    public static native String oEncrypt8b9c0d1e2f3a4b5c6d7e8f9a(String data);
}

方法名混淆分析

这些方法名看起来像是MD5哈希值,这是一种常见的混淆手段:

  • 原始方法名被替换为哈希值
  • 增加逆向分析的难度
  • 但不影响功能调用

4.5.3 编写Unidbg封装类

现在我们来编写完整的Unidbg封装类:

java 复制代码
package com.dreamworld.unidbg;

import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Module;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.linux.android.dvm.array.ByteArray;
import com.github.unidbg.memory.Memory;

import java.io.File;
import java.security.MessageDigest;

/**
 * Unidbg JNI包装器
 * 用于在PC上模拟执行梦想世界APP的Native库
 */
public class UnidbgJNIWrapper extends AbstractJni {
    
    private static final String TAG = "UnidbgJNIWrapper";
    
    private final AndroidEmulator emulator;
    private final VM vm;
    private final Module module;
    private final DvmClass jniWrapperClass;
    
    // JNI方法签名常量
    private static final String METHOD_GET_PRI_ID = 
        "oGetPriId2b3c4d5e6f7a8b9c0d1e2f3a4b()Ljava/lang/String;";
    private static final String METHOD_RSA_SIGN = 
        "oRsaSign3c4d5e6f7a8b9c0d1e2f3a4b5c(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;";
    private static final String METHOD_GET_KEY_PAIR = 
        "oGetKeyPair6f7a8b9c0d1e2f3a4b5c6d()Ljava/lang/String;";
    private static final String METHOD_SET_ENVIRONMENT = 
        "oSetEnv1a2b3c4d5e6f7a8b9c0d1e2f3a4(Ljava/lang/String;)V";
    private static final String METHOD_FORMAT_DK = 
        "oFormatDK4d5e6f7a8b9c0d1e2f3a4b5c(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)[Ljava/lang/String;";
    private static final String METHOD_HMAC_SIGN = 
        "oHmacSign5e6f7a8b9c0d1e2f3a4b5c6d(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;";
    
    /**
     * 构造函数 - 初始化Unidbg环境
     */
    public UnidbgJNIWrapper(String apkPath, String libDir) {
        System.out.println("[" + TAG + "] 初始化Unidbg JNI环境");
        
        File apkFile = new File(apkPath);
        File libDirectory = new File(libDir);
        
        // 验证文件存在
        if (!apkFile.exists()) {
            throw new RuntimeException("APK文件不存在: " + apkPath);
        }
        
        // ========== 步骤1: 创建ARM64模拟器 ==========
        System.out.println("[" + TAG + "] 创建ARM64模拟器");
        emulator = AndroidEmulatorBuilder.for64Bit()
            .setProcessName("com.dreamworld.app")
            .build();
        
        // 配置内存
        Memory memory = emulator.getMemory();
        memory.setLibraryResolver(new AndroidResolver(23));  // Android 6.0
        
        // ========== 步骤2: 创建DalvikVM ==========
        System.out.println("[" + TAG + "] 创建DalvikVM");
        vm = emulator.createDalvikVM(apkFile);
        vm.setJni(this);      // 设置JNI回调处理器
        vm.setVerbose(false); // 关闭详细日志
        
        // ========== 步骤3: 加载依赖库 ==========
        File libcxx = new File(libDirectory, "libc++_shared.so");
        if (libcxx.exists()) {
            System.out.println("[" + TAG + "] 加载依赖库: libc++_shared.so");
            vm.loadLibrary(libcxx, false);
        }
        
        // ========== 步骤4: 加载目标SO库 ==========
        File targetLib = new File(libDirectory, "libSecurityCore.so");
        if (!targetLib.exists()) {
            throw new RuntimeException("SO文件不存在: " + targetLib.getAbsolutePath());
        }
        
        System.out.println("[" + TAG + "] 加载目标SO库: libSecurityCore.so");
        DalvikModule dm = vm.loadLibrary(targetLib, false);
        module = dm.getModule();
        
        // ========== 步骤5: 调用JNI_OnLoad ==========
        System.out.println("[" + TAG + "] 调用JNI_OnLoad进行动态注册");
        dm.callJNI_OnLoad(emulator);
        
        // ========== 步骤6: 解析JNI类 ==========
        System.out.println("[" + TAG + "] 解析JNI包装类");
        jniWrapperClass = vm.resolveClass("com/dreamworld/secutil/JNIWrapper");
        
        System.out.println("[" + TAG + "] Unidbg JNI环境初始化完成");
    }
    
    // ... 后续方法实现
}

4.5.4 实现JNI方法调用

继续完善UnidbgJNIWrapper类,实现各个JNI方法的调用:

java 复制代码
    /**
     * 获取设备私钥ID
     * 这是安全激活的第一步
     */
    public String getPriId() {
        System.out.println("[" + TAG + "] 调用getPriId()");
        
        try {
            StringObject result = jniWrapperClass.callStaticJniMethodObject(
                emulator, 
                METHOD_GET_PRI_ID
            );
            
            if (result == null) {
                throw new RuntimeException("getPriId返回null");
            }
            
            String priId = result.getValue();
            System.out.println("[" + TAG + "] PriId: " + priId);
            return priId;
            
        } catch (Exception e) {
            System.err.println("[" + TAG + "] getPriId失败: " + e.getMessage());
            throw new RuntimeException("getPriId失败", e);
        }
    }
    
    /**
     * RSA签名
     * 用于key-suite接口的请求签名
     * 
     * @param prefix 签名前缀(通常为空字符串)
     * @param payload 待签名数据
     * @return Base64编码的签名结果
     */
    public String rsaSign(String prefix, String payload) {
        System.out.println("[" + TAG + "] 调用rsaSign()");
        System.out.println("[" + TAG + "] 待签名数据: " + payload);
        
        try {
            StringObject result = jniWrapperClass.callStaticJniMethodObject(
                emulator,
                METHOD_RSA_SIGN,
                new StringObject(vm, prefix),
                new StringObject(vm, payload)
            );
            
            if (result == null) {
                throw new RuntimeException("rsaSign返回null");
            }
            
            String signature = result.getValue();
            System.out.println("[" + TAG + "] 签名长度: " + signature.length());
            return signature;
            
        } catch (Exception e) {
            System.err.println("[" + TAG + "] rsaSign失败: " + e.getMessage());
            throw new RuntimeException("rsaSign失败", e);
        }
    }
    
    /**
     * 设置环境
     * 必须在获取密钥之前调用!
     * 
     * @param env 环境标识 (0=测试, 1=生产)
     */
    public void setEnvironment(int env) {
        System.out.println("[" + TAG + "] 调用setEnvironment(" + env + ")");
        
        try {
            jniWrapperClass.callStaticJniMethod(
                emulator, 
                METHOD_SET_ENVIRONMENT,
                new StringObject(vm, String.valueOf(env))
            );
            System.out.println("[" + TAG + "] 环境设置完成");
        } catch (Exception e) {
            System.err.println("[" + TAG + "] setEnvironment失败: " + e.getMessage());
        }
    }
    
    /**
     * 解密密钥
     * 用于解密服务器返回的加密密钥
     * 
     * @param tempAesKey RSA加密的临时AES密钥
     * @param tempIv 临时IV
     * @param aesKey 加密的AES密钥
     * @param hmacKey 加密的HMAC密钥
     * @return [解密后的AES密钥, 解密后的HMAC密钥]
     */
    public String[] formatDK(String tempAesKey, String tempIv, 
                             String aesKey, String hmacKey) {
        System.out.println("[" + TAG + "] 调用formatDK()");
        
        try {
            DvmObject<?> result = jniWrapperClass.callStaticJniMethodObject(
                emulator, 
                METHOD_FORMAT_DK,
                new StringObject(vm, tempAesKey),
                new StringObject(vm, tempIv),
                new StringObject(vm, aesKey != null ? aesKey : ""),
                new StringObject(vm, hmacKey != null ? hmacKey : "")
            );
            
            if (result == null) {
                System.err.println("[" + TAG + "] formatDK返回null");
                return null;
            }
            
            // 解析返回的字符串数组
            if (result instanceof com.github.unidbg.linux.android.dvm.array.ArrayObject) {
                com.github.unidbg.linux.android.dvm.array.ArrayObject arrayObj = 
                    (com.github.unidbg.linux.android.dvm.array.ArrayObject) result;
                
                int length = arrayObj.length();
                String[] keys = new String[length];
                
                for (int i = 0; i < length; i++) {
                    DvmObject<?> element = arrayObj.getValue()[i];
                    if (element instanceof StringObject) {
                        keys[i] = ((StringObject) element).getValue();
                    }
                }
                
                System.out.println("[" + TAG + "] formatDK成功,返回" + length + "个密钥");
                return keys;
            }
            
            return null;
            
        } catch (Exception e) {
            System.err.println("[" + TAG + "] formatDK失败: " + e.getMessage());
            return null;
        }
    }
    
    /**
     * HMAC签名
     * 用于API请求的签名
     * 
     * @param hacKey HMAC密钥
     * @param stringToSign 待签名字符串
     * @return 签名结果
     */
    public String hmacSign(String hacKey, String stringToSign) {
        System.out.println("[" + TAG + "] 调用hmacSign()");
        
        try {
            StringObject result = jniWrapperClass.callStaticJniMethodObject(
                emulator,
                METHOD_HMAC_SIGN,
                new StringObject(vm, hacKey),
                new StringObject(vm, stringToSign)
            );
            
            if (result == null) {
                return null;
            }
            
            String signature = result.getValue();
            System.out.println("[" + TAG + "] HMAC签名长度: " + signature.length());
            return signature;
            
        } catch (Exception e) {
            System.err.println("[" + TAG + "] hmacSign失败: " + e.getMessage());
            return null;
        }
    }
    
    /**
     * 关闭Unidbg环境
     * 释放资源
     */
    public void close() {
        System.out.println("[" + TAG + "] 关闭Unidbg环境");
        if (emulator != null) {
            try {
                emulator.close();
            } catch (Exception e) {
                System.err.println("[" + TAG + "] 关闭失败: " + e.getMessage());
            }
        }
    }

4.5.5 处理JNI回调

SO库在执行过程中可能会调用Java方法(JNI回调),我们需要实现这些回调:

java 复制代码
    // ========== JNI回调方法实现 ==========
    
    /**
     * 处理静态方法调用
     */
    @Override
    public DvmObject<?> callStaticObjectMethod(BaseVM vm, DvmClass dvmClass, 
                                                String signature, VarArg varArg) {
        // 处理ActivityThread.currentActivityThread()
        if ("android/app/ActivityThread->currentActivityThread()Landroid/app/ActivityThread;"
                .equals(signature)) {
            return dvmClass.newObject(null);
        }
        
        // 处理MessageDigest.getInstance()
        if ("java/security/MessageDigest->getInstance(Ljava/lang/String;)Ljava/security/MessageDigest;"
                .equals(signature)) {
            String algorithm = varArg.getObjectArg(0).getValue().toString();
            try {
                MessageDigest digest = MessageDigest.getInstance(algorithm);
                return vm.resolveClass("java/security/MessageDigest").newObject(digest);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
        
        // 其他未处理的调用交给父类
        return super.callStaticObjectMethod(vm, dvmClass, signature, varArg);
    }
    
    /**
     * 处理实例方法调用
     */
    @Override
    public DvmObject<?> callObjectMethod(BaseVM vm, DvmObject<?> dvmObject, 
                                          String signature, VarArg varArg) {
        // 处理ActivityThread.getApplication()
        if ("android/app/ActivityThread->getApplication()Landroid/app/Application;"
                .equals(signature)) {
            return vm.resolveClass("android/app/Application").newObject(null);
        }
        
        // 处理Application.getPackageManager()
        if ("android/app/Application->getPackageManager()Landroid/content/pm/PackageManager;"
                .equals(signature)) {
            return vm.resolveClass("android/content/pm/PackageManager").newObject(null);
        }
        
        // 处理MessageDigest.digest()
        if ("java/security/MessageDigest->digest()[B".equals(signature)) {
            MessageDigest digest = (MessageDigest) dvmObject.getValue();
            return new ByteArray(vm, digest.digest());
        }
        
        // 处理MessageDigest.digest(byte[])
        if ("java/security/MessageDigest->digest([B)[B".equals(signature)) {
            MessageDigest digest = (MessageDigest) dvmObject.getValue();
            ByteArray array = varArg.getObjectArg(0);
            return new ByteArray(vm, digest.digest(array.getValue()));
        }
        
        return super.callObjectMethod(vm, dvmObject, signature, varArg);
    }
    
    /**
     * 处理void方法调用
     */
    @Override
    public void callVoidMethod(BaseVM vm, DvmObject<?> dvmObject, 
                               String signature, VarArg varArg) {
        // 处理MessageDigest.update()
        if ("java/security/MessageDigest->update([B)V".equals(signature)) {
            MessageDigest digest = (MessageDigest) dvmObject.getValue();
            ByteArray array = varArg.getObjectArg(0);
            digest.update(array.getValue());
            return;
        }
        
        super.callVoidMethod(vm, dvmObject, signature, varArg);
    }
}

为什么需要实现这些回调?

SO库中的Native代码可能会通过JNI调用Java方法,例如:

  • 获取Application上下文
  • 调用Java的加密API
  • 读取系统属性

如果不实现这些回调,Unidbg会抛出UnsupportedOperationException


4.6 第一次运行测试

4.6.1 编写测试代码

java 复制代码
package com.dreamworld;

import com.dreamworld.unidbg.UnidbgJNIWrapper;

public class Main {
    
    private static final String APK_PATH = "apk/android-dreamworld-v8.x.x.apk";
    private static final String LIB_DIR = "unilib";
    
    public static void main(String[] args) {
        System.out.println("╔══════════════════════════════════════════════════════════════╗");
        System.out.println("║     梦想世界APP安全激活调用链 - Unidbg测试                   ║");
        System.out.println("╚══════════════════════════════════════════════════════════════╝");
        System.out.println();
        
        UnidbgJNIWrapper wrapper = null;
        
        try {
            // 步骤1: 初始化Unidbg环境
            System.out.println(">>> 步骤1: 初始化Unidbg环境");
            wrapper = new UnidbgJNIWrapper(APK_PATH, LIB_DIR);
            System.out.println("✓ Unidbg环境初始化成功");
            System.out.println();
            
            // 步骤2: 设置生产环境(重要!)
            System.out.println(">>> 步骤2: 设置生产环境");
            wrapper.setEnvironment(1);  // 1 = 生产环境
            System.out.println("✓ 环境设置完成");
            System.out.println();
            
            // 步骤3: 获取设备密钥ID
            System.out.println(">>> 步骤3: 获取设备密钥ID");
            String priId = wrapper.getPriId();
            System.out.println("✓ PriId: " + priId);
            System.out.println();
            
            // 步骤4: 测试RSA签名
            System.out.println(">>> 步骤4: 测试RSA签名");
            String testData = "test:data:for:signing";
            String signature = wrapper.rsaSign("", testData);
            System.out.println("✓ 签名成功,长度: " + signature.length());
            System.out.println("  签名前20字符: " + signature.substring(0, 20) + "...");
            System.out.println();
            
            System.out.println("╔══════════════════════════════════════════════════════════════╗");
            System.out.println("║                    测试完成!                                ║");
            System.out.println("╚══════════════════════════════════════════════════════════════╝");
            
        } catch (Exception e) {
            System.err.println("测试失败: " + e.getMessage());
            e.printStackTrace();
        } finally {
            if (wrapper != null) {
                wrapper.close();
            }
        }
    }
}

4.6.2 运行测试

bash 复制代码
# 编译项目
mvn clean compile

# 运行测试
mvn exec:java -Dexec.mainClass="com.dreamworld.Main" -q

预期输出

csharp 复制代码
╔══════════════════════════════════════════════════════════════╗
║     梦想世界APP安全激活调用链 - Unidbg测试                   ║
╚══════════════════════════════════════════════════════════════╝

>>> 步骤1: 初始化Unidbg环境
[UnidbgJNIWrapper] 初始化Unidbg JNI环境
[UnidbgJNIWrapper] 创建ARM64模拟器
[UnidbgJNIWrapper] 创建DalvikVM
[UnidbgJNIWrapper] 加载依赖库: libc++_shared.so
[UnidbgJNIWrapper] 加载目标SO库: libSecurityCore.so
[UnidbgJNIWrapper] 调用JNI_OnLoad进行动态注册
[UnidbgJNIWrapper] 解析JNI包装类
[UnidbgJNIWrapper] Unidbg JNI环境初始化完成
✓ Unidbg环境初始化成功

>>> 步骤2: 设置生产环境
[UnidbgJNIWrapper] 调用setEnvironment(1)
[UnidbgJNIWrapper] 环境设置完成
✓ 环境设置完成

>>> 步骤3: 获取设备密钥ID
[UnidbgJNIWrapper] 调用getPriId()
[UnidbgJNIWrapper] PriId: f1e2d3c4b5a6978869574a3b2c1d0e0f
✓ PriId: f1e2d3c4b5a6978869574a3b2c1d0e0f

>>> 步骤4: 测试RSA签名
[UnidbgJNIWrapper] 调用rsaSign()
[UnidbgJNIWrapper] 待签名数据: test:data:for:signing
[UnidbgJNIWrapper] 签名长度: 344
✓ 签名成功,长度: 344
  签名前20字符: RjOEWKNX2lfFXHlcN1...

╔══════════════════════════════════════════════════════════════╗
║                    测试完成!                                ║
╚══════════════════════════════════════════════════════════════╝

成功了! SO库成功加载,JNI方法成功调用!

4.6.3 关键发现

通过这次测试,我们发现了几个重要的点:

scss 复制代码
┌─────────────────────────────────────────────────────────────────┐
│                    关键发现                                      │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  1. 环境设置的重要性                                             │
│     - 必须先调用setEnvironment(1)设置生产环境                   │
│     - 不同环境返回不同的密钥ID                                   │
│       · env=0 (测试): priId = a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6  │
│       · env=1 (生产): priId = f1e2d3c4b5a6978869574a3b2c1d0e0f  │
│                                                                  │
│  2. 方法调用顺序                                                 │
│     - setEnvironment() → getPriId() → rsaSign()                 │
│     - 顺序错误会导致获取错误的密钥ID                            │
│                                                                  │
│  3. 签名格式                                                     │
│     - RSA签名结果是Base64编码                                    │
│     - 长度约344字符(2048位RSA密钥)                            │
│                                                                  │
│  4. 无检测触发                                                   │
│     - SO库成功加载,没有崩溃                                     │
│     - 说明Unidbg成功绕过了检测机制                              │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

4.7 深入理解:为什么检测没有触发?

4.7.1 分析SO库的检测代码

让我们用IDA Pro分析一下SO库中的检测代码:

c 复制代码
// .init_array 中的反调试初始化(伪代码)
void __attribute__((constructor)) anti_debug_init() {
    // 检测Frida
    if (check_frida_port() || check_frida_process()) {
        raise(SIGSEGV);  // 触发崩溃
    }
    
    // 检测调试器
    if (check_ptrace() || check_tracerpid()) {
        raise(SIGSEGV);
    }
    
    // 启动后台检测线程
    pthread_create(&detection_thread, NULL, continuous_detection, NULL);
}

// Frida端口检测
int check_frida_port() {
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    addr.sin_port = htons(27042);  // Frida默认端口
    
    if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) == 0) {
        close(sock);
        return 1;  // 检测到Frida
    }
    close(sock);
    return 0;
}

// 进程检测
int check_frida_process() {
    DIR *dir = opendir("/proc");
    struct dirent *entry;
    
    while ((entry = readdir(dir)) != NULL) {
        char cmdline_path[256];
        snprintf(cmdline_path, sizeof(cmdline_path), "/proc/%s/cmdline", entry->d_name);
        
        FILE *f = fopen(cmdline_path, "r");
        if (f) {
            char cmdline[256];
            fgets(cmdline, sizeof(cmdline), f);
            fclose(f);
            
            if (strstr(cmdline, "frida") || strstr(cmdline, "gum-js-loop")) {
                return 1;  // 检测到Frida
            }
        }
    }
    closedir(dir);
    return 0;
}

4.7.2 Unidbg如何绕过这些检测?

css 复制代码
┌─────────────────────────────────────────────────────────────────┐
│                    Unidbg绕过检测的原理                          │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  检测方法1: Frida端口检测                                        │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  SO库代码: connect(sock, "127.0.0.1:27042")             │    │
│  │       ↓                                                  │    │
│  │  Unidbg: 模拟socket系统调用                              │    │
│  │       ↓                                                  │    │
│  │  返回: ECONNREFUSED (连接被拒绝)                         │    │
│  │       ↓                                                  │    │
│  │  结果: 检测失败,认为没有Frida                           │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                  │
│  检测方法2: /proc目录扫描                                        │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  SO库代码: opendir("/proc")                              │    │
│  │       ↓                                                  │    │
│  │  Unidbg: 模拟文件系统调用                                │    │
│  │       ↓                                                  │    │
│  │  返回: 空目录或模拟的进程列表(不含frida)               │    │
│  │       ↓                                                  │    │
│  │  结果: 检测失败,认为没有Frida进程                       │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                  │
│  检测方法3: ptrace检测                                           │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  SO库代码: ptrace(PTRACE_TRACEME, 0, 0, 0)              │    │
│  │       ↓                                                  │    │
│  │  Unidbg: 模拟ptrace系统调用                              │    │
│  │       ↓                                                  │    │
│  │  返回: 0 (成功)                                          │    │
│  │       ↓                                                  │    │
│  │  结果: 检测失败,认为没有被调试                          │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                  │
│  核心原理:                                                       │
│  Unidbg完全控制了所有系统调用的返回值,                         │
│  可以让检测代码"看到"一个干净的环境。                           │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

4.7.3 .init_array的处理

你可能会问:.init_array中的检测代码不是在库加载时就执行了吗?

答案是:是的,但Unidbg可以控制它的执行

java 复制代码
// 加载SO库时,第二个参数控制是否执行.init_array
DalvikModule dm = vm.loadLibrary(targetLib, false);  // false = 不自动执行

// 如果需要,可以手动执行
// dm.callInit(emulator);  // 执行.init_array

// 调用JNI_OnLoad(这是必须的,用于动态注册)
dm.callJNI_OnLoad(emulator);

在我们的案例中,即使.init_array执行了,检测代码也会因为Unidbg的系统调用模拟而失败。


4.8 常见问题与解决方案

4.8.1 UnsupportedOperationException

问题

css 复制代码
com.github.unidbg.linux.android.dvm.UnsupportedOperationException: 
    android/content/Context->getSharedPreferences(Ljava/lang/String;I)Landroid/content/SharedPreferences;

原因:SO库调用了未实现的JNI方法。

解决方案:在AbstractJni子类中实现该方法:

java 复制代码
@Override
public DvmObject<?> callObjectMethod(BaseVM vm, DvmObject<?> dvmObject, 
                                      String signature, VarArg varArg) {
    if ("android/content/Context->getSharedPreferences(Ljava/lang/String;I)Landroid/content/SharedPreferences;"
            .equals(signature)) {
        // 返回一个模拟的SharedPreferences对象
        return vm.resolveClass("android/content/SharedPreferences").newObject(new HashMap<>());
    }
    return super.callObjectMethod(vm, dvmObject, signature, varArg);
}

4.8.2 找不到SO库依赖

问题

lua 复制代码
java.lang.IllegalStateException: load library failed: libc++_shared.so

原因:缺少依赖的SO库。

解决方案

  1. 从APK中提取所有需要的SO库
  2. 按正确顺序加载(先加载依赖库)
java 复制代码
// 先加载依赖库
vm.loadLibrary(new File(libDir, "libc++_shared.so"), false);
// 再加载目标库
vm.loadLibrary(new File(libDir, "libSecurityCore.so"), false);

4.8.3 JNI方法找不到

问题

makefile 复制代码
java.lang.NoSuchMethodError: getPriId

原因

  1. 类名不正确
  2. 方法签名不正确
  3. JNI_OnLoad未调用(动态注册未执行)

解决方案

  1. 确认类名与SO库中注册的一致
  2. 确认方法签名正确(包括参数类型和返回类型)
  3. 确保调用了dm.callJNI_OnLoad(emulator)

4.8.4 内存不足

问题

makefile 复制代码
java.lang.OutOfMemoryError: Java heap space

原因:Unidbg模拟器占用大量内存。

解决方案:增加JVM堆内存:

bash 复制代码
# 运行时指定内存
mvn exec:java -Dexec.mainClass="com.dreamworld.Main" -Dexec.args="" \
    -Dexec.vmArgs="-Xmx2g"

# 或在pom.xml中配置
<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>exec-maven-plugin</artifactId>
    <configuration>
        <mainClass>com.dreamworld.Main</mainClass>
        <arguments>
            <argument>-Xmx2g</argument>
        </arguments>
    </configuration>
</plugin>

4.8.5 ARM64 vs ARM32

问题:加载32位SO库到64位模拟器(或反之)。

解决方案:确保模拟器架构与SO库架构匹配:

java 复制代码
// 检查SO库架构
// 使用file命令
// $ file libSecurityCore.so
// libSecurityCore.so: ELF 64-bit LSB shared object, ARM aarch64

// 64位SO库使用64位模拟器
AndroidEmulator emulator = AndroidEmulatorBuilder.for64Bit().build();

// 32位SO库使用32位模拟器
// AndroidEmulator emulator = AndroidEmulatorBuilder.for32Bit().build();

4.9 调试技巧

4.9.1 开启详细日志

java 复制代码
// 开启DalvikVM详细日志
vm.setVerbose(true);

// 开启Unidbg调试日志
emulator.traceCode();  // 跟踪所有执行的指令
emulator.traceRead();  // 跟踪内存读取
emulator.traceWrite(); // 跟踪内存写入

4.9.2 Hook Native函数

java 复制代码
// Hook指定地址的函数
emulator.attach().addBreakPoint(module.base + 0x12345, new BreakPointCallback() {
    @Override
    public boolean onHit(Emulator<?> emulator, long address) {
        // 打印寄存器状态
        RegisterContext context = emulator.getContext();
        System.out.println("X0 = " + context.getLongArg(0));
        System.out.println("X1 = " + context.getLongArg(1));
        return true;  // 继续执行
    }
});

4.9.3 使用IDA Pro配合调试

  1. 在IDA Pro中找到目标函数的偏移地址
  2. 在Unidbg中设置断点
  3. 观察寄存器和内存状态
java 复制代码
// 假设IDA显示函数地址为0x12345
long functionOffset = 0x12345;
long actualAddress = module.base + functionOffset;

emulator.attach().addBreakPoint(actualAddress, (emulator, address) -> {
    System.out.println("Hit breakpoint at: 0x" + Long.toHexString(address));
    
    // 打印调用栈
    emulator.getUnwinder().unwind();
    
    return true;
});

4.10 性能优化

4.10.1 复用模拟器实例

创建模拟器实例是昂贵的操作,应该复用:

java 复制代码
public class UnidbgPool {
    private static UnidbgJNIWrapper instance;
    
    public static synchronized UnidbgJNIWrapper getInstance() {
        if (instance == null) {
            instance = new UnidbgJNIWrapper(APK_PATH, LIB_DIR);
        }
        return instance;
    }
    
    public static synchronized void close() {
        if (instance != null) {
            instance.close();
            instance = null;
        }
    }
}

4.10.2 减少不必要的日志

java 复制代码
// 生产环境关闭详细日志
vm.setVerbose(false);

// 使用SLF4J控制日志级别
// 在simplelogger.properties中配置
org.slf4j.simpleLogger.defaultLogLevel=warn

4.10.3 内存管理

java 复制代码
// 及时释放不需要的对象
// Unidbg会自动管理大部分内存,但大对象需要注意

// 如果需要多次调用,考虑重置VM状态而不是重新创建
// (目前Unidbg不直接支持,需要重新创建实例)

4.11 本章小结

4.11.1 核心知识点

yaml 复制代码
┌─────────────────────────────────────────────────────────────────┐
│                    本章核心知识点                                │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  1. Unidbg原理                                                   │
│     - 基于Unicorn引擎的CPU模拟                                   │
│     - 模拟Android/iOS的系统调用                                  │
│     - 模拟JNI环境                                                │
│     - 可以绕过大部分检测机制                                     │
│                                                                  │
│  2. 环境搭建                                                     │
│     - JDK 8+ + Maven                                             │
│     - 从JitPack引入unidbg-android依赖                           │
│     - 准备APK和SO库文件                                          │
│                                                                  │
│  3. 核心API                                                      │
│     - AndroidEmulatorBuilder: 创建模拟器                         │
│     - VM: DalvikVM虚拟机                                         │
│     - DalvikModule: SO库模块                                     │
│     - DvmClass: JNI类                                            │
│     - callStaticJniMethodObject: 调用JNI方法                     │
│                                                                  │
│  4. JNI回调处理                                                  │
│     - 继承AbstractJni                                            │
│     - 实现callStaticObjectMethod等方法                           │
│     - 处理SO库对Java方法的调用                                   │
│                                                                  │
│  5. 调试技巧                                                     │
│     - 开启verbose日志                                            │
│     - 使用断点和Hook                                             │
│     - 配合IDA Pro分析                                            │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

4.11.2 实战成果

通过本章的学习和实践,我们成功实现了:

  1. 搭建Unidbg开发环境
  2. 加载梦想世界APP的SO库
  3. 调用getPriId()获取设备密钥ID
  4. 调用rsaSign()进行RSA签名
  5. 绕过了APP的所有检测机制

这为后续的完整调用链实现奠定了基础。

4.11.3 下一步

在下一章中,我们将:

  1. 深入分析JNI调用链:理解每个方法的作用和调用顺序
  2. 实现完整的安全激活流程:从获取密钥到API调用
  3. 处理服务器响应:解密返回的密钥数据
  4. 构建可用的数据获取系统:实现商品数据抓取

本章思考题

  1. 为什么Unidbg能绕过Frida检测? 如果APP在检测代码中使用了更复杂的方法(如检测Unicorn引擎的特征),Unidbg还能绕过吗?

  2. JNI_OnLoad和.init_array的区别是什么? 为什么有些检测代码放在.init_array中而不是JNI_OnLoad中?

  3. 如果SO库使用了OLLVM混淆,Unidbg还能正常工作吗?为什么?

  4. 在生产环境中使用Unidbg有什么注意事项? 如何保证稳定性和性能?


章节附录

A. Unidbg项目结构

bash 复制代码
unidbg/
├── unidbg-android/          # Android模拟支持
│   ├── src/main/java/
│   │   └── com/github/unidbg/
│   │       ├── linux/android/
│   │       │   ├── dvm/     # DalvikVM实现
│   │       │   └── AndroidEmulatorBuilder.java
│   │       └── ...
│   └── src/main/resources/
│       └── android/sdk/     # Android SDK文件
├── unidbg-ios/              # iOS模拟支持
├── unidbg-api/              # 核心API
└── backend/
    ├── unicorn/             # Unicorn后端
    └── dynarmic/            # Dynarmic后端

B. 常用JNI签名速查

java 复制代码
// 基本类型
"()V"                    // void method()
"()I"                    // int method()
"()J"                    // long method()
"()Z"                    // boolean method()
"()Ljava/lang/String;"   // String method()

// 带参数
"(I)V"                   // void method(int)
"(II)I"                  // int method(int, int)
"(Ljava/lang/String;)V"  // void method(String)
"(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;"  
                         // String method(String, String)

// 数组
"()[B"                   // byte[] method()
"()[Ljava/lang/String;"  // String[] method()
"([B)V"                  // void method(byte[])

C. 参考资源

资源 链接
Unidbg GitHub github.com/zhkl0228/un...
Unicorn Engine www.unicorn-engine.org/
JNI规范 docs.oracle.com/javase/8/do...
ARM64指令集 developer.arm.com/documentati...

本章完

相关推荐
yintele2 小时前
类人机器人BMS的静电防护
网络·安全·机器人
●VON2 小时前
AI 保险机制:为智能时代的不确定性兜底
人工智能·学习·安全·制造·von
Bruce_Liuxiaowei3 小时前
内网探测常用技术方法整理
网络·安全·网络安全
乾元4 小时前
如何把 CCIE / HCIE 的实验案例改造成 AI 驱动的工程项目——从“实验室能力”到“可交付系统”的完整迁移路径
大数据·运维·网络·人工智能·深度学习·安全·机器学习
GC_ESD4 小时前
为芯片穿上“防弹衣”:PERC如何守护先进制程下的ESD安全
安全
acrelgxy4 小时前
从被动响应到主动预警:安科瑞门店电气安全全景管控方案
安全·电能管理系统·电力监控系统·智能电力仪表
南行*4 小时前
MSF安全开发
安全·网络安全·系统安全·ruby
dalerkd17 小时前
忙里偷闲叙-谈谈最近两年
网络·安全·web安全
牛三金18 小时前
匿踪查询沿革-Private Information Retrieval(PIR)
算法·安全