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

103 阅读24分钟

第四章: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库模拟执行。

┌─────────────────────────────────────────────────────────────────┐
│                    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的区别:

传统方法的执行流程

┌─────────────────────────────────────────────────────────────────┐
│                    传统方法执行流程                              │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  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的技术架构

┌─────────────────────────────────────────────────────────────────┐
│                    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模拟功能。

# 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?

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

4.3 Unidbg环境搭建

4.3.1 开发环境准备

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

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

安装JDK

# 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

# macOS
brew install maven

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

4.3.2 创建Maven项目

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

# 创建项目目录
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 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库,我们需要准备以下资源:

┌─────────────────────────────────────────────────────────────────┐
│                    所需资源文件                                  │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  必需文件:                                                       │
│  ├── 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库

# 解压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 项目目录结构

最终的项目结构如下:

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库的架构选择:

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();

模拟器配置选项

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调用:

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库

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方法的动态注册
  • 全局变量初始化
  • 某些检测代码的执行
// 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类

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方法:

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签名示例
voidV()V
booleanZ()Z
byteB()B
charC()C
shortS()S
intI()I
longJ()J
floatF()F
doubleD()D
StringLjava/lang/String;()Ljava/lang/String;
ObjectLjava/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方法的声明:

// 反编译得到的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封装类:

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方法的调用:

    /**
     * 获取设备私钥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回调),我们需要实现这些回调:

    // ========== 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 编写测试代码

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 运行测试

# 编译项目
mvn clean compile

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

预期输出

╔══════════════════════════════════════════════════════════════╗
║     梦想世界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 关键发现

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

┌─────────────────────────────────────────────────────────────────┐
│                    关键发现                                      │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  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库中的检测代码:

// .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如何绕过这些检测?

┌─────────────────────────────────────────────────────────────────┐
│                    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可以控制它的执行

// 加载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

问题

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

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

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

@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库依赖

问题

java.lang.IllegalStateException: load library failed: libc++_shared.so

原因:缺少依赖的SO库。

解决方案

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

4.8.3 JNI方法找不到

问题

java.lang.NoSuchMethodError: getPriId

原因

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

解决方案

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

4.8.4 内存不足

问题

java.lang.OutOfMemoryError: Java heap space

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

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

# 运行时指定内存
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库架构匹配:

// 检查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 开启详细日志

// 开启DalvikVM详细日志
vm.setVerbose(true);

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

4.9.2 Hook Native函数

// 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. 观察寄存器和内存状态
// 假设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 复用模拟器实例

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

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 减少不必要的日志

// 生产环境关闭详细日志
vm.setVerbose(false);

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

4.10.3 内存管理

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

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

4.11 本章小结

4.11.1 核心知识点

┌─────────────────────────────────────────────────────────────────┐
                    本章核心知识点                                
├─────────────────────────────────────────────────────────────────┤
                                                                  
  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项目结构

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签名速查

// 基本类型
"()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 GitHubgithub.com/zhkl0228/un…
Unicorn Enginewww.unicorn-engine.org/
JNI规范docs.oracle.com/javase/8/do…
ARM64指令集developer.arm.com/documentati…

本章完