EasyProtector Environment Detection Analysis

EasyProtector 环境检测机制深度分析报告

目录

  1. 项目概述
  2. 整体架构
  3. 模拟器检测机制 (EmulatorCheckUtil)
  4. 虚拟环境/多开检测机制 (VirtualApkCheckUtil)
  5. 安全检测机制 (SecurityCheckUtil)
  6. Native层反调试检测 (antitrace.cpp)
  7. 无障碍服务检测 (AccessibilityServicesCheckUtil)
  8. 辅助工具类
  9. 应用层实现与配置
  10. 项目构建与编译配置
  11. 检测流程图
  12. 检测结果判定逻辑
  13. 总结

1. 项目概述

1.1 项目简介

EasyProtector 是一个 Android 安全检测库,用于检测应用运行环境的安全性。主要功能包括:

  • 模拟器检测:识别应用是否运行在 Android 模拟器中
  • 多开/虚拟环境检测:检测应用是否被多开软件或虚拟机运行
  • Root 检测:检测设备是否已 Root
  • Xposed 框架检测:检测是否存在 Xposed Hook 框架
  • 调试检测:检测应用是否正在被调试
  • 进程追踪检测:检测是否有进程在追踪当前应用

1.2 核心类结构

com.lahm.library/
├── EasyProtectorLib.java          # 对外暴露的统一入口类
├── EmulatorCheckUtil.java         # 模拟器检测工具类
├── VirtualApkCheckUtil.java       # 虚拟环境/多开检测工具类
├── SecurityCheckUtil.java         # 安全检测工具类(Root/Debug/Xposed等)
├── AccessibilityServicesCheckUtil.java  # 无障碍服务检测
├── NDKUtil.java                   # Native库加载工具类
├── CommandUtil.java               # Shell命令执行工具类
├── CheckResult.java               # 检测结果封装类
├── EmulatorCheckCallback.java     # 模拟器检测回调接口
├── VirtualCheckCallback.java      # 虚拟环境检测回调接口
└── LibLoader.java                 # 库加载器接口

1.3 依赖关系

EasyProtectorLib (入口)
    ├── SecurityCheckUtil
    │       └── CommandUtil
    ├── EmulatorCheckUtil
    │       └── CommandUtil
    ├── VirtualApkCheckUtil
    │       └── CommandUtil
    ├── AccessibilityServicesCheckUtil
    └── NDKUtil
            └── antitrace.so (Native层)

2. 整体架构

2.1 入口类 EasyProtectorLib

文件位置: library/src/main/java/com/lahm/library/EasyProtectorLib.java

该类是整个库的统一入口,提供了以下静态方法:

方法名 功能 调用的工具类
checkSignature(Context) 获取应用签名 SecurityCheckUtil
checkIsDebug(Context) 检测Debug模式 SecurityCheckUtil
checkIsPortUsing(host, port) 检测端口占用 SecurityCheckUtil
checkIsRoot() 检测Root权限 SecurityCheckUtil
checkIsXposedExist() 检测Xposed框架 SecurityCheckUtil
checkXposedExistAndDisableIt() 尝试关闭Xposed SecurityCheckUtil
checkHasLoadSO(soName) 检测SO库加载 SecurityCheckUtil
checkIsBeingTracedByJava() Java层追踪检测 SecurityCheckUtil
checkIsBeingTracedByC() Native层追踪检测 NDKUtil
checkIsRunningInEmulator(Context, Callback) 模拟器检测 EmulatorCheckUtil
checkIsRunningInVirtualApk(msg, Callback) 多开环境检测 VirtualApkCheckUtil

2.2 检测结果类 CheckResult

文件位置: library/src/main/java/com/lahm/library/CheckResult.java

public class CheckResult {
    public static final int RESULT_MAYBE_EMULATOR = 0;  // 可能是模拟器
    public static final int RESULT_EMULATOR = 1;         // 确定是模拟器
    public static final int RESULT_UNKNOWN = 2;          // 可能是真机

    public int result;   // 检测结果状态码
    public String value; // 检测到的具体值
}

3. 模拟器检测机制 (EmulatorCheckUtil)

文件位置: library/src/main/java/com/lahm/library/EmulatorCheckUtil.java

模拟器检测是该库最核心和复杂的功能,采用多维度特征检测 + 嫌疑值累计的策略。

3.1 检测总体流程

主入口方法 readSysProperty(Context, EmulatorCheckCallback) 执行以下检测步骤:

public boolean readSysProperty(Context context, EmulatorCheckCallback callback) {
    int suspectCount = 0;  // 嫌疑值计数器

    // 1. 检测硬件名称
    // 2. 检测渠道(flavor)
    // 3. 检测设备型号
    // 4. 检测硬件制造商
    // 5. 检测主板名称
    // 6. 检测主板平台
    // 7. 检测基带信息
    // 8. 检测传感器数量
    // 9. 检测已安装应用数量
    // 10. 检测相机闪光灯支持
    // 11. 检测相机支持
    // 12. 检测蓝牙支持
    // 13. 检测光线传感器
    // 14. 检测进程组信息(cgroup)

    // 嫌疑值 > 3 则判定为模拟器
    return suspectCount > 3;
}

3.2 详细检测维度分析

3.2.1 硬件名称检测 (checkFeaturesByHardware)

检测原理: 通过读取系统属性 ro.hardware 获取硬件名称,匹配已知模拟器特征值。

实现代码分析:

private CheckResult checkFeaturesByHardware() {
    String hardware = getProperty("ro.hardware");
    if (null == hardware) return new CheckResult(RESULT_MAYBE_EMULATOR, null);

    String tempValue = hardware.toLowerCase();
    switch (tempValue) {
        case "ttvm":        // 天天模拟器
        case "nox":         // 夜神模拟器
        case "cancro":      // 网易MUMU模拟器
        case "intel":       // 逍遥模拟器
        case "vbox":        // VirtualBox虚拟机
        case "vbox86":      // 腾讯手游助手
        case "android_x86": // 雷电模拟器
            result = RESULT_EMULATOR;
            break;
        default:
            result = RESULT_UNKNOWN;
            break;
    }
    return new CheckResult(result, hardware);
}

检测到的模拟器列表:

特征值 对应模拟器
ttvm 天天模拟器
nox 夜神模拟器
cancro 网易MUMU模拟器
intel 逍遥模拟器
vbox / vbox86 VirtualBox/腾讯手游助手
android_x86 雷电模拟器

判定逻辑:

  • 若属性为 null → 可能是模拟器 (suspectCount++)
  • 若匹配已知特征 → 确定是模拟器 (直接返回 true)
  • 其他 → 未知,继续检测

3.2.2 渠道信息检测 (checkFeaturesByFlavor)

检测原理: 通过读取 ro.build.flavor 系统属性,识别编译渠道。

private CheckResult checkFeaturesByFlavor() {
    String flavor = getProperty("ro.build.flavor");
    if (null == flavor) return new CheckResult(RESULT_MAYBE_EMULATOR, null);

    String tempValue = flavor.toLowerCase();
    if (tempValue.contains("vbox"))         // VirtualBox特征
        result = RESULT_EMULATOR;
    else if (tempValue.contains("sdk_gphone"))  // Google官方模拟器
        result = RESULT_EMULATOR;
    else
        result = RESULT_UNKNOWN;
    return new CheckResult(result, flavor);
}

检测特征:

  • vbox → VirtualBox 虚拟机
  • sdk_gphone → Google 官方 Android 模拟器 (AVD)

3.2.3 设备型号检测 (checkFeaturesByModel)

检测原理: 通过 ro.product.model 获取设备型号名称。

private CheckResult checkFeaturesByModel() {
    String model = getProperty("ro.product.model");
    if (null == model) return new CheckResult(RESULT_MAYBE_EMULATOR, null);

    String tempValue = model.toLowerCase();
    if (tempValue.contains("google_sdk"))               // Google SDK
        result = RESULT_EMULATOR;
    else if (tempValue.contains("emulator"))            // 包含emulator字样
        result = RESULT_EMULATOR;
    else if (tempValue.contains("android sdk built for x86"))  // x86架构SDK
        result = RESULT_EMULATOR;
    else
        result = RESULT_UNKNOWN;
    return new CheckResult(result, model);
}

检测特征:

特征关键词 含义
google_sdk Google SDK 模拟器
emulator 通用模拟器标识
android sdk built for x86 x86架构的SDK模拟器

3.2.4 硬件制造商检测 (checkFeaturesByManufacturer)

检测原理: 通过 ro.product.manufacturer 获取制造商信息。

private CheckResult checkFeaturesByManufacturer() {
    String manufacturer = getProperty("ro.product.manufacturer");
    if (null == manufacturer) return new CheckResult(RESULT_MAYBE_EMULATOR, null);

    String tempValue = manufacturer.toLowerCase();
    if (tempValue.contains("genymotion"))   // Genymotion模拟器
        result = RESULT_EMULATOR;
    else if (tempValue.contains("netease")) // 网易MUMU模拟器
        result = RESULT_EMULATOR;
    else
        result = RESULT_UNKNOWN;
    return new CheckResult(result, manufacturer);
}

检测特征:

  • genymotion → Genymotion 模拟器
  • netease → 网易 MUMU 模拟器

3.2.5 主板名称检测 (checkFeaturesByBoard)

检测原理: 通过 ro.product.board 获取主板名称。

private CheckResult checkFeaturesByBoard() {
    String board = getProperty("ro.product.board");
    if (null == board) return new CheckResult(RESULT_MAYBE_EMULATOR, null);

    String tempValue = board.toLowerCase();
    if (tempValue.contains("android"))   // 通用android主板
        result = RESULT_EMULATOR;
    else if (tempValue.contains("goldfish"))  // goldfish是QEMU模拟器的代号
        result = RESULT_EMULATOR;
    else
        result = RESULT_UNKNOWN;
    return new CheckResult(result, board);
}

检测特征:

  • android → 通用 Android 模拟器主板
  • goldfish → QEMU 模拟器核心(Android 模拟器底层)

3.2.6 主板平台检测 (checkFeaturesByPlatform)

检测原理: 通过 ro.board.platform 获取平台信息。

private CheckResult checkFeaturesByPlatform() {
    String platform = getProperty("ro.board.platform");
    if (null == platform) return new CheckResult(RESULT_MAYBE_EMULATOR, null);

    String tempValue = platform.toLowerCase();
    if (tempValue.contains("android"))
        result = RESULT_EMULATOR;
    else
        result = RESULT_UNKNOWN;
    return new CheckResult(result, platform);
}

3.2.7 基带信息检测 (checkFeaturesByBaseBand)

检测原理: 通过 gsm.version.baseband 获取基带版本。模拟器通常没有真实的基带信息。

private CheckResult checkFeaturesByBaseBand() {
    String baseBandVersion = getProperty("gsm.version.baseband");
    if (null == baseBandVersion)
        return new CheckResult(RESULT_MAYBE_EMULATOR, null);

    if (baseBandVersion.contains("1.0.0.0"))  // 模拟器常用的假基带版本
        result = RESULT_EMULATOR;
    else
        result = RESULT_UNKNOWN;
    return new CheckResult(result, baseBandVersion);
}

特殊处理: 基带信息为 null 时,嫌疑值 +2(而非 +1),因为模拟器基带为空的概率极高。

case RESULT_MAYBE_EMULATOR:
    suspectCount += 2;  // 权重更高
    break;

3.2.8 传感器数量检测 (getSensorNumber)

检测原理: 真机通常有大量传感器(陀螺仪、加速度计、光线传感器等),而模拟器传感器数量很少。

private int getSensorNumber(Context context) {
    SensorManager sm = (SensorManager) context.getSystemService(SENSOR_SERVICE);
    return sm.getSensorList(Sensor.TYPE_ALL).size();
}

// 判断逻辑
if (sensorNumber <= 7) ++suspectCount;

阈值: 传感器数量 ≤ 7 → 嫌疑值 +1

3.2.9 已安装应用数量检测 (getUserAppNumber)

检测原理: 真机用户通常会安装大量第三方应用,模拟器通常是干净环境。

private int getUserAppNumber() {
    String userApps = CommandUtil.getSingleInstance().exec("pm list package -3");
    return getUserAppNum(userApps);
}

// 判断逻辑
if (userAppNumber <= 5) ++suspectCount;

实现细节:

  • 执行 Shell 命令 pm list package -3 列出所有第三方应用
  • 统计返回结果中的包数量
  • 阈值:应用数量 ≤ 5 → 嫌疑值 +1

3.2.10 相机功能检测 (supportCamera / supportCameraFlash)

检测原理: 模拟器通常不支持真实的相机和闪光灯。

private boolean supportCamera(Context context) {
    return context.getPackageManager()
        .hasSystemFeature(PackageManager.FEATURE_CAMERA);
}

private boolean supportCameraFlash(Context context) {
    return context.getPackageManager()
        .hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH);
}

// 判断逻辑
if (!supportCameraFlash) ++suspectCount;
if (!supportCamera) ++suspectCount;

3.2.11 蓝牙功能检测 (supportBluetooth)

检测原理: 模拟器通常不支持蓝牙。

private boolean supportBluetooth(Context context) {
    return context.getPackageManager()
        .hasSystemFeature(PackageManager.FEATURE_BLUETOOTH);
}

// 判断逻辑
if (!supportBluetooth) ++suspectCount;

3.2.12 光线传感器检测 (hasLightSensor)

检测原理: 光线传感器是手机必备传感器,模拟器通常没有。

private boolean hasLightSensor(Context context) {
    SensorManager sensorManager = (SensorManager) context.getSystemService(SENSOR_SERVICE);
    Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
    return sensor != null;
}

// 判断逻辑
if (!hasLightSensor) ++suspectCount;

3.2.13 进程组信息检测 (checkFeaturesByCgroup)

检测原理: 通过读取 /proc/self/cgroup 分析进程组信息。

private CheckResult checkFeaturesByCgroup() {
    String filter = CommandUtil.getSingleInstance().exec("cat /proc/self/cgroup");
    if (null == filter)
        return new CheckResult(RESULT_MAYBE_EMULATOR, null);
    return new CheckResult(RESULT_UNKNOWN, filter);
}

3.3 综合判定逻辑

最终判定规则:suspectCount > 3 → 判定为模拟器

嫌疑值累加规则:
┌─────────────────────────────────────┬─────────────┐
│ 检测项                              │ 嫌疑值增量  │
├─────────────────────────────────────┼─────────────┤
│ 硬件名称为null                      │ +1          │
│ 渠道信息为null                      │ +1          │
│ 设备型号为null                      │ +1          │
│ 制造商为null                        │ +1          │
│ 主板名称为null                      │ +1          │
│ 平台信息为null                      │ +1          │
│ 基带信息为null                      │ +2 (权重高) │
│ 传感器数量 ≤ 7                      │ +1          │
│ 第三方应用 ≤ 5                      │ +1          │
│ 不支持闪光灯                        │ +1          │
│ 不支持相机                          │ +1          │
│ 不支持蓝牙                          │ +1          │
│ 无光线传感器                        │ +1          │
│ cgroup信息为null                    │ +1          │
└─────────────────────────────────────┴─────────────┘

若任一检测项返回 RESULT_EMULATOR → 直接判定为模拟器

4. 虚拟环境/多开检测机制 (VirtualApkCheckUtil)

文件位置: library/src/main/java/com/lahm/library/VirtualApkCheckUtil.java

4.1 检测目标

检测应用是否运行在以下多开/虚拟环境中:

包名 对应软件
com.bly.dkplat 多开分身
com.by.chaos Chaos引擎
com.lbe.parallel 平行空间
com.excelliance.dualaid 双开助手
com.lody.virtual VirtualXposed / VirtualApp
com.qihoo.magic 360分身大师

4.2 检测方法详解

4.2.1 私有文件路径检测 (checkByPrivateFilePath)

检测原理: 多开软件运行应用时,应用的私有目录路径会包含多开软件的包名。

public boolean checkByPrivateFilePath(Context context, VirtualCheckCallback callback) {
    String path = context.getFilesDir().getPath();
    // 正常路径: /data/data/com.your.app/files
    // 多开路径: /data/data/com.lody.virtual/virtual/data/data/com.your.app/files

    for (String virtualPkg : virtualPkgs) {
        if (path.contains(virtualPkg)) {
            if (callback != null) callback.findSuspect();
            return true;
        }
    }
    return false;
}

检测原理图:

正常环境:
/data/data/com.your.app/files
              └── 应用包名

多开环境:
/data/data/com.lody.virtual/virtual/data/data/com.your.app/files
              └── 多开软件包名              └── 应用包名

4.2.2 包名重复检测 (checkByOriginApkPackageName)

检测原理: 多开软件会 hook getPackageName() 方法。检测系统中是否存在多个相同包名的应用。

public boolean checkByOriginApkPackageName(Context context, VirtualCheckCallback callback) {
    int count = 0;
    String packageName = context.getPackageName();
    PackageManager pm = context.getPackageManager();
    List<PackageInfo> pkgs = pm.getInstalledPackages(0);

    for (PackageInfo info : pkgs) {
        if (packageName.equals(info.packageName)) {
            count++;
        }
    }

    // 正常情况下 count = 1,多开情况下 count > 1
    return count > 1;
}

4.2.3 进程Maps检测 (checkByMultiApkPackageName)

检测原理: 被多开的应用运行时会加载多开软件的 SO 库,通过读取 /proc/self/maps 检测。

public boolean checkByMultiApkPackageName(VirtualCheckCallback callback) {
    BufferedReader bufr = new BufferedReader(new FileReader("/proc/self/maps"));
    String line;
    while ((line = bufr.readLine()) != null) {
        for (String pkg : virtualPkgs) {
            if (line.contains(pkg)) {
                // maps中出现多开软件包名 → 被多开
                return true;
            }
        }
    }
    return false;
}

/proc/self/maps 文件内容示例:

地址范围                 权限 偏移    设备   inode  路径
7f6d5e8000-7f6d5eb000   r-xp 00000000 fd:00 123456 /data/app/com.lody.virtual/lib/arm64/libvirtual.so

4.2.4 UID检测 (checkByHasSameUid)

检测原理: Android 系统中一个 APP 对应一个 UID。如果同一 UID 下有多个进程对应不同的包名,则可能被多开。

public boolean checkByHasSameUid(VirtualCheckCallback callback) {
    String filter = getUidStrFormat();  // 获取当前进程的UID格式字符串,如 "u0_a123"
    String result = CommandUtil.getSingleInstance().exec("ps");  // 执行ps命令

    String[] lines = result.split("\n");
    int exitDirCount = 0;

    for (String line : lines) {
        if (line.contains(filter)) {
            // 提取进程名
            String processName = extractProcessName(line);
            File dataFile = new File("/data/data/" + processName);
            if (dataFile.exists()) {
                exitDirCount++;
            }
        }
    }

    // 同一UID下存在多个私有目录 → 被多开
    return exitDirCount > 1;
}

UID解析过程:

private String getUidStrFormat() {
    // 1. 读取 /proc/self/cgroup 获取 uid 信息
    String filter = exec("cat /proc/self/cgroup");
    // 内容示例: "2:cpu:/uid_10123/pid_1234"

    // 2. 提取uid数字
    int uid = extractUid(filter);  // 得到 10123

    // 3. 转换为ps命令中的格式
    // Android用户空间UID从10000开始,所以 u0_a123 = uid 10123
    return String.format("u0_a%d", uid - 10000);  // "u0_a123"
}

4.2.5 LocalServerSocket检测 (checkByCreateLocalServerSocket) [推荐]

检测原理: 利用 LocalServerSocket 的唯一性特性。同一个 uniqueMsg 只能创建一个 socket,如果创建失败说明已有另一个实例在运行。

private volatile LocalServerSocket localServerSocket;

public boolean checkByCreateLocalServerSocket(String uniqueMsg, VirtualCheckCallback callback) {
    if (localServerSocket != null) return false;

    try {
        localServerSocket = new LocalServerSocket(uniqueMsg);
        return false;  // 创建成功 → 没有被多开
    } catch (IOException e) {
        // 创建失败 → 已有同名socket存在 → 被多开
        if (callback != null) callback.findSuspect();
        return true;
    }
}

使用建议:

  • 单进程应用:使用 context.getPackageName() 作为 uniqueMsg
  • 多进程应用:使用 SecurityCheckUtil.getCurrentProcessName() 作为 uniqueMsg

4.2.6 端口监听检测 (checkByPortListening) [已废弃]

检测原理: 通过在 localhost 上建立 TCP 连接,发送暗号识别是否有另一个实例存在。

@Deprecated
public void checkByPortListening(String secret, VirtualCheckCallback callback) {
    startClient(secret);  // 先尝试连接已存在的服务端
    new ServerThread(secret, callback).start();  // 启动服务端监听
}

工作流程:

1. 扫描 /proc/net/tcp6 获取已开启的本地端口
2. 尝试连接每个端口并发送 secret
3. 如果收到相同 secret 的响应 → 被多开
4. 同时启动 ServerSocket 监听,等待其他实例连接

废弃原因: 实现复杂,不如 LocalServerSocket 方案简洁可靠。

4.2.7 TopTask/TopActivity 检测

检测原理: 通过检查顶层 Task 获取真实的包名/Activity名。

public String checkByTopTask(Context context) {
    ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
    List<RunningTaskInfo> rtis = am.getRunningTasks(1);
    return rtis.get(0).topActivity.getPackageName();
}

public String checkByTopActivity(Context context) {
    ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
    List<RunningTaskInfo> rtis = am.getRunningTasks(1);
    return rtis.get(0).topActivity.getClassName();
}

注意: 此方法需要 GET_TASKS 权限,且在高版本 Android 中受限。


5. 安全检测机制 (SecurityCheckUtil)

文件位置: library/src/main/java/com/lahm/library/SecurityCheckUtil.java

5.1 应用签名获取 (getSignature)

用途: 获取应用签名用于校验应用完整性,防止被篡改。

public String getSignature(Context context) {
    PackageInfo packageInfo = context.getPackageManager()
        .getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES);

    Signature[] signatures = packageInfo.signatures;
    StringBuilder builder = new StringBuilder();
    for (Signature signature : signatures) {
        builder.append(signature.toCharsString());
    }
    return builder.toString();
}

5.2 Debug检测

5.2.1 检测Debug版本 (checkIsDebugVersion)

public boolean checkIsDebugVersion(Context context) {
    return (context.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
}

检测原理: 检查 ApplicationInfo.flags 中的 FLAG_DEBUGGABLE 标志位。

5.2.2 检测调试器连接 (checkIsDebuggerConnected)

public boolean checkIsDebuggerConnected() {
    return android.os.Debug.isDebuggerConnected();
}

检测原理: 调用 Android SDK 提供的 API 直接检测。

5.2.3 USB充电检测 (checkIsUsbCharging)

public boolean checkIsUsbCharging(Context context) {
    IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
    Intent batteryStatus = context.registerReceiver(null, filter);
    int chargePlug = batteryStatus.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
    return chargePlug == BatteryManager.BATTERY_PLUGGED_USB;
}

用途: 辅助判断调试状态(调试时通常会连接USB)。

5.3 Root检测 (isRoot)

public boolean isRoot() {
    int secureProp = getroSecureProp();
    if (secureProp == 0)  // eng/userdebug版本,自带root权限
        return true;
    else
        return isSUExist();  // user版本,检查su文件
}

5.3.1 系统属性检测 (getroSecureProp)

private int getroSecureProp() {
    String roSecureObj = CommandUtil.getSingleInstance().getProperty("ro.secure");
    if (roSecureObj == null) return 1;
    return "0".equals(roSecureObj) ? 0 : 1;
}

检测原理: ro.secure=0 表示系统是 eng 或 userdebug 版本,自带 root 权限。

5.3.2 SU文件检测 (isSUExist)

private boolean isSUExist() {
    String[] paths = {
        "/sbin/su",
        "/system/bin/su",
        "/system/xbin/su",
        "/data/local/xbin/su",
        "/data/local/bin/su",
        "/system/sd/xbin/su",
        "/system/bin/failsafe/su",
        "/data/local/su"
    };
    for (String path : paths) {
        File file = new File(path);
        if (file.exists()) return true;
    }
    return false;
}

检测路径说明:

路径 说明
/sbin/su 系统sbin目录
/system/bin/su 系统bin目录
/system/xbin/su 系统扩展bin目录
/data/local/xbin/su 本地扩展目录
/data/local/bin/su 本地bin目录
/system/sd/xbin/su SD卡系统目录
/system/bin/failsafe/su 安全模式目录
/data/local/su 本地数据目录

5.4 Xposed框架检测

5.4.1 类加载检测 (isXposedExists) [已废弃]

private static final String XPOSED_HELPERS = "de.robv.android.xposed.XposedHelpers";
private static final String XPOSED_BRIDGE = "de.robv.android.xposed.XposedBridge";

@Deprecated
public boolean isXposedExists() {
    try {
        ClassLoader.getSystemClassLoader()
            .loadClass(XPOSED_HELPERS)
            .newInstance();
    } catch (ClassNotFoundException e) {
        return false;  // 类不存在 → 无Xposed
    } catch (InstantiationException | IllegalAccessException e) {
        return true;   // 类存在但无法实例化 → 有Xposed
    }
    // 同样检查 XPOSED_BRIDGE 类
    return true;
}

5.4.2 异常堆栈检测 (isXposedExistByThrow) [推荐]

public boolean isXposedExistByThrow() {
    try {
        throw new Exception("gg");
    } catch (Exception e) {
        for (StackTraceElement element : e.getStackTrace()) {
            if (element.getClassName().contains(XPOSED_BRIDGE)) {
                return true;  // 堆栈中出现Xposed类 → 有Xposed
            }
        }
        return false;
    }
}

检测原理: Xposed 通过 AOP 方式 hook 方法,在调用栈中会留下痕迹。主动抛出异常并检查堆栈信息。

5.4.3 尝试禁用Xposed (tryShutdownXposed)

public boolean tryShutdownXposed() {
    try {
        Field xpdisabledHooks = ClassLoader.getSystemClassLoader()
            .loadClass(XPOSED_BRIDGE)
            .getDeclaredField("disableHooks");
        xpdisabledHooks.setAccessible(true);
        xpdisabledHooks.set(null, Boolean.TRUE);  // 设置 disableHooks = true
        return true;
    } catch (Exception e) {
        return false;
    }
}

检测原理: Xposed 框架有一个 disableHooks 静态变量,设置为 true 可以禁用 hook。

局限性: 如果 Xposed 先 hook 了 isXposedExistByThrow 的返回值,则无法执行后续禁用操作。

5.5 SO库加载检测 (hasReadProcMaps)

public boolean hasReadProcMaps(String paramString) {
    Set<String> loadedLibs = new HashSet<>();
    BufferedReader reader = new BufferedReader(
        new FileReader("/proc/" + Process.myPid() + "/maps"));

    String line;
    while ((line = reader.readLine()) != null) {
        if (line.endsWith(".so") || line.endsWith(".jar")) {
            loadedLibs.add(line.substring(line.lastIndexOf(" ") + 1));
        }
    }

    for (String lib : loadedLibs) {
        if (lib.contains(paramString)) {
            return true;  // 找到目标SO库
        }
    }
    return false;
}

用途: 检测是否加载了特定的 SO 库(如恶意注入的库)。

5.6 进程追踪检测 (readProcStatus)

public boolean readProcStatus() {
    BufferedReader reader = new BufferedReader(
        new FileReader("/proc/" + Process.myPid() + "/status"));

    String tracerPid = "";
    String line;
    while ((line = reader.readLine()) != null) {
        if (line.contains("TracerPid")) {
            tracerPid = line.substring(line.indexOf(":") + 1).trim();
            break;
        }
    }

    return !"0".equals(tracerPid);  // TracerPid != 0 表示正在被追踪
}

/proc/[pid]/status 文件示例:

Name:   com.lahm.app
State:  S (sleeping)
Tgid:   12345
Pid:    12345
PPid:   1
TracerPid:  0     ← 0表示未被追踪,非0表示追踪进程的PID
...

5.7 端口检测

public boolean isLocalPortUsing(int port) {
    return isPortUsing("127.0.0.1", port);
}

public boolean isPortUsing(String host, int port) throws UnknownHostException {
    InetAddress address = InetAddress.getByName(host);
    try {
        Socket socket = new Socket(address, port);
        return true;  // 能连接 → 端口被占用
    } catch (IOException e) {
        return false; // 连接失败 → 端口未被占用
    }
}

用途: 检测调试端口(如 adb 默认端口 5555)是否被占用。

5.8 获取当前进程名

public String getCurrentProcessName() {
    FileInputStream fis = new FileInputStream("/proc/self/cmdline");
    byte[] buffer = new byte[256];
    int len = 0;
    int b;
    while ((b = fis.read()) > 0 && len < buffer.length) {
        buffer[len++] = (byte) b;
    }
    return new String(buffer, 0, len, "UTF-8");
}

用途: 在多进程应用中获取当前进程名,用于区分不同进程。


6. Native层反调试检测 (antitrace.cpp)

文件位置: library/src/main/jni/antitrace.cpp

6.1 整体架构

// JNI入口点
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
    // 1. 可选:自我附加防止被其他进程附加
    // ptrace(PTRACE_TRACEME, 0, 0, 0);  // 已注释

    // 2. 启动循环检测线程
    create_thread_check_traceid();

    return JNI_VERSION_1_6;
}

6.2 TracerPid检测线程

void *thread_function(void *argv) {
    int pid = getpid();
    char file_name[20] = {'\0'};
    sprintf(file_name, "/proc/%d/status", pid);

    while (1) {
        FILE *fp = fopen(file_name, "r");

        // 读取第6行(TracerPid所在行)
        char linestr[256];
        for (int i = 0; i <= 5; i++) {
            fgets(linestr, 256, fp);
        }

        // 提取TracerPid数值
        int traceid = get_number_for_str(linestr);

        if (traceid > 1000) {
            // 被追踪且追踪者PID > 1000
            // 华为P9会主动附加系统进程,PID < 1000,所以设置阈值
            exit(0);  // 直接退出进程
        }

        fclose(fp);
        sleep(5);  // 每5秒检测一次
    }
    return ((void *) 0);
}

6.3 数字提取函数

int get_number_for_str(char *str) {
    if (str == NULL) return -1;

    char result[20];
    int count = 0;
    while (*str != '\0') {
        if (*str >= 48 && *str <= 57) {  // ASCII码 '0'-'9'
            result[count++] = *str;
        }
        str++;
    }
    return atoi(result);
}

6.4 检测流程图

┌─────────────────────────────────────────────────────────────────┐
│                     JNI_OnLoad 被调用                            │
└─────────────────────────────┬───────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│                  create_thread_check_traceid()                  │
│                     创建检测线程                                 │
└─────────────────────────────┬───────────────────────────────────┘
                              │
                              ▼
         ┌────────────────────────────────────────┐
         │           thread_function              │
         │          (循环检测线程)                 │
         └────────────────────┬───────────────────┘
                              │
                              ▼
         ┌────────────────────────────────────────┐
         │    读取 /proc/[pid]/status 文件        │
         └────────────────────┬───────────────────┘
                              │
                              ▼
         ┌────────────────────────────────────────┐
         │        解析 TracerPid 字段值           │
         └────────────────────┬───────────────────┘
                              │
              ┌───────────────┴───────────────┐
              │                               │
              ▼                               ▼
    ┌─────────────────┐            ┌─────────────────┐
    │ TracerPid > 1000│            │ TracerPid ≤ 1000│
    │   被恶意追踪    │            │    正常/系统    │
    └────────┬────────┘            └────────┬────────┘
             │                              │
             ▼                              ▼
    ┌─────────────────┐            ┌─────────────────┐
    │    exit(0)      │            │   sleep(5)      │
    │   退出进程      │            │   继续循环      │
    └─────────────────┘            └─────────────────┘

7. 无障碍服务检测 (AccessibilityServicesCheckUtil)

文件位置: library/src/main/java/com/lahm/library/AccessibilityServicesCheckUtil.java

7.1 检测指定包名的无障碍服务

public boolean checkAccessibilityEnabled(Context context, String packageName) {
    // 1. 检查无障碍服务是否开启
    int hasSetSetting = Settings.Secure.getInt(
        context.getContentResolver(),
        Settings.Secure.ACCESSIBILITY_ENABLED);

    if (hasSetSetting == 1) {
        // 2. 获取已启用的无障碍服务列表
        String settingValue = Settings.Secure.getString(
            context.getContentResolver(),
            Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);

        // 3. 解析服务列表(以:分隔)
        TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter(':');
        splitter.setString(settingValue);

        // 4. 检查是否包含目标包名
        while (splitter.hasNext()) {
            String accessibilityService = splitter.next();
            if (accessibilityService.contains(packageName)) {
                return true;
            }
        }
    }
    return false;
}

7.2 跳转到无障碍设置

public Intent go2SetAccessibilityService() {
    Intent intent = new Intent();
    intent.setAction(Settings.ACTION_ACCESSIBILITY_SETTINGS);
    return intent;
}

8. 辅助工具类

8.1 CommandUtil - 命令执行工具

文件位置: library/src/main/java/com/lahm/library/CommandUtil.java

8.1.1 获取系统属性

public String getProperty(String propName) {
    try {
        Object roSecureObj = Class.forName("android.os.SystemProperties")
            .getMethod("get", String.class)
            .invoke(null, propName);
        return (String) roSecureObj;
    } catch (Exception e) {
        return null;
    }
}

实现原理: 通过反射调用隐藏 API android.os.SystemProperties.get()

8.1.2 执行Shell命令

public String exec(String command) {
    Process process = Runtime.getRuntime().exec("sh");

    BufferedOutputStream bos = new BufferedOutputStream(process.getOutputStream());
    bos.write(command.getBytes());
    bos.write('\n');
    bos.flush();
    bos.close();

    process.waitFor();

    BufferedInputStream bis = new BufferedInputStream(process.getInputStream());
    return readFromStream(bis);
}

8.2 NDKUtil - Native库加载工具

文件位置: library/src/main/java/com/lahm/library/NDKUtil.java

public static void loadLibrariesOnce(LibLoader libLoader) {
    synchronized (NDKUtil.class) {
        if (!mIsLibLoaded) {
            if (libLoader == null) {
                libLoader = new LibLoader() {
                    public void loadLibrary(String libName) {
                        System.loadLibrary(libName);
                    }
                };
            }
            libLoader.loadLibrary("antitrace");
            mIsLibLoaded = true;
        }
    }
}

9. 应用层实现与配置

9.1 TestApplication - 应用启动时的安全防护

文件位置: app/src/main/java/com/lahm/easyprotector/TestApplication.java

应用在启动时即进行安全检测和防护,这是推荐的最佳实践。

public class TestApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();

        // 应用启动时立即尝试禁用 Xposed 框架
        EasyProtectorLib.checkXposedExistAndDisableIt();

        // 可选:在启动时检测多开环境并退出
        // EasyProtectorLib.checkIsRunningInVirtualApk(getPackageName(), new VirtualCheckCallback() {
        //     @Override
        //     public void findSuspect() {
        //         System.exit(0);  // 检测到多开直接退出
        //     }
        // });
    }
}

关键设计点:

时机 操作 目的
Application.onCreate 禁用Xposed 在任何Activity启动前禁用hook
Application.onCreate 检测多开 创建LocalServerSocket占位
检测到威胁 System.exit(0) 立即终止应用

启动时检测的优势:

  1. 最早时机执行,减少被绕过的可能
  2. 在任何业务代码执行前完成检测
  3. LocalServerSocket需要尽早创建以占位

9.2 TestAccessibilityService - 无障碍服务演示

文件位置: app/src/main/java/com/lahm/easyprotector/TestAccessibilityService.java

这是一个无障碍服务的示例实现,展示了自动安装APK的功能(与环境检测无直接关系,但展示了无障碍服务的能力)。

public class TestAccessibilityService extends AccessibilityService {
    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        int eventType = event.getEventType();
        switch (eventType) {
            case AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED:  // 通知栏事件
            case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:        // 页面切换事件
                break;
        }

        // 检测是否为系统安装器
        if (event.getPackageName().toString().equals("com.android.packageinstaller")) {
            installAPK(event);
        }
    }

    private void installAPK(AccessibilityEvent event) {
        AccessibilityNodeInfo source = getRootInActiveWindow();

        // 自动点击"下一步"按钮
        List<AccessibilityNodeInfo> nextInfos = source.findAccessibilityNodeInfosByText("下一步");
        nextClick(nextInfos);

        // 自动点击"安装"按钮
        List<AccessibilityNodeInfo> installInfos = source.findAccessibilityNodeInfosByText("安装");
        nextClick(installInfos);

        // 自动点击"打开"按钮
        List<AccessibilityNodeInfo> openInfos = source.findAccessibilityNodeInfosByText("打开");
        nextClick(openInfos);
    }

    private void nextClick(List<AccessibilityNodeInfo> infos) {
        if (infos != null) {
            for (AccessibilityNodeInfo info : infos) {
                if (info.isEnabled() && info.isClickable()) {
                    info.performAction(AccessibilityNodeInfo.ACTION_CLICK);
                }
            }
        }
    }
}

功能说明:

  • 监听系统安装器(com.android.packageinstaller)的界面事件
  • 自动查找并点击"下一步"、"安装"、"打开"按钮
  • 实现APK静默安装的自动化

9.3 AndroidManifest.xml 配置分析

文件位置: app/src/main/AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.lahm.easyprotector">

    <!-- 网络权限:用于端口检测 -->
    <uses-permission android:name="android.permission.INTERNET" />
    <!-- 无障碍服务绑定权限 -->
    <uses-permission android:name="android.permission.BIND_ACCESSIBILITY_SERVICE" />

    <application
        android:name=".TestApplication"
        android:allowBackup="true"
        ...>

        <!-- 主Activity -->
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <!-- 无障碍服务声明 -->
        <service
            android:name=".TestAccessibilityService"
            android:enabled="true"
            android:exported="true"
            android:label="@string/access_name"
            android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
            <intent-filter>
                <action android:name="android.accessibilityservice.AccessibilityService" />
            </intent-filter>
            <meta-data
                android:name="android.accessibilityservice"
                android:resource="@xml/test_access_service_config" />
        </service>
    </application>
</manifest>

权限分析:

权限 用途 必要性
INTERNET 端口检测、Socket通信 可选
BIND_ACCESSIBILITY_SERVICE 无障碍服务 仅演示用

注意: 核心检测功能不需要任何特殊权限,这是该库的设计亮点之一。

9.4 无障碍服务配置文件

文件位置: app/src/main/res/xml/test_access_service_config.xml

<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
    android:accessibilityEventTypes="typeAllMask"
    android:accessibilityFeedbackType="feedbackGeneric"
    android:canRetrieveWindowContent="true"
    android:description="@string/access_description"
    android:notificationTimeout="100"
    android:packageNames="com.lahm.easyprotector,com.android.packageinstaller" />

配置参数详解:

参数 含义
accessibilityEventTypes typeAllMask 监听所有类型的无障碍事件
accessibilityFeedbackType feedbackGeneric 通用反馈类型
canRetrieveWindowContent true 允许访问活动窗口内容
notificationTimeout 100 事件响应超时时间(毫秒)
packageNames 两个包名 只监听指定应用的事件

9.5 MainActivity - 测试界面实现

文件位置: app/src/main/java/com/lahm/easyprotector/MainActivity.java

主界面提供了所有检测功能的测试按钮:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 多开检测按钮
        findViewById(R.id.checkByPrivateFilePath).setOnClickListener(this);
        findViewById(R.id.checkByOriginApkPackageName).setOnClickListener(this);
        findViewById(R.id.checkByMultiApkPackageName).setOnClickListener(this);
        findViewById(R.id.checkByHasSameUid).setOnClickListener(this);
        findViewById(R.id.checkByPortListening).setOnClickListener(this);
        findViewById(R.id.checkByCreateLocalServerSocket).setOnClickListener(this);

        // 安全检测按钮
        findViewById(R.id.checkRoot).setOnClickListener(this);
        findViewById(R.id.checkDebuggable).setOnClickListener(this);
        findViewById(R.id.checkDebuggerAttach).setOnClickListener(this);
        findViewById(R.id.checkTracer).setOnClickListener(this);
        findViewById(R.id.checkXP).setOnClickListener(this);

        // 模拟器检测按钮
        findViewById(R.id.readSysProperty).setOnClickListener(this);
    }
}

测试功能矩阵:

按钮ID 功能 调用方法
checkByPrivateFilePath 私有路径检测 VirtualApkCheckUtil.checkByPrivateFilePath()
checkByOriginApkPackageName 包名检测 VirtualApkCheckUtil.checkByOriginApkPackageName()
checkByMultiApkPackageName Maps文件检测 VirtualApkCheckUtil.checkByMultiApkPackageName()
checkByHasSameUid UID检测 VirtualApkCheckUtil.checkByHasSameUid()
checkByPortListening 端口监听检测 VirtualApkCheckUtil.checkByPortListening()
checkByCreateLocalServerSocket Socket检测 EasyProtectorLib.checkIsRunningInVirtualApk()
checkRoot Root检测 EasyProtectorLib.checkIsRoot()
checkDebuggable Debug版本检测 EasyProtectorLib.checkIsDebug()
checkDebuggerAttach 调试器连接检测 SecurityCheckUtil.checkIsDebuggerConnected()
checkTracer C层追踪检测 EasyProtectorLib.checkIsBeingTracedByC()
checkXP Xposed检测 EasyProtectorLib.checkIsXposedExist()
readSysProperty 模拟器检测 EasyProtectorLib.checkIsRunningInEmulator()

9.6 测试界面布局

文件位置: app/src/main/res/layout/activity_main.xml

界面采用垂直滚动布局,分为多个检测区域:

┌─────────────────────────────────────────────┐
│              VirtualApp 检测区域             │
├─────────────────────────────────────────────┤
│ [checkByPrivateFilePath]      结果显示      │
│ [checkByOriginApkPackageName] 结果显示      │
│ [checkByMultiApkPackageName]  结果显示      │
│ [checkByHasSameUid]           结果显示      │
│ [checkByPortListening]        结果显示      │
│ [checkByCreateLocalServerSocket] 结果显示   │
├─────────────────────────────────────────────┤
│                 Root 检测区域               │
├─────────────────────────────────────────────┤
│ [checkRoot]                   结果显示      │
├─────────────────────────────────────────────┤
│                Debug 检测区域               │
├─────────────────────────────────────────────┤
│ [checkDebuggable]             结果显示      │
│ [checkDebuggerAttach]         结果显示      │
│ [checkTracer]                 结果显示      │
├─────────────────────────────────────────────┤
│            XPosed Framework 检测区域         │
├─────────────────────────────────────────────┤
│ [checkXP]                     结果显示      │
├─────────────────────────────────────────────┤
│              Emulator info 检测区域          │
├─────────────────────────────────────────────┤
│ [click me emulator info]                    │
│ 详细模拟器信息输出区域                        │
├─────────────────────────────────────────────┤
│ [for test]                                  │
└─────────────────────────────────────────────┘

10. 项目构建与编译配置

10.1 项目结构

EasyProtector/
├── app/                          # 演示应用模块
│   ├── build.gradle             # 应用构建配置
│   ├── proguard-rules.pro       # 应用混淆规则
│   └── src/main/
│       ├── AndroidManifest.xml
│       ├── java/                # Java源码
│       └── res/                 # 资源文件
├── library/                      # 核心库模块
│   ├── build.gradle             # 库构建配置
│   ├── CMakeLists.txt           # Native编译配置
│   ├── proguard-rules.pro       # 库混淆规则
│   └── src/main/
│       ├── AndroidManifest.xml
│       ├── java/                # Java源码
│       └── jni/                 # Native源码
├── build.gradle                  # 根项目配置
├── settings.gradle               # 模块包含配置
└── gradle.properties             # Gradle属性配置

10.2 根项目 build.gradle

文件位置: build.gradle

buildscript {
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:4.0.1'
        classpath 'com.novoda:bintray-release:0.9.2'  // 用于发布到bintray
    }
}

allprojects {
    repositories {
        google()
        jcenter()
    }
}

10.3 Library模块 build.gradle

文件位置: library/build.gradle

apply plugin: 'com.android.library'

android {
    compileSdkVersion 29
    defaultConfig {
        minSdkVersion 16          // 最低支持 Android 4.1
        targetSdkVersion 29
        versionCode gitVersionCode()
        versionName gitVersionName()

        ndk {
            abiFilters 'armeabi-v7a','arm64-v8a'  // 支持的CPU架构
        }
    }

    // Native编译配置
    externalNativeBuild {
        cmake {
            path 'CMakeLists.txt'
        }
    }
}

// 通过git commit数获取版本号
def static gitVersionCode() {
    def cmd = 'git rev-list HEAD --count'
    10000 + cmd.execute().text.trim().toInteger()
}

// 通过git tag获取版本名
def static gitVersionName() {
    def cmd = 'git describe --tags'
    def tag = cmd.execute().text.trim()
    def arr = tag.split("-")
    return arr[0]
}

版本管理策略:

方法 命令 计算公式 示例
gitVersionCode() git rev-list HEAD --count 10000 + commit数 10150
gitVersionName() git describe --tags 最近的tag名 v1.1.1

10.4 CMakeLists.txt - Native编译配置

文件位置: library/CMakeLists.txt

cmake_minimum_required(VERSION 3.4.1)

# 编译 antitrace.so 共享库
add_library(
    antitrace           # 库名称(不需要lib前缀)
    SHARED              # 共享库类型
    src/main/jni/antitrace.cpp  # 源文件
)

# 查找log库(用于在C/C++中打印日志)
find_library(
    log-lib
    log
)

# 将log库链接到antitrace
target_link_libraries(
    antitrace
    ${log-lib}
)

编译输出:

  • lib/armeabi-v7a/libantitrace.so - 32位ARM架构
  • lib/arm64-v8a/libantitrace.so - 64位ARM架构

10.5 App模块 build.gradle

文件位置: app/build.gradle

apply plugin: 'com.android.application'

android {
    compileSdkVersion 29
    buildToolsVersion = '29.0.3'

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

    defaultConfig {
        applicationId "com.lahm.easyprotector"
        minSdkVersion 19          // 演示App最低支持 Android 4.4
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"
    }
}

dependencies {
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation project(':library')  // 依赖library模块
}

10.6 Library混淆规则

文件位置: library/proguard-rules.pro

# 基础配置
-dontskipnonpubliclibraryclasses  # 不忽略非公共的库类
-optimizationpasses 5              # 代码压缩级别
-dontusemixedcaseclassnames       # 不使用大小写混合
-dontpreverify                    # 不做预校验
-verbose                          # 记录日志
-keepattributes *Annotation*      # 保持注解
-ignorewarning                    # 忽略警告
-dontoptimize                     # 不优化

# 混淆算法
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*

# 生成调试文件
-dump class_files.txt             # APK内所有class的内部结构
-printseeds seeds.txt             # 未混淆的类和成员
-printusage unused.txt            # 未被使用的代码
-printmapping mapping.txt         # 混淆前后的映射

# 保持规则
-keep class android.support.**
-keep class * extends android.support.**
-dontwarn android.support.**

# 保持 Parcelable 不被混淆
-keep class * implements android.os.Parcelable {
    public static final android.os.Parcelable$Creator *;
}

# 不混淆 Serializable
-keepnames class * implements java.io.Serializable

# 保持 native 方法不被混淆
-keepclasseswithmembernames class * {
    native <methods>;
}

关键混淆保护:

规则 目的
-keepclasseswithmembernames ... native 保护JNI方法名不被混淆
-keepattributes *Annotation* 保持注解(反射需要)
-printmapping mapping.txt 生成混淆映射(用于堆栈还原)

10.7 SDK版本兼容性

配置项 Library App 说明
minSdkVersion 16 19 库支持Android 4.1+
targetSdkVersion 29 29 目标Android 10
compileSdkVersion 29 29 编译SDK版本

10.8 CPU架构支持

ndk {
    abiFilters 'armeabi-v7a','arm64-v8a'
}
架构 说明 覆盖设备
armeabi-v7a 32位ARM 绝大多数Android手机
arm64-v8a 64位ARM 新款Android手机

未支持的架构:

  • x86 / x86_64 - Intel模拟器(检测库本身要对抗模拟器,不需要支持)
  • armeabi - 旧版ARM(Android 4.0以下,已弃用)

11. 检测流程图

11.1 完整检测流程

                    ┌─────────────────────────────────────┐
                    │         EasyProtectorLib            │
                    │           (统一入口)                 │
                    └─────────────────┬───────────────────┘
                                      │
          ┌───────────────────────────┼───────────────────────────┐
          │                           │                           │
          ▼                           ▼                           ▼
┌─────────────────┐       ┌─────────────────────┐      ┌──────────────────┐
│ EmulatorCheck   │       │  VirtualApkCheck    │      │  SecurityCheck   │
│    模拟器检测    │       │    多开环境检测      │      │    安全检测      │
└────────┬────────┘       └──────────┬──────────┘      └────────┬─────────┘
         │                           │                          │
         ▼                           ▼                          ▼
┌─────────────────┐       ┌─────────────────────┐      ┌──────────────────┐
│• 系统属性检测    │       │• 私有路径检测        │      │• Root检测        │
│• 硬件特征检测    │       │• 包名重复检测        │      │• Xposed检测      │
│• 传感器检测      │       │• Maps文件检测        │      │• Debug检测       │
│• 功能支持检测    │       │• UID检测            │      │• 追踪检测        │
│• 进程组检测      │       │• Socket检测         │      │• 签名检测        │
└─────────────────┘       └─────────────────────┘      └──────────────────┘
         │                           │                          │
         └───────────────────────────┼──────────────────────────┘
                                     │
                                     ▼
                        ┌─────────────────────────┐
                        │    NDKUtil (Native层)   │
                        │    C层追踪检测          │
                        └─────────────────────────┘

11.2 模拟器检测决策树

                              开始检测
                                 │
            ┌────────────────────┼────────────────────┐
            │                    │                    │
            ▼                    ▼                    ▼
      系统属性检测          硬件特征检测          软件特征检测
            │                    │                    │
    ┌───────┴───────┐    ┌───────┴───────┐    ┌───────┴───────┐
    │               │    │               │    │               │
    ▼               ▼    ▼               ▼    ▼               ▼
 hardware        flavor  传感器数量    光线传感器  已安装APP数   相机/蓝牙
 model           board   (≤7)        (无)        (≤5)        (不支持)
 manufacturer    platform
 baseband
    │               │    │               │    │               │
    └───────────────┴────┴───────────────┴────┴───────────────┘
                                 │
                                 ▼
                    ┌─────────────────────────┐
                    │   计算嫌疑值 suspectCount │
                    └────────────┬────────────┘
                                 │
                    ┌────────────┴────────────┐
                    │                         │
                    ▼                         ▼
           suspectCount > 3           suspectCount ≤ 3
                    │                         │
                    ▼                         ▼
           ┌─────────────┐           ┌─────────────┐
           │  是模拟器    │           │  是真机      │
           └─────────────┘           └─────────────┘

12. 检测结果判定逻辑

12.1 模拟器检测判定表

检测维度 检测方法 确定是模拟器的特征值 嫌疑值
硬件名称 ro.hardware ttvm/nox/cancro/intel/vbox/vbox86/android_x86 null→+1
渠道信息 ro.build.flavor 包含vbox或sdk_gphone null→+1
设备型号 ro.product.model 包含google_sdk/emulator/android sdk built for x86 null→+1
制造商 ro.product.manufacturer 包含genymotion/netease null→+1
主板名称 ro.product.board 包含android/goldfish null→+1
平台信息 ro.board.platform 包含android null→+1
基带信息 gsm.version.baseband 包含1.0.0.0 null→+2
传感器数量 SensorManager - ≤7→+1
第三方APP数 pm list package -3 - ≤5→+1
闪光灯 FEATURE_CAMERA_FLASH - 不支持→+1
相机 FEATURE_CAMERA - 不支持→+1
蓝牙 FEATURE_BLUETOOTH - 不支持→+1
光线传感器 TYPE_LIGHT - 无→+1
进程组 /proc/self/cgroup - null→+1

最终判定: suspectCount > 3 → 模拟器

12.2 多开环境检测判定表

检测方法 判定为多开的条件 推荐程度
checkByPrivateFilePath 私有目录路径包含多开软件包名 ★★★
checkByOriginApkPackageName 系统中存在多个相同包名的应用 ★★★
checkByMultiApkPackageName /proc/self/maps包含多开软件包名 ★★★
checkByHasSameUid 同一UID下存在多个私有目录 ★★☆
checkByCreateLocalServerSocket 创建LocalServerSocket失败 ★★★★★
checkByPortListening 端口通信检测到相同secret ★☆☆ (已废弃)

12.3 Root检测判定表

检测项 判定为Root的条件
ro.secure = 0 (eng/userdebug版本)
su文件 以下路径任一存在su文件

su文件检测路径:

  • /sbin/su
  • /system/bin/su
  • /system/xbin/su
  • /data/local/xbin/su
  • /data/local/bin/su
  • /system/sd/xbin/su
  • /system/bin/failsafe/su
  • /data/local/su

12.4 Xposed检测判定表

检测方法 判定为存在Xposed的条件
isXposedExists XposedHelpers或XposedBridge类存在
isXposedExistByThrow 异常堆栈中包含XposedBridge
tryShutdownXposed 能成功设置disableHooks字段

12.5 追踪检测判定表

检测层 检测方法 判定为被追踪的条件
Java层 readProcStatus TracerPid ≠ 0
Native层 thread_function TracerPid > 1000

13. 总结

13.1 检测能力矩阵

检测类型 Java层 Native层 检测手段数量
模拟器 14种
多开/虚拟环境 6种
Root 2种
Xposed 3种
调试 3种
进程追踪 2种

13.2 核心技术点总结

  1. 系统属性读取: 通过反射调用 SystemProperties.get() 读取系统属性
  2. 文件系统分析: 读取 /proc/self/maps/proc/[pid]/status 等文件
  3. Shell命令执行: 通过 Runtime.exec() 执行 ps、pm 等命令
  4. 硬件特征检测: 通过 PackageManager.hasSystemFeature()SensorManager
  5. LocalServerSocket: 利用其唯一性特性检测多开
  6. 异常堆栈分析: 通过主动抛异常检测 Xposed hook
  7. Native层循环检测: 后台线程持续监控 TracerPid

13.3 检测策略特点

  1. 多维度检测: 从多个角度检测,提高准确率
  2. 嫌疑值累计: 不依赖单一特征,综合判断
  3. Java+Native双层: 提高反逆向难度
  4. 回调机制: 提供检测结果详情,方便调试

13.4 已知局限性

  1. 模拟器检测: 新版模拟器可能修改系统属性绕过检测
  2. Root检测: 可通过隐藏su文件或hook文件存在检测绕过
  3. Xposed检测: EdXposed/LSPosed 等新框架可能有不同特征
  4. 多开检测: 多开软件持续更新,包名列表需要维护
  5. Native检测: TracerPid > 1000 的阈值在某些设备上可能误判

13.5 项目测试数据

根据项目 image/v1.1.1/ 目录中的截图,该库已在以下环境中测试:

测试环境 版本 测试结果
AS模拟器 Android 9.0 可检测
MUMU模拟器 1.1.0.2 可检测
夜神模拟器 6.5.0.0 可检测
腾讯手游助手 1.0.10158.123 可检测
逍遥模拟器 7.0.2 可检测
雷电模拟器 3.75 可检测
OPPO R9tm Android 5.1 ColorOS 3.0 真机识别
VIVO Y76A Android 6.0 FuntouchOS 2.6 真机识别
华为P9 Android 8.0 EMUI 8.0 真机识别

附录A: 完整项目文件清单

EasyProtector-master/
│
├── .gitignore                                    # Git忽略配置
├── LICENSE                                       # Apache 2.0 开源协议
├── README.md                                     # 项目说明文档
├── build.gradle                                  # 根项目Gradle配置
├── gradle.properties                             # Gradle属性配置
├── settings.gradle                               # 模块包含配置 (include ':app', ':library')
│
├── gradle/
│   └── wrapper/
│       ├── gradle-wrapper.jar                    # Gradle Wrapper JAR
│       └── gradle-wrapper.properties             # Gradle Wrapper配置
│
├── image/
│   └── v1.1.1/                                   # 测试截图
│       ├── AS模拟器9.0.png
│       ├── MUMU模拟器1.1.0.2.png
│       ├── OPPO R9tm  5.1 colorOS3.0.png
│       ├── VIVO Y76A 6.0 FuntouchOS2.6.png
│       ├── 华为P9 8.0.0 EMUI8.0.0.jpg
│       ├── 夜神模拟器6.5.0.0.png
│       ├── 腾讯手游助手 1.0.10158.123.png
│       ├── 逍遥模拟器7.0.2.png
│       └── 雷电模拟器3.75.png
│
├── app/                                          # 演示应用模块
│   ├── .gitignore
│   ├── build.gradle                              # App构建配置
│   ├── proguard-rules.pro                        # App混淆规则
│   └── src/main/
│       ├── AndroidManifest.xml                   # App清单文件
│       ├── java/com/lahm/easyprotector/
│       │   ├── MainActivity.java                 # 测试主界面
│       │   ├── TestAccessibilityService.java     # 无障碍服务示例
│       │   └── TestApplication.java              # Application类
│       └── res/
│           ├── drawable/
│           │   └── ic_launcher_background.xml
│           ├── drawable-v24/
│           │   └── ic_launcher_foreground.xml
│           ├── layout/
│           │   └── activity_main.xml             # 主界面布局
│           ├── mipmap-*/                         # 应用图标
│           ├── values/
│           │   ├── colors.xml
│           │   ├── strings.xml                   # 字符串资源
│           │   └── styles.xml
│           └── xml/
│               └── test_access_service_config.xml # 无障碍服务配置
│
└── library/                                      # 核心检测库模块
    ├── .gitignore
    ├── build.gradle                              # Library构建配置
    ├── CMakeLists.txt                            # Native编译配置
    ├── proguard-rules.pro                        # Library混淆规则
    └── src/main/
        ├── AndroidManifest.xml                   # Library清单文件
        ├── java/com/lahm/library/
        │   ├── EasyProtectorLib.java             # [核心] 统一入口类
        │   ├── EmulatorCheckUtil.java            # [核心] 模拟器检测 (14种方法)
        │   ├── VirtualApkCheckUtil.java          # [核心] 多开检测 (6种方法)
        │   ├── SecurityCheckUtil.java            # [核心] 安全检测 (Root/Xposed/Debug)
        │   ├── AccessibilityServicesCheckUtil.java # 无障碍服务检测
        │   ├── NDKUtil.java                      # Native库加载工具
        │   ├── CommandUtil.java                  # Shell命令执行工具
        │   ├── CheckResult.java                  # 检测结果封装类
        │   ├── EmulatorCheckCallback.java        # 模拟器检测回调接口
        │   ├── VirtualCheckCallback.java         # 多开检测回调接口
        │   └── LibLoader.java                    # 库加载器接口
        ├── jni/
        │   └── antitrace.cpp                     # [核心] Native层反调试检测
        └── res/values/
            └── strings.xml

附录B: 所有检测方法索引

B.1 按检测类型分类

模拟器检测 (EmulatorCheckUtil - 14种):

  1. checkFeaturesByHardware() - ro.hardware属性检测
  2. checkFeaturesByFlavor() - ro.build.flavor属性检测
  3. checkFeaturesByModel() - ro.product.model属性检测
  4. checkFeaturesByManufacturer() - ro.product.manufacturer属性检测
  5. checkFeaturesByBoard() - ro.product.board属性检测
  6. checkFeaturesByPlatform() - ro.board.platform属性检测
  7. checkFeaturesByBaseBand() - gsm.version.baseband属性检测
  8. getSensorNumber() - 传感器数量检测
  9. getUserAppNumber() - 第三方应用数量检测
  10. supportCamera() - 相机支持检测
  11. supportCameraFlash() - 闪光灯支持检测
  12. supportBluetooth() - 蓝牙支持检测
  13. hasLightSensor() - 光线传感器检测
  14. checkFeaturesByCgroup() - 进程组信息检测

多开/虚拟环境检测 (VirtualApkCheckUtil - 7种):

  1. checkByPrivateFilePath() - 私有文件路径检测
  2. checkByOriginApkPackageName() - 包名重复检测
  3. checkByMultiApkPackageName() - Maps文件检测
  4. checkByHasSameUid() - UID检测
  5. checkByCreateLocalServerSocket() - LocalServerSocket检测 [推荐]
  6. checkByPortListening() - 端口监听检测 [已废弃]
  7. checkByTopTask()/checkByTopActivity() - TopTask检测

安全检测 (SecurityCheckUtil):

  1. getSignature() - 应用签名获取
  2. checkIsDebugVersion() - Debug版本检测
  3. checkIsDebuggerConnected() - 调试器连接检测
  4. checkIsUsbCharging() - USB充电检测
  5. isRoot() - Root权限检测
  6. isXposedExists() - Xposed类加载检测 [已废弃]
  7. isXposedExistByThrow() - Xposed堆栈检测 [推荐]
  8. tryShutdownXposed() - 尝试禁用Xposed
  9. hasReadProcMaps() - SO库加载检测
  10. readProcStatus() - TracerPid检测
  11. isPortUsing() - 端口占用检测
  12. getCurrentProcessName() - 当前进程名获取

Native层检测 (antitrace.cpp):

  1. thread_function() - 循环检测TracerPid

B.2 按检测技术分类

技术类型 使用的方法
系统属性读取 模拟器检测的7个属性检测方法
文件系统分析 hasReadProcMaps, readProcStatus, checkByMultiApkPackageName
硬件特征检测 getSensorNumber, hasLightSensor, supportCamera等
包管理器API checkByOriginApkPackageName, supportBluetooth等
反射调用 CommandUtil.getProperty, isXposedExists, tryShutdownXposed
Socket通信 checkByCreateLocalServerSocket, checkByPortListening
Shell命令 getUserAppNumber, checkByHasSameUid
异常分析 isXposedExistByThrow
Native检测 antitrace.cpp中的TracerPid循环检测

附录C: 系统属性检测完整列表

属性名 用途 模拟器典型值
ro.hardware 硬件名称 ttvm/nox/cancro/intel/vbox/vbox86/android_x86
ro.build.flavor 编译渠道 含vbox/sdk_gphone
ro.product.model 设备型号 含google_sdk/emulator/android sdk built for x86
ro.product.manufacturer 制造商 含genymotion/netease
ro.product.board 主板名称 含android/goldfish
ro.board.platform 平台名称 含android
gsm.version.baseband 基带版本 null/1.0.0.0
ro.secure 安全模式 0 (eng/userdebug版本)
ro.debuggable 可调试标志 1

附录D: 已知多开软件包名列表

包名 软件名称
com.bly.dkplat 多开分身
com.by.chaos Chaos引擎
com.lbe.parallel 平行空间
com.excelliance.dualaid 双开助手
com.lody.virtual VirtualXposed / VirtualApp
com.qihoo.magic 360分身大师