前言
本文涉及的 Android
源码部分基于 Android 10
。
Xposed
在聊 Xposed
前我们先简单在脑海里回顾下 Zygote
进程的启动流程,如果记不清的可以先看一看 Android Framework 之 Zygote。
入口函数的差异
Zygote
进程 native
层入口函数为 frameworks/base/cmds/app_process/app_main.cpp
,而在 Xposed 项目中也有 app_main.cpp
。
那 Xposed
的这个文件有什么用呢?
其实在 recovery
模式下刷 Xposed
时,它会用 app_main.cpp
或 app_main2.cpp
替换系统的 app_process/app_main.cpp
,在 Android.mk
中有如下代码。
// Android.mk
ifeq (1,$(strip $(shell expr $(PLATFORM_SDK_VERSION) \>= 21)))
LOCAL_SRC_FILES := app_main2.cpp
else
LOCAL_SRC_FILES := app_main.cpp
endif
即版本高于 21 时使用 app_main2.cpp
,否则用 app_main.cpp
。
来看看 app_main2.cpp
有哪些主要的改动。
// app_process/app_main.cpp
if (zygote) {
runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
} else if (className) {
runtime.start("com.android.internal.os.RuntimeInit", args, zygote);
}
// app_main2.cpp
if (zygote) {
isXposedLoaded = xposed::initialize(true, startSystemServer, NULL, argc, argv);
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);
}
可以看到,主要有如下差异:
- 在启动
Zygote
进程或孵化应用进程时会先执行xposed::initialize()
进行初始化; - 执行
runtimeStart()
,注意这里的XPOSED_CLASS_DOTS_ZYGOTE
和XPOSED_CLASS_DOTS_TOOLS
。
xposed::initalize()
先看看 xposed::initialize()
,源码在 xposed.h
和 xposed.cpp
。
// xposed.h
#define XPOSED_JAR "/system/framework/XposedBridge.jar"
// xposed.cpp
bool initialize(bool zygote, bool startSystemServer, const char* className, int argc, char* const argv[]) {
// ...
return addJarToClasspath();
}
bool addJarToClasspath() {
//...
if (access(XPOSED_JAR, R_OK) == 0) {
// 把 XposedBridge.jar 添加到 classPath
if (!addPathToEnv("CLASSPATH", XPOSED_JAR))
return false;
return true;
}
}
总的来说 xposed::initialize()
会把 XposedBridge.jar
添加到 classpath
,这样 Zygote
进程孵化的应用进程就都具备了 XposedBridge.jar
的代码。
执行 runtimeStart()
来看看 runtimeStart()
。
// app_main2.cpp
static void runtimeStart(AppRuntime& runtime, const char *classname, const Vector<String8>& options, bool zygote)
{
#if PLATFORM_SDK_VERSION >= 23
runtime.start(classname, options, zygote);
#else
void (*ptr1)(AppRuntime&, const char*, const Vector<String8>&, bool);
*(void **) (&ptr1) = dlsym(RTLD_DEFAULT, "_ZN7android14AndroidRuntime5startEPKcRKNS_6VectorINS_7String8EEEb");
if (ptr1 != NULL) {
ptr1(runtime, classname, options, zygote);
return;
}
void (*ptr2)(AppRuntime&, const char*, const Vector<String8>&);
*(void **) (&ptr2) = dlsym(RTLD_DEFAULT, "_ZN7android14AndroidRuntime5startEPKcRKNS_6VectorINS_7String8EEE");
if (ptr2 != NULL) {
ptr2(runtime, classname, options);
return;
}
#endif
}
做了版本判断,这里最终还是会调用 AndroidRuntime
的 start()
,了解 Zygote
进程启动流程的应该知道这里主要做了三件事,创建虚拟机,注册 JNI
,通过反射调用 classname
的 main()
。
创建虚拟机
Xposed
在创建虚拟机之后做了一些修改,先看看 AndroidRuntime
的 onVmCreated()
。
// app_main2.cpp
class AppRuntime : public AndroidRuntime
{
virtual void onVmCreated(JNIEnv* env)
{
if (isXposedLoaded)
xposed::onVmCreated(env);
}
}
这里调用 xposed::onVmCreated()
,其源码在 xposed.cpp
。
// xposed.cpp
void onVmCreated(JNIEnv* env) {
// 加载 libxposed_art.so
void* xposedLibHandle = dlopen(xposedLibPath, RTLD_NOW);
// 初始化 xposed 相关 library
bool (*xposedInitLib)(XposedShared* shared) = NULL;
*(void **) (&xposedInitLib) = dlsym(xposedLibHandle, "xposedInitLib");
if (xposedInitLib(xposed)) {
// 执行 onVmCreated(env)
xposed->onVmCreated(env);
}
}
这里通过 dlopen()
函数加载 libxposed_art.so
,在 Android.mk
中有如下定义。
// Android.mk
ifeq (1,$(strip $(shell expr $(PLATFORM_SDK_VERSION) \>= 21)))
include frameworks/base/cmds/xposed/ART.mk
endif
使用 ART.mk
生成 libxposed_art.so
。
// ART.mk
LOCAL_SRC_FILES += \
libxposed_common.cpp \
libxposed_art.cpp
LOCAL_MODULE := libxposed_art
libxposed_art.so
加载后,接着会初始化 xposed
相关 library
,然后调用 xposedInitLib()
,其源码在 libxposed_art.cpp
。
// libxposed_art.cpp
bool xposedInitLib(XposedShared* shared) {
xposed = shared;
xposed->onVmCreated = &onVmCreatedCommon;
return true;
}
这里进行赋值,即 xposed->onVmCreated(env)
会执行 onVmCreatedCommon()
,其源码在 libxposed_common.cpp
。
// libxposed_common.cpp
void onVmCreatedCommon(JNIEnv* env) {
if (!initXposedBridge(env) || !initZygoteService(env)) {
return;
}
if (!onVmCreated(env)) {
return;
}
xposedLoadedSuccessfully = true;
return;
}
initXposedBridge()
会初始化 XposedBridge.java
,initZygoteService()
会初始化 ZygoteService.java
,主要看看 initXposedBridge()
。
bool initXposedBridge(JNIEnv* env) {
classXposedBridge = env->FindClass(CLASS_XPOSED_BRIDGE);
classXposedBridge = reinterpret_cast<jclass>(env->NewGlobalRef(classXposedBridge));
// 这里会注册 native 函数
if (register_natives_XposedBridge(env, classXposedBridge) != JNI_OK) {
return false;
}
// 这里会获取到 XposedBridge 的 handleHookedMethod
methodXposedBridgeHandleHookedMethod = env->GetStaticMethodID(classXposedBridge, "handleHookedMethod",
"(Ljava/lang/reflect/Member;ILjava/lang/Object;Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;");
return true;
}
// 注册 native 函数
int register_natives_XposedBridge(JNIEnv* env, jclass clazz) {
const JNINativeMethod methods[] = {
NATIVE_METHOD(XposedBridge, hadInitErrors, "()Z"),
NATIVE_METHOD(XposedBridge, getStartClassName, "()Ljava/lang/String;"),
NATIVE_METHOD(XposedBridge, getRuntime, "()I"),
NATIVE_METHOD(XposedBridge, startsSystemServer, "()Z"),
NATIVE_METHOD(XposedBridge, getXposedVersion, "()I"),
NATIVE_METHOD(XposedBridge, initXResourcesNative, "()Z"),
NATIVE_METHOD(XposedBridge, hookMethodNative, "(Ljava/lang/reflect/Member;Ljava/lang/Class;ILjava/lang/Object;)V"),
NATIVE_METHOD(XposedBridge, setObjectClassNative, "(Ljava/lang/Object;Ljava/lang/Class;)V"),
NATIVE_METHOD(XposedBridge, dumpObjectNative, "(Ljava/lang/Object;)V"),
NATIVE_METHOD(XposedBridge, cloneToSubclassNative, "(Ljava/lang/Object;Ljava/lang/Class;)Ljava/lang/Object;"),
NATIVE_METHOD(XposedBridge, removeFinalFlagNative, "(Ljava/lang/Class;)V"),
#if PLATFORM_SDK_VERSION >= 21
NATIVE_METHOD(XposedBridge, invokeOriginalMethodNative,
"!(Ljava/lang/reflect/Member;I[Ljava/lang/Class;Ljava/lang/Class;Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"),
NATIVE_METHOD(XposedBridge, closeFilesBeforeForkNative, "()V"),
NATIVE_METHOD(XposedBridge, reopenFilesAfterForkNative, "()V"),
#endif
#if PLATFORM_SDK_VERSION >= 24
NATIVE_METHOD(XposedBridge, invalidateCallersNative, "([Ljava/lang/reflect/Member;)V"),
#endif
};
return env->RegisterNatives(clazz, methods, NELEM(methods));
}
这里主要是注册 native
函数,并获取 handleHookedMethod()
赋值给 methodXposedBridgeHandleHookedMethod
。
总的来说这里主要是加载 libxposed_art.so
,并初始化一些 library
,然后注册 native
函数。
反射调用 XPOSED_CLASS_DOTS_ZYGOTE 的 main()
最终会通过反射调用 XPOSED_CLASS_DOTS_ZYGOTE
的 main()
,其定义位于 xposed.h
。
#define XPOSED_CLASS_DOTS_ZYGOTE "de.robv.android.xposed.XposedBridge"
即最终会执行 de.robv.android.xposed.XposedBridge.main()
。它是 XposedBridge.jar
中的类,源码位于 XposedBridge 项目,来看看其 main()
。
protected static void main(String[] args) {
// 1. 初始化 Xposed framework 和 Xposed module(也就是我们平常写的 Xposed 插件)
try {
if (!hadInitErrors()) {
initXResources();
SELinuxHelper.initOnce();
SELinuxHelper.initForProcess(null);
runtime = getRuntime();
XPOSED_BRIDGE_VERSION = getXposedVersion();
if (isZygote) {
// hook 各版本资源获取与创建
XposedInit.hookResources();
// hook 系统关键方法
XposedInit.initForZygote();
}
// 加载 Xposed Modules
XposedInit.loadModules();
}
} catch (Throwable t) {
disableHooks = true;
}
// 2. 原 Zygote 和 应用进程 启动流程
if (isZygote) {
ZygoteInit.main(args);
} else {
RuntimeInit.main(args);
}
}
这里主要做了如下事情:
- 调用
XposedInit.hookResources()
来hook
系统资源相关方法; - 调用
XposedInit.initForZygote()
来hook Zygote
相关方法; - 调用
XposedInit.loadModules()
来加载Xposed Module
。
这里说下 loadModules()
,来看看 loadModules()
源码。
private static final String INSTALLER_PACKAGE_NAME = "de.robv.android.xposed.installer";
private static final String BASE_DIR = Build.VERSION.SDK_INT >= 24
? "/data/user_de/0/" + INSTALLER_PACKAGE_NAME + "/"
: "/data/data/" + INSTALLER_PACKAGE_NAME + "/";
static void loadModules() throws IOException {
final String filename = BASE_DIR + "conf/modules.list";
ClassLoader topClassLoader = XposedBridge.BOOTCLASSLOADER;
ClassLoader parent;
while ((parent = topClassLoader.getParent()) != null) {
topClassLoader = parent;
}
// 读安装目录的 conf/modules.list 文件
InputStream stream = service.getFileInputStream(filename);
BufferedReader apks = new BufferedReader(new InputStreamReader(stream));
String apk;
while ((apk = apks.readLine()) != null) {
// 调用 loadModule() 加载 xposed module
loadModule(apk, topClassLoader);
}
apks.close();
}
这里会加载安装目录下的 conf/modules.list
文件,然后对每个 xposed module
调用 loadModule()
来进行注册。
private static void loadModule(String apk, ClassLoader topClassLoader) {
DexFile dexFile;
try {
dexFile = new DexFile(apk);
} catch (IOException e) {
return;
}
// 禁止 Android Studio 的 instant run
if (dexFile.loadClass(INSTANT_RUN_CLASS, topClassLoader) != null) {
Log.e(TAG, " Cannot load module, please disable \"Instant Run\" in Android Studio.");
closeSilently(dexFile);
return;
}
// 注意 Xposed Module 对 xposed 库的依赖是 compileOnly,不会被打入到 apk 中
if (dexFile.loadClass(XposedBridge.class.getName(), topClassLoader) != null) {
Log.e(TAG, " Cannot load module:");
Log.e(TAG, " The Xposed API classes are compiled into the module's APK.");
Log.e(TAG, " This may cause strange issues and must be fixed by the module developer.");
Log.e(TAG, " For details, see: http://api.xposed.info/using.html");
closeSilently(dexFile);
return;
}
closeSilently(dexFile);
// 读取 assets/xposed_init 配置文件
ZipFile zipFile = null;
InputStream is;
try {
zipFile = new ZipFile(apk);
ZipEntry zipEntry = zipFile.getEntry("assets/xposed_init");
// 找不到 xposed_init 配置文件
if (zipEntry == null) {
Log.e(TAG, " assets/xposed_init not found in the APK");
closeSilently(zipFile);
return;
}
is = zipFile.getInputStream(zipEntry);
} catch (IOException e) {
closeSilently(zipFile);
return;
}
// 找注册类
ClassLoader mcl = new PathClassLoader(apk, XposedBridge.BOOTCLASSLOADER);
BufferedReader moduleClassesReader = new BufferedReader(new InputStreamReader(is));
try {
String moduleClassName;
while ((moduleClassName = moduleClassesReader.readLine()) != null) {
moduleClassName = moduleClassName.trim();
if (moduleClassName.isEmpty() || moduleClassName.startsWith("#"))
continue;
try {
Class<?> moduleClass = mcl.loadClass(moduleClassName);
if (!IXposedMod.class.isAssignableFrom(moduleClass)) {
continue;
} else if (disableResources && IXposedHookInitPackageResources.class.isAssignableFrom(moduleClass)) {
continue;
}
final Object moduleInstance = moduleClass.newInstance();
if (XposedBridge.isZygote) {
if (moduleInstance instanceof IXposedHookZygoteInit) {
IXposedHookZygoteInit.StartupParam param = new IXposedHookZygoteInit.StartupParam();
param.modulePath = apk;
param.startsSystemServer = startsSystemServer;
((IXposedHookZygoteInit) moduleInstance).initZygote(param);
}
if (moduleInstance instanceof IXposedHookLoadPackage)
XposedBridge.hookLoadPackage(new IXposedHookLoadPackage.Wrapper((IXposedHookLoadPackage) moduleInstance));
if (moduleInstance instanceof IXposedHookInitPackageResources)
XposedBridge.hookInitPackageResources(new IXposedHookInitPackageResources.Wrapper((IXposedHookInitPackageResources) moduleInstance));
}
} catch (Throwable t) {
}
}
} catch (IOException e) {
} finally {
closeSilently(is);
closeSilently(zipFile);
}
}
这里会解析 assets/xposed_init
配置文件,然后对配置中声明的类进行注册。
方法如何被 hook
在 Xposed Module
中,想要 hook
某个方法可以通过 XposedHelpers.findAndHookMethod()
,来看看其源码。
public static XC_MethodHook.Unhook findAndHookMethod(String className, ClassLoader classLoader, String methodName, Object... parameterTypesAndCallback) {
return findAndHookMethod(findClass(className, classLoader), methodName, parameterTypesAndCallback);
}
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");
// 就是 findAndHookMethod() 的最后一个参数
XC_MethodHook callback = (XC_MethodHook) parameterTypesAndCallback[parameterTypesAndCallback.length-1];
// 反射找方法
Method m = findMethodExact(clazz, methodName, getParameterClasses(clazz.getClassLoader(), parameterTypesAndCallback));
// 调用 XposedBridge.hookMethod()
return XposedBridge.hookMethod(m, callback);
}
来看看 XposedBridge.hookMethod()
。
public static XC_MethodHook.Unhook hookMethod(Member hookMethod, XC_MethodHook callback) {
// 方法是否满足 hook 条件
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<XC_MethodHook>();
sHookedMethodCallbacks.put(hookMethod, callbacks);
newMethod = true;
}
}
callbacks.add(callback);
// 主要是这里
if (newMethod) {
Class<?> declaringClass = hookMethod.getDeclaringClass();
int slot;
Class<?>[] parameterTypes;
Class<?> returnType;
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);
// 调用 native 方法
hookMethodNative(hookMethod, declaringClass, slot, additionalInfo);
}
return callback.new Unhook(hookMethod);
}
从这里可以看到,一个方法能否被 hook
,必须满足:
- 是普通的方法或构造器;
- 不是接口方法;
- 不是抽象方法。
最终执行 hook
操作的是 native
方法 hookMethodNative()
,其源码位于 libxposed_art.cpp
void XposedBridge_hookMethodNative(JNIEnv* env, jclass, jobject javaReflectedMethod,
jobject, jint, jobject javaAdditionalInfo) {
// 把 java method 转换成 ArtMethod
ArtMethod* artMethod = ArtMethod::FromReflectedMethod(soa, javaReflectedMethod);
// 这里是真正的 hook 逻辑
artMethod->EnableXposedHook(soa, javaAdditionalInfo);
}
这里将 Java
的 Method
转为 ArtMethod
,然后调用 EnableXposedHook()
,这里是最终做 hook
的地方,其源码位于 android_art 的 art_method.cc
。
void ArtMethod::EnableXposedHook(ScopedObjectAccess& soa, jobject additional_info) {
// 1. 备份
auto* cl = Runtime::Current()->GetClassLinker();
auto* linear_alloc = cl->GetAllocatorForClassLoader(GetClassLoader());
ArtMethod* backup_method = cl->CreateRuntimeMethod(linear_alloc);
backup_method->CopyFrom(this, cl->GetImagePointerSize());
backup_method->SetAccessFlags(backup_method->GetAccessFlags() | kAccXposedOriginalMethod);
// 2. 创建备份方法的反射对象
mirror::AbstractMethod* reflected_method;
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);
// 3. 将信息存放到结构体中
XposedHookInfo* hook_info = reinterpret_cast<XposedHookInfo*>(linear_alloc->Alloc(soa.Self(), sizeof(XposedHookInfo)));
hook_info->reflected_method = soa.Vm()->AddGlobalRef(soa.Self(), reflected_method);
hook_info->additional_info = soa.Env()->NewGlobalRef(additional_info);
hook_info->original_method = backup_method;
// 4. 准备工作,处理函数的JIT即时编译以及其他
ScopedThreadSuspension sts(soa.Self(), kSuspended);
jit::ScopedJitSuspend sjs;
gc::ScopedGCCriticalSection gcs(soa.Self(),
gc::kGcCauseXposed,
gc::kCollectorTypeXposed);
ScopedSuspendAll ssa(__FUNCTION__);
cl->InvalidateCallersForMethod(soa.Self(), this);
jit::Jit* jit = art::Runtime::Current()->GetJit();
if (jit != nullptr) {
jit->GetCodeCache()->MoveObsoleteMethod(this, backup_method);
}
// 5. 设置被 hook 函数的入口点(关键的 hook 逻辑)
// 将 hook 信息存到 entry_point_from_jni 这个指针
SetEntryPointFromJniPtrSize(reinterpret_cast<uint8_t*>(hook_info), sizeof(void*));
// 设替换函数入口点 entry_point_from_quick_compiled_code_ 为自己的 art_quick_proxy_invoke_handler
SetEntryPointFromQuickCompiledCode(GetQuickProxyInvokeHandler());
// 设置函数在 CodeItem 偏移
SetCodeItemOffset(0);
// 更改属性并添加 kAccXposedHookedMethod 标记
const uint32_t kRemoveFlags = kAccNative | kAccSynchronized | kAccAbstract | kAccDefault | kAccDefaultConflict;
SetAccessFlags((GetAccessFlags() & ~kRemoveFlags) | kAccXposedHookedMethod);
MutexLock mu(soa.Self(), *Locks::thread_list_lock_);
Runtime::Current()->GetThreadList()->ForEach(StackReplaceMethodAndInstallInstrumentation, this);
}
这里面涉及太多 art
相关知识,暂时没能力细究,简单说一下,其实就是找到被 hook
函数的地址值,然后替换成另外一个函数,这样当函数执行时会先执行 callback
的 beforeHookedMethod()
,然后执行被 hook
函数,最后执行 callback
的afterHookedMethod()
。
Xposed Module
下面再说说如何编写一个 Xposed Module
,编写 Xposed Module
其实只需要四步即可。
添加对 Xposed 库的依赖
在 app/build.gradle
中添加对 Xposed
库的依赖。
compileOnly 'de.robv.android.xposed:api:82'
compileOnly 'de.robv.android.xposed:api:82:sources'
注意这里依赖的方式为 compileOnly
,不知道为什么的往前看 loadModule()
。
增加 Xposed 配置
在 AndroidManifest.xml
中添加 Xposed
相关配置。
<!-- 标志该 apk 为一个 Xposed 模块,供 Xposed 框架识别-->
<meta-data
android:name="xposedmodule"
android:value="true" />
<!--模块说明,一般为模块的功能描述-->
<meta-data
android:name="xposeddescription"
android:value="这个模块是用来检测用户隐私合规的,在用户未授权同意前,调用接口获取信息属于违规" />
<!--模块兼容版本-->
<meta-data
android:name="xposedminversion"
android:value="54" />
编写 hook 插件
一般情况下会编写类实现 IXposedHookLoadPackage
,并在 handleLoadPackage()
做相关操作。
public class HookTrack implements IXposedHookLoadPackage {
@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) {
// ... 在这里进行相关操作
}
增加 xposed_init 配置
在 assets
目录下新增 xposed_init
配置文件,并添加插件类的全路径声明。
这样 Xposed Module
就编写完了。