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系统的启动大致如下:
从图中可以很方便的理解从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
S
/
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_APPLICATION
和EXIT_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字节码的insns
,reflectedMethod
是原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的reflectedMethod
,original
是原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.XposedBridge
的main()
中可以看到
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