Xposed源码笔记
Xposed
XposedBridge
hook模板
public static XC_MethodHook.Unhook findAndHookMethod(Class<?> clazz, String methodName, Object... parameterTypesAndCallback) {
if (parameterTypesAndCallback.length == 0 || !(parameterTypesAndCallback[parameterTypesAndCallback.length-1] instanceof XC_MethodHook))
throw new IllegalArgumentException("no callback defined");
XC_MethodHook callback = (XC_MethodHook) parameterTypesAndCallback[parameterTypesAndCallback.length-1];
Method m = findMethodExact(clazz, methodName, getParameterClasses(clazz.getClassLoader(), parameterTypesAndCallback));
return XposedBridge.hookMethod(m, callback);
}
public static XC_MethodHook.Unhook hookMethod(Member hookMethod, XC_MethodHook callback) {
...
//省略检查
callbacks.add(callback);
if (newMethod) {
Class<?> declaringClass = hookMethod.getDeclaringClass();
int slot;
Class<?>[] parameterTypes;
Class<?> returnType;
if (runtime == RUNTIME_ART) {
slot = 0;
parameterTypes = null;
returnType = null;
} else if (hookMethod instanceof Method) {
slot = getIntField(hookMethod, "slot");
parameterTypes = ((Method) hookMethod).getParameterTypes();
returnType = ((Method) hookMethod).getReturnType();
} else {
slot = getIntField(hookMethod, "slot");
parameterTypes = ((Constructor<?>) hookMethod).getParameterTypes();
returnType = null;
}
// 需要hook函数的信息封装到AdditionalHookInfo中
AdditionalHookInfo additionalInfo = new AdditionalHookInfo(callbacks, parameterTypes, returnType);
hookMethodNative(hookMethod, declaringClass, slot, additionalInfo);
}
return callback.new Unhook(hookMethod);
}
封装到AdditionalHookInfo
中,进入native中
Xposed
这里我们只关注art,不关注dalvik
// libxposed_art.cpp
void XposedBridge_hookMethodNative(JNIEnv* env, jclass, jobject javaReflectedMethod,
jobject, jint, jobject javaAdditionalInfo) {
// Detect usage errors.
ScopedObjectAccess soa(env);
// Get the ArtMethod of the method to be hooked.
ArtMethod* artMethod = ArtMethod::FromReflectedMethod(soa, javaReflectedMethod);
// Hook the method
artMethod->EnableXposedHook(soa, javaAdditionalInfo);
}
FromReflectedMethod
方法接收反射的 Java 方法对象javaReflectedMethod
拿到 ArtMethod
对象
android_art
void ArtMethod::EnableXposedHook(ScopedObjectAccess& soa, jobject additional_info) {
if (UNLIKELY(IsXposedHookedMethod())) {
// Already hooked
return;
} else if (UNLIKELY(IsXposedOriginalMethod())) {
// This should never happen
ThrowIllegalArgumentException(StringPrintf("Cannot hook the method backup: %s", PrettyMethod(this).c_str()).c_str());
return;
}
// Create a backup of the ArtMethod object
auto* cl = Runtime::Current()->GetClassLinker();
auto* linear_alloc = cl->GetAllocatorForClassLoader(GetClassLoader());
ArtMethod* backup_method = cl->CreateRuntimeMethod(linear_alloc);
backup_method->CopyFrom(this, cl->GetImagePointerSize());
backup_method->SetAccessFlags(backup_method->GetAccessFlags() | kAccXposedOriginalMethod);
// Create a Method/Constructor object for the backup ArtMethod object
mirror::AbstractMethod* reflected_method;
if (IsConstructor()) {
reflected_method = mirror::Constructor::CreateFromArtMethod(soa.Self(), backup_method);
} else {
reflected_method = mirror::Method::CreateFromArtMethod(soa.Self(), backup_method);
}
reflected_method->SetAccessible<false>(true);
// Save extra information in a separate structure, stored instead of the native method
XposedHookInfo* hook_info = reinterpret_cast<XposedHookInfo*>(linear_alloc->Alloc(soa.Self(), sizeof(XposedHookInfo)));
hook_info->reflected_method = soa.Vm()->AddGlobalRef(soa.Self(), reflected_method);
hook_info->additional_info = soa.Env()->NewGlobalRef(additional_info);
hook_info->original_method = backup_method;
ScopedThreadSuspension sts(soa.Self(), kSuspended);
jit::ScopedJitSuspend sjs;
gc::ScopedGCCriticalSection gcs(soa.Self(),
gc::kGcCauseXposed,
gc::kCollectorTypeXposed);
ScopedSuspendAll ssa(__FUNCTION__);
cl->InvalidateCallersForMethod(soa.Self(), this);
jit::Jit* jit = art::Runtime::Current()->GetJit();
if (jit != nullptr) {
jit->GetCodeCache()->MoveObsoleteMethod(this, backup_method);
}
// 设置JNI方法入口点
SetEntryPointFromJniPtrSize(reinterpret_cast<uint8_t*>(hook_info), sizeof(void*));
// 设置QuickCompiledCode入口点
SetEntryPointFromQuickCompiledCode(GetQuickProxyInvokeHandler());
SetCodeItemOffset(0);
// Adjust access flags.
const uint32_t kRemoveFlags = kAccNative | kAccSynchronized | kAccAbstract | kAccDefault | kAccDefaultConflict;
SetAccessFlags((GetAccessFlags() & ~kRemoveFlags) | kAccXposedHookedMethod);
MutexLock mu(soa.Self(), *Locks::thread_list_lock_);
Runtime::Current()->GetThreadList()->ForEach(StackReplaceMethodAndInstallInstrumentation, this);
}
将函数信息保存到XposedHookInfo* hook_info
中,紧接着调用两个关键函数SetEntryPointFromJniPtrSize
和SetEntryPointFromQuickCompiledCode
ALWAYS_INLINE void SetEntryPointFromJniPtrSize(const void* entrypoint, size_t pointer_size) {
DCHECK(Runtime::Current()->IsAotCompiler() || !IsXposedHookedMethod());
SetNativePointer(EntryPointFromJniOffset(pointer_size), entrypoint, pointer_size);
}
template<typename T>
ALWAYS_INLINE void SetNativePointer(MemberOffset offset, T new_value, size_t pointer_size) {
static_assert(std::is_pointer<T>::value, "T must be a pointer type");
DCHECK(ValidPointerSize(pointer_size)) << pointer_size;
const auto addr = reinterpret_cast<uintptr_t>(this) + offset.Uint32Value();
if (pointer_size == sizeof(uint32_t)) {
uintptr_t ptr = reinterpret_cast<uintptr_t>(new_value);
*reinterpret_cast<uint32_t*>(addr) = dchecked_integral_cast<uint32_t>(ptr);
} else {
*reinterpret_cast<uint64_t*>(addr) = reinterpret_cast<uintptr_t>(new_value);
}
}
- SetEntryPointFromJniPtrSize
到这里为止,已经将需要hook的java method的ArtMethod
的entry节点替换成了XposedHookInfo
的地址
额外插入下jni注册调用链
jni_internal.cc#RegisterNatives -> 对每个方法,找到对应的java方法(ArtMethod对象),交由class_linker.cc#RegisterNative -> art_method.h#SetEntryPointFromJni -> SetEntryPointFromJniPtrSize -> SetDataPtrSize -> SetNativePointer 这里直接利用artmethod基地址 + 偏移量的方式快速找到方法指针,并存下对应的native方法
- SetEntryPointFromQuickCompiledCode
实际是修改entry_point_from_quick_compiled_code
字段
ArtMethod::Invoke
在快速执行模式下都会跳转到entry_point_from_quick_compiled_code_
进行执行,实际就是进入art_quick_proxy_invoke_handler
// Method dispatch from quick compiled code invokes this pointer which may cause bridging into
// the interpreter.
void* entry_point_from_quick_compiled_code_;
extern "C" void art_quick_proxy_invoke_handler();
static inline const void* GetQuickProxyInvokeHandler() {
return reinterpret_cast<const void*>(art_quick_proxy_invoke_handler);
}
// ./runtime/arch/arm64/quick_entrypoints_arm64.S
ENTRY art_quick_proxy_invoke_handler
SETUP_REFS_AND_ARGS_CALLEE_SAVE_FRAME_WITH_METHOD_IN_X0
mov x2, xSELF // pass Thread::Current
mov x3, sp // pass SP
bl artQuickProxyInvokeHandler // (Method* proxy method, receiver, Thread*, SP)
ldr x2, [xSELF, THREAD_EXCEPTION_OFFSET]
cbnz x2, .Lexception_in_proxy // success if no exception is pending
RESTORE_REFS_AND_ARGS_CALLEE_SAVE_FRAME // Restore frame
fmov d0, x0 // Store result in d0 in case it was float or double
ret // return on success
.Lexception_in_proxy:
RESTORE_REFS_AND_ARGS_CALLEE_SAVE_FRAME
DELIVER_PENDING_EXCEPTION
END art_quick_proxy_invoke_handler
根据这段汇编代码,我们知道会bl
到 artQuickProxyInvokeHandler
函数,回调就是在这里得到执行
artQuickProxyInvokeHandle
当函数执行时候进入artQuickProxyInvokeHandle
extern "C" uint64_t artQuickProxyInvokeHandler(
ArtMethod* proxy_method, mirror::Object* receiver, Thread* self, ArtMethod** sp)
SHARED_REQUIRES(Locks::mutator_lock_) {
const bool is_xposed = proxy_method->IsXposedHookedMethod();
if (!is_xposed) {
DCHECK(proxy_method->IsRealProxyMethod()) << PrettyMethod(proxy_method);
DCHECK(receiver->GetClass()->IsProxyClass()) << PrettyMethod(proxy_method);
}
...
if (is_xposed) {//
jmethodID proxy_methodid = soa.EncodeMethod(proxy_method);
self->EndAssertNoThreadSuspension(old_cause);
JValue result = InvokeXposedHandleHookedMethod(soa, shorty, rcvr_jobj, proxy_methodid, args);
local_ref_visitor.FixupReferences();
return result.GetJ();
}
...
return result.GetJ();
}
JValue InvokeXposedHandleHookedMethod(ScopedObjectAccessAlreadyRunnable& soa, const char* shorty,
jobject rcvr_jobj, jmethodID method,
std::vector<jvalue>& args) {
...
const XposedHookInfo* hook_info = soa.DecodeMethod(method)->GetXposedHookInfo();
// Call XposedBridge.handleHookedMethod(Member method, int originalMethodId, Object additionalInfoObj,
// Object thisObject, Object[] args)
jvalue invocation_args[5];
invocation_args[0].l = hook_info->reflected_method;
invocation_args[1].i = 1;
invocation_args[2].l = hook_info->additional_info;
invocation_args[3].l = rcvr_jobj;
invocation_args[4].l = args_jobj;
jobject result = // 函数执行前主动调用回调
soa.Env()->CallStaticObjectMethodA(ArtMethod::xposed_callback_class,
ArtMethod::xposed_callback_method,
invocation_args);
...
}
CallStaticObjectMethodA --> InvokeWithJValues --> method->Invoke
这里开始回调Java的CallBack了
private static Object handleHookedMethod(Member method, int originalMethodId, Object additionalInfoObj,
Object thisObject, Object[] args) throws Throwable {
AdditionalHookInfo additionalInfo = (AdditionalHookInfo) additionalInfoObj;
if (disableHooks) {
try {
//调用原方法
return invokeOriginalMethodNative(method, originalMethodId, additionalInfo.parameterTypes,
additionalInfo.returnType, thisObject, args);
} catch (InvocationTargetException e) {
throw e.getCause();
}
}
...
MethodHookParam param = new MethodHookParam();
param.method = method;
param.thisObject = thisObject;
param.args = args;
// call "before method" callbacks
int beforeIdx = 0;
do {
try {
((XC_MethodHook) callbacksSnapshot[beforeIdx]).beforeHookedMethod(param);
} catch (Throwable t) {
XposedBridge.log(t);
// reset result (ignoring what the unexpectedly exiting callback did)
param.setResult(null);
param.returnEarly = false;
continue;
}
if (param.returnEarly) {
// skip remaining "before" callbacks and corresponding "after" callbacks
beforeIdx++;
break;
}
} while (++beforeIdx < callbacksLength);
// call original method if not requested otherwise
if (!param.returnEarly) {
try {
param.setResult(invokeOriginalMethodNative(method, originalMethodId,
additionalInfo.parameterTypes, additionalInfo.returnType, param.thisObject, param.args));
} catch (InvocationTargetException e) {
param.setThrowable(e.getCause());
}
}
...
}
jobject XposedBridge_invokeOriginalMethodNative(JNIEnv* env, jclass, jobject javaMethod,
jint isResolved, jobjectArray, jclass, jobject javaReceiver, jobjectArray javaArgs) {
ScopedFastNativeObjectAccess soa(env);
if (UNLIKELY(!isResolved)) {
ArtMethod* artMethod = ArtMethod::FromReflectedMethod(soa, javaMethod);
if (LIKELY(artMethod->IsXposedHookedMethod())) {
javaMethod = artMethod->GetXposedHookInfo()->reflected_method;
}
}
#if PLATFORM_SDK_VERSION >= 23
return InvokeMethod(soa, javaMethod, javaReceiver, javaArgs);
#else
return InvokeMethod(soa, javaMethod, javaReceiver, javaArgs, true);
#endif
}
总结
- Xposed通过修改libart源码方式使用
SetEntryPointFromJniPtrSize
将ArtMethod的entry地址设置为XposedHookInfo
的地址 - 然后通过
SetEntryPointFromQuickCompiledCode
函数使art虚拟机当函数执行时进入quick_entrypoints_arm64
中,并在artQuickProxyInvokeHandler
进行回调,因为此函数带有函数调用时候传递的参数,因此可以拿到APP的参数
Lsposed
public static XC_MethodHook.Unhook hookMethod(Member hookMethod, XC_MethodHook callback) {
...
// 从xposed中调用lsp的 native 方法 hookMethod
if (!HookBridge.hookMethod(false, (Executable) hookMethod, LSPosedBridge.NativeHooker.class, callback.priority, callback)) {
log("Failed to hook " + hookMethod);
return null;
}
return callback.new Unhook(hookMethod);
}
从xposed中调用lsp的 native 方法 hookMethod,通过LSP_DEF_NATIVE_METHOD
宏定义完成
// ./LSPosed/core/src/main/jni/src/jni/hook_bridge.cpp
LSP_DEF_NATIVE_METHOD(jboolean, HookBridge, hookMethod, jboolean useModernApi, jobject hookMethod,
jclass hooker, jint priority, jobject callback) {
...
if (newHook) {
// hooker 对应 LSPosedBridge.NativeHooker.class
auto init = env->GetMethodID(hooker, "<init>", "(Ljava/lang/reflect/Executable;)V");
// NativeHooker.caallback();
auto callback_method = env->ToReflectedMethod(hooker, env->GetMethodID(hooker, "callback",
"([Ljava/lang/Object;)Ljava/lang/Object;"),
false);
// 实例化 LSPosedBridge.NativeHooker类
/* 对应java代码
private NativeHooker(Executable method) {
var isStatic = Modifier.isStatic(method.getModifiers());
Object returnType;
if (method instanceof Method) {
returnType = ((Method) method).getReturnType();
} else {
returnType = null;
}
params = new Object[]{
method,
returnType,
isStatic,
};
}
*/
auto hooker_object = env->NewObject(hooker, init, hookMethod);
hook_item->SetBackup(lsplant::Hook(env, hookMethod, hooker_object, callback_method));
env->DeleteLocalRef(hooker_object);
}
jobject backup = hook_item->GetBackup();
if (!backup) return JNI_FALSE;
JNIMonitor monitor(env, backup);
if (useModernApi) { // false
if (before_method_field == nullptr) {
auto callback_class = JNI_GetObjectClass(env, callback);
callback_ctor = JNI_GetMethodID(env, callback_class, "<init>", "(Ljava/lang/reflect/Method;Ljava/lang/reflect/Method;)V");
before_method_field = JNI_GetFieldID(env, callback_class, "beforeInvocation", "Ljava/lang/reflect/Method;");
after_method_field = JNI_GetFieldID(env, callback_class, "afterInvocation", "Ljava/lang/reflect/Method;");
}
auto before_method = JNI_GetObjectField(env, callback, before_method_field);
auto after_method = JNI_GetObjectField(env, callback, after_method_field);
auto callback_type = ModuleCallback {
.before_method = env->FromReflectedMethod(before_method),
.after_method = env->FromReflectedMethod(after_method),
};
hook_item->modern_callbacks.emplace(priority, callback_type);
} else {
hook_item->legacy_callbacks.emplace(priority, env->NewGlobalRef(callback));
}
return JNI_TRUE;
}
在native中实例化LSPosedBridge.NativeHooker
类,并调用lsplant::Hook
进行hook,将返回值保存在hook_item
中
lsplant::Hook
// ./LSPosed/external/lsplant/lsplant/src/main/jni/lsplant.cc
[[maybe_unused]] jobject Hook(JNIEnv *env, jobject target_method, jobject hooker_object,
jobject callback_method) {
...
jmethodID hook_method = nullptr;
jmethodID backup_method = nullptr;
jfieldID hooker_field = nullptr;
//通过method_get_declaring_class拿到了target_method被申明的类的Class对象
//也就是说我们知道target_method申明在哪个类了
auto target_class =
JNI_Cast<jclass>(JNI_CallObjectMethod(env, target_method, method_get_declaring_class));
constexpr static uint32_t kAccClassIsProxy = 0x00040000;
bool is_proxy = JNI_GetIntField(env, target_class, class_access_flags) & kAccClassIsProxy;
//拿到target_method对应的ArtMethod
auto *target = ArtMethod::FromReflectedMethod(env, target_method);
bool is_static = target->IsStatic();
if (IsHooked(target, true)) {
LOGW("Skip duplicate hook");
return nullptr;
}
ScopedLocalRef<jclass> built_class{env};
{
auto callback_name =
JNI_Cast<jstring>(JNI_CallObjectMethod(env, callback_method, method_get_name));
JUTFString callback_method_name(callback_name);
auto target_name =
JNI_Cast<jstring>(JNI_CallObjectMethod(env, target_method, method_get_name));
JUTFString target_method_name(target_name);
auto callback_class = JNI_Cast<jclass>(
JNI_CallObjectMethod(env, callback_method, method_get_declaring_class));
auto callback_class_loader =
JNI_CallObjectMethod(env, callback_class, class_get_class_loader);
auto callback_class_name =
JNI_Cast<jstring>(JNI_CallObjectMethod(env, callback_class, class_get_name));
JUTFString class_name(callback_class_name);
if (!JNI_IsInstanceOf(env, hooker_object, callback_class)) {
LOGE("callback_method is not a method of hooker_object");
return nullptr;
}
std::tie(built_class, hooker_field, hook_method, backup_method) = WrapScope(
env,
//创建LSPHooker_类的Dex文件并加载,并返回LSPHooker_类的信息
BuildDex(env, callback_class_loader,
__builtin_expect(is_proxy, 0) ? GetProxyMethodShorty(env, target_method)
: ArtMethod::GetMethodShorty(env, target_method),
is_static, target->IsConstructor() ? "constructor" : target_method_name.get(),
class_name.get(), callback_method_name.get()));
if (!built_class || !hooker_field || !hook_method || !backup_method) {
LOGE("Failed to generate hooker");
return nullptr;
}
}
auto reflected_hook = JNI_ToReflectedMethod(env, built_class, hook_method, is_static);
auto reflected_backup = JNI_ToReflectedMethod(env, built_class, backup_method, is_static);
JNI_CallVoidMethod(env, reflected_backup, set_accessible, JNI_TRUE);
auto *hook = ArtMethod::FromReflectedMethod(env, reflected_hook);
auto *backup = ArtMethod::FromReflectedMethod(env, reflected_backup);
// std::string_view generated_field_name = "hooker";
JNI_SetStaticObjectField(env, built_class, hooker_field, hooker_object);
// LSPHooker_ 类准备完成,开始Hook
if (DoHook(target, hook, backup)) {
std::apply(
[backup_method, target_method_id = env->FromReflectedMethod(target_method)](auto... v) {
((*v == target_method_id &&
(LOGD("Propagate internal used method because of hook"), *v = backup_method)) ||
...);
},
kInternalMethods);
jobject global_backup = JNI_NewGlobalRef(env, reflected_backup);
RecordHooked(target, target->GetDeclaringClass()->GetClassDef(), global_backup, backup);
if (!is_proxy) [[likely]] {
RecordJitMovement(target, backup);
}
// Always record backup as deoptimized since we dont want its entrypoint to be updated
// by FixupStaticTrampolines on hooker class
// Used hook's declaring class here since backup's is no longer the same with hook's
RecordDeoptimized(hook->GetDeclaringClass()->GetClassDef(), backup);
return global_backup;
}
return nullptr;
}
这里method_get_declaring_class
的定义,返回表示在它被声明的类的Class对象,也就是说我们知道我们要hook那个类了
if (method_get_declaring_class =
JNI_GetMethodID(env, executable, "getDeclaringClass", "()Ljava/lang/Class;");
!method_get_declaring_class) {
LOGE("Failed to find getDeclaringClass method");
return false;
}
通过method_get_declaring_class
获取了target_method申明的类
通过ArtMethod::FromReflectedMethod
获取了target_method对应的ArtMethod
lsplant::BuildDex
std::tuple<jclass, jfieldID, jmethodID, jmethodID> BuildDex(JNIEnv *env, jobject class_loader,
std::string_view shorty, bool is_static,
std::string_view method_name,
std::string_view hooker_class,
std::string_view callback_name) {
// NOLINTNEXTLINE
using namespace startop::dex;
if (shorty.empty()) {
LOGE("Invalid shorty");
return {nullptr, nullptr, nullptr, nullptr};
}
DexBuilder dex_file;
auto parameter_types = std::vector<TypeDescriptor>();
parameter_types.reserve(shorty.size() - 1);
auto return_type =
shorty[0] == 'L' ? TypeDescriptor::Object : TypeDescriptor::FromDescriptor(shorty[0]);
if (!is_static) parameter_types.push_back(TypeDescriptor::Object); // this object
for (const char ¶m : shorty.substr(1)) {
parameter_types.push_back(param == 'L'
? TypeDescriptor::Object
: TypeDescriptor::FromDescriptor(static_cast<char>(param)));
}
// std::string_view generated_class_name = "LSPHooker_";
// 创建了LSPHooker_类,同时还创建了{target}方法
ClassBuilder cbuilder{dex_file.MakeClass(generated_class_name)};
if (!generated_source_name.empty()) cbuilder.set_source_file(generated_source_name);
auto hooker_type = TypeDescriptor::FromClassname(hooker_class.data());
auto *hooker_field = cbuilder.CreateField(generated_field_name/*字段名 hooker*/, hooker_type)
.access_flags(dex::kAccStatic)
.Encode();
auto hook_builder{cbuilder.CreateMethod(
generated_method_name == "{target}" ? method_name.data() /*hook的函数名, 比如"getInstallPackageInfo"*/: generated_method_name,
Prototype{return_type, parameter_types})};
// allocate tmp first because of wide
auto tmp{hook_builder.AllocRegister()};
hook_builder.BuildConst(tmp, static_cast<int>(parameter_types.size()));
auto hook_params_array{hook_builder.AllocRegister()};
hook_builder.BuildNewArray(hook_params_array, TypeDescriptor::Object, tmp);
for (size_t i = 0U, j = 0U; i < parameter_types.size(); ++i, ++j) {
hook_builder.BuildBoxIfPrimitive(Value::Parameter(j), parameter_types[i],
Value::Parameter(j));
hook_builder.BuildConst(tmp, static_cast<int>(i));
hook_builder.BuildAput(Instruction::Op::kAputObject, hook_params_array, Value::Parameter(j),
tmp);
if (parameter_types[i].is_wide()) ++j;
}
auto handle_hook_method{dex_file.GetOrDeclareMethod(
hooker_type, callback_name.data()/*callback*/,
Prototype{TypeDescriptor::Object, TypeDescriptor::Object.ToArray()})};
hook_builder.AddInstruction(
Instruction::GetStaticObjectField(hooker_field->decl->orig_index, tmp));
hook_builder.AddInstruction(
Instruction::InvokeVirtualObject(handle_hook_method.id, tmp, tmp, hook_params_array));
if (return_type == TypeDescriptor::Void) {
hook_builder.BuildReturn();
} else if (return_type.is_primitive()) {
auto box_type{return_type.ToBoxType()};
const ir::Type *type_def = dex_file.GetOrAddType(box_type);
hook_builder.AddInstruction(Instruction::Cast(tmp, Value::Type(type_def->orig_index)));
hook_builder.BuildUnBoxIfPrimitive(tmp, box_type, tmp);
hook_builder.BuildReturn(tmp, false, return_type.is_wide());
} else {
const ir::Type *type_def = dex_file.GetOrAddType(return_type);
hook_builder.AddInstruction(Instruction::Cast(tmp, Value::Type(type_def->orig_index)));
hook_builder.BuildReturn(tmp, true);
}
auto *hook_method = hook_builder.Encode();
auto backup_builder{cbuilder.CreateMethod("backup", Prototype{return_type, parameter_types})};
if (return_type == TypeDescriptor::Void) {
backup_builder.BuildReturn();
} else if (return_type.is_wide()) {
LiveRegister zero = backup_builder.AllocRegister();
LiveRegister zero_wide = backup_builder.AllocRegister();
backup_builder.BuildConstWide(zero, 0);
backup_builder.BuildReturn(zero, /*is_object=*/false, true);
} else {
LiveRegister zero = backup_builder.AllocRegister();
LiveRegister zero_wide = backup_builder.AllocRegister();
backup_builder.BuildConst(zero, 0);
backup_builder.BuildReturn(zero, /*is_object=*/!return_type.is_primitive(), false);
}
auto *backup_method = backup_builder.Encode();
slicer::MemView image{dex_file.CreateImage()};
jclass target_class = nullptr;
ScopedLocalRef<jobject> java_dex_file{nullptr};
if (auto dex_file_class = JNI_FindClass(env, "dalvik/system/DexFile"); dex_file_init_with_cl) {
java_dex_file = JNI_NewObject(
env, dex_file_class, dex_file_init_with_cl,
JNI_NewObjectArray(
env, 1, JNI_FindClass(env, "java/nio/ByteBuffer"),
JNI_NewDirectByteBuffer(env, const_cast<void *>(image.ptr()), image.size())),
nullptr, nullptr);
} else if (dex_file_init) {
java_dex_file = JNI_NewObject(
env, dex_file_class, dex_file_init,
JNI_NewDirectByteBuffer(env, const_cast<void *>(image.ptr()), image.size()));
} else {
void *target =
mmap(nullptr, image.size(), PROT_WRITE | PROT_READ, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
memcpy(target, image.ptr(), image.size());
mprotect(target, image.size(), PROT_READ);
std::string err_msg;
const auto *dex = DexFile::OpenMemory(
target, image.size(), generated_source_name.empty() ? "lsplant" : generated_source_name,
&err_msg);
if (!dex) {
LOGE("Failed to open memory dex: %s", err_msg.data());
} else {
java_dex_file = WrapScope(env, dex ? dex->ToJavaDexFile(env) : jobject{nullptr});
}
}
// 加载之前创建的dexfile,然后实例化LSPHooker_类
if (auto path_class_loader = JNI_FindClass(env, "dalvik/system/PathClassLoader");
java_dex_file) {
auto my_cl = JNI_NewObject(env, path_class_loader, path_class_loader_init,
JNI_NewStringUTF(env, ""), class_loader);
target_class =
JNI_Cast<jclass>(
JNI_CallObjectMethod(env, java_dex_file, load_class,
JNI_NewStringUTF(env, generated_class_name.data()), my_cl))
.release();
}
if (target_class) {
return {
target_class,
JNI_GetStaticFieldID(env, target_class, hooker_field->decl->name->c_str(),
hooker_field->decl->type->descriptor->c_str()),
JNI_GetStaticMethodID(env, target_class, hook_method->decl->name->c_str(),
hook_method->decl->prototype->Signature().data()),
JNI_GetStaticMethodID(env, target_class, backup_method->decl->name->c_str(),
backup_method->decl->prototype->Signature().data()),
};
}
return {nullptr, nullptr, nullptr, nullptr};
}
创建一个内存镜像,最后通过PathClassLoader
将dex加载进去,然后loadClass
加载LSPHooker_
类,最终返回这个类的一些信息
DexBuilder部分来自aosp项目,可以参考frameworks/base/startop/view_compiler/dex_builder.c
此时相当于加载了类,但函数体是什么就不知道了,对DexBuilder不了解
class LSPHooker_ {
// JNI_SetStaticObjectField(env, built_class, hooker_field, hooker_object);
LSPosedBridge.NativeHooker hooker = hooker_object;
//假设hook的函数是getInstalldPackageInfo
getInstalldPackageInfo();
backup();
....
}
lsplant::DoHook
bool DoHook(ArtMethod *target, ArtMethod *hook, ArtMethod *backup) {
ScopedGCCriticalSection section(art::Thread::Current(), art::gc::kGcCauseDebugger,
art::gc::kCollectorTypeDebugger);
ScopedSuspendAll suspend("LSPlant Hook", false);
LOGV("Hooking: target = %s(%p), hook = %s(%p), backup = %s(%p)", target->PrettyMethod().c_str(),
target, hook->PrettyMethod().c_str(), hook, backup->PrettyMethod().c_str(), backup);
// 创建要Hook函数的entrypoint
if (auto *entrypoint = GenerateTrampolineFor(hook); !entrypoint) {
LOGE("Failed to generate trampoline");
return false;
// NOLINTNEXTLINE
} else {
LOGV("Generated trampoline %p", entrypoint);
target->SetNonCompilable();
hook->SetNonCompilable();
// copy after setNonCompilable
backup->CopyFrom(target); //复制被hook函数到backup上
target->ClearFastInterpretFlag();
target->SetEntryPoint(entrypoint); //设置函数被hook函数的入口
if (!backup->IsStatic()) backup->SetPrivate();
LOGV("Done hook: target(%p:0x%x) -> %p; backup(%p:0x%x) -> %p; hook(%p:0x%x) -> %p", target,
target->GetAccessFlags(), target->GetEntryPoint(), backup, backup->GetAccessFlags(),
backup->GetEntryPoint(), hook, hook->GetAccessFlags(), hook->GetEntryPoint());
return true;
}
}
void SetEntryPoint(void *entry_point) {
*reinterpret_cast<void **>(reinterpret_cast<uintptr_t>(this) + entry_point_offset) =
entry_point;
if (interpreter_entry_point_offset) [[unlikely]] {
*reinterpret_cast<void **>(reinterpret_cast<uintptr_t>(this) +
interpreter_entry_point_offset) =
reinterpret_cast<void *>(art_interpreter_to_compiled_code_bridgeSym);
}
}
- 创建被HOOK函数的蹦床
entrypoint
- 备份被Hook函数到
LSPHooker_.backup
上 - 设置被Hook函数的入口
entrypoint
这里涉及ART,基本和Xposed都一样,包括备份通过CopyFrom
备份ArtMethod,以及关键的SetEntryPoint
函数
GenerateTrampolineFor 蹦床
void *GenerateTrampolineFor(art::ArtMethod *hook) {
...
address = reinterpret_cast<uintptr_t>(mmap(nullptr, kPageSize,
PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_ANONYMOUS | MAP_PRIVATE, -1, 0));
auto *address_ptr = reinterpret_cast<char *>(address);
std::memcpy(address_ptr, trampoline.data(), trampoline.size()); // 对应的汇编拷贝到内存
// arm64架构下art_method_offset为12 就是跳转到hook函数的ArtMethodd地址
*reinterpret_cast<art::ArtMethod **>(address_ptr + art_method_offset) = hook;
return address_ptr;
}
看下trampoline的汇编
consteval inline auto GetTrampoline() {
if constexpr (kArch == Arch::kArm) {
return std::make_tuple("\x00\x00\x9f\xe5\x00\xf0\x90\xe5\x78\x56\x34\x12"_uarr,
// NOLINTNEXTLINE
uint8_t{32u}, uintptr_t{8u});
}
if constexpr (kArch == Arch::kArm64) {
return std::make_tuple(
"\x60\x00\x00\x58\x10\x00\x40\xf8\x00\x02\x1f\xd6\x78\x56\x34\x12\x78\x56\x34\x12"_uarr,
// NOLINTNEXTLINE
uint8_t{44u}, uintptr_t{12u});
}
if constexpr (kArch == Arch::kX86) {
return std::make_tuple("\xb8\x78\x56\x34\x12\xff\x70\x00\xc3"_uarr,
// NOLINTNEXTLINE
uint8_t{56u}, uintptr_t{1u});
}
if constexpr (kArch == Arch::kX86_64) {
return std::make_tuple("\x48\xbf\x78\x56\x34\x12\x78\x56\x34\x12\xff\x77\x00\xc3"_uarr,
// NOLINTNEXTLINE
uint8_t{96u}, uintptr_t{2u});
}
if constexpr (kArch == Arch::kRiscv64) {
return std::make_tuple(
"\x17\x05\x00\x00\x03\x35\xc5\x00\x67\x00\x05\x00\x78\x56\x34\x12\x78\x56\x34\x12"_uarr,
// NOLINTNEXTLINE
uint8_t{84u}, uintptr_t{12u});
}
}
auto [trampoline, entry_point_offset, art_method_offset] = GetTrampoline();
对应汇编代码,参考LSPlant 不确定是不是对的
0x0000000000000000: 60 00 00 58 ldr x0, #0xc # 读相对第一条指令 0xc 偏移的位置的内存,即 hook 的 ArtMethod 地址到第一个参数 (x0)
0x0000000000000004: 10 x0 4x F8 ldur x16, [x0] # 取 entry_point_from_quick_compiled_code_
0x0000000000000008: 00 02 1F D6 br x16 # 跳转到 hook
0x000000000000000c: 78 56 34 12 and w24, w19, #0xfffff003 # ArtMethod 地址
0x0000000000000010: 78 56 34 12 and w24, w19, #0xfffff003
quick_compiled_code 又出现了,基本xposed也是引导往走里走, 0xc
正好就是arm64架构下art_method_offset
的值
arm64下entry_point_offset是uint8_t{44u}
ART深度探索开篇:从Method Hook谈起 这里面的骚招就是替换artMethod字段
SetEntryPoint 设置ArtMethod入口
SetEntryPoint的关键就是entry_point_offset的计算
void SetEntryPoint(void *entry_point) {
*reinterpret_cast<void **>(reinterpret_cast<uintptr_t>(this) + entry_point_offset) =
entry_point;
if (interpreter_entry_point_offset) [[unlikely]] {
*reinterpret_cast<void **>(reinterpret_cast<uintptr_t>(this) +
interpreter_entry_point_offset) =
reinterpret_cast<void *>(art_interpreter_to_compiled_code_bridgeSym);
}
}
可以看出本质就是修改ArtMethod的结构体成员,位置位于this + entry_point_offset
处
static size_t GetEntryPointOffset() { return entry_point_offset; }
// 所以这是位于ArtMethod末尾?这里好像多个地方赋值,这是init中赋值,trampoline中也有
entry_point_offset = art_method_size - kPointerSize;
data_offset = entry_point_offset - kPointerSize;
entry_point_offset 表示 ArtMethod 的 entry_point_from_quick_compiled_code_
偏移在指令中的位置
参考下面
lsplant
SandHookArtMethod 结构介绍
这里有涉及到ArtMethod大小,来计算出entry_point_offset
ArtMethod 的大小
ArtMethod 被存放在线性内存区域,并且不会 Moving GC,那么,相邻的两个方法他们的 ArtMethod 也是相邻的,所以 size = ArtMethod2 - ArtMethod1
看下lsp的实现
// lsplant/src/main/jni/art/runtime/art_method.hpp
auto throwable = JNI_FindClass(env, "java/lang/Throwable");
if (!throwable) {
LOGE("Failed to found Executable");
return false;
}
// class.getDeclaredConstructors 返回Constructor数组
auto clazz = JNI_FindClass(env, "java/lang/Class");
static_assert(std::is_same_v<decltype(clazz)::BaseType, jclass>);
jmethodID get_declared_constructors = JNI_GetMethodID(env, clazz, "getDeclaredConstructors",
"()[Ljava/lang/reflect/Constructor;");
// 选择Throwable 我认为仅仅是因为他构造函数够多
const auto constructors =
JNI_Cast<jobjectArray>(JNI_CallObjectMethod(env, throwable, get_declared_constructors));
if (constructors.size() < 2) {
LOGE("Throwable has less than 2 constructors");
return false;
}
auto &first_ctor = constructors[0];
auto &second_ctor = constructors[1];
auto *first = FromReflectedMethod(env, first_ctor.get()); // 返回ArtMethod *
auto *second = FromReflectedMethod(env, second_ctor.get()); // 返回的ArtMethod *
// 这能算出ArtMethod的大小? 连续内存? 两个ArtMethod 相减得到大小? 所以挑选一个包含两个构造函数的Throwable类?
art_method_size = reinterpret_cast<uintptr_t>(second) - reinterpret_cast<uintptr_t>(first);
LOGD("ArtMethod size: %zu", art_method_size);
获取ArtMethod结构体
static art::ArtMethod *FromReflectedMethod(JNIEnv *env, jobject method) {
if (art_method_field) [[likely]] {
return reinterpret_cast<art::ArtMethod *>(
JNI_GetLongField(env, method, art_method_field));
} else {
return reinterpret_cast<art::ArtMethod *>(env->FromReflectedMethod(method));
}
}
上面展示了两种取ArtMethod结构体的方法,这个很重要,因为Art的所有操作都是围绕ArtMethod展开的
-
通过 env->FromReflectedMethod,可以通过 java Method 得到这个方法对应的 ArtMethod 结构体。
-
通过artMethod字段,
art_method_field
指的是java.lang.reflect.Executable
的artMethod
字段
// art_method_field = JNI_GetFieldID(env, executable, "artMethod", "J")
/**
* The ArtMethod associated with this Executable, required for dispatching due to entrypoints
* Classloader is held live by the declaring class.
*/
@SuppressWarnings("unused") // set by runtime
private long artMethod;
Art执行模式
-
ART运行时有两种执行方法:解释执行模式和本地机器指令执行模式。默认是本地机器指令执行模式,但是在启动ART运行时时可以通过-Xint选项指定为解释执行模式。
-
即使是在本地机器指令模式中,也有类方法可能需要以解释模式执行。反之亦然。
解释执行的类方法通过函数artInterpreterToCompiledCodeBridge的返回值调用本地机器指令执行的类方法;
本地机器指令执行的类方法通过函数GetQuickToInterpreterBridge的返回值调用解释执行的类方法;
-
在解释执行模式下,除了JNI方法和动态Proxy方法,其余所有的方法均通过解释器执行,它们的入口点设置为函数GetQuickToInterpreterBridge的返回值。
-
抽象方法不能执行,它必须要由子类实现,因此会将抽象方法的入口点设置为函数GetCompiledCodeToInterpreterBridge的返回值,目的检测是否在本地机器指令中调用了抽象方法。如果调用了,上述入口点就会抛出一个异常。
-
静态类方法的执行模式延迟至类初始化确定。在类初始化之前,它们的入口点由函数GetResolutionTrampoline的返回值代理。
快速提炼,Android版本不同,函数名可能变化,下面仅本人用于笔记
本地机器代码执行: ArtMethod的成员函数Invoke来执行被调用类方法的本地机器指令
解释器执行:
函数art_quick_to_interpreter_bridge通过调用另外一个函数artQuickToInterpreterBridge从本地机器指令进入到解释器中去
本地机器指令进入到解释器中就是找到被调用类方法method的DEX字节码code_item,然后根据调用传入的参数构造一个解释器调用栈帧shadow_frame,最后就可以通过函数interpreter::EnterInterpreterFromStub进入到解释器去执行了。
如果要执行的类方法method是一个静态方法,那么我们就需要确保它的声明类是已经初始化过了的。如果还没有初始化过,那么就需要调用ClassLinker类的成员函数EnsureInitialized来对它进行初始化。
参考
ART 在 Android 安全攻防中的应用
使用Frida分析动态注册jni函数绑定流程
ART虚拟机 | JNI优化简史
xposed源码分析
Android运行时ART执行类方法的过程分析
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】