在使用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