在使用YAHFA框架的過程當中,遇到了些問題,爲了解決這些問題在YAHFA的基礎上寫了FastHook框架。本文分析內容基於Android 8.1。git
項目地址:FastHook:github.com/turing-tech…github
首先咱們來看看YAHFA框架基本流程,再分析其實現原理。 數組
FastHook提供了兩種方案,一種相似Native Inline Hook,另外一個依舊是Entrypoint替換。安全
綜上可知,原方法沒有任何修改、而Forward方法僅僅修改了EntryPoint,從理論上解決了方法解析和Moving GC所帶來的問題。bash
Inline模式要求方法必須有編譯後的機器代碼,而7.0以後默認不會進行AOT編譯,於是必須找到一個能編譯方法的方案。幸運的是Android默認的JIT便提供了這樣的方法:「jit_compile_method」。該方法由libart-compile.so導出,能夠利用dlsym獲取(7.0以後限制了dlsym,改用enhanced_dlsym代替,不只支持.dynsym(動態符號表)查詢,還支持.symtab(符號表)查詢)。值得注意的是,JIT編譯會改變線程狀態,爲了線程保持正確的狀態,編譯完成後須要恢復線程狀態。微信
對於Thumb2指令集, JumpTrampoline是8字節 ,但Thumb有16位和32位兩種模式,也就是說JumpTrampoline覆蓋掉的指令有多是不完整的,所以須要作指令判斷,複製完整的指令,多是8字節,也多是10字節。架構
覆蓋的指令若包含PC相關指令,須要進行指令恢復,否則計算出來的地址將是錯誤的。FastHook並不作實際修復,僅判斷覆蓋的指令是否包含有PC相關指令,若是包含就使用EntryPoint模式。框架
下列幾種狀況下將Hook失敗:post
當Inline模式Hook失敗將自動轉換爲EntryPoint模式。性能
綜上可知,雖然原方法EntryPoint被修改了,但其將固定以解釋模式執行,雖然犧牲了性能,可是也完全解決了方法解析與Moving GC所帶來的問題。
在8.0以後,若是在Debug編譯版本,使用EntrypPoint替換模式會出現Hook失效的狀況,方法調用進入InterpreterTointerpreter,不會用到EntryPoint,這裏採用YAHFA的方案,Target方法設置kAccNative來規避,只在Debug版本下修改,Release版本不受影響,不修改。
不管Inline模式仍是EntryPoint模式,都要求EntryPoint不能改變。下列幾種狀況會改變方法EntryPoint:
當進行Hook時,方法所在類必定是初始化了的。因此只須要處理JIT,要準確的判斷出當前方法的JIT狀態。若是其等待JIT編譯或者正在JIT編譯,則需待其編譯完成再Hook,其餘狀況可安全Hook。
不管Inline模式仍是EntryPoint模式,方法內聯都會致使Hook失效,所以須要想方法禁止方法內聯。先看看什麼狀況下會進行內聯。
//代理方法不內聯
if (method->IsProxyMethod()) {
return false;
}
//遞歸超過限制不內聯
if (CountRecursiveCallsOf(method) > kMaximumNumberOfRecursiveCalls) {
return false;
}
const DexFile::CodeItem* code_item = method->GetCodeItem();
//native方法不內聯
if (code_item == nullptr) {
return false;
}
//方法指令大小超過nline_max_code_units不內聯
size_t inline_max_code_units = compiler_driver_->GetCompilerOptions().GetInlineMaxCodeUnits();
if (code_item->insns_size_in_code_units_ > inline_max_code_units) {
return false;
}
//有異常捕獲不內聯
if (code_item->tries_size_ != 0) {
return false;
}
//設置了kAccCompileDontBother,這裏沒有返回false,因此並不能阻止內聯
if (!method->IsCompilable()) {
}
//Verifiy失敗不內聯
if (!method->GetDeclaringClass()->IsVerified()) {
uint16_t class_def_idx = method->GetDeclaringClass()->GetDexClassDefIndex();
if (Runtime::Current()->UseJitCompilation() ||
!compiler_driver_->IsMethodVerifiedWithoutFailures(
method->GetDexMethodIndex(), class_def_idx, *method->GetDexFile())) {
return false;
}
}
//靜態方法或私有方法關聯<clinit>不內聯
if (invoke_instruction->IsInvokeStaticOrDirect() &&
invoke_instruction->AsInvokeStaticOrDirect()->IsStaticWithImplicitClinitCheck()) {
return false;
}
複製代碼
考慮到修改方法屬性可能會其餘未知的風險,所以選擇修改inline_max_code_units。inline_max_code_units是CompilerOptions的成員,CompilerOptions是jit_compile_handle的成員,jit_compile_handle是一個全局靜態變量,所以能夠經過dlsym獲取。經過修改其爲0來禁止JIT編譯。這種方式只能阻止JIT內聯,對AOT無效。AOT編譯的時候會新創建Runtime環境,而咱們只能修改當前Runtime環境。對OSR也無能爲力。
簡而言之,FastHook方案就是:Hook方法Hook原方法,原方法Hook Forward方法,Hook方法調用Forward方法來實現調用原方法。
private static String[] mHookItem = {
"mode",
"targetClassName","targetMethodName","targetParamSig",
"hookClassName","hookMethodName","hookParamSig",
"forwardClassName","forwardMethodName","forwardParamSig"
};
public static String[][] HOOK_ITEMS = {
mHookItem
};
複製代碼
/**
*
*@param hookInfoClassName HookInfo類名
*@param hookInfoClassLoader HookInfo類所在的ClassLoader,若是爲null,表明當前ClassLoader
*@param targetClassLoader Target方法所在的ClassLoader,若是爲null,表明當前ClassLoader
*@param hookClassLoader Hook方法所在的ClassLoader,若是爲null,表明當前ClassLoader
*@param forwardClassLoader Forward方法所在的ClassLoader,若是爲null,表明當前ClassLoader
*@param jitInline 是否內聯,false,禁止內聯;true,容許內聯
*
*/
public static void doHook(String hookInfoClassName, ClassLoader hookInfoClassLoader, ClassLoader targetClassLoader, ClassLoader hookClassLoader, ClassLoader forwardClassLoader, boolean jitInline)
複製代碼
1. 插件式Hook:建議在attachBaseContext方法裏調用。
//插件式Hook,須要提供插件的ClassLoader
FastHookManger.doHook("hookInfoClassName",pluginsClassloader,null,pluginsClassloader,pluginsClassloader,false);
複製代碼
2. 內置Hook,建議在attachBaseContext方法裏調用。
//內置Hook,都位於當前ClassLoader
FastHookManger.doHook("hookInfoClassName",null,null,null,null,false);
複製代碼
3. Root Hook,建議在handleBindApplication方法裏合適的地方調用,通常在加載apk後,調用attachBaseContext前。
//Root Hook,須要體供插件的ClassLoader和apk的ClassLoader
FastHookManger.doHook("hookInfoClassName",pluginsClassloader,apkClassLoader,pluginsClassloader,pluginsClassloader,false);
複製代碼
5.0 ~ 9.0
Thumb2 Arm64