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中,紧接着调用两个关键函数SetEntryPointFromJniPtrSizeSetEntryPointFromQuickCompiledCode

  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

根据这段汇编代码,我们知道会blartQuickProxyInvokeHandler函数,回调就是在这里得到执行

这里参考Android运行时ART执行类方法的过程分析

image

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
}

总结

  1. Xposed通过修改libart源码方式使用SetEntryPointFromJniPtrSize将ArtMethod的entry地址设置为XposedHookInfo的地址
  2. 然后通过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 &param : 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);
    }
}
  1. 创建被HOOK函数的蹦床entrypoint
  2. 备份被Hook函数到 LSPHooker_.backup
  3. 设置被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

image

看下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展开的

  1. 通过 env->FromReflectedMethod,可以通过 java Method 得到这个方法对应的 ArtMethod 结构体。

  2. 通过artMethod字段,art_method_field 指的是java.lang.reflect.ExecutableartMethod字段

    // 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执行模式

  1. ART运行时有两种执行方法:解释执行模式和本地机器指令执行模式。默认是本地机器指令执行模式,但是在启动ART运行时时可以通过-Xint选项指定为解释执行模式。

  2. 即使是在本地机器指令模式中,也有类方法可能需要以解释模式执行。反之亦然。

    解释执行的类方法通过函数artInterpreterToCompiledCodeBridge的返回值调用本地机器指令执行的类方法;

    本地机器指令执行的类方法通过函数GetQuickToInterpreterBridge的返回值调用解释执行的类方法;

  3. 在解释执行模式下,除了JNI方法和动态Proxy方法,其余所有的方法均通过解释器执行,它们的入口点设置为函数GetQuickToInterpreterBridge的返回值。

  4. 抽象方法不能执行,它必须要由子类实现,因此会将抽象方法的入口点设置为函数GetCompiledCodeToInterpreterBridge的返回值,目的检测是否在本地机器指令中调用了抽象方法。如果调用了,上述入口点就会抛出一个异常。

  5. 静态类方法的执行模式延迟至类初始化确定。在类初始化之前,它们的入口点由函数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执行类方法的过程分析

posted @ 2024-06-24 18:42  梦过无声  阅读(97)  评论(0编辑  收藏  举报