Android安全交流群:478084054
whale是lody大神開源的一個Hook框架,支持ART下Java方法的HOOK,也支持native InlineHook。
本文走馬觀花一下。
whale支持Xposed-Style Method Hook。
Xposed Hook的代碼通常這么寫:
在whale的java/com/lody/whale/xposed目錄中存放的是兼容Xposed風格的代碼。
所以,先看一下whale的XposedHelpers.findAndHookMethod和XC_MethodHook的實現源碼。
其中,findAndHookMethod用來完成Method Hook,XC_MethodHook對象用于回調。
簡單看一下class XC_MethodHook:
重寫XC_MethodHook的beforeHookedMethod或者afterHookedMethod,這兩個方法相當于是在原方法的調用前后插樁。根據需要,把我們邏輯代碼放在這兩個函數中就可以了。
重點看一下XposedHelpers.findAndHookMethod。
findAndHookMethod先調用findMethodExact得到要Hook的Method對象,然后調用XposedBridge.hookMethod完成Hook。
getParameterClasses用于獲取參數的Class類型:
從getParameterClasses的實現來看,參數類型不僅可以傳Class,還可以直接傳String。
如:Bundle.class或"android.os.Bundle "。
有了方法名和參數類型,以及實現該方法的clazz,findMethodExact的實現就很簡單了:
這里還維護了一個methodCache,用于存儲所有已經find過的method。
回到findAndHookMethod,繼續跟蹤代碼,看XposedBridge.hookMethod的實現。
所有已經Hook過的method及其對應的callbacks,全部存儲在sHookedMethodCallbacks中,這是一個HashMap。如果該method已經Hook過,那直接把callback回調對象加入到其對應的callbacks集合中就可以了。這樣在該method被調用時,callbacks集合中所有回調都會被遍歷執行。
如果該method沒有被Hook過,那就調用WhaleRuntime.hookMethodNative進行Hook。
這是一個native方法,對應的代碼在whale/src/android/art/native_on_load.cc中。
繼續跟蹤ArtRuntime::HookMethod,完成Hook的關鍵代碼就在這里了。
這里省略了大量細節代碼。先抓主干,了解基本原理。
ArtRuntime::HookMethod將被Hook的method設置為native方法,然后將Jni入口點設置為一個closure。這樣當該method被調用時,就會執行這里設置的closure。因為被Hook的method已經為native,所以ArtMethod結構中的dex_code_item_offset_成員就沒用了,直接清0。
另外,將quick_compiled_code和interpreter的入口點分別設置為quick_generic_jni_trampoline_和artInterpreterToCompiledCodeBridge,這樣無論被Hook的方法從解釋器執行,還是直接以本地指令的方式執行,最終都會執行Jni入口點,都會執行這里設置的closure。
所以,ArtRuntime::HookMethod執行之后,被Hook方法的執行就由closure接管了,這個closure是由BuildJniClosure構造的,繼續跟蹤該方法。
這里利用libffi根據原method的參數和返回類型構造一個jni-closure(jni函數相比原Java method會多兩個參數,一個是JNIEnv*,另一個是jclass或者jobject)。
libffi是一個開源項目,可以用于動態生成遵守特定調用約定的代碼。android系統源碼中也有這個庫(android/platform/external/libffi)。
當被Hook的method被調用時,就會執行這里構造的closure的callback,也就是FFIJniDispatcher,并將原參數傳入。另外,這里創建closure時,還將ArtHookParam*類型的參數param作為userdata傳入,所以FFIJniDispatcher被調用的時候,這里的參數param也會作為userdata傳入。
看一下FFIJniDispatcher的實現:
FFIJniDispatcher先利用QuickArgumentBuilder將傳給原methhod的所有參數存放到一個jobjectArray參數數組中。然后根據原method的返回類型,調用InvokeJavaBridge或InvokeVoidJavaBridge。
繼續跟蹤ArtRuntime::InvokeHookedMethodBridge:
這里是直接調用java_class_的bridge_method_方法。
java_class_和bridge_method_是在ArtRuntime::OnLoad中初始化的:
而ArtRuntime::OnLoad是被libwhale.so的JNI_Onload方法調用的。
所以,這個bridge_method_就是com/lody/whale/WhaleRuntime的handleHookedMethod方法。
繼續看XposedBridge.handleHookedMethod:
beforeHookedMethod和afterHookedMethod的調用就在這里了,相當于在原函數的調用點前后各插了一個樁。
這里的callbacks是通過參數additionalInfo得到的,而參數additionalInfo是早在XposedBridge.hookMethod方法中就創建的,并一路傳到了XposedBridge.handleHookedMethod。
再把上面的圖重復貼一下:
在XposedBridge.handleHookedMethod中,通過XposedBridge.invokeOriginalMethod來調用原方法,繼續跟蹤一下:
XposedBridge.invokeOriginalMethod又調用了WhaleRuntime.invokeOriginalMethodNative。
這是一個native方法,對應源碼在whale/src/android/art/native_on_load.cc中。
繼續ArtRuntime::InvokeOriginalMethod:
ArtRuntime::InvokeOriginalMethod先通過參數拿到原始的method,然后通過java.lang.reflect.Method.Invoke()反射調用。
參數slot是在ArtRuntime::HookMethod方法中創建的,類型是“ArtHookParam*”,param->origin_method_中保存原method對象。
至此,Java method的Hook流程基本上跟蹤完了。不過,忽略了很多值得關注和學習的細節,后面筆記再寫。
除了Java method的Hook之外,whale還支持對native函數的InlineHook。
whale/include/whale.h:
在built目錄下還有編譯好的libwhale.so可以直接使用。
看一下WInlineHookFunction的實現:
看一下arm平臺下的Hook實現ArmInlineHook:
繼續看ArmInlineHook::StartHook:
native層的InlineHook都是通過在目標方法的指令開始處加跳轉指令來實現的。
這塊很復雜,先不看了。
ArmInlineHook的實現借助了vixl,這是一個arm平臺的運行時代碼生成庫。android系統源碼也有這個庫(android/platform/external/vixl)。
文/十八坰