目录
  1. 1. 一、Xposed 框架原理
    1. 1.1. 一.1 Zygote 进程在 Android 中的作用
    2. 1.2. 一.2 Xposed 如何劫持 Zygote
    3. 1.3. 一.3 XposedBridge 的初始化过程
    4. 1.4. 一.4 ART 方法 Hook 的底层实现
  2. 2. 二、XposedBridge API 详解
    1. 2.1. 二.1 XposedHelpers
    2. 2.2. 二.2 XC_MethodHook
    3. 2.3. 二.3 XC_MethodReplacement
    4. 2.4. 二.4 XC_LoadPackage
    5. 2.5. 二.5 IXposedHookZygoteInit
  3. 3. 三、Xposed 模块开发完整指南
    1. 3.1. 三.1 项目结构
    2. 3.2. 三.2 build.gradle 配置
    3. 3.3. 三.3 模块 Manifest 声明
    4. 3.4. 三.4 Hook 入口类
    5. 3.5. 三.5 声明入口(assets/xposed_init)
  4. 4. 四、实战案例详解
    1. 4.1. 四.1 监控所有字符串比较(密码/Token泄漏检测)
    2. 4.2. 四.2 Hook 加密操作获取密钥
    3. 4.3. 四.3 Hook Mac.getInstance 获取 HMAC 密钥
    4. 4.4. 四.4 Hook SharedPreferences 读取监控
    5. 4.5. 四.5 绕过 Root 检测
    6. 4.6. 四.6 绕过 SSL Pinning
  5. 5. 五、Xposed 的检测与反检测
    1. 5.1. 五.1 应用检测 Xposed 的常见手段
    2. 5.2. 五.2 开发者在模块中对抗检测
  6. 6. 六、Xposed vs EdXposed vs LSPosed 演进史
    1. 6.1. 六.1 技术演进路线
    2. 6.2. 六.2 LSPosed 的 Zygisk 注入
    3. 6.3. 六.3 特性对比表
  7. 7. 七、AOSP 相关源码导读
  8. 8. 面试常考问题
【逆向安全技术-工具篇】Hook神器XPosed

一、Xposed 框架原理

Xposed 是目前最强大的 Android Java 层 Hook 框架。其核心原理是劫持 Zygote 进程 —— 在 Android 启动过程中,Zygote 是所有应用进程的父进程,Xposed 向 Zygote 注入自己的 app_process,使得每个新应用进程启动时都会加载 XposedBridge.jar

一.1 Zygote 进程在 Android 中的作用

要理解 Xposed 的实现,必须先理解 Zygote。Zygote 是 Android 中第一个 Dalvik/ART 虚拟机进程,所有应用进程都由它 fork 而来:

系统启动流程(简化):
init (PID 1)
├── Zygote (PID ?)
│ ├── system_server (PID ?)
│ │ ├── ActivityManagerService (AMS)
│ │ ├── PackageManagerService (PMS)
│ │ └── WindowManagerService (WMS)
│ ├── com.android.phone
│ ├── com.android.launcher
│ └── com.example.app1 ← 用户启动的应用
└── ...

Zygote fork 的优势:

  1. 预加载共享资源:Zygote 在 fork 前预加载 Framework 核心类(preloaded-classes列表)、公共资源、共享库到内存。fork 之后子进程通过 Copy-On-Write 共享这些页面,节省大量内存。

  2. 快速启动:子进程不需要重新初始化 ART 运行时,直接继承 Zygote 的运行时状态。

Zygote 的关键源码(AOSP):

/frameworks/base/cmds/app_process/app_main.cpp   → app_process 入口
/frameworks/base/core/java/com/android/internal/os/ZygoteInit.java
→ Zygote Java 层初始化
/frameworks/base/core/java/com/android/internal/os/Zygote.java
→ Zygote fork 逻辑
/system/core/rootdir/init.zygote64_32.rc → init 启动脚本

一.2 Xposed 如何劫持 Zygote

Xposed 替换了系统的 /system/bin/app_process 文件。app_process 是 Zygote 的原生入口程序。替换后的流程:

原始链路:
init → /system/bin/app_process → AndroidRuntime::start()
→ com.android.internal.os.ZygoteInit.main()
→ ZygoteServer.runSelectLoop() → fork 子进程

Xposed 替换后的链路:
init → Xposed 修改的 app_process → Xposed 增强的 AndroidRuntime::start()
→ 加载 /system/framework/XposedBridge.jar
→ 调用 XposedBridge.main() 初始化 Hook 框架
→ com.android.internal.os.ZygoteInit.main()
→ ZygoteServer.runSelectLoop() → fork 子进程(此时子进程已包含 Xposed)

在 app_process 中的关键修改(app_main.cpp 层面):

// frameworks/base/cmds/app_process/app_main.cpp(Xposed 修改版本)

int main(int argc, char* const argv[]) {
// ... 常规参数解析 ...

AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv));
// ...

// 原始:直接启动 ZygoteInit
// runtime.start("com.android.internal.os.ZygoteInit", args, zygote);

// Xposed 修改后:先加载 XposedBridge,再启动 ZygoteInit
if (zygote) {
// 1. 将 XposedBridge.jar 加入 classpath
addXposedToClasspath();

// 2. 先调用 de.robv.android.xposed.XposedBridge.main()
// 在其中完成 JNI 层初始化和 ART hook 安装
runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
}
// ...
}

一.3 XposedBridge 的初始化过程

XposedBridge.jar 被加载后的初始化流程:

XposedBridge.main()

├── 1. 加载 libxposed_art.so(JNI 库)
│ system.loadLibrary("xposed_art")

├── 2. 调用 native 方法初始化 ART hook 机制
│ - 修改 ART 中的方法入口表 (ArtMethod)
│ - 安装 JIT 编译器回调拦截
│ - 修改 entry_point_from_quick_compiled_code_

├── 3. 读取已安装模块列表
│ 扫描 /data/data/de.robv.android.xposed.installer/conf/modules.list

├── 4. 加载每个模块的 APK
│ 读取 assets/xposed_init 文件获取入口类名

└── 5. 注册 JIT 编译钩子
当方法被 JIT 编译时,Xposed 拦截编译过程插入 Hook 检查代码

一.4 ART 方法 Hook 的底层实现

这是 Xposed 最核心也最精妙的部分。在 ART 运行时中,每个 Java 方法在 Native 层对应一个 ArtMethod 结构体:

// art/runtime/art_method.h(AOSP,简化版)
class ArtMethod {
public:
// ... 访问标志、方法索引等 ...

protected:
// 方法的入口点(重要!)
// 当方法被调用时,ART 直接跳转到这个地址
void* entry_point_from_quick_compiled_code_; // JIT/AOT 编译后的代码入口

// 如果方法被 Xposed hook 了,这个指针会被修改
// 从原来的编译后代码入口 → 指向 Xposed 的 Hook 分发逻辑
};

Xposed 的工作方式(简化):

原始方法调用流程:
invoke-virtual {v0}, LTarget;->method()V
→ ArtMethod::entry_point_from_quick_compiled_code_
→ 【编译后的 Native 代码】→ 执行方法体

Xposed Hook 后的方法调用流程:
invoke-virtual {v0}, LTarget;->method()V
→ ArtMethod::entry_point_from_quick_compiled_code_
→ 【Xposed 插入的 dispatcher 代码】
→ 遍历注册的 callback (XC_MethodHook)
→ beforeHookedMethod()
→ 原始方法体
→ afterHookedMethod()
→ 返回

关键:Xposed 直接修改了 ArtMethod 对象中的入口点指针。当 ART 调用这个方法时,它会跳转到 Xposed 的控制代码而不是原始方法体。Xposed 将 entry_point 设置为指向一段 trampoline 代码,该 trampoline 先调用所有注册的 before 回调,然后调用原始方法(通过备份的原始入口点),最后调用所有 after 回调。

二、XposedBridge API 详解

Xposed 的核心 API 在 de.robv.android.xposed 包中:

二.1 XposedHelpers

// 查找类和方法
Class<?> cls = XposedHelpers.findClass("com.target.MyClass", classLoader);

// findAndHookMethod 是最常用的方法,它结合了查找和 Hook
XposedHelpers.findAndHookMethod("com.target.MyClass", classLoader,
"targetMethod", // 方法名
String.class, // 参数类型1
int.class, // 参数类型2
new XC_MethodHook() { ... } // Hook 回调
);

// 查找和 Hook 构造函数
XposedHelpers.findAndHookConstructor("com.target.MyClass", classLoader,
String.class,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
// 可以修改构造函数的参数
}
}
);

// 获取静态字段值
int value = XposedHelpers.getStaticIntField(cls, "STATIC_FIELD_NAME");
// 获取实例字段值
Object value = XposedHelpers.getObjectField(instance, "fieldName");

// 调用方法(包括 private 方法)
XposedHelpers.callMethod(instance, "privateMethod", arg1, arg2);
XposedHelpers.callStaticMethod(cls, "staticMethod", arg1);

二.2 XC_MethodHook

// 方法拦截回调
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
// 方法执行前:查看/修改参数
String arg0 = (String) param.args[0];
XposedBridge.log("Called with arg: " + arg0);
param.args[0] = "modified_value"; // 修改参数

// 注意:在 before 中调用 param.setResult()
// 会跳过原始方法的执行和后续 after 回调
// param.setResult("skip_original");
}

@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
// 方法执行后:查看/修改返回值
boolean originalResult = (boolean) param.getResult();
param.setResult(true); // 强制改变返回值

// 也可以获取方法抛出的异常
if (param.getThrowable() != null) {
XposedBridge.log("Exception: " + param.getThrowable());
}
}
};

MethodHookParam 关键字段:

字段 类型 说明
thisObject Object 调用对象(static 方法为 null)
args Object[] 方法参数数组
method Member 被 Hook 的方法或构造函数
result Object (get/set) 方法的返回值
throwable Throwable (get/set) 方法抛出的异常
getResult() Object 获取返回值
setResult(Object) void 设置返回值
getThrowable() Throwable 获取异常
setThrowable(Throwable) void 设置异常

二.3 XC_MethodReplacement

// 完全替换方法实现
XposedHelpers.findAndHookMethod("com.target.MyClass", classLoader,
"checkLicense", new XC_MethodReplacement() {
@Override
protected Object replaceHookedMethod(MethodHookParam param) {
// 这个方法完全替代了原始方法
// 返回的值就是方法的返回值
return true; // 直接返回 true,跳过原始逻辑
}
});

// 也可以返回 null(对于 void 方法)
XC_MethodReplacement.returnConstant(null);

// 静态辅助方法
XC_MethodReplacement.returnConstant(true); // 总是返回 true
XC_MethodReplacement.returnConstant(1); // 总是返回 1
XC_MethodReplacement.DO_NOTHING; // 什么也不做(replacement 体为空)

二.4 XC_LoadPackage

public class MainHook implements IXposedHookLoadPackage {
@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) {
// lpparam.packageName: 正在加载的包名
// lpparam.processName: 进程名称
// lpparam.classLoader: 该应用的 ClassLoader
// lpparam.appInfo: ApplicationInfo 对象
// lpparam.isFirstApplication: 是否第一个加载的包

if (!lpparam.packageName.equals("com.target.app")) return;

XposedBridge.log("Hooking package: " + lpparam.packageName);
// 在此处安装 Hook
}
}

二.5 IXposedHookZygoteInit

在 Zygote 初始化阶段执行,此时还没有任何应用加载:

public class ZygoteHook implements IXposedHookZygoteInit {
@Override
public void initZygote(StartupParam startupParam) throws Throwable {
// modulePath: 模块 APK 的路径
// startsSystemServer: Zygote 是否将启动 system_server

// 此阶段适合:
// 1. Hook 系统框架类(所有应用共享)
// 2. Hook Zygote 相关的初始化逻辑
// 3. 修改系统属性
// 注意:这里不能使用任何应用的 ClassLoader
}
}

三、Xposed 模块开发完整指南

三.1 项目结构

XposedModule/
├── build.gradle
├── src/main/
│ ├── AndroidManifest.xml ← 声明模块属性
│ ├── java/com/example/module/
│ │ └── MainHook.java ← 主 Hook 逻辑
│ └── assets/
│ └── xposed_init ← 声明 Hook 入口类
└── libs/
└── XposedBridgeApi-89.jar ← Xposed API jar(compileOnly)

三.2 build.gradle 配置

apply plugin: 'com.android.application'

android {
compileSdkVersion 33
defaultConfig {
applicationId "com.example.xposedmodule"
minSdkVersion 21
targetSdkVersion 33
versionCode 1
versionName "1.0"
}
}

dependencies {
// Xposed API: compileOnly(编译时需要但不打包进 APK)
compileOnly files('libs/XposedBridgeApi-89.jar')

// 或从 JitPack 获取:
// compileOnly 'de.robv.android.xposed:api:82'
}

三.3 模块 Manifest 声明

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

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="Xposed Module Demo">

<!-- Xposed 模块标志(必须) -->
<meta-data
android:name="xposedmodule"
android:value="true" />

<!-- 模块描述(显示在 Xposed Installer 中) -->
<meta-data
android:name="xposeddescription"
android:value="Bypass license check for TargetApp v3.2.1" />

<!-- 最低 Xposed 版本要求(Bridge 版本号) -->
<meta-data
android:name="xposedminversion"
android:value="82" />

<!-- 可选:支持的作用域(LSPosed 特性) -->
<meta-data
android:name="xposedscope"
android:resource="@array/xposed_scope" />

</application>

</manifest>

res/values/arrays.xml(用于作用域限制):

<resources>
<string-array name="xposed_scope">
<item>com.target.app</item>
<item>com.another.target</item>
</string-array>
</resources>

三.4 Hook 入口类

package com.example.xposedmodule;

import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;

public class MainHook implements IXposedHookLoadPackage {

private static final String TARGET_PACKAGE = "com.target.app";

@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) {
// 过滤目标应用
if (!lpparam.packageName.equals(TARGET_PACKAGE)) return;

XposedBridge.log("[" + TARGET_PACKAGE + "] Starting hooks...");

// Hook 1: 修改返回值
hookVerifyMethod(lpparam.classLoader);

// Hook 2: 监控方法调用
hookLogMethod(lpparam.classLoader);

// Hook 3: 绕过签名校验
hookSignatureCheck(lpparam.classLoader);

XposedBridge.log("[" + TARGET_PACKAGE + "] Hooks installed.");
}

private void hookVerifyMethod(ClassLoader cl) {
try {
XposedHelpers.findAndHookMethod(
"com.target.app.VerifyActivity",
cl,
"verify",
String.class,
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
XposedBridge.log("verify() returned: " + param.getResult());
param.setResult("success"); // 强制返回 "success"
}
}
);
} catch (XposedHelpers.ClassNotFoundError e) {
XposedBridge.log("Class not found: " + e.getMessage());
}
}

private void hookLogMethod(ClassLoader cl) {
try {
XposedHelpers.findAndHookMethod(
"com.target.app.util.Logger",
cl,
"log",
String.class,
String.class,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
// 收集所有日志输出
String tag = (String) param.args[0];
String msg = (String) param.args[1];
XposedBridge.log("[AppLog] " + tag + ": " + msg);
}
}
);
} catch (XposedHelpers.ClassNotFoundError ignored) {}
}

private void hookSignatureCheck(ClassLoader cl) {
try {
XposedHelpers.findAndHookMethod(
"com.target.app.security.SignatureChecker",
cl,
"isValidSignature",
new XC_MethodReplacement() {
@Override
protected Object replaceHookedMethod(MethodHookParam param) {
return true; // 始终返回签名有效
}
}
);
} catch (XposedHelpers.ClassNotFoundError ignored) {}
}
}

三.5 声明入口(assets/xposed_init)

com.example.xposedmodule.MainHook

四、实战案例详解

四.1 监控所有字符串比较(密码/Token泄漏检测)

// Hook String.equals 监控所有字符串比较
XposedHelpers.findAndHookMethod(String.class, "equals", Object.class,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
String thisStr = (String) param.thisObject;
String other = (String) param.args[0];

// 过滤噪声(短的、常见的字符串)
if (thisStr == null || other == null) return;
if (thisStr.length() < 6 || other.length() < 6) return;

XposedBridge.log("String.equals: [" + thisStr
+ "] == [" + other + "] ?");
}
});

四.2 Hook 加密操作获取密钥

// Hook Cipher.init 获取加密密钥和算法
XposedHelpers.findAndHookMethod(
"javax.crypto.Cipher",
lpparam.classLoader,
"init",
int.class,
Key.class,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
int opmode = (int) param.args[0];
Key key = (Key) param.args[1];

String opStr;
switch (opmode) {
case 1: opStr = "ENCRYPT"; break;
case 2: opStr = "DECRYPT"; break;
default: opStr = "UNKNOWN(" + opmode + ")";
}

XposedBridge.log("Cipher.init: mode=" + opStr
+ ", algorithm=" + key.getAlgorithm());

if (key instanceof SecretKeySpec) {
SecretKeySpec sk = (SecretKeySpec) key;
String keyHex = bytesToHex(sk.getEncoded());
XposedBridge.log("Cipher key (hex): " + keyHex);
}
}
});

// 辅助方法:byte[] 转 16 进制
private static String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02x", b & 0xff));
}
return sb.toString();
}

四.3 Hook Mac.getInstance 获取 HMAC 密钥

XposedHelpers.findAndHookMethod(
"javax.crypto.Mac",
lpparam.classLoader,
"init",
Key.class,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
Key key = (Key) param.args[0];
if (key instanceof SecretKeySpec) {
byte[] keyBytes = ((SecretKeySpec) key).getEncoded();
XposedBridge.log("HMAC key: " + bytesToHex(keyBytes));
}
}
});

四.4 Hook SharedPreferences 读取监控

// 监控所有 SharedPreferences 读取
XposedHelpers.findAndHookMethod(
"android.app.SharedPreferencesImpl",
lpparam.classLoader,
"getString",
String.class,
String.class,
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
String key = (String) param.args[0];
String def = (String) param.args[1];

// 过滤常见 key
if (key.contains("token") || key.contains("secret")
|| key.contains("password") || key.contains("auth")) {
XposedBridge.log("SharedPreferences.getString: "
+ key + " = " + param.getResult());
}
}
});

四.5 绕过 Root 检测

多数 Android 应用的 Root 检测依赖以下技术,均可通过 Xposed 逐一绕过:

// 方式1:Hook Runtime.exec 阻止 su 命令执行
XposedHelpers.findAndHookMethod(
"java.lang.Runtime",
lpparam.classLoader,
"exec",
String[].class,
String[].class,
File.class,
new XC_MethodReplacement() {
@Override
protected Object replaceHookedMethod(MethodHookParam param) {
String[] cmd = (String[]) param.args[0];
for (String c : cmd) {
if (c != null && (c.contains("su") || c.contains("which"))) {
XposedBridge.log("Blocked shell command: " + c);
throw new IOException("Permission denied");
}
}
try {
return XposedBridge.invokeOriginalMethod(
param.method, param.thisObject, param.args);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
});

// 方式2:Hook File.exists 隐藏 root 相关文件
XposedHelpers.findAndHookMethod(File.class, "exists",
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
String path = ((File) param.thisObject).getAbsolutePath();
if (path.contains("su") || path.contains("Superuser")
|| path.contains("magisk") || path.contains("supersu")) {
param.setResult(false);
XposedBridge.log("Hidden root file: " + path);
}
}
});

// 方式3:Hook PackageManager 隐藏 Magisk/SuperSU
XposedHelpers.findAndHookMethod(
"android.app.ApplicationPackageManager",
lpparam.classLoader,
"getInstalledPackages",
int.class,
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
@SuppressWarnings("unchecked")
List<PackageInfo> packages = (List<PackageInfo>) param.getResult();
List<PackageInfo> filtered = new ArrayList<>();
for (PackageInfo pkg : packages) {
String name = pkg.packageName;
if (!name.contains("magisk") && !name.contains("supersu")
&& !name.contains("de.robv.android.xposed")) {
filtered.add(pkg);
}
}
param.setResult(filtered);
}
});

// 方式4:Hook Build.TAGS 隐藏 test-keys
XposedHelpers.findAndHookMethod(
"android.os.Build",
lpparam.classLoader,
"getSerial",
new XC_MethodReplacement() {
@Override
protected Object replaceHookedMethod(MethodHookParam param) {
// Build.TAGS 为 test-keys 表示调试/开发者版本
// 替换为 release-keys 绕过检测
return "release-keys";
}
});

四.6 绕过 SSL Pinning

// Hook 所有 SSL 证书验证方法
// 方案一:Hook javax.net.ssl.HttpsURLConnection
XposedHelpers.findAndHookMethod(
"javax.net.ssl.HttpsURLConnection",
lpparam.classLoader,
"setDefaultHostnameVerifier",
HostnameVerifier.class,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
// 替换为信任所有主机名的 verifier
param.args[0] = new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
};
}
});

// 方案二:Hook okhttp3.CertificatePinner(如果使用 OkHttp)
try {
Class<?> certPinnerCls = XposedHelpers.findClass(
"okhttp3.CertificatePinner", lpparam.classLoader);
XposedHelpers.findAndHookMethod(certPinnerCls,
"check", String.class, List.class,
new XC_MethodReplacement() {
@Override
protected Object replaceHookedMethod(MethodHookParam param) {
// 空方法,不做任何证书检查
return null;
}
});
} catch (XposedHelpers.ClassNotFoundError e) {
// 应用没有使用 OkHttp
}

// 方案三:Hook TrustManager 实现
// 替换默认的 TrustManager,接受所有证书

五、Xposed 的检测与反检测

五.1 应用检测 Xposed 的常见手段

// 检测 1:检查 ClassLoader 中是否加载了 Xposed 相关类
public static boolean checkXposedByClassLoader() {
try {
ClassLoader.getSystemClassLoader()
.loadClass("de.robv.android.xposed.XposedBridge");
return true; // Xposed 类被加载了
} catch (ClassNotFoundException e) {
return false;
}
}

// 检测 2:检查抛出的异常栈中是否包含 Xposed 方法
public static boolean checkXposedByStackTrace() {
try {
throw new Exception("check");
} catch (Exception e) {
for (StackTraceElement element : e.getStackTrace()) {
if (element.getClassName().contains("de.robv.android.xposed")) {
return true;
}
}
}
return false;
}

// 检测 3:通过 PackageManager 查找 Xposed Installer
public static boolean checkXposedByPackage(Context context) {
try {
List<PackageInfo> packages = context.getPackageManager()
.getInstalledPackages(0);
for (PackageInfo info : packages) {
if (info.packageName.equals("de.robv.android.xposed.installer")) {
return true;
}
}
} catch (Exception ignored) {}
return false;
}

// 检测 4:检查 /proc/self/maps 中的 Xposed 库
public static boolean checkXposedByMaps() {
try {
BufferedReader reader = new BufferedReader(
new FileReader("/proc/self/maps"));
String line;
while ((line = reader.readLine()) != null) {
if (line.contains("xposed") || line.contains("XposedBridge")) {
reader.close();
return true;
}
}
reader.close();
} catch (Exception ignored) {}
return false;
}

// 检测 5:检查系统属性
public static boolean checkXposedByProp() {
try {
Class<?> systemProperties = Class.forName("android.os.SystemProperties");
Method get = systemProperties.getMethod("get", String.class);
// ro.dalvik.vm.native.bridge 在 Xposed 环境下会被修改
String value = (String) get.invoke(null, "ro.dalvik.vm.native.bridge");
return value != null && !value.isEmpty();
} catch (Exception ignored) {}
return false;
}

五.2 开发者在模块中对抗检测

// 反检测方案 1:Hook 检测方法使其返回 false
XposedHelpers.findAndHookMethod(
"com.target.app.security.AntiHookChecker",
lpparam.classLoader,
"checkXposedByClassLoader",
new XC_MethodReplacement() {
@Override
protected Object replaceHookedMethod(MethodHookParam param) {
return false;
}
});

// 反检测方案 2:Hook PackageManager 隐藏自身
// 见上文"绕过 Root 检测"方式3

// 反检测方案 3:Hook /proc/self/maps 读取
XposedHelpers.findAndHookMethod(
"java.io.BufferedReader",
lpparam.classLoader,
"readLine",
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
String line = (String) param.getResult();
if (line != null && (line.contains("xposed")
|| line.contains("XposedBridge")
|| line.contains("libxposed"))) {
XposedBridge.log("Filtered maps line: " + line);
param.setResult(null); // 隐藏这一行
}
}
});

六、Xposed vs EdXposed vs LSPosed 演进史

六.1 技术演进路线

Xposed (Original, 2012-2017)
│ 原理:替换 app_process
│ Android: 4.0 - 8.x
│ 作者:rovo89
│ 状态:已停止维护


Xposed for ART (2015-2018)
│ 原理:ART Hook(修改 ArtMethod.entry_point)
│ 支持:5.0 - 8.x


EdXposed (2019-2021)
│ 原理:Riru(注入 Zygote)→ 加载 EdXposedBridge
│ Android: 9 - 12
│ 作者:ElderDrivers
│ 状态:已停止维护


LSPosed (2021-至今)
│ 原理:Zygisk(Magisk 内置注入)→ 加载 LSPosed
│ Android: 8.1 - 14
│ 作者:LSPosed 团队
│ 状态:活跃维护

六.2 LSPosed 的 Zygisk 注入

LSPosed 利用 Magisk 的 Zygisk 功能,无需替换 app_process,而是通过 Zygisk 的注入机制在 Zygote 进程中加载自己的 Native 库:

Magisk Zygisk 注入流程:
Zygote fork
→ Zygisk 拦截
→ 通过 `LD_PRELOAD` 式机制加载 libzygisk.so
→ libzygisk.so 加载 LSPosed 的 so
→ LSPosed so 在 ART 初始化后安装 Hook
→ 加载 LSPosedBridge.jar
→ 读取模块列表并激活

优势:
- 无需修改 system 分区(全部在 Magisk 层面完成)
- 更难被检测(无 app_process 替换痕迹)
- 支持作用域(scope)限制,只 Hook 指定应用

六.3 特性对比表

特性 Xposed (Original) EdXposed LSPosed
原理 替换 app_process Riru / Zygisk 注入 Zygisk 注入
Android 版本 5.0 - 8.x 9 - 12 8.1 - 14
检测难度 极易被检测 中等 较难检测
模块兼容性 100% ~90% ~95%
作用域限制
系统资源占用 极小
维护状态 停止更新 (2017) 停止更新 (2021) 活跃维护 (2026)
安装复杂度 中(需刷入) 高(需 Riru+Magisk) 低(Magisk 模块)
仓库 github.com/rovo89 github.com/ElderDrivers/EdXposed github.com/LSPosed/LSPosed

七、AOSP 相关源码导读

理解 ART 运行时有助于掌握 Xposed 的实现限制和边界:

模块 源码路径 关键内容
app_process /frameworks/base/cmds/app_process/ Zygote 入口(app_process 替换点)
ZygoteInit frameworks/base/core/java/.../ZygoteInit.java Zygote Java 层初始化
Zygote frameworks/base/core/java/.../Zygote.java fork 和进程管理
ART Runtime /art/runtime/runtime.cc ART 启动与初始化
ArtMethod /art/runtime/art_method.h Java 方法的 Native 表示(Hook 目标)
ClassLinker /art/runtime/class_linker.cc 类加载与方法解析
JIT Compiler /art/runtime/jit/ JIT 编译钩子(Xposed 拦截点)
JNI /art/runtime/jni_internal.cc JNI 调用桥接

关键源码片段——ArtMethod 中 entry_point 的结构:

// art/runtime/art_method.h (AOSP, 简化)
class ArtMethod {
public:
// ...
// 指向当前方法入口代码的指针
// 可能指向:
// 1. 解释器入口 (entry_point_from_interpreter_)
// 2. JIT 编译后代码 (entry_point_from_jit_)
// 3. AOT 编译后代码 (entry_point_from_quick_compiled_code_)
void* entry_point_from_quick_compiled_code_;

// 当 Xposed 安装 Hook 后,这个指针被替换为指向 Xposed 的分发代码
// Xposed 保存原始值用于"恢复"原始调用
};

面试常考问题

Q1:Xposed Hook 和 Frida Hook 的区别?各自适用场景?

A:

区别:
(1)工作层级:Xposed 工作在 Zygote 层(Android 启动时注入),Frida 工作在应用运行时(动态注入 frida-agent.so)。

(2)Hook 范围:Xposed 仅支持 Java 层 Hook(通过修改 ART 的 ArtMethod.entry_point),Frida 同时支持 Java 层和 Native 层 Hook(Java 层通过 ART 内部 API,Native 层通过 Gum 的 Interceptor)。

(3)持久性:Xposed 的 Hook 在设备重启后持续生效,与目标进程生命周期绑定;Frida 默认是临时的(断开后 Hook 失效),除非使用 frida-gadget 嵌入目标应用。

(4)开发语言:Xposed 模块使用 Java 编写(打包为 APK),Frida 使用 JavaScript/Python 编写脚本。

(5)灵活性:Frida 可以随时 attach/detach,实时修改脚本;Xposed 修改模块后需要软重启/重启设备。

适用场景:

  • Xposed:需要长期运行、发布给非技术用户使用的模块(如广告拦截、隐私保护、功能增强);需要 Hook 系统 Framework 级别的多个应用。
  • Frida:逆向分析时的动态探索、临时验证猜想、自动化 Fuzzing、不需要持久化的安全审计。

Q2:为什么有些应用能检测到 Xposed?如何绕过?

A:

常见检测方式:
(1)ClassLoader 检测:尝试 Class.forName("de.robv.android.xposed.XposedBridge") 判断是否抛 ClassNotFoundException。

(2)PackageManager 检测:扫描已安装包中是否包含 de.robv.android.xposed.installer

(3)文件系统检测:检查 /system/framework/XposedBridge.jar/system/lib/libxposed_art.so 等文件是否存在。

(4)异常栈检测:抛异常并检查调用栈中是否出现 Xposed 相关的类名或方法名。

(5)/proc/self/maps 检测:读取进程内存映射,检查是否加载了 Xposed 相关的 so 文件。

(6)系统属性检测:检查 ro.dalvik.vm.native.bridge 等属性是否被修改。

(7)方法签名检测:对关键方法做 CRC/哈希校验,判断是否被修改过。

绕过手段:

  • 使用 LSPosed(自带 Zygisk 级别的隐藏,比 EdXposed 更难检测)
  • 配合 Magisk + Shamiko 模块隐藏 Zygisk 和 Xposed 特征
  • 在自定义模块中主动 Hook 这些检测方法返回假值
  • 使用 Xposed Hide 功能(LSPosed 自带,对特定应用禁用 Xposed)
  • 如果目标使用 Native 层检测,需要结合 Frida 在更底层进行对抗

Q3:beforeHookedMethod 和 afterHookedMethod 的执行顺序是怎样的?如果多个模块 Hook 了同一个方法会怎样?

A:

单个 Hook 的执行顺序:beforeHookedMethod → 原始方法 → afterHookedMethod。如果在 beforeHookedMethod 中调用 param.setResult(),会跳过原始方法执行,但仍会调用 afterHookedMethod。

多个模块 Hook 同一方法时,Xposed 按模块加载顺序(modules.list 中的顺序)构建 Hook 链:

模块A 加载 → 模块B 加载 → 模块C 加载

调用链:
模块A.before → 模块B.before → 模块C.before
→ 原始方法
→ 模块C.after → 模块B.after → 模块A.after

关键行为:
(1)任何 before 回调中调用 param.setResult() 会跳过原始方法和后续所有 before 回调,但 after 回调仍会被调用(基于链中已注册的回调)。

(2)任何 after 回调中可以查看 param.getResult()(可能是原始方法返回的,也可能是某个 after 回调通过 setResult 修改后的)。

(3)修改 param.args[] 在 before 中有效,后续回调和原始方法看到的都是修改后的参数。

(4)如果某个模块使用 XC_MethodReplacement,它不再是一个”拦截器”而是完全替代了原始方法——在这种情况下,原始方法和 after 回调都不会被调用,除非 Replacement 实现中显式调用了 XposedBridge.invokeOriginalMethod()

Q4:Xposed 在 Android 8.0+ 上面临的主要技术挑战是什么?

A:

(1)AOT 编译的变化:Android 7.0+ 引入了混合编译模式(JIT + AOT),系统不再全量 AOT 编译所有方法。方法可能在运行时由 JIT 编译,而 Xposed 必须在 JIT 编译后重新 patch entry_point。这要求 Xposed 实现 JIT 编译完成的回调机制。

(2)Profile-guided compilation:Android 使用执行 profile 指导 AOT 编译,同一个方法可能被编译多次(不同优化级别)。每次重新编译后 Xposed 都需要重新安装 Hook。

(3)DEX 布局限制:Android 8.0+ 增强了 DEX 编译产物的完整性验证,包括对 ArtMethod 结构体的额外 CRC 检查。

(4)系统分区完整性保护(dm-verity):Android 8.0+ 的系统分区默认启用 dm-verity,任何对 /system 的修改(如替换 app_process)都会导致设备无法启动。这正是 EdXposed/LSPosed 转而使用 Riru/Zygisk 注入而非替换 app_process 的根本原因。

(5)SELinux 策略收紧:新版本 SELinux 策略更加严格,限制了应用进程对系统资源的访问,增加了注入难度。

(6)隐藏 API 限制:Android 9+ 引入了隐藏 API 限制(Hidden API Restrictions),而 Xposed 本身就大量使用了隐藏 API。LSPosed 需要绕过这些限制才能正常工作。

Q5:Xposed 修改 ArtMethod.entry_point 为什么能实现 Hook?在这个过程中有哪些需要注意的边界条件?

A:

原理:ART 中每个 Java 方法在 Native 层对应一个 ArtMethod 对象。当方法被调用时,ART 直接从 ArtMethod::entry_point_from_quick_compiled_code_ 指针跳转到编译后的代码。Xposed 修改这个指针,使其指向 Xposed 的分发 trampoline,从而拦截每一次方法调用。

边界条件:
(1)解释执行 vs 编译执行:方法可能同时被解释执行(通过 art_quick_to_interpreter_bridge)和编译执行(通过 entry_point 指针)。Xposed 需要确保两种路径都被拦截。对于解释执行,Xposed 修改方法访问标志(access_flags),使其进入解释器时也经过 Hook 检查。

(2)JIT 重编译:JIT 可能在运行时重新编译方法(生成新的优化代码),并更新 entry_point 指向新代码。Xposed 必须注册 JIT 编译完成的事件回调,在每次 JIT 编译后重新安装 Hook。

(3)内联(Inlining):JIT/AOT 编译器可能将被调用的小方法直接内联到调用者代码中,从而绕过了被调用方法的 entry_point。Xposed 需要强制 ART 不对被 Hook 的方法执行内联优化。

(4)JNI 方法:native 方法(通过 JNI 注册)的 entry_point 指向 JNI 桥接代码,而非普通的解释/编译入口。Xposed 对 JNI 方法的 Hook 走的是不同的路径——它修改 JNI 入口表中的函数指针而非 ArtMethod.entry_point。

(5)多线程并发:在 Xposed 修改 entry_point 时,目标方法可能正在被其他线程执行(特别是对于系统框架中频繁调用的方法)。这种并发修改可能导致崩溃。Xposed 需要处理这个问题(通过暂停其他线程或使用原子操作)。

(6)Android 版本差异:不同 Android 版本中 ArtMethod 的内存布局不同,entry_point 的偏移也可能不同。Xposed(及其变体)需要针对每个 ART 版本适配(这解释了为什么 Xposed 不兼容所有 Android 版本,以及为什么 LSPosed 需要频繁更新以支持新的 Android 版本)。

打赏
  • 微信
  • 支付宝

评论