• 咱们好,我叫 Jack Darren,现在主要负责国内游戏发行 Android SDK 开发

  • 自从前次发布的两篇关于 Android 逆向的文章(Android 逆向入门保姆级教程 和 Android 逆向之脱壳实战篇)火了之后,从中感觉出来咱们对这个系列的文章仍是比较感兴趣的,所以续写了这个系列的文章,这次给咱们带来的是 Xposed 开发相关的文章。

目录

  • Xposed 介绍

  • 集成 Xposed

  • 运用 Xposed

  • Xposed 完结原理

  • Xposed 疑问回答

Xposed 介绍

  • Xposed 结构是一个强壮的 Android 逆向工程工具,它允许开发者在不修正运用程序源代码的状况下,动态地注入和修正Android 运用程序的行为。这使得开发者能够履行各种使命,包含修正运用程序的行为、禁用广告、添加新功能、提高隐私保护等。

  • Xposed 不只能够 Hook 方针运用的 API,还能够 Hook 方针运用调用体系的 API,Xposed 能够监控和操控到方针运用的一切 Java 层的操作。

  • 运用 Xposed 的前提条件

    • 手机有必要 Root:这儿引荐运用 Magisk(面具)

    • 手机有必要装 xp 结构:这儿引荐运用 LSPosed,原因也很简单,由于 XPosed Installer 和 EdXposed 现已弃更了,现在只有 LSPosed 还在更新,讲到这儿,许多同学应该都懵逼了,这三个到底是啥?有什么关系?这三个其实都是 xp 结构,只不过 XPosed Installer 不保护了,后边就有大神根据这个版别保护了 EdXposed 结构,仅仅 EdXposed 结构后边也不保护了,又有大神根据 EdXposed 保护了 LSPosed,前史总是在重蹈覆辙。

集成 Xposed

  • 第一步:在项目主模块下的 build.gradle 文件中加入远程依赖
dependencies {
    // XP 结构:https://github.com/rovo89/Xposed
    compileOnly 'de.robv.android.xposed:api:82'
    compileOnly 'de.robv.android.xposed:api:82:sources'
}
  • 第二步:在 AndroidManifest.xml 中加入装备
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.android.xposed.demo">
    <application>
        <!-- 当时运用是否为 Xposed 模块 -->
        <meta-data
            android:name="xposedmodule"
            android:value="true" />
        <!-- Xposed 模块描绘 -->
        <meta-data
            android:name="xposeddescription"
            android:value="我是模块的描绘" />
        <!-- 最小要求 Xposed 版别号 -->
        <meta-data
            android:name="xposedminversion"
            android:value="53" />
    </application>
</manifest>

Android 逆向之 Xposed 开发

  • 第三步:创建一个 Hook 进口类,示例这儿创建了一个名为 XposedHookMain 类
package com.android.xposed.demo;
public class XposedHookMain implements IXposedHookLoadPackage {
    @Override
    public void handleLoadPackage(LoadPackageParam loadPackageParam) throws Throwable {
        // 打印方针运用的包名
        XposedBridge.log("Loaded app: " + loadPackageParam.packageName);
    }
}
  • 然后在主模块中的 src/main/assets/创建一个名为 xposed_init 文件,并加入刚刚创建的 Hook 类
com.android.xposed.demo.XposedHookMain
  • 第四步:打开 Xposed 结构中启用 Xposed 运用,并且选择这个 Xposed 模块对哪些方针运用收效(效果域)

Android 逆向之 Xposed 开发

Android 逆向之 Xposed 开发

Android 逆向之 Xposed 开发

  • 至此,Xposed 结构运用环境现已搭建完结,能够进行下一步 Hook 操作了

运用 Xposed

  • 我假如想 Hook 方针运用的 Application 类的 onCreate 办法的话,能够添加以下 Hook 代码
public class XposedHookMain implements IXposedHookLoadPackage {
    @Override
    public void handleLoadPackage(LoadPackageParam loadPackageParam) throws Throwable {
        // 打印方针运用的包名
        XposedBridge.log("Loaded app: " + loadPackageParam.packageName);
        XposedHelpers.findAndHookMethod("android.app.Application", loadPackageParam.classLoader, "onCreate", new XC_MethodHook() {
            @Override
            protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                super.beforeHookedMethod(param);
                // Hook 到办法履行前,能够在此处理履行一些代码逻辑,一般常用于修正办法传入的参数
                XposedBridge.log("Hook 到 Application.onCreate 履行前");
            }
            @Override
            protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                super.afterHookedMethod(param);
                // Hook 到办法履行后,能够在此处理履行一些代码逻辑,一般常用于修正办法回来参数
                XposedBridge.log("Hook 到 Application.onCreate 履行前");
            }
        });
    }
}
  • MethodHookParam 类运用介绍
// 当时 Hook 办法的方针实例,假如 Hook 的办法是静态办法便是 null
Object thisObject = param.thisObject;
// 当时 Hook 办法的参数值
Object[] args = param.args;
// 当时 Hook 办法的信息(办法的名称、办法所在类名、办法修饰符、是否为同步办法)
Member method = param.method;
// 获取办法的回来值
Object result = param.getResult();
// 修正办法的回来值
param.setResult(result);
// 判断办法履行是否呈现了反常
param.hasThrowable();
// 获取办法调用呈现的反常
Throwable throwable = param.getThrowable();
// 修正办法调用呈现的反常
param.setThrowable(throwable);
// 回来办法调用的成果,这个成果可能是正常的成果,也可能是一个反常方针
Object resultOrThrowable = param.getResultOrThrowable();
  • 除了 Hook 办法,Xposed 还提供了其他办法,例如:

    • XposedHelpers.findAndHookConstructor:Hook 构建函数

    • XposedHelpers.findField:Hook 字段

    • ……

  • 这些仅仅 API 调用,这儿就不展开细讲了

Xposed 完结原理

  • 其实这个 Xposed 结构 wiki 上面现已写了,由所以英文的,咱们可能不太好了解,所以这儿我跟咱们做一下翻译

Android 体系发动时,有一个名为“Zygote”的进程,它是Android运转时的核心。每个运用程序都是作为它的一个副本(“分支”)发动的。当手机发动时,经过/init.rc脚本发动了这个进程。进程的发动是经过/system/bin/app_process完结的,它加载所需的类并调用初始化办法。

  • 每个运用程序发动的时分的,都会经过 Zygote 进程 fork 出来一个新的进程,那么 Zygote 进程是怎样来的呢?当 Android 体系开机时,会经过 /init.rc 脚本来开启,Zygote 进程的发动是经过 /system/bin/app_process 来完结,在这儿会加载所需求的类并进行初始化。

  • Xposed 的原理其实很简单,便是搞了一个扩展版的 app_process,并进行狸猫换太子,这个扩展版的 app_process 会在发动过程中会将 Xposed Jar 包添加到类加载器中,并初始化 XposedBridge main 函数进口,main 函数其实就干了两件事

    1. initNative:初始化钩子,注册办法、字段等监听

    2. loadModules:加载 xp 模块,便是读取 assets/xposed_init 注册的类名,然后进行反射创建和加载

  • 涉及到的核心代码有以下这些:

XposedBridge#main

public final class XposedBridge {
    @SuppressWarnings("deprecation")
    protected static void main(String[] args) {
        // Initialize the Xposed framework and modules
        try {
            SELinuxHelper.initOnce();
            SELinuxHelper.initForProcess(null);
            runtime = getRuntime();
            if (initNative()) {
                XPOSED_BRIDGE_VERSION = getXposedVersion();
                if (isZygote) {
                    startsSystemServer = startsSystemServer();
                    initForZygote();
                }
                loadModules();
            } else {
                log("Errors during native Xposed initialization");
            }
        } catch (Throwable t) {
            log("Errors during Xposed initialization");
            log(t);
            disableHooks = true;
        }
        // Call the original startup code
        if (isZygote)
            ZygoteInit.main(args);
        else
            RuntimeInit.main(args);
    }
    private static void loadModules() throws IOException {
        final String filename = BASE_DIR + "conf/modules.list";
        BaseService service = SELinuxHelper.getAppDataFileService();
        if (!service.checkFileExists(filename)) {
            Log.e(TAG, "Cannot load any modules because " + filename + " was not found");
            return;
        }
        InputStream stream = service.getFileInputStream(filename);
        BufferedReader apks = new BufferedReader(new InputStreamReader(stream));
        String apk;
        while ((apk = apks.readLine()) != null) {
            loadModule(apk);
        }
        apks.close();
    }
}

runtime#InitNativeMethods

void Runtime::InitNativeMethods() {
  VLOG(startup) << "Runtime::InitNativeMethods entering";
  Thread* self = Thread::Current();
  JNIEnv* env = self->GetJniEnv();
  // Must be in the kNative state for calling native methods (JNI_OnLoad code).
  CHECK_EQ(self->GetState(), kNative);
  // First set up JniConstants, which is used by both the runtime's built-in native
  // methods and libcore.
  JniConstants::init(env);
  // Then set up the native methods provided by the runtime itself.
  RegisterRuntimeNativeMethods(env);
  // Initialize classes used in JNI. The initialization requires runtime native
  // methods to be loaded first.
  WellKnownClasses::Init(env);
  // Then set up libjavacore / libopenjdk, which are just a regular JNI libraries with
  // a regular JNI_OnLoad. Most JNI libraries can just use System.loadLibrary, but
  // libcore can't because it's the library that implements System.loadLibrary!
  {
    std::string error_msg;
    if (!java_vm_->LoadNativeLibrary(env, "libjavacore.so", nullptr, nullptr, &error_msg)) {
      LOG(FATAL) << "LoadNativeLibrary failed for \"libjavacore.so\": " << error_msg;
    }
  }
  {
    constexpr const char* kOpenJdkLibrary = kIsDebugBuild
                                                ? "libopenjdkd.so"
                                                : "libopenjdk.so";
    std::string error_msg;
    if (!java_vm_->LoadNativeLibrary(env, kOpenJdkLibrary, nullptr, nullptr, &error_msg)) {
      LOG(FATAL) << "LoadNativeLibrary failed for \"" << kOpenJdkLibrary << "\": " << error_msg;
    }
  }
  // Initialize well known classes that may invoke runtime native methods.
  WellKnownClasses::LateInit(env);
  VLOG(startup) << "Runtime::InitNativeMethods exiting";
}

runtime#RegisterRuntimeNativeMethods

void Runtime::RegisterRuntimeNativeMethods(JNIEnv* env) {
  register_dalvik_system_DexFile(env);
  register_dalvik_system_VMDebug(env);
  register_dalvik_system_VMRuntime(env);
  register_dalvik_system_VMStack(env);
  register_dalvik_system_ZygoteHooks(env);
  register_java_lang_Class(env);
  register_java_lang_DexCache(env);
  register_java_lang_Object(env);
  register_java_lang_ref_FinalizerReference(env);
  register_java_lang_reflect_AbstractMethod(env);
  register_java_lang_reflect_Array(env);
  register_java_lang_reflect_Constructor(env);
  register_java_lang_reflect_Field(env);
  register_java_lang_reflect_Method(env);
  register_java_lang_reflect_Proxy(env);
  register_java_lang_ref_Reference(env);
  register_java_lang_String(env);
  register_java_lang_StringFactory(env);
  register_java_lang_System(env);
  register_java_lang_Thread(env);
  register_java_lang_Throwable(env);
  register_java_lang_VMClassLoader(env);
  register_java_util_concurrent_atomic_AtomicLong(env);
  register_libcore_util_CharsetUtils(env);
  register_org_apache_harmony_dalvik_ddmc_DdmServer(env);
  register_org_apache_harmony_dalvik_ddmc_DdmVmInternal(env);
  register_sun_misc_Unsafe(env);
}

Xposed 疑问回答

运用 Xposed 结构有什么需求注意的点或许坑吗?
  • 假如你用的是旧版别的 Android Studio,需求在设置中禁用 Instant Run,否则编译时类不会直接包含在 apk 中,会导致 hook 失败

Android 逆向之 Xposed 开发

项目依赖 Xposed 结构为什么用的是 compileOnly,而不是用 implementation 或许 api?
  • 由于没有必要,由于装了 Xposed 模块的体系上面,是存在 Xposed 的调用相关类的,所以没有必要打到包里边去。
Hook 了方针运用之后,没有收效该怎样办?
  • 呈现这种问题大概率是 Xposed 模块晚于方针运用履行,这样就会导致 Hook 不收效,重启一下方针运用即可。
Xposed 能够 Hook native 层的办法吗?
  • 不行,Xposed 只能用于 Java 层代码的 Hook,现在不支持 Native 层代码的 Hook。
Hook 到办法后,当时代码环境是在方针运用的进程中仍是 Xposed 运用的进程中?
  • 是在方针进程中,而不是在 Xposed 运用进程中,由于 Xposed 模块要依赖方针运用才能收效,本质上是寄生在方针的运用上做监控和修正,所以进程不会独立于方针运用。
在没有装 Xposed 结构的手机运转莫非不会崩溃吗?
  • 理论上不会的,由于 Xposed 会经过读取 src/main/assets/xposed_init 文件中的类名,咱们在这个类里边调用 Xposed 的 API,假如用户的手机没有装 Xposed 结构,那么自然也不会去进行这一操作,当然有一种状况在外,假如是经过其他进口的加载的 Hook 类,那就另当别论了。
在项目中运用 XposedBridge.log 打印日志和运用 Log 打印日志有什么区别吗?
  • 区别在于 XposedBridge.log 打印的日志,不只能够在 Logcat 操控台看到,也能够在 Xposed 结构上看到,这儿以 LSPosed 为例,能够在此处看到。

Android 逆向之 Xposed 开发

完结,撒花 ✿✿ヽ(▽)ノ✿