Xposed注入实现分析及免重启定制

Xposed的实现解析

Xposed简介

Xposed已经作为Android的Java层Hook大哥好多年了,不仅仅是安全研究者和逆向工程师手中的神器,还拥有着很多的插件开发者,在官方仓库收录的插件也已经超过了1000+个,而其服务的用户以各搞机论坛用户为主,也是不尽其数。
Xposed的代码是完全开源的,并且代码量也不算很大,对于安全研究者来说呢,我觉得研究一下Xposed的原理还是很有必要的。
Xposed的github地址:
https://github.com/rovo89/Xposed
https://github.com/rovo89/XposedBridge

 

本文使用Android4.4的源码进行分析

如何实现全局注入

简单的讲,Xposed是通过替换修改过的app_process来注入Zygote以实现的全局注入。

Android的启动过程

想要知道Xposed是如何实现的全局注入,首先要分析一下Android系统的启动过程,就可以很方便的理解Xposed选择的注入点。
我画了张图,Android系统的启动大致如下:
Android系统启动流程
从图中可以很方便的理解从BootLoader到显示系统界面的过程。

应用孵化器 - Zygote

Zygote介绍

Zygote,中文是"受精卵"...实际上他就是一个孵化器,所有应用程序的进程都是由它fork出来的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
USER     PID   PPID  VSIZE  RSS     WCHAN    PC         NAME
root       155    1      509740  41876  ffffffff b75761e0 S zygote
drm        156    1      18508   4064   ffffffff b7599ff6 S  / system / bin / drmserver
media      157    1      69296   15656  ffffffff b755bff6 S  / system / bin / mediaserver
install    158    1      6580    1228   c04d1048 b7507bb6 S  / system / bin / installd
keystore   159    1      10172   2088   c03f4a25 b7561ff6 S  / system / bin / keystore
root       160    1      1096    4      c044881c  080ddd03  / system / xbin / su
system     187    1      81040   3860   ffffffff b75b0ff6 S  / system / bin / surfaceflinger
root       343    2      0       0      c01cfbd5  00000000  S flush - 8 : 16
root       414    62     6732    1412   c02f4452 b74c6bb6 S  / system / bin / sh
root       418    62     6688    1464   c01c0a90 b752e1e0 S logcat
system     424    155    629276  46384  ffffffff b7575ff6 S system_server
u0_a44     506    155    564252  75032  ffffffff b75779eb S com.android.systemui
u0_a0      551    155    525484  25696  ffffffff b75779eb S android.process.acore
wifi       565    1      10776   2912   c01c0a90 b741d1e0 S  / system / bin / wpa_supplicant
system     566    155    526468  21732  ffffffff b75779eb S com.android.settings
u0_a22     606    155    521276  24932  ffffffff b75779eb S com.android.inputmethod.latin
radio      623    155    539356  28428  ffffffff b75779eb S com.android.phone
u0_a23     637    155    557680  45252  ffffffff b75779eb S com.android.launcher
u0_a29     673    155    520300  20324  ffffffff b75779eb S com.android.music
u0_a16     693    155    521532  25876  ffffffff b75779eb S android.process.media
u0_a49     719    155    517580  18600  ffffffff b75779eb S com.android.smspush
u0_a15     806    155    521632  19920  ffffffff b75779eb S com.android.dialer
bluetooth  821    155    522828  22284  ffffffff b75779eb S com.android.bluetooth
dhcp       845    1      6656    1452   c01c0a90 b74e6ab6 S  / system / bin / dhcpcd
u0_a6      916    155    527828  22036  ffffffff b75779eb S com.android.calendar
u0_a7      943    155    519636  23176  ffffffff b75779eb S com.android.providers.calendar
u0_a12     967    155    521156  21960  ffffffff b75779eb S com.android.deskclock
u0_a17     1019   155    528300  24912  ffffffff b75779eb S com.android.email
u0_a18     1078   155    524976  20436  ffffffff b75779eb S com.android.exchange
u0_a28     1168   155    530852  23100  ffffffff b75779eb S com.android.mms
u0_a32     1215   155    517576  19140  ffffffff b75779eb S com.android.onetimeinitializer
u0_a47     1251   155    517732  19072  ffffffff b75779eb S com.android.voicedialer

可以看到,这些包名格式的进程的PPID(父进程PID)都是155,即zygote进程。
Zygote启动之后会建立一个Socket Server,然后fork自身成为一个新的进程——system_server,并让它成为"前端代言人",当system_server接收到打开进程的请求时,它就会通过Socket跟Zygote通讯,Zygote就再fork自身称为新的进程。

Zygote的启动

在init.rc中可以看到,Zygote是通过app_process启动的,

1
service zygote  / system / bin / app_process  - Xzygote  / system / bin  - - zygote  - - start - system - server

这时app_process的任务也很简单,就是把自己的进程名变成Zygote,然后反射调用Zygote的主方法,即com.android.internal.os.ZygoteInit的main()。之后Zygote做的事情就是在介绍中说过的了。

Hook Zygote

那么Xposed就是通过修改app_process的方式来实现注入Zygote的。
项目地址:https://github.com/rovo89/Xposed 即是修改app_process的源码,在app_main.cpp的main函数中可以看到

1
2
3
4
5
6
7
8
9
10
11
12
isXposedLoaded  =  xposed::initialize(zygote, startSystemServer, className, argc, argv);
if  (zygote) {
     runtime.start(isXposedLoaded ? XPOSED_CLASS_DOTS_ZYGOTE :  "com.android.internal.os.ZygoteInit" ,
             startSystemServer ?  "start-system-server"  : "");
else  if  (className) {
     / /  Remainder of args get passed to startup  class  main()
     runtime.mClassName  =  className;
     runtime.mArgC  =  argc  -  i;
     runtime.mArgV  =  argv  +  i;
     runtime.start(isXposedLoaded ? XPOSED_CLASS_DOTS_TOOLS :  "com.android.internal.os.RuntimeInit" ,
             application ?  "application"  "tool" );
}

可以看到,Xposed首先尝试加载自己的库,如果成功的话,就将runtime.start的类名替换成自己的类:de.robv.android.xposed.XposedBridge,因为app_processs是采用反射main()的方法来调用加载方法,所以只要在这个类中定义一个main()方法,就可以为所欲为了,最后只需在末尾调用一下com.android.internal.os.ZygoteInit的main()让系统继续跑下去就可以实现对Zygote的Hook。

注入应用进程

既然已经拿到了Zygote的所有权了,但是现在Application还没跑起来呢,如果这个时候就执行Hook模块,肯定是无法Hook到相关代码的。那么这时候就有必要提一下从Zygote接受到请求之后做了什么,我又简单的画了张图:

值得注意的是,因为fork之后的子进程是会继承父进程的状态继续往下跑,所以到handleChildProc的时候,此时已经是子进程在执行程序了,而ActivityThread也是在新进程中loop。
在Activity的loop中,可以接收的消息也包括了Appcalition的生命周期BIND_APPLICATIONEXIT_APPLICATION。处理BIND_APPLICATION的时候是直接调用了handleBindApplication方法,Xposed便是在Zygote中Hook了这个方法,在这里面执行用户的Hook模块,因为在这个时候,已经可以拿到应用的classLoader了,然后将此classLoader封装成一个LoadPackageParam再传给各个模块的handleLoadPackage即可。
在XposedBridge的main中可看到其调用了initForZygote()

1
2
3
4
5
6
7
8
protected static void main(String[] args) {
     ...
         if  (isZygote) {
             XposedInit.hookResources();
             XposedInit.initForZygote();
         }
     ...
}

initForZygote中又Hook了handleBindApplication

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
static void initForZygote() throws Throwable {
     ...
     findAndHookMethod(ActivityThread. class "handleBindApplication" "android.app.ActivityThread.AppBindData" , new XC_MethodHook() {
         @Override
         protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
             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" );
             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  =  activityThread.getPackageInfoNoCheck(appInfo, compatInfo);
             XResources.setPackageNameForResDir(appInfo.packageName, loadedApk.getResDir());
 
             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;
             XC_LoadPackage.callAll(lpparam);
 
             if  (reportedPackageName.equals(INSTALLER_PACKAGE_NAME))
                 hookXposedInstaller(lpparam.classLoader);
         }
     });
     ...
}

Xposed在Zygote进程中执行完这些之后,每次fork出来的都是handleBindApplication已经被Hook过的进程,所以当每个应用进程被打开的时候,都会最开始就先执行Xposed的模块插件,然后才开始执行本身的代码。这样就实现了注入到每个应用程序来进行Hook。

如何实现Hook

findAndHookMethod

findAndHookMethod想必是大家写Xposed插件最常用及最熟悉的一个方法,我们就通过这个方法来分析Xposed是如何实现的Java Hook。

1
2
3
4
5
6
7
8
9
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 ];
     Method m  =  findMethodExact(clazz, methodName, getParameterClasses(clazz.getClassLoader(), parameterTypesAndCallback));
 
     return  XposedBridge.hookMethod(m, callback);
}

大家知道这个方法的参数是个可变长参数列表,然后它会取最后一个参数来作为callback,即hook时的注入的代码。然后通过反射的方法来得到原本的Method。最后将这两个传给hookMethod。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public static XC_MethodHook.Unhook hookMethod(Member hookMethod, XC_MethodHook 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);
         hookMethodNative(hookMethod, declaringClass, slot, additionalInfo);
     }
     ...
}

可以看到hookMethod实际上时调用了一个native函数hookMethodNative,其他参数都很好理解,但是取的这个slot是个什么东西?翻了一下百度,在Dalvik的源码中dalvik/vm/reflect.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static  int  methodToSlot(const Method *  meth)
{
     ClassObject *  clazz  =  meth - >clazz;
     int  slot;
 
     if  (dvmIsDirectMethod(meth)) {
         slot  =  meth  -  clazz - >directMethods;
         slot  =  - (slot + 1 );
     else  {
         slot  =  meth  -  clazz - >virtualMethods;
     }
 
     return  slot;
}

原来这个slot就是此method在ClassObject的methods中的偏移,directMethods为负,virtualMethods为正。

偷梁换柱

hookMethodNative对应C函数XposedBridge_hookMethodNative

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
void XposedBridge_hookMethodNative(JNIEnv *  env, jclass clazz, jobject reflectedMethodIndirect,
             jobject declaredClassIndirect, jint slot, jobject additionalInfoIndirect) {
     / /  Usage errors?
     if  (declaredClassIndirect  = =  NULL || reflectedMethodIndirect  = =  NULL) {
         dvmThrowIllegalArgumentException( "method and declaredClass must not be null" );
         return ;
     }
 
     / /  Find the internal representation of the method
     ClassObject *  declaredClass  =  (ClassObject * ) dvmDecodeIndirectRef(dvmThreadSelf(), declaredClassIndirect);
     Method *  method  =  dvmSlotToMethod(declaredClass, slot);
     if  (method  = =  NULL) {
         dvmThrowNoSuchMethodError( "Could not get internal representation for method" );
         return ;
     }
 
     if  (isMethodHooked(method)) {
         / /  already hooked
         return ;
     }
 
     / /  Save a copy of the original method  and  other hook info
     XposedHookInfo *  hookInfo  =  (XposedHookInfo * ) calloc( 1 , sizeof(XposedHookInfo));
     memcpy(hookInfo, method, sizeof(hookInfo - >originalMethodStruct));
     hookInfo - >reflectedMethod  =  dvmDecodeIndirectRef(dvmThreadSelf(), env - >NewGlobalRef(reflectedMethodIndirect));
     hookInfo - >additionalInfo  =  dvmDecodeIndirectRef(dvmThreadSelf(), env - >NewGlobalRef(additionalInfoIndirect));
 
     / /  Replace method with our own code
     SET_METHOD_FLAG(method, ACC_NATIVE);
     method - >nativeFunc  =  &hookedMethodCallback;
     method - >insns  =  (const u2 * ) hookInfo;
     method - >registersSize  =  method - >insSize;
     method - >outsSize  =  0 ;
 
     if  (PTR_gDvmJit ! =  NULL) {
         / /  reset JIT cache
         char currentValue  =  * ((char * )PTR_gDvmJit  +  MEMBER_OFFSET_VAR(DvmJitGlobals,codeCacheFull));
         if  (currentValue  = =  0  || currentValue  = =  1 ) {
             MEMBER_VAL(PTR_gDvmJit, DvmJitGlobals, codeCacheFull)  =  true;
         else  {
             ALOGE( "Unexpected current value for codeCacheFull: %d" , currentValue);
         }
     }
}

首先是通过slot获取到Method对象在内存中的Method结构体指针,然后将它设置为一个native方法,再将他的nativeFunc即执行时对应的函数地址设置为hookedMethodCallback的指针,将保存着原method信息的hookInfo暂时存放在本是dalvik字节码的insnsreflectedMethod是原method的java对象,additionalInfo是AdditionalHookInfo对象。
然后当调用到这个方法的时候,执行的就变成了hookedMethodCallback函数了。

1
2
3
4
5
6
void hookedMethodCallback(const u4 *  args, JValue *  pResult, const Method *  method, ::Thread *  self ) {
     ...
     dvmCallMethod( self , (Method * ) methodXposedBridgeHandleHookedMethod, NULL, &result,
         originalReflected, ( int ) original, additionalInfo, thisObject, argsArray);
     ...
}

主要就是又调了一个java方法methodXposedBridgeHandleHookedMethod,解释一下各个参数,originalReflected就是上面hookInfo的reflectedMethodoriginal是原method结构体指针,后面的不必解释了吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
private static  Object  handleHookedMethod(Member method,  int  originalMethodId,  Object  additionalInfoObj,
             Object  thisObject,  Object [] args) throws Throwable {
         ...
         int  beforeIdx  =  0 ;
         do {
             try  {
                 ((XC_MethodHook) callbacksSnapshot[beforeIdx]).beforeHookedMethod(param);
             } catch (Throwable t) {
                 XposedBridge.log(t);
 
                 / /  reset result (ignoring what the unexpectedly exiting callback did)
                 param.setResult(null);
                 param.returnEarly  =  false;
                 continue ;
             }
 
             if  (param.returnEarly) {
                 / /  skip remaining  "before"  callbacks  and  corresponding  "after"  callbacks
                 beforeIdx + + ;
                 break ;
             }
         while  ( + + beforeIdx < callbacksLength);
 
         / /  call original method  if  not  requested otherwise
         if  (!param.returnEarly) {
             try  {
                 param.setResult(invokeOriginalMethodNative(method, originalMethodId,
                         additionalInfo.parameterTypes, additionalInfo.returnType, param.thisObject, param.args));
             } catch (InvocationTargetException e) {
                 param.setThrowable(e.getCause());
             }
         }
 
         / /  call  "after method"  callbacks
         int  afterIdx  =  beforeIdx  -  1 ;
         do {
             Object  lastResult  =   param.getResult();
             Throwable lastThrowable  =  param.getThrowable();
 
             try  {
                 ((XC_MethodHook) callbacksSnapshot[afterIdx]).afterHookedMethod(param);
             } catch (Throwable t) {
                 XposedBridge.log(t);
 
                 / /  reset to last result (ignoring what the unexpectedly exiting callback did)
                 if  (lastThrowable  = =  null)
                     param.setResult(lastResult);
                 else
                     param.setThrowable(lastThrowable);
             }
         while  ( - - afterIdx > =  0 );
     ...
}

那么methodXposedBridgeHandleHookedMethod做的事情也很好理解,就是先把所有的beforeHookedMethod给调用一遍,然后再还原原来的method调用一遍,最后再把所有的afterHookMethod的给调用一遍,就完成了。

还原执行

执行原来的方法调用的是native方法invokeOriginalMethodNative。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void XposedBridge_invokeOriginalMethodNative(const u4 *  args, JValue *  pResult,
             const Method *  method, ::Thread *  self ) {
     Method *  meth  =  (Method * ) args[ 1 ];
     if  (meth  = =  NULL) {
         meth  =  dvmGetMethodFromReflectObj(( Object * ) args[ 0 ]);
         if  (isMethodHooked(meth)) {
             meth  =  (Method * ) meth - >insns;
         }
     }
     ArrayObject *  params  =  (ArrayObject * ) args[ 2 ];
     ClassObject *  returnType  =  (ClassObject * ) args[ 3 ];
     Object *  thisObject  =  ( Object * ) args[ 4 ];  / /  null  for  static methods
     ArrayObject *  argList  =  (ArrayObject * ) args[ 5 ];
 
     / /  invoke the method
     pResult - >l  =  dvmInvokeMethod(thisObject, meth, argList, params, returnType, true);
     return ;
}

emm..就是通过拿到备份的method结构体,然后直接调用dvmInvokeMethod即可。

改造更新免重启的Xposed

大家在写Xposed模块的时候是不是有一个很不爽的地方,就是每次改两行代码之后又得重启,非常的烦,那么清楚了注入的原理之后,发现这点好像不是必须的!只是rovo89可能是考虑到性能的问题,所以才采用这种方案。实际上,只要轻轻的改动两行代码,就可以实现免重启更新!

Xposed原加载机制

Xposed原本是在app_process的时候一次性将模块列表读取,Load之后将其hook类放到一个Set里面,在de.robv.android.xposed.XposedBridgemain()中可以看到

1
2
3
4
5
6
7
8
9
10
protected static void main(String[] args) {
     ...
         if  (isZygote) {
             XposedInit.hookResources();
             XposedInit.initForZygote();
         }
 
         XposedInit.loadModules();
     ...
}

待到应用启动的时候再分别调用每个handleLoadPackage,之后就不再读取插件。因为这样,所以每次fork之后,插件已经被加载在内存中了,就算更新插件也不会被读取加载,所以必须得重启才能使得插件生效。

处理办法

之前说到,Xposed通过HookhandleBindApplication来进入每个应用进程,那只要在这个时候再loadModules,那不就ojbk了吗?
loadModule中,进行加载不仅有handleLoadPackage,还有hook资源及hook命令行程序的包,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private static void loadModule(String apk, ClassLoader topClassLoader) {
         ...
                         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));
                     else  {
                         if  (moduleInstance instanceof IXposedHookCmdInit) {
                             IXposedHookCmdInit.StartupParam param  =  new IXposedHookCmdInit.StartupParam();
                             param.modulePath  =  apk;
                             param.startClassName  =  startClassName;
                             ((IXposedHookCmdInit) moduleInstance).initCmdApp(param);
         ...
     }

但是我的需求就是只需要处理handleLoadPackage就可以了,所以给这个参数加个开关isLoadHookLoadPackage,我的代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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)
             if (isLoadHookLoadPackage)
             {
                 XposedBridge.hookLoadPackage(new IXposedHookLoadPackage.Wrapper((IXposedHookLoadPackage) moduleInstance));
             }
         if  (moduleInstance instanceof IXposedHookInitPackageResources)
             XposedBridge.hookInitPackageResources(new IXposedHookInitPackageResources.Wrapper((IXposedHookInitPackageResources) moduleInstance));
     else  {
         if  (moduleInstance instanceof IXposedHookCmdInit) {
             IXposedHookCmdInit.StartupParam param  =  new IXposedHookCmdInit.StartupParam();
             param.modulePath  =  apk;
             param.startClassName  =  startClassName;
             ((IXposedHookCmdInit) moduleInstance).initCmdApp(param);
         }
     }

实际上就是简单的加了一个if而已,然后在main的地方改成

1
XposedInit.loadModules(false);

最后就是在hook handleBindApplication的地方加上XposedInit.loadModules(true);

1
2
3
4
5
findAndHookMethod(ActivityThread. class "handleBindApplication" "android.app.ActivityThread.AppBindData" , new XC_MethodHook() {
     @Override
     protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
         XposedInit.loadModules(true);
...

嗯,炒鸡简单,改源码不超过2分钟。

编译

改源码两分钟,编译五小时..
rovo89还提供了另外一个项目:https://github.com/rovo89/XposedTools
因为Xposed编译需要依赖AOSP来进行编译,这个工具就是专门用来编译Xposed源码的,使用方法也很简单,填好build.conf然后一把梭就可以了,具体的可以查看百度。

 

需要注意的是,上叙的源码是新版的xposed,就是支持5.0+art的,而我使用的是4.4.4的源码,模拟器也是4.4,所以clone源码的时候要-b master来切换分支。然后修改的代码也大同小异,就是loadmodule多了个startClassName参数,可以通过getStartClassName()方法获取,就是这样:
XposedBridge.loadModules(getStartClassName(),true);
因为这个老工程是个Eclipse工程,XposedTool是依赖于Gradle,所以无法使用XposedTool编译。直接使用Android Studio - Build Apk最后把文件名改成XposedBridge.jar即可,
最后你可以选择直接修改Xposed Installer将其中的XposedBridge.jar替换再安装,或者在手机上安装好Xposed,然后将修改好的XposedBridge.jar替换/data/data/de.robv.android.xposed.installer/bin/XposedBridge.jar,重启,就可以愉快的调插件玩耍了。

 

顺便fork了一份,并把代码上传了,虽说总共也没改几行代码,2333。。

 

5.0- : https://github.com/hluwa/XposedBridge/tree/master

4.4+ : https://github.com/hluwa/XposedBridge/tree/art


原文地址:https://bbs.pediy.com/thread-223713.htm

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值