0x1、Xposed的组成
这个庞大的工程由下面四个项目组成:
Xposed
→ C++部分,Xposed版的zygote,用于替换原生zygote,并为XposedBridge提供JNI方法,需由XposedInstaller在root后放到/system/bin目录下;
XposedBridge
→ Java部分,编译后会生成一个jar包,负责在Native层与Framework层进行交互;
XposedInstaller
→ Xposed插件管理及功能控制的APP,包括启用、下载、禁用插件等功能;
XposedTools
→ 用于编译Xposed及XposedBridge;
0x2、Xposed的使用
一个简单的Hook示例如下:
几步走:
- ① 类实现
IXposedHookLoadPackage
接口
- ② 重写
handleLoadPackage()
方法
- ③ 调用
XposedHelpers.findAndHookMethod()
,传入完整类名、类加载器、方法名,参数类型,XC_MethodHook实例;
- ④ 按需重写
beforeHookedMethod()
(方法调用前执行代码) 和afterHookedMethod()
方法(方法调用后执行代码);
通过这样的操作,即可随意蹂躏一个Java方法的逻辑,以达到自己的目的。
0x3、Android系统的启动过程
在开始探索实现原理前,先来了解下Android系统的启动过程,简略流程图如下:
关注 Zygote进程
, 它由 init进程
启动,启动时会创建一个Davlik虚拟机实例,并把Java运行时库加载到进程中,并注册一些Android核心类的JNI到前面创建的Dalvik虚拟机实例中。
所有APP进程都是由Zygote进程孵化(fork)
而来的,fork时不仅仅会获得Zygote进程中的Dalvik虚拟机实例拷贝,还会与Zygote一起 共享Java运行时库
。
Xposed就是利用这样的机制,只往Zygote中注入 XposedBridge.jar
,就可以实现全局注入。
Tips:Android 5.0 开始zygote是两个进程,32位(兼容armeabi和armeabi-v7a等32 位架构的本地动态库的应用) 和 64位(arm64-v8a等64 位架构本地库动态库),init.rc 文件也做了区分,init.zygote32.rc 启动 32位的zygote,init.zygote64 启动 64位的zygote。
0x4、Zygote的启动流程
跟下 /system/core/rootdir/init.zygote.rc
:
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
分段解析下这段代码:
- service → ATL语言语法,启动一个服务进程;
- zygote → 启动的程序名称,这指zygote进程;
- /system/bin/app_process → 可执行文件路径( app_main.cpp );
- -Xzygote /system/bin → 指定参数传到app_main.cpp中;
- --zygote --start-system-server → 传的具体参数值;
简单点说就是:启动了Zygote进程,传递的参数可在 /frameworks/base/cmds/app_process/app_main.cpp
中找到:
对传进来的参数做匹配,zygote、startSystem标志位设置为true,接着定位下哪里用到了zygote这个标记:
跟下:runtime.start()
定位到 frameworks/base/core/jni/AndroidRuntime.cpp
,关键代码如下:
// ① 初始化jni接口 JniInvocation jni_invocation; jni_invocation.Init(NULL); // ② 创建VM虚拟机 JNIEnv* env; if (startVm(&mJavaVM, &env) != 0) { return; } onVmCreated(env); // ③ 注册JNI方法 if (startReg(env) < 0) { ALOGE("Unable to register all android natives\n"); return; } // ④ 调用className类的static void main(String args[]) 方法 slashClassName = toSlashClassName(className); jclass startClass = env->FindClass(slashClassName); // 找到main函数 jmethodID startMeth = env->GetStaticMethodID(startClass, "main", "([Ljava/lang/String;)V"); if (startMeth == NULL) { ALOGE("JavaVM unable to find main() in '%s'\n", className); /* keep going */ } else { // 通过 JNI 调用 main 函数,从 C++ 到 Java env->CallStaticVoidMethod(startClass, startMeth, strArray); if (env->ExceptionCheck()) threadExitUncaughtException(env); }
所以这里创建了一个虚拟机,注册JNI方法,然后调用 com.android.internal.os.ZygoteInit
的 main()
,跟下frameworks/base/core/java/com/android/internal/os/ZygoteInit.java
:
public static void main(String argv[]) { try { ... // ① 注册一个name为zygote的socket,用于和其他进程通信 registerZygoteSocket(socketName); // ② 预加载所需资源到VM中,如class、resource、OpenGL、公用Library等; // 所有fork的子进程共享这份空间而无需重新加载,减少了应用程序的启动时间, // 但也增加了系统的启动时间,Android启动最耗时的部分之一。 preload(); // ③ 初始化gc,只是通知VM进行垃圾回收,具体回收时间、怎么回收,由VM内部算法决定。 // gc()需在fork前完成,这样将来复制的子进程才能有尽可能少的垃圾内存没释放; gcAndFinalize(); // ④ 启动system_server,即fork一个Zygote子进程 if (startSystemServer) { startSystemServer(abiList, socketName); } // ⑤ 进入循环模式,获取客户端连接并处理 runSelectLoop(abiList); // ⑥ 关闭和清理zygote socket closeServerSocket(); } catch (MethodAndArgsCaller caller) { caller.run(); } catch (RuntimeException ex) { Log.e(TAG, "Zygote died with exception", ex); closeServerSocket(); throw ex; } }
跟下 startSystemServer()
:
private static boolean startSystemServer(String abiList, String socketName) throws MethodAndArgsCaller, RuntimeException { int pid; try { ... // fork出system_server进程,返回pid,此处pid为0 pid = Zygote.forkSystemServer( parsedArgs.uid, parsedArgs.gid, parsedArgs.gids, parsedArgs.debugFlags, null, parsedArgs.permittedCapabilities, parsedArgs.effectiveCapabilities); } catch (IllegalArgumentException ex) { throw new RuntimeException(ex); } /* 进入子进程 */ if (pid == 0) { // Android 5.0上有两个Zygote进程:zygote 和 zygote64 // 对于有两个zygote进程的情况,需等待第二个zygote创建完成; if (hasSecondZygote(abiList)) { waitForSecondaryZygote(socketName); } // 完成system_server进程的剩余工作 handleSystemServerProcess(parsedArgs); } return true; }
Tips:fork()方法被调用一次,返回两次,区别是:子进程的返回值是0,父进程的返回值是子进程的进程id,可以保证子进程的进程id不可能为0。