作者 |俞家欢
低头需要勇气,抬头需要实力
引言
平时用着 Android 手机,喜欢折腾的同学或多或少都接触过 Xposed 框架,解锁、Root、刷包,一气呵成。本文将从原理和实践两部分带大家了解 Xposed 框架。
Xposed 框架介绍
Xposed 框架是一个运行在 Android 操作系统之上的钩子框架,是以模块扩展方式来实现对系统部分功能的修改。其可以通过编写模块代码,在不修改 Apk 文件的情况下拦截 Java 函数的调用,自定义函数行为,需要安装在 Root 过的 Android 手机上,需要系统最高权限。其是由 rovo89 大佬开发的,并发布在 GitHub 上的开源项目,项目地址为:https://github.com/rovo89。当前作者已经不再维护项目,仅支持 Android 9 以下系统。对于 Android 9 及其以上的系统,有一些的衍生框架可以支持,如 Exposed、VirtualXposed、太极框架。
Xposed 框架能做什么
Xposed 框架可以说是无所不能,可以编写模块欺骗微信获取随意设置的步数,让你每天都荣登榜首;可以随意设定虚拟定位,让 App 获取到你设定的地理位置;可以解放双手,实现自动化抢红包、领蚂蚁森林等等。总的来说,你可以对安装在设备上的 App 为所欲为。

Tips: 有些应用会检测 Xposed 框架,会造成封号等不可挽回的损失,请谨慎使用。
Xposed 框架的组成
此框架工程由下面五个子项目构成:
Xposed
Xposed 框架 Native 部分,Xposed 框架版的 app_process,用于替换原生 app_process,并为 XposedBridge 提供 JNI 方法。
XposedBridge
Xposed 框架 Java 部分,编译后会生成一个 jar 包,Xposed 框架的 app_process 会将此加入到系统 class path 中。
android_art
Xposed 框架定制的 Android ART。
XposedInstaller
Xposed 框架插件管理 App。
XposedTools
用于编译项目的工具集。

Xposed 框架原理
Android 系统是基于 Linux 的,其第一个由内核启动的用户进程是 init 进程。init 进程随后会创建 zygote 进程,Android 应用程序进程都是由 zygote 进程孵化而来。zygote 所对应的可执行程序是 app_process,xposed 框架通过替换系统的 app_process 可执行文件以及虚拟机动态链接库,让 zygote 在启动应用程序进程时注入框架代码,进而实现对应用程序进程的劫持。
Tips:
以下涉及到的 Android 源代码都是基于 android-8.1.0_r62,在线查看 Android 源码的地址为:https://cs.android.com
以下涉及到的 Xposed 框架原理都是基于 Android 5.0 及其版本之后的分析,也就是基于 ART 虚拟机的实现分析。
Android 系统启动流程
按下电源,引导芯片会从固化的 ROM 处执行预设代码,将 Bootloader 加载到 RAM 中。
Bootloader 设置系统硬件参数,检查 RAM,把操作系统映像文件拷贝到RAM中去,然后跳转到它的入口处去执行。
内核启动,创建第一个内核进程 idle 进程,最终创建第一个用户空间进程 init。
init 进程负责创建 zygote 进程。
init 进程代码分析
init 的入口函数对应的是 system/core/init/init.cpp 文件的 main 方法。
int main(int argc, char** argv) {
......
bool is_first_stage = (getenv("INIT_SECOND_STAGE") == nullptr);
if (is_first_stage) {
// 一阶段工作
}
// 二阶段工作
......
}
从上面代码中可以看到,init 进程启动主要分两个阶段:
一阶段工作
......
// 挂在文件系统
mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");
mkdir("/dev/pts", 0755);
mkdir("/dev/socket", 0755);
mount("devpts", "/dev/pts", "devpts", 0, NULL);
mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC));
chmod("/proc/cmdline", 0440);
gid_t groups[] = { AID_READPROC };
setgroups(arraysize(groups), groups);
mount("sysfs", "/sys", "sysfs", 0, NULL);
mount("selinuxfs", "/sys/fs/selinux", "selinuxfs", 0, NULL);
mknod("/dev/kmsg", S_IFCHR | 0600, makedev(1, 11));
mknod("/dev/random", S_IFCHR | 0666, makedev(1, 8));
mknod("/dev/urandom", S_IFCHR | 0666, makedev(1, 9));
......
setenv("INIT_SECOND_STAGE", "true", 1);
char* path = argv[0];
char* args[] = { path, nullptr };
execv(path, args);此阶段的主要工作是挂载一些虚拟文件系统。方法最后会将 INIT_SECOND_STAGE 置入环境中,并使用 execv 函数,重新执行当前 main 方法。
二阶段工作
......
// 初始化 property 服务
property_init();
property_set("ro.boottime.init", getenv("INIT_STARTED_AT"));
property_set("ro.boottime.init.selinux", getenv("INIT_SELINUX_TOOK"));
unsetenv("INIT_SECOND_STAGE");
unsetenv("INIT_STARTED_AT");
unsetenv("INIT_SELINUX_TOOK");
unsetenv("INIT_AVB_VERSION");
......
// 解析执行 rc 配置文件
std::string bootscript = GetProperty("ro.boot.init_rc", "");
if (bootscript.empty()) {
parser.ParseConfig("/init.rc");
parser.set_is_system_etc_init_loaded(
parser.ParseConfig("/system/etc/init"));
parser.set_is_vendor_etc_init_loaded(
parser.ParseConfig("/vendor/etc/init"));
parser.set_is_odm_etc_init_loaded(parser.ParseConfig("/odm/etc/init"));
} else {
parser.ParseConfig(bootscript);
parser.set_is_system_etc_init_loaded(true);
parser.set_is_vendor_etc_init_loaded(true);
parser.set_is_odm_etc_init_loaded(true);
}
......此阶段的主要工作是启动 property 服务(可以简单类比理解为 Windows 操作系统下的注册表服务)以及加载 .rc 文件。.rc 文件是配置文件,是由 Android 初始化语言(Android Init Language)编写的脚本,这里直接跟看下 system/core/rootdir/init.zygote64.rc 的内容:
service zygote system/bin/app_process64 -Xzygote system/bin --zygote --start-system-server
......service:Android 初始化语言中的一种语法,表示启动一个服务进程。
zygote:为启动服务进程的名字。
/system/bin/app_process:服务执行的入口文件路径。
-Xzygote system/bin、--zygote 以及 --start-system-server:执行时传递进去的参数。
这段内容的解析与执行标志着 zygote 进程的启动,接下来正式进入 zygote 进程的启动流程。
总结一下 init 进程的启动:
创建和挂载启动相关的文件目录。
初始化和启动属性服务。
解析 .rc 配置文件,启动 zygote 进程。
Zygote 进程代码分析
zygote 启动流程对应的源代码是在 frameworks/base/cmds/app_process/app_main.cpp 中:
int main(int argc, char* const argv[])
{
......
++i; // Skip unused "parent dir" argument.
while (i < argc) {
const char* arg = argv[i++];
if (strcmp(arg, "--zygote") == 0) {
zygote = true;
niceName = ZYGOTE_NICE_NAME;
} else if (strcmp(arg, "--start-system-server") == 0) {
startSystemServer = true;
} else if (strcmp(arg, "--application") == 0) {
application = true;
} else if (strncmp(arg, "--nice-name=", 12) == 0) {
niceName.setTo(arg + 12);
} else if (strncmp(arg, "--", 2) != 0) {
className.setTo(arg);
break;
} else {
--i;
break;
}
}
......
}
此处根据解析 .rc 文件,并启动传递进来的参数,程序将标志位 zygote 以及标志位 startSystemServer 置为 true。查看标志位使用的地方:
......
if (startSystemServer) {
args.add(String8("start-system-server"));
}
......
if (zygote) {
runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
} else if (className) {
runtime.start("com.android.internal.os.RuntimeInit", args, zygote);
} else {
fprintf(stderr, "Error: no class name or --zygote supplied.\n");
app_usage();
LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied.");
}
......
依据这个两个标志位,程序会执行 runtime.start 函数,并携带 start-sytem-server 参数。继续跟进 runtime.start,代码在 frameworks/base/core/jni/AndroidRuntime.cpp 中:
void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
{
......
创建虚拟机
JniInvocation jni_invocation;
// 加载 ART 虚拟机的核心动态库,如:libart.so
jni_invocation.Init(NULL);
JNIEnv* env;
// 启动 ART 虚拟机
if (startVm(&mJavaVM, &env, zygote, primary_zygote) != 0) {
return;
}
onVmCreated(env);
......
jclass stringClass;
jobjectArray strArray;
jstring classNameStr;
stringClass = env->FindClass("java/lang/String");
assert(stringClass != NULL);
strArray = env->NewObjectArray(options.size() + 1, stringClass, NULL);
assert(strArray != NULL);
classNameStr = env->NewStringUTF(className);
assert(classNameStr != NULL);
env->SetObjectArrayElement(strArray, 0, classNameStr);
for (size_t i = 0; i < options.size(); ++i) {
jstring optionsStr = env->NewStringUTF(options.itemAt(i).string());
assert(optionsStr != NULL);
env->SetObjectArrayElement(strArray, i + 1, optionsStr);
}
char* slashClassName = toSlashClassName(className != NULL ? className : "");
jclass startClass = env->FindClass(slashClassName);
if (startClass == NULL) {
ALOGE("JavaVM unable to locate class '%s'\n", slashClassName);
} else {
// 找到 com.android.internal.os.ZygoteInit 中 static void main(String argv[]) 方法
jmethodID startMeth = env->GetStaticMethodID(startClass, "main",
"([Ljava/lang/String;)V");
if (startMeth == NULL) {
ALOGE("JavaVM unable to find main() in '%s'\n", className);
} else {
// 执行上述查询到的方法
env->CallStaticVoidMethod(startClass, startMeth, strArray);
}
}
......
}
此处 jni 调用了 com.android.internal.os.ZygoteInit 类中的 main 方法,是程序由执行 c 代码转向执行 java 代码的入口,以这个函数为入口,开始运行 ART 虚拟机。继续跟进代码,来到 Java 层,查看 frameworks/base/core/java/com/android/internal/os/ZygoteInit.java 文件:
public static void main(String[] argv) {
// 创建 Server 端 Socket,用于和其他进程通信
ZygoteServer zygoteServer = new ZygoteServer();
......
Runnable caller;
try {
for (int i = 1; i < argv.length; i++) {
if ("start-system-server".equals(argv[i])) {
// 根据先前的分析,会有 start-system-server 参数
startSystemServer = true;
} else if ("--enable-lazy-preload".equals(argv[i])) {
enableLazyPreload = true;
} else if (argv[i].startsWith(ABI_LIST_ARG)) {
abiList = argv[i].substring(ABI_LIST_ARG.length());
} else if (argv[i].startsWith(SOCKET_NAME_ARG)) {
zygoteSocketName = argv[i].substring(SOCKET_NAME_ARG.length());
} else {
throw new RuntimeException("Unknown command line argument: " + argv[i]);
}
}
......
if (startSystemServer) {
// 启动 SystemServer 进程
Runnable r = forkSystemServer(abiList, zygoteSocketName, zygoteServer);
if (r != null) {
r.run();
return;
}
}
// 等待 ActivityManagerService 的请求来创建新的应用程序进程
caller = zygoteServer.runSelectLoop(abiList);
} catch (Throwable ex) {
Log.e(TAG, "System zygote died with exception", ex);
throw ex;
} finally {
if (zygoteServer != null) {
// 关闭 Socket 监听
zygoteServer.closeServerSocket();
}
}
}
此处代码主要是工作是注册服务端 Socket ,等待 ActivityManagerService 请求,fork 创建应用程序进程。
至此 Zygote 进程启动就结束了。总会一下 zygote 进程启动流程:
调用 AppRuntime start 方法,启动 zygote 进程。
创建 Java 虚拟机,并注册 JNI 方法。
通过 JNI 调用 ZygoteInit 的 main 方法进入 Java 框架层。
注册服务端 Socket ,等待 ActivityManagerService 请求创建应用程序进程。
开启 SystemServer 进程。
Android 启动流程总结
以上即为 Android 系统启动流程,总结归纳梳理成一张流程图:

Xposed 框架实现 Hook 代码分析
在 Xposed 开源工程中,可以看到两个 app_main 文件
app_main.cpp
app_main2.cpp
从 Android.mk 文件中可以看出,app_main.cpp 是针对 Android 5.0 之前的,app_main2.cpp 针对的是 Android 5.0 及其之后的版本。
ifeq (1,$(strip $(shell expr $(PLATFORM_SDK_VERSION) \>= 21)))
LOCAL_SRC_FILES := app_main2.cpp
LOCAL_MULTILIB := both
LOCAL_MODULE_STEM_32 := app_process32_xposed
LOCAL_MODULE_STEM_64 := app_process64_xposed
else
LOCAL_SRC_FILES := app_main.cpp
LOCAL_MODULE_STEM := app_process_xposed
endif
编译之后的可执行文件会在 recovery 模式刷入框架时,替换到系统自带的 app_process。
在 app_main2.cpp main 方法中关键修改点有两处:
// 1.新增
// 对 Xposed 框架的一些启动信息进行检测,打印一些日志
if (xposed::handleOptions(argc, argv)) {
return 0;
}
// 2.修改
if (zygote) {
// 初始化 Xposed 框架
isXposedLoaded = xposed::initialize(true, startSystemServer, NULL, argc, argv);
// 根据 Xposed 框架初始化结果,执行原逻辑/修改的逻辑
runtimeStart(runtime, isXposedLoaded ? XPOSED_CLASS_DOTS_ZYGOTE : "com.android.internal.os.ZygoteInit", args, zygote);
} else if (className) {
isXposedLoaded = xposed::initialize(false, false, className, argc, argv);
runtimeStart(runtime, isXposedLoaded ? XPOSED_CLASS_DOTS_TOOLS : "com.android.internal.os.RuntimeInit", args, zygote);
} else {
fprintf(stderr, "Error: no class name or --zygote supplied.\n");
app_usage();
LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied.");
return 10;
}
查看 initialize 方法,位于 Xposed/xposed.cpp:
bool initialize(bool zygote, bool startSystemServer, const char* className, int argc, char* const argv[]) {
......
xposed->zygote = zygote;
xposed->startSystemServer = startSystemServer;
xposed->startClassName = className;
xposed->xposedVersionInt = xposedVersionInt;
......
// 将 system/framework/XposedBridge.jar 添加到系统 class path 中
return addJarToClasspath();
}
initialize 会将 XposedBridge.jar 添加到 class path 中,这么一来。所有从 zygote fork 的应用程序都具备了 XposedBridge.jar 的代码。初始化成功之后,将会调用 XPOSED_CLASS_DOTS_TOOLS (de.robv.android.xposed.XposedBridge)中的 main 方法,de.robv.android.xposed.XposedBridge 是在 XposedBridge 项目工程中,代码如下:
@SuppressWarnings("deprecation")
protected static void main(String[] args) {
try {
if (!hadInitErrors()) {
......
runtime = getRuntime();
XPOSED_BRIDGE_VERSION = getXposedVersion();
if (isZygote) {
// Hook 相关资源方法
XposedInit.hookResources();
// Hook 相关系统方法
XposedInit.initForZygote();
}
// 加载 Xposed 模块
XposedInit.loadModules();
} else {
Log.e(TAG, "Not initializing Xposed because of previous errors");
}
} catch (Throwable t) {
Log.e(TAG, "Errors during Xposed initialization", t);
disableHooks = true;
}
if (isZygote) {
// 根据上述分析,在 AndroidRuntime::start 中会加载 Xposed 框架的 android_art ART 虚拟机核心库
ZygoteInit.main(args);
} else {
RuntimeInit.main(args);
}
}
此处,Xposed 框架会使用 XposedHelpers.findAndHookMethod 方法去 Hook 系统的一些方法,如:
findAndHookMethod(ActivityThread.class, "handleBindApplication", "android.app.ActivityThread.AppBindData", new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
......
}
});
XposedHelpers.findAndHookMethod 也是开发 Xposed 框架插件最核心的方法,来分析一下此方法的实现。原文件位于 XposedBridge/app/src/main/java/de/robv/android/xposed/XposedHelpers.java。
public static XC_MethodHook.Unhook findAndHookMethod(Class<?> clazz, String methodName, Object... parameterTypesAndCallback) {
if (parameterTypesAndCallback.length == 0 || !(parameterTypesAndCallback[parameterTypesAndCallback.length-1] instanceof XC_MethodHook))
throw new IllegalArgumentException("no callback defined");
XC_MethodHook callback = (XC_MethodHook) parameterTypesAndCallback[parameterTypesAndCallback.length-1];
// 反射机制查询到 Method
Method m = findMethodExact(clazz, methodName, getParameterClasses(clazz.getClassLoader(), parameterTypesAndCallback));
return XposedBridge.hookMethod(m, callback);
}
查看 XposedBridge/app/src/main/java/de/robv/android/xposed/XposedBridge.java 中的 hookMethod 方法:
public static XC_MethodHook.Unhook hookMethod(Member hookMethod, XC_MethodHook callback) {
if (!(hookMethod instanceof Method) && !(hookMethod instanceof Constructor<?>)) {
throw new IllegalArgumentException("Only methods and constructors can be hooked: " + hookMethod.toString());
} else if (hookMethod.getDeclaringClass().isInterface()) {
throw new IllegalArgumentException("Cannot hook interfaces: " + hookMethod.toString());
} else if (Modifier.isAbstract(hookMethod.getModifiers())) {
throw new IllegalArgumentException("Cannot hook abstract methods: " + hookMethod.toString());
}
boolean newMethod = false;
CopyOnWriteSortedSet<XC_MethodHook> callbacks;
synchronized (sHookedMethodCallbacks) {
callbacks = sHookedMethodCallbacks.get(hookMethod);
if (callbacks == null) {
callbacks = new CopyOnWriteSortedSet<>();
sHookedMethodCallbacks.put(hookMethod, callbacks);
newMethod = true;
}
}
callbacks.add(callback);
if (newMethod) {
Class<?> declaringClass = hookMethod.getDeclaringClass();
int slot;
Class<?>[] parameterTypes;
Class<?> returnType;
// slot 在 Android 5.0 以下是 java.reflect.Method 类中的成员,它是这个 Method 在 Dalvik 虚拟机中的地址,因此在 ART 虚拟机中不存在这个成员。
if (runtime == RUNTIME_ART) {
slot = 0;
parameterTypes = null;
returnType = null;
} else if (hookMethod instanceof Method) {
slot = getIntField(hookMethod, "slot");
parameterTypes = ((Method) hookMethod).getParameterTypes();
returnType = ((Method) hookMethod).getReturnType();
} else {
slot = getIntField(hookMethod, "slot");
parameterTypes = ((Constructor<?>) hookMethod).getParameterTypes();
returnType = null;
}
AdditionalHookInfo additionalInfo = new AdditionalHookInfo(callbacks, parameterTypes, returnType);
// private native synchronized static void hookMethodNative(Member method, Class<?> declaringClass, int slot, Object additionalInfo);
// 调用 Native 方法
hookMethodNative(hookMethod, declaringClass, slot, additionalInfo);
}
return callback.new Unhook(hookMethod);
}
hookMethodNative 是 Native 方法,对于 ART 虚拟机具体的实现是在 Xposed/libxposed_art.cpp 文件中:
void XposedBridge_hookMethodNative(JNIEnv* env, jclass, jobject javaReflectedMethod,
jobject, jint, jobject javaAdditionalInfo) {
ScopedObjectAccess soa(env);
if (javaReflectedMethod == nullptr) {
#if PLATFORM_SDK_VERSION >= 23
ThrowIllegalArgumentException("method must not be null");
#else
ThrowIllegalArgumentException(nullptr, "method must not be null");
#endif
return;
}
// javaReflectedMethod 就是 Java 层的 Method 对象,通过它去寻找 ArtMethod
ArtMethod* artMethod = ArtMethod::FromReflectedMethod(soa, javaReflectedMethod);
// 替换方法
artMethod->EnableXposedHook(soa, javaAdditionalInfo);
}
接下来,我们来看下 EnableXposedHook 方法的实现,代码位于 android_art/runtime/art_method.cc,贴上最关键的代码:
mirror::AbstractMethod* reflected_method;// 对于 Hook 的构造方法或者普通方法进行区分if (IsConstructor()) { reflected_method = mirror::Constructor::CreateFromArtMethod(soa.Self(), backup_method);} else { reflected_method = mirror::Method::CreateFromArtMethod(soa.Self(), backup_method);}reflected_method->SetAccessible<false>(true);// Save extra information in a separate structure, stored instead of the native methodXposedHookInfo* hook_info = reinterpret_cast<XposedHookInfo*>(linear_alloc->Alloc(soa.Self(), sizeof(XposedHookInfo)));hook_info->reflected_method = soa.Vm()->AddGlobalRef(soa.Self(), reflected_method);// Hook 对象方法的 before 和 after 实现hook_info->additional_info = soa.Env()->NewGlobalRef(additional_info);// Hook 对象方法的原实现hook_info->original_method = backup_method;...... SetEntryPointFromJniPtrSize(reinterpret_cast<uint8_t*>(hook_info), sizeof(void*));SetEntryPointFromQuickCompiledCode(GetQuickProxyInvokeHandler());SetCodeItemOffset(0);const uint32_t kRemoveFlags = kAccNative | kAccSynchronized | kAccAbstract | kAccDefault | kAccDefaultConflict;SetAccessFlags((GetAccessFlags() & ~kRemoveFlags) | kAccXposedHookedMethod);
这里涉及较深入的 ART 虚拟机知识,大致可以这么理解这一段代码的意思,首先拿到 Hook 对象方法的执行地址,在此地址上替换成增加了 before 以及 after 实现的方法,让 Hook 对象方法运行时,可以按顺序执行 before、原方法以及 after。

以上即为 Xposed 框架实现 Hook 的基本实现。
Xposed 框架模块开发实践
Xposed 框架模块开发最核心的流程就两部,一是寻找应用程序的 Hook 点,即想要去修改的原方法签名(方法名、参数和返回值),二是通过 XposedBridge 提供的 findAndHookMethod、findAndHookConstructor 等 Hook 方法去修改原方法。下面以 Hook 企业微信消息发送按钮,在每次发送按钮点击时弹出 Toast 为例,展示框架模块开发基本流程。
Tips:
示例中的企业微信版本为:3.0.31。
和开发普通的 Android 应用一样,首先创建一个 Android 工程

在 app module 的 build.gradle 中添加 Xposed 框架相关依赖:
compileOnly "de.robv.android.xposed:api:82"
这里使用 compileOnly 表示依赖包只在编译期间参与,打包时不会将此包打入到产物中。理由是,XposedBridge.jar 本声由框架加入到 ClassPath 中。
在 AndroidManifest.xml 中添加如下配置,表明此 App 是 Xposed 框架插件:
<application>
.......
<!-- 标记此应用是 Xposed 模块 -->
<meta-data
android:name="xposedmodule"
android:value="true" >
<!-- 模块描述 -->
<meta-data
android:name="xposeddescription"
android:value="监听企微消息" >
<!-- Xposed 框架最低版本 -->
<meta-data
android:name="xposedminversion"
android:value="54" >
.......
</application>

在 assets 文件夹中创建一个名为 xposed_init 的文本文件,设定程序的入口:
me.jiahuan.xposed.sample.HookLoadPackageEntry

HookLoadPackageEntry 需要继承自 IXposedHookLoadPackage,对设备上的进程进行选择性 Hook:
class HookLoadPackageEntry : IXposedHookLoadPackage {
override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) {
// 只 Hook 企业微信进程
if(lpparam.packageName == "com.tencent.wework") {
// Hook 程序
}
}
}
寻找企业微信 Hook 点,首先利用 jadx工具去反编译企业微信

利用工具反编译之后就能看到混淆后的原代码,此时面对如此庞大的代码库寻找 Hook 点是相当困难的,需要缩小一下范围。可以利用 AndroidStudio 中 Profiler 模块去追踪指定进程 Java 方法的调用,查看点击企业微信发送按钮时调用了哪些 Java 方法,然后再配合 jadx 去查看那一块的相关逻辑。
Profiler 模块使用的前提是应用程序进程是可以 debuggable 的,所以此处需要借助 Magisk 面具(现代化 root 必备)去全局开启设备的 debuggable,这样一来可以观察设备上所有的进程。键入命令如下开启全局 debuggable:
# adb进入命令行模式
adb shell
# 切换至超级用户
su
magisk resetprop ro.debuggable 1
# 一定要通过该方式重启
stop;start;
通过 Profiler 模块,查找到点击发送按钮时执行了 com.tencent.wework.msg.views.MessageEditBar 中的 ftt 方法。

接下来,Hook 一下 ftt 方法,添加 Toast 提示:
class HookLoadPackageEntry : IXposedHookLoadPackage {
override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) {
// 只 Hook 企业微信进程
if (lpparam.packageName == "com.tencent.wework") {
// Hook 程序
XposedHelpers.findAndHookMethod(
"com.tencent.wework.msg.views.MessageEditBar",
lpparam.classLoader,
"ftt",
object :
XC_MethodHook() {
override fun afterHookedMethod(param: MethodHookParam) {
super.afterHookedMethod(param)
// 获取 Context
val context =
XposedHelpers.callMethod(param.thisObject, "getContext") as Context
Toast.makeText(context, "发送按钮点击", Toast.LENGTH_SHORT).show()
}
})
}
}
}
安装到设备上后在 Installer 上勾选相应的模块,重启设备:

最终展示:

Tips:
不同版本的企业微信反编译之后混淆的方法名都会有所不同,严格的说每次编译混淆都会导致方法名不一致。
Xposed 框架带来的安全问题
Xposed 框架对于三方应用开发者来说需要注意的是其带来的安全问题。利用此黑科技技术,一些灰色产业、爬虫数据采集、外挂软件等等也接踵而来。对于某些应用开发者,应该了解去如何去检测 Xposed 框架。
这里简单总结一下网络上搜寻到的检测方案,大致分两大类,一种是 Java 层检测,一种是 Native 层检测。
Java 层检测(不靠谱)
遍历应用安装列表,查找本机是否安装 Xposed Installer 应用(Android 11 无法获取所有安装应用)
通过自造 Crash,读取堆栈信息
从之前的小节了解到,每个应用进程都会由 zygote 进程孵化而来,而 Xposed 框架会走自己独特的 zygote 进程启动流程,所以可以在堆栈中看到带有 Xposed 关键字的错误信息。
检查关键 Java 方法被变为 Native 方法
当一个应用中的 Java 方法被莫名其妙地变成了 Native 方法,则非常有可能被 Xposed 框架 Hook 了。由此可得,检查关键方法是不是变成 Native 方法,也可以检测是否被 Hook。通过反射调用 Modifier.isNative(method.getModifiers()) 方法可以校验方法是不是 Native 方法。
Xposed 框架可以非常简单的替换 Java 方法,其可以完全替换这些检测方法,返回合规的结果,去欺骗应用。因此这一类的检测方法都是不可靠的。
Native 层检测
Native 层使用 C 来解析 /proc/self/maps 文件,搜检 App 自身加载的库中是否存在 XposedBridge.jar、相关的 Dex、Jar 和 So 库等文件。
XposedChecker 开源库:https://github.com/w568w/XposedChecker
参考
1、Android Hook技术防范漫谈:https://tech.meituan.com/2018/02/02/android-anti-hooking.html
2、Android 启动流程:https://www.cnblogs.com/wangjie1990/p/11310913.html
3、Android 在线源码查看:https://cs.android.com/
4、抱歉,Xposed 真的可以为所欲为——终 · 庖丁解码:https://juejin.cn/post/6945000696441896973
全文完
以下文章您可能也会感兴趣:
我们正在招聘 Java 工程师,欢迎有兴趣的同学投递简历到 rd-hr@xingren.com 。





