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)) {
相關文章
相關標籤/搜索