An overview,
In the process of using YAHFA framework, some problems were encountered. In order to solve these problems, FastHook framework was written on the basis of YAHFA. This analysis is based on Android 8.1.
Project address: FastHook: github.com/turing-tech…
Second, the YAHFA
2.1 YAHFA principle
First, let’s take a look at the basic process of YAHFA framework and then analyze its implementation principle.
- The Target method EntryPoint is replaced with HookTrampoline. When the Target method is executed, the HookTrampoline is actually executed to implement the method Hook.
- HookTrampoline sets r0 to Hook and then jumps to EntryPoint to execute Hook.
- Hook method parameters and Target method parameters must be consistent, call Backup method in Hook method to achieve the purpose of indirect call Target method.
- The Backup method must be static (if the Target method is not static, the first parameter of the Backup method must be this, and the Hook method does not need to be static, as long as the arguments are consistent). Static methods are statically dispatched, which ensures that the Backup method itself is called.
- Backup method must fully Backup Target method. ART needs to know the mapping relationship between native code and dex code, such as which dex instruction a native instruction corresponds to. This mapping relationship needs to be calculated by EntryPoint. We replaced EntryPoint for the Target method. So we have to copy the Backup method exactly, but the contents of the Backup method are exactly the same as the Target method, which indirectly achieves the purpose of calling Target.
2.2 YAHFA defects
- Method execution is inefficient. YAHFA avoids some of these problems by disallowing JIT and AOT compilation, but it also greatly reduces method execution efficiency.
- The Backup method cannot be parsed again. Because the Backup method is backed up as a Target method, resolving this again will raise a NoSuchMethod exception.
- The Moving GC raises a null pointer exception. When some member of the Target method (such as Class) is moved, the null pointer exception occurs because the Backup method is backed up and is not updated to the new address.
- Method inlining causes Hook failure. A Hook is implemented using a replacement method called EntryPoint, so EntryPoint is not used when the method is inlined, and the Hook will fail.
Third, FastHook
FastHook offers two solutions, one similar to Native Inline Hook, and the other is still Entrypoint replacement.
3.1 the Inline schema
- JumpTrampoline: The head node of a Hook list, a jump instruction that covers the first few bytes of the original method and jumps to a HookTrampoline.
- HookTrampoline: Determines whether a Hook is needed. If so, set R0 to Hook and jump to Hook, otherwise jump to the next HookTrampoline (multiple similar methods may share the same instruction, so multiple method hooks form a linked list).
- OriginalTrampoline: The last node of a Hook list used to restore the original method execution flow.
- Hook method: Perform the desired logic, modify the original method parameters, mask the original method call (Hook method by calling Forward method to implement the original method call).
- Forward method: a static native method. The EntryPoint method has no method body and will not actually be called. If its name merely acts as a Forward, the method will be replaced by TargetTrampoline.
- TargetTrampoline: Used to execute the original method, set R0 to the original method and restore the original method execution flow.
To sum up, the original method does not have any modification, while the Forward method only changes EntryPoint, which theoretically solves the problems caused by method parsing and Moving GC.
3.1.1 Method compilation
Inline mode requires methods to have compiled machine code, and AOT compilation is not done by default after 7.0, so a way to compile methods must be found. Fortunately, the Android default JIT provides this method: “jit_compile_method”. This method is derived from libart-compile.so and can be obtained using DLSYm (after 7.0, dlSYm was restricted to enhanced_dlSYm, which supports not only.dynsym (dynamic symbol table) queries but also.symtab (symbol table) queries). It is important to note that JIT compilation changes the thread state, which needs to be restored after compilation in order for the thread to remain in the correct state.
3.1.2 Instruction alignment
For Thumb2 instructions, the JumpTrampoline is 8 bytes, but Thumb has both 16 and 32 bit modes, meaning that the JumpTrampoline can overwrite instructions that are incomplete, so you need to make a judgment call and copy the entire instruction, either 8 bytes or 10 bytes.
3.1.3 PC-related Commands
If the overwritten instruction contains PC-related instruction, instruction recovery is required, otherwise the calculated address will be wrong. FastHook does not do actual fixes, but only determines whether the overridden instruction contains PC-specific instructions, and if so, uses EntryPoint mode.
3.1.4 Hook limit
The Hook will fail in the following cases:
- JIT compilation failed.
- The compiled instruction is smaller than the JumpTrampoline length.
- Native methods (have no actual method body and therefore cannot Hook).
When Inline mode Hook fails, the mode is automatically converted to EntryPoint mode.
3.2 EntryPoint Replacement Mode
- HookTrampoline: Sets r0 to Hook and jumps to Hook.
- Hook method: Consistent with Inline mode.
- Forward method: Consistent with Inline mode.
- TargetTrampoline: Used to execute the original method, which, unlike Inline mode, is fixed to execute in explain mode.
In summary, although the original method EntryPoint has been modified, it will be fixed to explain mode execution, although sacrificing performance, but also completely solve the problems caused by method parsing and Moving GC.
3.2.1 InterpreterToInterpreter
After 8.0, a Hook failure will occur if the EntrypPoint substitution mode is used in Debug compilations. Method calls to InterpreterTointerpreter will not use EntryPoint. The Target method is used to set kAccNative to avoid this. This method can only be modified in the Debug version and the Release version is not affected.
3.3 Hook security
EntryPoint cannot be changed in either Inline mode or EntryPoint mode. The following situations can change the method EntryPoint:
- The dex file is loaded.
- Class initialization.
- The JIT compiler.
- JIT garbage collection (similar to mark-sweep, set to QuickToInterpreterBridge).
- Explain execution (set to JIT entry if there is one).
When a Hook is performed, the class of the method must be initialized. So only need to deal with JIT, to accurately determine the JIT state of the current method. If it is waiting for JIT compilation or is JIT compilation, it needs to wait until the compilation is complete before Hook. Otherwise, it can be Hook safely.
3.4 Method inlining
Inline methods in either Inline or EntryPoint mode will cause the Hook to fail, so you need to find a way to disable method inlining. Let’s take a look at what happens when it’s inlined.
// Proxy methods are not inlineif (method->IsProxyMethod()) {
return false; } // Recursion is not inline beyond the limitif (CountRecursiveCallsOf(method) > kMaximumNumberOfRecursiveCalls) {
return false; } const DexFile::CodeItem* code_item = method->GetCodeItem(); // Native methods are not inlinedif (code_item == nullptr) {
return false; } // Method instructions whose size exceeds nline_max_code_units are not inline size_t inline_max_code_units = compiler_driver_->GetCompilerOptions().GetInlineMaxCodeUnits();if (code_item->insns_size_in_code_units_ > inline_max_code_units) {
return false; } // There are exceptions that cannot be caught inlineif(code_item->tries_size_ ! = 0) {return false; } // kAccCompileDontBother is setfalse, so it does not prevent inliningif(! Method ->IsCompilable()) {} //Verifiy failed to be inlinedif(! 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; } // Static methods or private methods associated with <clinit> are not inlineif (invoke_instruction->IsInvokeStaticOrDirect() &&
invoke_instruction->AsInvokeStaticOrDirect()->IsStaticWithImplicitClinitCheck()) {
return false;
}
Copy the code
In consideration of other unknown risks associated with modifying method attributes, modify inline_MAX_CODE_units. Inline_max_code_units is a member of CompilerOptions, which is a member of jit_compile_handle, which is a global static variable, Therefore, it can be obtained through DLSYM. Disable JIT compilation by changing it to 0. This approach only prevents Jits from inlining, not AOT. AOT compilation creates a new Runtime environment, and we can only modify the current Runtime environment. Nothing can be done about OSR.
3.5 summary
In short, the FastHook scheme is: Hook method Hook original method, original method Hook Forward method, Hook method call Forward method to call the original method.
Use FastHook
4.1 provide HookInfo
private static String[] mHookItem = {
"mode"."targetClassName"."targetMethodName"."targetParamSig"."hookClassName"."hookMethodName"."hookParamSig"."forwardClassName"."forwardMethodName"."forwardParamSig"
};
public static String[][] HOOK_ITEMS = {
mHookItem
};
Copy the code
- The HookInfo class can be any class, but there must be a static, two-dimensional array member variable named HOOK_ITEMS.
- The HookItem format is fixed. As shown in the figure above, mode has two values: “1”:Inline mode;” 2″:EntryPoint replacement mode. Note that sig requires parameter signatures rather than full method signatures.
4.2 Hook interface
/** ** @param hookInfoClassName HookInfo class name *@param hookInfoClassLoader ClassLoader where HookInfo resides. If null, Represents the current ClassLoader *@param targetClassLoader Target method of the ClassLoader, if null, *@param hookClassLoader (); Null indicates whether the current ClassLoader *@param forwardClassLoader Forward method is inline.false, forbid inline;true* */ public static voiddoHook(String hookInfoClassName, ClassLoader hookInfoClassLoader, ClassLoader targetClassLoader, ClassLoader hookClassLoader, ClassLoader forwardClassLoader, boolean jitInline)
Copy the code
1. Plug-in Hook: It is recommended to call the attachBaseContext method.
FastHookManger. DoHook ()"hookInfoClassName",pluginsClassloader,null,pluginsClassloader,pluginsClassloader,false);
Copy the code
2. Built-in Hook. It is recommended to call the attachBaseContext method.
// Built-in hooks, both located in the current ClassLoader FastHookManger. DoHook ("hookInfoClassName",null,null,null,null,false);
Copy the code
3. Root Hook. It is recommended to call it at an appropriate place in the handleBindApplication method, usually after loading apK and before calling attachBaseContext.
//Root Hook, which needs to be used for ClassLoader and apk ClassLoader"hookInfoClassName",pluginsClassloader,apkClassLoader,pluginsClassloader,pluginsClassloader,false);
Copy the code
4.3 Supported Android versions
5.0 ~ 9.0
4.4 Supported Architectures
Thumb2 Arm64
reference
- YAHFA:github.com/rk700/YAHFA
- Enhanced_dlfunctions:Github.com/turing-tech…
FastHook series
- FastHook — Smart use of dynamic proxies for non-invasive AOP
- FastHook — Superior stability over YAHFA
- FastHook — How to use FastHook to avoid root hook wechat
- FastHook – implements.dynsym and.symtab symbol queries