抱歉,Xposed真的可以为所欲为
本文作者
作者:CoderPig
链接:
https://juejin.cn/post/6945000696441896973
本文由作者授权发布。
前言
https://juejin.cn/post/6844903593628139528
《抱歉,Xposed真的可以为所欲为——2.改为OV机型流畅玩耍高帧率王者农药》https://juejin.cn/post/6844903593988849671
《抱歉,Xposed真的可以为所欲为——3.微信运动占领封面》https://juejin.cn/post/6844903598036353037
《抱歉,Xposed真的可以为所欲为——4.猜拳投骰子你能赢算我输》https://juejin.cn/post/6844903599076540430
《抱歉,Xposed真的可以为所欲为——5.我自己刷的Xposed凭什么不给我用》https://juejin.cn/post/6844903616877182984
《抱歉,Xposed真的可以为所欲为——6.你的表白撤不回了》https://juejin.cn/post/6844903624263335943
哦,还写过Xposed插件啊,都写过些什么插件?有了解过Xposed底层是怎么实现的吗?
Tips:Xposed通常只能Hook java层及应用资源的替换,有两个实现版本:4.4前的Dalvik虚拟机实现和5.0后ART虚拟机实现,本文针对后者进行分析,同时搭配Android 5.1.1_r6源码食用。 http://androidxref.com/5.1.1_r6/xref/frameworks/
Xposed
https://github.com/rovo89/Xposed
XposedBridge
https://github.com/rovo89/XposedBridge
XposedInstaller
https://github.com/rovo89/XposedInstaller
XposedTools
https://github.com/rovo89/XposedTools
① 类实现IXposedHookLoadPackage接口。
② 重写handleLoadPackage()方法。
③ 调用XposedHelpers.findAndHookMethod(),传入完整类名、类加载器、方法名,参数类型,XC_MethodHook实例。
④ 按需重写beforeHookedMethod()(方法调用前执行代码) 和afterHookedMethod()方法(方法调用后执行代码)。
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。
跟下 /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 → 传的具体参数值;
// ① 初始化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);
}
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;
}
}
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。
从上面的跟踪结果,不难得出这样的调用链:
init进程→init.rc→app_process(app_main.cpp) →启动Zygote进程→ZygoteInit的main() →startSystemServer() →fork出system_server子进程。
bool initialize(bool zygote, bool startSystemServer, const char* className, int argc, char* const argv[]) {
// ① 初始化xposed相关变量
xposed->zygote = zygote;
xposed->startSystemServer = startSystemServer;
xposed->startClassName = className;
xposed->xposedVersionInt = xposedVersionInt;
...
// ② 将XposedBridge.jar加载到系统的CLASSPATH路径中。
return addJarToClasspath();
}
编译生成自定义app_process→ 把原先调用ZygoteInit.main()处 改为调用XposedInit.main() → Hook资源和一些准备工作 → 调用系统原本启动Zygote的方法。
上面说到系统的app_process替换成自定义的了,那具体是怎么替换的呢?
http://dl-xda.xposed.info/framework.json
http://dl-xda.xposed.info/framework/sdk23/arm64/xposed-v89-sdk23-arm64.zip
XposedInstaller下载补丁包 → 获取root权限 → 解压复制update-binary文件到特定目录 → 文件执行时会调用flash-script.sh脚本,将app_process、Xposedbridge.jar、so库等写到系统私有目录。
在了解Xposed如何注入APP进程前,先要简单了解下APP启动创建进程的过程。
小插曲:为什么SystemServer进程与Zygote进程通讯采用Socket而不是Binder? 答:Binder通信是多线程的,可能存在一种情况,父进程binder线程有锁,fork子进程也有锁,但是父进程的子线程并没有拷贝过来,此时子进程会处于死锁。为了规避这种情况,fork不允许存在多线程转而使用socket通信。
private static void runSelectLoop(String abiList) throws MethodAndArgsCaller {
ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>();
ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();
FileDescriptor[] fdArray = new FileDescriptor[4];
while (true) {
int index;
//...
if (index < 0) {
throw new RuntimeException("Error in select()");
} else if (index == 0) {
// 有客户端连接请求,创建ZygoteConnection并加入到fds中
ZygoteConnection newPeer = acceptCommandPeer(abiList);
peers.add(newPeer);
fds.add(newPeer.getFileDescriptor());
} else {
// 通过socket接收来自对端的数据,执行相应操作
boolean done;
done = peers.get(index).runOnce();
if (done) {
peers.remove(index);
fds.remove(index); // 处理完移除该文件描述符
}
}
}
}
// fork子进程
pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid, parsedArgs.gids,
parsedArgs.debugFlags, rlimits, parsedArgs.mountExternal, parsedArgs.seInfo,
parsedArgs.niceName, fdsToClose, parsedArgs.instructionSet,
parsedArgs.appDataDir);
if (pid == 0) {
// 子进程执行
IoUtils.closeQuietly(serverPipeFd);
serverPipeFd = null;
// 进入子进程流程
handleChildProc(parsedArgs, descriptors, childPipeFd, newStderr);
return true;
} else {
// 父进程执行
IoUtils.closeQuietly(childPipeFd);
childPipeFd = null;
// 进入父进程流程
return handleParentProc(pid, descriptors, serverPipeFd, parsedArgs);
}
if (parsedArgs.invokeWith != null) {
WrapperInit.execApplication(parsedArgs.invokeWith,
parsedArgs.niceName, parsedArgs.targetSdkVersion,
pipeFd, parsedArgs.remainingArgs);
} else {
RuntimeInit.zygoteInit(parsedArgs.targetSdkVersion,
parsedArgs.remainingArgs, null /* classLoader */);
}
public static final void zygoteInit(int targetSdkVersion, String[] argv, ClassLoader classLoader)
throws ZygoteInit.MethodAndArgsCaller {
if (DEBUG) Slog.d(TAG, "RuntimeInit: Starting application from zygote");
redirectLogStreams();
// 通用初始化
commonInit();
// Zygote初始化
nativeZygoteInit();
// 应用初始化
applicationInit(targetSdkVersion, argv, classLoader);
}
private static void applicationInit(int targetSdkVersion, String[] argv, ClassLoader classLoader)
throws ZygoteInit.MethodAndArgsCaller {
// true代表APP退出时不调用AppRuntime.onExit(),否则会在退出前调用
nativeSetExitWithoutCleanup(true);
// 设置虚拟机的内存利用率参数为0.75
VMRuntime.getRuntime().setTargetHeapUtilization(0.75f);
VMRuntime.getRuntime().setTargetSdkVersion(targetSdkVersion);
// 参数解析
final Arguments args;
try {
args = new Arguments(argv);
} catch (IllegalArgumentException ex) {
Slog.e(TAG, ex.getMessage());
return;
}
// 此处args.startClass为android.app.ActivityThread,调用它的main方法
invokeStaticMain(args.startClass, args.startArgs, classLoader);
}
public static void main(String[] args) {
// 创建主线程的Looper
Looper.prepareMainLooper();
// 关联AMS
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
// 初始化主线程Handler
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
// 开启主线程消息循环
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
// ① 获取AMS的代理对象
final IActivityManager mgr = ActivityManager.getService();
try {
// ② 通过代理对象调用attachApplication()获得启动application的所需信息(进程相关数据)
mgr.attachApplication(mAppThread, startSeq);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
public final void attachApplication(IApplicationThread thread, long startSeq) {
synchronized (this) {
int callingPid = Binder.getCallingPid();
final int callingUid = Binder.getCallingUid();
final long origId = Binder.clearCallingIdentity();
attachApplicationLocked(thread, callingPid, callingUid, startSeq);// 1
Binder.restoreCallingIdentity(origId);
}
}
private final boolean attachApplicationLocked(IApplicationThread thread,
int pid, int callingUid, long startSeq) {
// 根据pid获取存储在AMS中,对应进程的相关信息
ProcessRecord app;
long startTime = SystemClock.uptimeMillis();
if (pid != MY_PID && pid >= 0) {
synchronized (mPidsSelfLocked) {
app = mPidsSelfLocked.get(pid);
}
} else {
app = null;
}
// IApplicationThread是ActivityThread的内部类,负责管理与AMS的通讯,
// 此处是通知ActivityThread启动Application
thread.bindApplication(processName, appInfo, providers, null, profilerInfo,
null, null, null, testMode,
mBinderTransactionTrackingEnabled, enableTrackAllocation,
isRestrictedBackupMode || !normalMode, app.persistent,
new Configuration(getGlobalConfiguration()), app.compat,
getCommonServicesLocked(app.isolated),
mCoreSettingsObserver.getCoreSettingsLocked(),
buildSerial, isAutofillCompatEnabled);
// ...
}
public final void bindApplication(String processName, ApplicationInfo appInfo,
//...
AppBindData data = new AppBindData();
data.processName = processName;
data.appInfo = appInfo;
data.providers = providers;
//...
sendMessage(H.BIND_APPLICATION, data);
}
public void handleMessage(Message msg) {
switch (msg.what) {
case BIND_APPLICATION:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
AppBindData data = (AppBindData)msg.obj;
handleBindApplication(data);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
private void handleBindApplication(AppBindData data) {
// 将UI线程注册为运行时虚拟机
VMRuntime.registerSensitiveThread();
// 创建上下文对象
final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);
updateLocaleListFromAppContext(appContext,
mResourcesManager.getConfiguration().getLocales());
// 创建Instrumentation实例,用于创建、启动Application,并跟踪Application的生命周期。
try {
final ClassLoader cl = instrContext.getClassLoader();
mInstrumentation = (Instrumentation)
cl.loadClass(data.instrumentationName.getClassName()).newInstance();
} catch (Exception e) {
throw new RuntimeException(
"Unable to instantiate instrumentation "
+ data.instrumentationName + ": " + e.toString(), e);
}
// 创建Application对象
Application app;
app = data.info.makeApplication(data.restrictedBackupMode, null);
// 调用Instrumentation的onCreate(),内部是空实现
mInstrumentation.onCreate(data.instrumentationArgs);
// 内部实际上调用的application的onCreate()
mInstrumentation.callApplicationOnCreate(app);
① XposedInit.initForZygote()
if (needsToCloseFilesForFork()) {
XC_MethodHook callback = new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
XposedBridge.closeFilesBeforeForkNative();
}
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
XposedBridge.reopenFilesAfterForkNative();
}
};
Class<?> zygote = findClass("com.android.internal.os.Zygote", null);
hookAllMethods(zygote, "nativeForkAndSpecialize", callback);
hookAllMethods(zygote, "nativeForkSystemServer", callback);
}
// normal process initialization (for new Activity, Service, BroadcastReceiver etc.)
findAndHookMethod(ActivityThread.class, "handleBindApplication", "android.app.ActivityThread.AppBindData", new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
// 获得activityThread、ApplicationInfo、ComponentName实例
ActivityThread activityThread = (ActivityThread) param.thisObject;
ApplicationInfo appInfo = (ApplicationInfo) getObjectField(param.args[0], "appInfo");
String reportedPackageName = appInfo.packageName.equals("android") ? "system" : appInfo.packageName;
SELinuxHelper.initForProcess(reportedPackageName);
ComponentName instrumentationName = (ComponentName) getObjectField(param.args[0], "instrumentationName");
// 判断ComponentName是否为null,null说明没有hook成功
if (instrumentationName != null) {
Log.w(TAG, "Instrumentation detected, disabling framework for " + reportedPackageName);
XposedBridge.disableHooks = true;
return;
}
CompatibilityInfo compatInfo = (CompatibilityInfo) getObjectField(param.args[0], "compatInfo");
if (appInfo.sourceDir == null)
return;
setObjectField(activityThread, "mBoundApplication", param.args[0]);
loadedPackagesInProcess.add(reportedPackageName);
// 获得LoadedApk实例,设置资源目录
LoadedApk loadedApk = activityThread.getPackageInfoNoCheck(appInfo, compatInfo);
XResources.setPackageNameForResDir(appInfo.packageName, loadedApk.getResDir());
// 初始化LoadPackageParam,塞packageName、processName、classloader 等
XC_LoadPackage.LoadPackageParam lpparam = new XC_LoadPackage.LoadPackageParam(XposedBridge.sLoadedPackageCallbacks);
lpparam.packageName = reportedPackageName;
lpparam.processName = (String) getObjectField(param.args[0], "processName");
lpparam.classLoader = loadedApk.getClassLoader();
lpparam.appInfo = appInfo;
lpparam.isFirstApplication = true;
// 将lpparam参数传递到所有Xposed模块
XC_LoadPackage.callAll(lpparam);
}
});
① HookActivityThread的handleBindApplication(),参数类型为:android.app.ActivityThread.AppBindData在,这里有疑惑可以看回App启动创建进程部分代码;
②param.args[0]为上面的AppBindData参数,获取这个参数里的属性和对一些属性进行覆盖;
③ 通过activityThread实例获得loadedApk实例,拿到ClassLoader,然后传递到所有Xposed模块;
② findAndHookMethod()
③ hookMethodNative()
④ handleHookedMethod()
总结
Xposed需要root权限,安装XposedInstaller获得root权限后,执行update-binary将app_process、Xposedbridge.jar、so库等刷入到系统私有目录中;
init进程解析执行init.rc,通过app_process启动Zygote进程,Zygote是所有APP进程的父进程;
Xposed的app_process把原先ZygoteInit.main()改为调用XposedInit.main(),执行完一些Hook工作再调用原本启动的Zygote的方法;
子进程fork时不仅会获得虚拟机实例拷贝,还会和Zygote共享Java运行时库,所以只需在Zygote注入一次XposedBridge.jar,即可实现全局注入;
加餐:如何检测Xposed
https://tech.meituan.com/2018/02/02/android-anti-hooking.html
Java层检测
PackageManager packageManager = context.getPackageManager();
List applicationInfoList = packageManager.getInstalledApplications(PackageManager.GET_META_DATA);
for (ApplicationInfo applicationInfo: applicationInfoList) {
if (applicationInfo.packageName.equals("de.robv.android.xposed.installer")) {
// is Xposed TODO... }
}
try {
throw new Exception("blah");
} catch(Exception e) {
for (StackTraceElement stackTraceElement: e.getStackTrace()) {
// stackTraceElement.getClassName() stackTraceElement.getMethodName() 是否存 在Xposed
}
}
boolean methodCache = CheckHook(clsXposedHelper, "methodCache", keyWord);
private static boolean CheckHook(Object cls, String filedName, String str) {
boolean result = false;
String interName;
Set keySet;
try {
Field filed = cls.getClass().getDeclaredField(filedName);
filed.setAccessible(true);
keySet = filed.get(cls)).keySet();
if (!keySet.isEmpty()) {
for (Object aKeySet: keySet) {
interName = aKeySet.toString().toLowerCase();
if (interName.contains("meituan") || interName.contains("dianping") ) {
result = true;
break;
}
}
}
...
return result;
}
native层检测
bool is_xposed()
{
bool rel = false;
FILE *fp = NULL;
char* filepath = "/proc/self/maps";
...
string xp_name = "XposedBridge.jar";
fp = fopen(filepath,"r"))
while (!feof(fp))
{
fgets(strLine,BUFFER_SIZE,fp);
origin_str = strLine;
str = trim(origin_str);
if (contain(str,xp_name))
{
rel = true; //检测到Xposed模块
break;
}
}
...
}
https://github.com/KagurazakaHanabi/XposedHider/blob/master/app/src/main/java/com/yaerin/xposed/hider/XposedHook.java
https://github.com/w568w/XposedChecker
Xposed注入实现分析及免重启定制
https://bbs.pediy.com/thread-223713.htm
从源码解析-Android中Zygote进程是如何fork一个APP进程的
https://blog.csdn.net/qq_30993595/article/details/82747738
https://blog.csdn.net/ascii2/article/details/47974217
为所欲为的 Xposed 是怎么实现的?
https://mp.weixin.qq.com/s/zvxrLasOMOP9J_XSeonpvA
Application启动过程(最详细&最简单)
https://www.jianshu.com/p/4a8f44b6eecb
Android Hook框架Xposed原理与源代码分析
https://blog.csdn.net/wxyyxc1992/article/details/17320911
最后推荐一下我做的网站,玩Android: wanandroid.com ,包含详尽的知识体系、好用的工具,还有本公众号文章合集,欢迎体验和收藏!
推荐阅读:
点击 关注我的公众号
如果你想要跟大家分享你的文章,欢迎投稿~
┏(^0^)┛明天见!