作者 |俞家欢

低头需要勇气,抬头需要实力

引言

平时用着 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 系统启动流程

  1. 按下电源,引导芯片会从固化的 ROM 处执行预设代码,将 Bootloader 加载到 RAM 中。

  2. Bootloader 设置系统硬件参数,检查 RAM,把操作系统映像文件拷贝到RAM中去,然后跳转到它的入口处去执行。

  3. 内核启动,创建第一个内核进程 idle 进程,最终创建第一个用户空间进程 init。

  4. 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 进程的启动:

  1. 创建和挂载启动相关的文件目录。

  2. 初始化和启动属性服务。

  3. 解析 .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.sojni_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 进程启动流程:

  1. 调用 AppRuntime start 方法,启动 zygote 进程。

  2. 创建 Java 虚拟机,并注册 JNI 方法。

  3. 通过 JNI 调用 ZygoteInit 的 main 方法进入 Java 框架层。

  4. 注册服务端 Socket ,等待 ActivityManagerService 请求创建应用程序进程。

  5. 开启 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.cppLOCAL_MULTILIB := bothLOCAL_MODULE_STEM_32 := app_process32_xposedLOCAL_MODULE_STEM_64 := app_process64_xposed
elseLOCAL_SRC_FILES := app_main.cppLOCAL_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() {@Overrideprotected 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];// 反射机制查询到 MethodMethod 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 >= 23ThrowIllegalArgumentException("method must not be null");
#elseThrowIllegalArgumentException(nullptr, "method must not be null");
#endifreturn;}// javaReflectedMethod 就是 Java 层的 Method 对象,通过它去寻找 ArtMethodArtMethod* 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-dataandroid:name="xposedmodule"android:value="true" /><!-- 模块描述 --><meta-dataandroid:name="xposeddescription"android:value="监听企微消息" /><!-- Xposed 框架最低版本 --><meta-dataandroid: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)// 获取 Contextval context =XposedHelpers.callMethod(param.thisObject, "getContext") as ContextToast.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

全文完


以下文章您可能也会感兴趣:

  • Linux 的 IO 通信 以及 Reactor 线程模型浅析

  • 浅谈 BI 与数据分析的可视化

  • iOS 照片涂鸦功能的实现

  • 捋一捋 App 性能测试中的几个重要概念

  • 所谓 Serverless,你理解对了吗?

  • 如何写好产品中的提示文案

  • Web 开发打印总结

  • JVM 揭秘: 一个 class 文件的前世今生

  • Android 图片编辑的原理与实现 -- 涂鸦与马赛克

  • 微服务环境下的集成测试探索(二)—— 契约式测试

  • 微服务环境下的集成测试探索(一) —— 服务 Stub & Mock

  • Objective-C 中的语法糖

  • Facebook、Google、Amazon 是如何高效开会的

  • 谈谈到底什么是抽象,以及软件设计的抽象原则

  • 后端的缓存系统浅谈

  • 从 React 到 Preact 迁移指南

  • 如何成为一名数据分析师:数据的初步认知

  • 复杂业务状态的处理:从状态模式到 FSM

  • 聊聊移动端跨平台数据库 Realm

我们正在招聘 Java 工程师,欢迎有兴趣的同学投递简历到 rd-hr@xingren.com 。

万物皆可 Hook,探究 Xposed 框架 Hook 原理相关推荐

  1. xposed 修改参数_【Android 原创】2020春节红包第三题Xposed框架Hook的应用

    作者论坛账号:CrazyNut 准备工具以及思路 首先不了解Xposed框架Hook的可以看看大佬的基础教程 - <教我兄弟学Android逆向12 编写xposed模块> 本文不需要会看 ...

  2. 【Android 插件化】Hook 插件化框架 ( Hook Activity 启动流程 | 主线程创建 Activity 实例之前使用插件 Activity 类替换占位的组件 )

    Android 插件化系列文章目录 [Android 插件化]插件化简介 ( 组件化与插件化 ) [Android 插件化]插件化原理 ( JVM 内存数据 | 类加载流程 ) [Android 插件 ...

  3. 【Android 插件化】Hook 插件化框架 ( Hook Activity 启动流程 | AMS 启动前使用动态代理替换掉插件 Activity 类 )

    Android 插件化系列文章目录 [Android 插件化]插件化简介 ( 组件化与插件化 ) [Android 插件化]插件化原理 ( JVM 内存数据 | 类加载流程 ) [Android 插件 ...

  4. 【Android 插件化】Hook 插件化框架 ( Hook Activity 启动流程 | 反射获取 IActivityManager 对象 )

    Android 插件化系列文章目录 [Android 插件化]插件化简介 ( 组件化与插件化 ) [Android 插件化]插件化原理 ( JVM 内存数据 | 类加载流程 ) [Android 插件 ...

  5. 【Android 插件化】Hook 插件化框架 ( Hook Activity 启动流程 | Hook 点分析 )

    Android 插件化系列文章目录 [Android 插件化]插件化简介 ( 组件化与插件化 ) [Android 插件化]插件化原理 ( JVM 内存数据 | 类加载流程 ) [Android 插件 ...

  6. 【Android 插件化】Hook 插件化框架 ( hook 插件化原理 | 插件包管理 )

    Android 插件化系列文章目录 [Android 插件化]插件化简介 ( 组件化与插件化 ) [Android 插件化]插件化原理 ( JVM 内存数据 | 类加载流程 ) [Android 插件 ...

  7. 【Android 插件化】Hook 插件化框架 ( Hook Activity 启动过程 | 静态代理 )

    Android 插件化系列文章目录 [Android 插件化]插件化简介 ( 组件化与插件化 ) [Android 插件化]插件化原理 ( JVM 内存数据 | 类加载流程 ) [Android 插件 ...

  8. 【Android 插件化】Hook 插件化框架 ( Hook 实现思路 | Hook 按钮点击事件 )

    Android 插件化系列文章目录 [Android 插件化]插件化简介 ( 组件化与插件化 ) [Android 插件化]插件化原理 ( JVM 内存数据 | 类加载流程 ) [Android 插件 ...

  9. 【Android 插件化】Hook 插件化框架 ( Hook 技术 | 代理模式 | 静态代理 | 动态代理 )

    Android 插件化系列文章目录 [Android 插件化]插件化简介 ( 组件化与插件化 ) [Android 插件化]插件化原理 ( JVM 内存数据 | 类加载流程 ) [Android 插件 ...

最新文章

  1. java实现fread_fread函数读取到的数据和实际数据不一样
  2. php gd库截图,php使用gd库实现截屏的实例代码
  3. 图像处理之添加文字水印
  4. 清除元素中的子元素html_HTML中的元素简介
  5. 学计算机必须学会模拟电路,2016年广西大学计算机与电子信息学院1304电路分析基础与模拟电子线路之电路分析基础复试笔试仿真模拟题...
  6. winform 报表的基本使用
  7. Servlet原理解析
  8. 为什么都不想去中科创达_那些过年不想回家的人,都去了哪?
  9. 三大迷宫生成算法 (Maze generation algorithm) -- 深度优先,随机Prim,递归分割
  10. 基于JSoup的网络爬虫爬取小说内容
  11. 微信开发者工具 设置wxml属性换行
  12. 飞卡日常进度之K60DN/K60FX/K66对比
  13. 计算机开机按f1,电脑开机要按f1怎么解决 开机按F1的各种解决方法整理
  14. 工具善其事,必先被苦逼的其器所钝伤然后打磨之才能利其器
  15. 修复win10的更新服务器,Win10系统无法更新如何修复Windows Update组件
  16. 专升本英语——语法知识——高频语法——第二节 非谓语动词【学习笔记】
  17. 携程移动端静态页面仿写
  18. 计算机教学得意之处,看不懂没关系,知道厉害就行了:中科大俩教授11年解了两道数学难题...
  19. 原生Hadoop环境下安装Hue
  20. matlab中globalsearch,MATLAB中fmincon和globalsearch、multistart优化问题

热门文章

  1. 关于虚拟机搭建Hadoop的几个坑
  2. el-table样式总结—持续更新
  3. 鸿蒙是另一种安卓吗,鸿蒙不是另一个安卓或者iOS!鸿蒙2.0上线倒计时
  4. 服务器异地备份共享文件夹方法,mssql数据库异地备份的两种方法
  5. 提取网页内容存储为word的方法
  6. numpy 一维矩阵乘法
  7. 红米k40可以升级鸿蒙系统吗,红米k40哪个版本系统版本好
  8. 计算机导航窗格里没有桌面,win10系统资源管理器导航面板没有桌面项的教程
  9. 一个用来学习CoAP协议的小例子
  10. 【机房收费系统】之结账