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以實現的全局注入。
想要知道Xposed是如何實現的全局注入,首先要分析一下Android系統的啓動過程,就可以很方便的理解Xposed選擇的注入點。
我畫了張圖,Android系統的啓動大致如下:
從圖中可以很方便的理解從BootLoader到顯示系統界面的過程。
Zygote,中文是"受精卵"...實際上他就是一個孵化器,所有應用程序的進程都是由它fork出來的。
可以看到,這些包名格式的進程的PPID(父進程PID)都是155,即zygote進程。
Zygote啓動之後會建立一個Socket Server,然後fork自身成爲一個新的進程——system_server,並讓它成爲"前端代言人",當system_server接收到打開進程的請求時,它就會通過Socket跟Zygote通訊,Zygote就再fork自身稱爲新的進程。
在init.rc中可以看到,Zygote是通過app_process啓動的,
這時app_process的任務也很簡單,就是把自己的進程名變成Zygote,然後反射調用Zygote的主方法,即com.android.internal.os.ZygoteInit的main()。之後Zygote做的事情就是在介紹中說過的了。
那麼Xposed就是通過修改app_process的方式來實現注入Zygote的。
項目地址:https://github.com/rovo89/Xposed 即是修改app_process的源碼,在app_main.cpp的main函數中可以看到
可以看到,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()
initForZygote中又Hook了handleBindApplication
Xposed在Zygote進程中執行完這些之後,每次fork出來的都是handleBindApplication
已經被Hook過的進程,所以當每個應用進程被打開的時候,都會最開始就先執行Xposed的模塊插件,然後纔開始執行本身的代碼。這樣就實現了注入到每個應用程序來進行Hook。
findAndHookMethod
想必是大家寫Xposed插件最常用及最熟悉的一個方法,我們就通過這個方法來分析Xposed是如何實現的Java Hook。
大家知道這個方法的參數是個可變長參數列表,然後它會取最後一個參數來作爲callback,即hook時的注入的代碼。然後通過反射的方法來得到原本的Method。最後將這兩個傳給hookMethod。
可以看到hookMethod實際上時調用了一個native函數hookMethodNative
,其他參數都很好理解,但是取的這個slot是個什麼東西?翻了一下百度,在Dalvik的源碼中dalvik/vm/reflect.c
:
原來這個slot就是此method在ClassObject的methods中的偏移,directMethods爲負,virtualMethods爲正。
hookMethodNative對應C函數XposedBridge_hookMethodNative
首先是通過slot獲取到Method對象在內存中的Method結構體指針,然後將它設置爲一個native方法,再將他的nativeFunc
即執行時對應的函數地址設置爲hookedMethodCallback
的指針,將保存着原method信息的hookInfo暫時存放在本是dalvik字節碼的insns
,reflectedMethod
是原method的java對象,additionalInfo
是AdditionalHookInfo對象。
然後當調用到這個方法的時候,執行的就變成了hookedMethodCallback
函數了。
主要就是又調了一個java方法methodXposedBridgeHandleHookedMethod
,解釋一下各個參數,originalReflected
就是上面hookInfo的reflectedMethod
,original
是原method結構體指針,後面的不必解釋了吧。
那麼methodXposedBridgeHandleHookedMethod
做的事情也很好理解,就是先把所有的beforeHookedMethod
給調用一遍,然後再還原原來的method調用一遍,最後再把所有的afterHookMethod
的給調用一遍,就完成了。
執行原來的方法調用的是native方法invokeOriginalMethodNative。