From 6a6715268f2f63ba9078f3a417ae9ce29a71bb3e Mon Sep 17 00:00:00 2001 From: Fankesyooni Date: Sat, 5 Feb 2022 05:21:43 +0800 Subject: [PATCH] ... --- README.md | 2 +- app/build.gradle | 6 +++ app/src/main/assets/xposed_init | 2 +- .../yukihookapi/demo/MainActivity.kt | 11 +++- .../yukihookapi/demo/hook/MainHooker.kt | 19 +++++++ .../demo/hook/inject/MainInjecter.kt | 28 ++++++++-- .../YukiHookXposedProcessor.kt | 29 ++++++++--- .../highcapable/yukihookapi/YukiHookAPI.kt | 9 ++++ .../yukihookapi/hook/core/YukiHookCreater.kt | 31 +++++++++-- .../hook/utils/ReflectionUtils.java | 14 ++--- .../yukihookapi/hook/utils/UtilsFactory.kt | 52 +++++++++++++++++++ 11 files changed, 175 insertions(+), 28 deletions(-) create mode 100644 yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/utils/UtilsFactory.kt diff --git a/README.md b/README.md index 7f817b1f..c4dbe68e 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ # Get Startted -- 暂定 1.0 版本,还有很多问题没有解决,暂时不要使用,敬请期待... +- 暂定 1.0 版本,可做学习参考但暂时不要 fork 也不要使用,还差 Wiki 没有撰写 demo 没有写完和 API 未提交至 maven 敬请期待... # License diff --git a/app/build.gradle b/app/build.gradle index ff7db3dc..ded8b2cb 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -30,6 +30,12 @@ android { kotlinOptions { jvmTarget = '1.8' } + kotlin { + sourceSets.main { + kotlin.srcDir("build/generated/ksp/debug/kotlin") + kotlin.srcDir("build/generated/ksp/release/kotlin") + } + } } dependencies { diff --git a/app/src/main/assets/xposed_init b/app/src/main/assets/xposed_init index 2f660c0f..0512548b 100644 --- a/app/src/main/assets/xposed_init +++ b/app/src/main/assets/xposed_init @@ -1 +1 @@ -com.highcapable.yukihookapi.demo.InjectTest_YukiHookXposedInit \ No newline at end of file +com.highcapable.yukihookapi.demo.hook.inject.MainInjecter_YukiHookXposedInit \ No newline at end of file diff --git a/app/src/main/java/com/highcapable/yukihookapi/demo/MainActivity.kt b/app/src/main/java/com/highcapable/yukihookapi/demo/MainActivity.kt index e25eb587..359cc135 100644 --- a/app/src/main/java/com/highcapable/yukihookapi/demo/MainActivity.kt +++ b/app/src/main/java/com/highcapable/yukihookapi/demo/MainActivity.kt @@ -30,6 +30,7 @@ package com.highcapable.yukihookapi.demo import android.os.Bundle +import android.widget.Toast import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import com.highcapable.yukihookapi.hook.xposed.YukiHookModuleStatus @@ -48,7 +49,7 @@ class MainActivity : AppCompatActivity() { .setPositiveButton("下一个") { _, _ -> AlertDialog.Builder(this) .setTitle("Hook 方法参数测试") - .setMessage(test("这是没有更改的文字") + "\n模块是否已激活:${YukiHookModuleStatus.isActive()}") + .setMessage(test("这是没有更改的文字") + "\n${a(content = "这是原文")}\n模块是否已激活:${YukiHookModuleStatus.isActive()}") .setPositiveButton("下一个") { _, _ -> AlertDialog.Builder(this) .setTitle("Hook 构造方法测试(stub)") @@ -57,13 +58,19 @@ class MainActivity : AppCompatActivity() { AlertDialog.Builder(this) .setTitle("Hook 构造方法测试(名称)") .setMessage(InjectTestName("文字没更改").getString() + "\n模块是否已激活:${YukiHookModuleStatus.isActive()}") - .setPositiveButton("完成", null) + .setPositiveButton("完成") { _, _ -> toast() } .show() }.show() }.show() }.show() } + // for test + private fun toast() = Toast.makeText(this, "我弹出来了,没有 Hook", Toast.LENGTH_SHORT).show() + + // for test + private fun a(content1: String = "前缀", content: String) = "$content1${content}_后面加了一段文字" + // for test private fun test() = "正常显示的一行文字" diff --git a/app/src/main/java/com/highcapable/yukihookapi/demo/hook/MainHooker.kt b/app/src/main/java/com/highcapable/yukihookapi/demo/hook/MainHooker.kt index 4e00d9c4..294711d5 100644 --- a/app/src/main/java/com/highcapable/yukihookapi/demo/hook/MainHooker.kt +++ b/app/src/main/java/com/highcapable/yukihookapi/demo/hook/MainHooker.kt @@ -35,6 +35,7 @@ import com.highcapable.yukihookapi.demo.MainActivity import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker import com.highcapable.yukihookapi.hook.type.BundleClass import com.highcapable.yukihookapi.hook.type.StringType +import com.highcapable.yukihookapi.hook.type.UnitType // for test class MainHooker : YukiBaseHooker() { @@ -54,6 +55,24 @@ class MainHooker : YukiBaseHooker() { }.set(instance, "这段文字被修改成功了") } } + injectMember { + method { + name = "toast" + returnType = UnitType + } + intercept() + } + injectMember { + method { + name = "a" + param(StringType, StringType) + returnType = StringType + } + beforeHook { + args(index = 0).set("改了前面的") + args(index = 1).set("改了后面的") + } + } injectMember { method { name = "test" diff --git a/app/src/main/java/com/highcapable/yukihookapi/demo/hook/inject/MainInjecter.kt b/app/src/main/java/com/highcapable/yukihookapi/demo/hook/inject/MainInjecter.kt index 2386dd09..b61e3a8d 100644 --- a/app/src/main/java/com/highcapable/yukihookapi/demo/hook/inject/MainInjecter.kt +++ b/app/src/main/java/com/highcapable/yukihookapi/demo/hook/inject/MainInjecter.kt @@ -31,28 +31,30 @@ package com.highcapable.yukihookapi.demo.hook.inject import android.app.AlertDialog import android.widget.Toast +import com.highcapable.yukihookapi.YukiHookAPI import com.highcapable.yukihookapi.annotation.xposed.InjectYukiHookWithXposed import com.highcapable.yukihookapi.demo.BuildConfig import com.highcapable.yukihookapi.demo.InjectTest import com.highcapable.yukihookapi.demo.MainActivity -import com.highcapable.yukihookapi.demo.hook.MainHooker -import com.highcapable.yukihookapi.demo.hook.SecondHooker import com.highcapable.yukihookapi.hook.factory.encase import com.highcapable.yukihookapi.hook.factory.findMethod import com.highcapable.yukihookapi.hook.proxy.YukiHookXposedInitProxy import com.highcapable.yukihookapi.hook.type.ActivityClass import com.highcapable.yukihookapi.hook.type.BundleClass import com.highcapable.yukihookapi.hook.type.StringType +import com.highcapable.yukihookapi.hook.type.UnitType // for test @InjectYukiHookWithXposed class MainInjecter : YukiHookXposedInitProxy { override fun onHook() { + // 设置模式 + YukiHookAPI.isDebug = true // 方案 1 - encase(MainHooker(), SecondHooker()) + // encase(MainHooker(), SecondHooker()) // 方案 2 - encase(BuildConfig.APPLICATION_ID) { + encase { loadApp(name = BuildConfig.APPLICATION_ID) { MainActivity::class.java.hook { injectMember { @@ -67,6 +69,24 @@ class MainInjecter : YukiHookXposedInitProxy { }.set(instance, "这段文字被修改成功了") } } + injectMember { + method { + name = "toast" + returnType = UnitType + } + intercept() + } + injectMember { + method { + name = "a" + param(StringType, StringType) + returnType = StringType + } + beforeHook { + args(index = 0).set("改了前面的") + args(index = 1).set("改了后面的") + } + } injectMember { method { name = "test" diff --git a/yukihookapi-ksp-xposed/src/main/java/com/highcapable/yukihookapi_ksp_xposed/YukiHookXposedProcessor.kt b/yukihookapi-ksp-xposed/src/main/java/com/highcapable/yukihookapi_ksp_xposed/YukiHookXposedProcessor.kt index 9a45b3af..dda515e2 100644 --- a/yukihookapi-ksp-xposed/src/main/java/com/highcapable/yukihookapi_ksp_xposed/YukiHookXposedProcessor.kt +++ b/yukihookapi-ksp-xposed/src/main/java/com/highcapable/yukihookapi_ksp_xposed/YukiHookXposedProcessor.kt @@ -52,6 +52,17 @@ class YukiHookXposedProcessor : SymbolProcessorProvider { /** 插入 Xposed 尾部的名称 */ private val xposedClassShortName = "_YukiHookXposedInit" + /** + * 获取父类名称 - 只取最后一个 + * @return [String] + */ + private val KSClassDeclaration.superName + get() = try { + superTypes.last().element.toString() + } catch (_: Exception) { + "" + } + /** * 创建一个环境方法体方便调用 * @param env 方法体 @@ -72,14 +83,16 @@ class YukiHookXposedProcessor : SymbolProcessorProvider { resolver.getSymbolsWithAnnotation(InjectYukiHookWithXposed::class.java.name) .asSequence() .filterIsInstance().forEach { - if (injectOnce) { - injectAssets( - codePath = (it.location as? FileLocation?)?.filePath ?: "", - packageName = it.packageName.asString(), - className = it.simpleName.asString() - ) - injectClass(it.packageName.asString(), it.simpleName.asString()) - } else logger.error(message = "@InjectYukiHookWithXposed only can be use in once times") + if (injectOnce) + if (it.superName == "YukiHookXposedInitProxy") { + injectAssets( + codePath = (it.location as? FileLocation?)?.filePath ?: "", + packageName = it.packageName.asString(), + className = it.simpleName.asString() + ) + injectClass(it.packageName.asString(), it.simpleName.asString()) + } else logger.error(message = "HookEntryClass \"${it.simpleName.asString()}\" must be implements YukiHookXposedInitProxy") + else logger.error(message = "@InjectYukiHookWithXposed only can be use in once times") /** 仅处理第一个标记的类 - 再次处理将拦截并报错 */ injectOnce = false } diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/YukiHookAPI.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/YukiHookAPI.kt index a416a940..a7ee8aef 100644 --- a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/YukiHookAPI.kt +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/YukiHookAPI.kt @@ -52,6 +52,15 @@ object YukiHookAPI { /** 全局标识 */ const val TAG = "YukiHookAPI" + /** + * 是否开启调试模式 - 默认启用 + * + * 启用后将交由日志输出管理器打印详细 Hook 日志到控制台 + * + * 请过滤 [TAG] (YukiHookAPI) 即可找到每条日志 + */ + var isDebug = true + /** * Xposed Hook API 绑定的模块包名 - 未写将自动生成 * - 你不应该设置此变量的名称 - 请使用 [encase] 装载模块包名 diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/YukiHookCreater.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/YukiHookCreater.kt index 2612dfb5..f45ca35a 100644 --- a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/YukiHookCreater.kt +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/YukiHookCreater.kt @@ -29,13 +29,16 @@ package com.highcapable.yukihookapi.hook.core +import com.highcapable.yukihookapi.YukiHookAPI import com.highcapable.yukihookapi.annotation.DoNotUseMethod import com.highcapable.yukihookapi.hook.core.finder.ConstructorFinder import com.highcapable.yukihookapi.hook.core.finder.FieldFinder import com.highcapable.yukihookapi.hook.core.finder.MethodFinder import com.highcapable.yukihookapi.hook.log.loggerE +import com.highcapable.yukihookapi.hook.log.loggerI import com.highcapable.yukihookapi.hook.param.HookParam import com.highcapable.yukihookapi.hook.param.PackageParam +import com.highcapable.yukihookapi.hook.utils.runBlocking import de.robv.android.xposed.XC_MethodHook import de.robv.android.xposed.XC_MethodReplacement import de.robv.android.xposed.XposedBridge @@ -120,7 +123,7 @@ class YukiHookCreater(private val packageParam: PackageParam, val hookClass: Cla * 你只能使用一次 [method] 或 [constructor] 方法 - 否则结果会被替换 * @param initiate 方法体 */ - fun method(initiate: MethodFinder.() -> Unit) { + fun method(initiate: MethodFinder.() -> Unit) = runBlocking { runCatching { member = MethodFinder(hookClass).apply(initiate).find() }.onFailure { @@ -129,7 +132,7 @@ class YukiHookCreater(private val packageParam: PackageParam, val hookClass: Cla onAllFailureCallback?.invoke(it) if (onNoSuchMemberCallback == null && onAllFailureCallback == null) onHookFailureMsg(it) } - } + }.result { onHookLogMsg(msg = "Find Method [$member] takes ${it}ms") } /** * 查找需要 Hook 的构造类 @@ -137,7 +140,7 @@ class YukiHookCreater(private val packageParam: PackageParam, val hookClass: Cla * 你只能使用一次 [method] 或 [constructor] 方法 - 否则结果会被替换 * @param initiate 方法体 */ - fun constructor(initiate: ConstructorFinder.() -> Unit) { + fun constructor(initiate: ConstructorFinder.() -> Unit) = runBlocking { runCatching { member = ConstructorFinder(hookClass).apply(initiate).find() }.onFailure { @@ -146,7 +149,7 @@ class YukiHookCreater(private val packageParam: PackageParam, val hookClass: Cla onAllFailureCallback?.invoke(it) if (onNoSuchMemberCallback == null && onAllFailureCallback == null) onHookFailureMsg(it) } - } + }.result { onHookLogMsg(msg = "Find Constructor [$member] takes ${it}ms") } /** * 查找 [Field] @@ -155,7 +158,11 @@ class YukiHookCreater(private val packageParam: PackageParam, val hookClass: Cla */ fun HookParam.field(initiate: FieldFinder.() -> Unit) = try { - FieldFinder(hookClass).apply(initiate).find() + var result: FieldFinder.Result? = null + runBlocking { + result = FieldFinder(hookClass).apply(initiate).find() + }.result { onHookLogMsg(msg = "Find Field [${result?.give()}] takes ${it}ms") } + result!! } catch (e: Throwable) { isStopHookMode = true onNoSuchMemberCallback?.invoke(e) @@ -277,6 +284,8 @@ class YukiHookCreater(private val packageParam: PackageParam, val hookClass: Cla if (baseParam == null) return null return HookParam(baseParam).let { param -> try { + if (replaceHookCallback != null) + onHookLogMsg(msg = "Replace Hook Member [${member}] done") replaceHookCallback?.invoke(param) } catch (e: Throwable) { onConductFailureCallback?.invoke(param, e) @@ -295,6 +304,8 @@ class YukiHookCreater(private val packageParam: PackageParam, val hookClass: Cla HookParam(baseParam).also { param -> runCatching { beforeHookCallback?.invoke(param) + if (beforeHookCallback != null) + onHookLogMsg(msg = "Before Hook Member [${member}] done") }.onFailure { onConductFailureCallback?.invoke(param, it) onAllFailureCallback?.invoke(it) @@ -309,6 +320,8 @@ class YukiHookCreater(private val packageParam: PackageParam, val hookClass: Cla HookParam(baseParam).also { param -> runCatching { afterHookCallback?.invoke(param) + if (afterHookCallback != null) + onHookLogMsg(msg = "After Hook Member [${member}] done") }.onFailure { onConductFailureCallback?.invoke(param, it) onAllFailureCallback?.invoke(it) @@ -333,6 +346,14 @@ class YukiHookCreater(private val packageParam: PackageParam, val hookClass: Cla private fun onHookFailureMsg(throwable: Throwable) = loggerE(msg = "Try to hook $hookClass[$member] got an Exception", e = throwable) + /** + * Hook 过程中开启了 [YukiHookAPI.isDebug] 输出调试信息 + * @param msg 调试日志内容 + */ + private fun onHookLogMsg(msg: String) { + if (YukiHookAPI.isDebug) loggerI(msg = msg) + } + override fun toString() = "$member#YukiHook" /** diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/utils/ReflectionUtils.java b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/utils/ReflectionUtils.java index 32daef60..b764e2cc 100644 --- a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/utils/ReflectionUtils.java +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/utils/ReflectionUtils.java @@ -171,7 +171,7 @@ public class ReflectionUtils { * @param methodName 方法名 */ public static Method findMethodNoParam(Class clazz, Class returnType, String methodName) { - String fullMethodName = clazz.getName() + '#' + methodName + returnType + "#exact#NoParam"; + String fullMethodName = "name:[" + methodName + "] in Class [" + clazz.getName() + "] by YukiHook#finder"; if (!methodCache.containsKey(fullMethodName)) { Method method = findMethodIfExists(clazz, returnType, methodName); methodCache.put(fullMethodName, method); @@ -190,7 +190,7 @@ public class ReflectionUtils { * @param parameterTypes 方法参数类型数组 */ public static Method findMethodBestMatch(Class clazz, Class returnType, String methodName, Class... parameterTypes) { - String fullMethodName = clazz.getName() + '#' + methodName + returnType + getParametersString(parameterTypes) + "#exact"; + String fullMethodName = "name:[" + methodName + "] paramType:[" + getParametersString(parameterTypes) + "] in Class [" + clazz.getName() + "] by YukiHook#finder"; if (!methodCache.containsKey(fullMethodName)) { Method method = findMethodIfExists(clazz, returnType, methodName, parameterTypes); methodCache.put(fullMethodName, method); @@ -207,24 +207,24 @@ public class ReflectionUtils { * @param parameterTypes 构造类方法参数类型数组 */ public static Constructor findConstructorExact(Class clazz, Class... parameterTypes) { - String fullConstructorName = clazz.getName() + getParametersString(parameterTypes) + "#exact"; + String fullConstructorName = "paramType:[" + getParametersString(parameterTypes) + "in Class [" + clazz.getName() + "] by YukiHook#finder"; try { Constructor constructor = clazz.getDeclaredConstructor(parameterTypes); constructor.setAccessible(true); return constructor; } catch (NoSuchMethodException e) { - throw new NoSuchMethodError("Can't find constructor " + fullConstructorName); + throw new NoSuchMethodError("Can't find this constructor --> " + fullConstructorName); } } private static Method findMethodExact(Class clazz, String methodName, Class... parameterTypes) { - String fullMethodName = clazz.getName() + '#' + methodName + getParametersString(parameterTypes) + "#zYukiHook#exact"; + String fullMethodName = "name:[" + methodName + "] paramType:[" + getParametersString(parameterTypes) + "] in Class [" + clazz.getName() + "] by YukiHook#finder"; try { Method method = clazz.getDeclaredMethod(methodName, parameterTypes); method.setAccessible(true); return method; } catch (NoSuchMethodException e) { - throw new NoSuchMethodError("Can't find method " + fullMethodName); + throw new NoSuchMethodError("Can't find this method --> " + fullMethodName); } } @@ -238,6 +238,6 @@ public class ReflectionUtils { for (Method method : methods) if (method.getName().equals(methodName)) return method; } while ((clz = clz.getSuperclass()) != null); } - throw new IllegalArgumentException("Method not found in Class <" + clazz.getName() + ">"); + throw new IllegalArgumentException("Can't find this method --> name:[" + methodName + "] returnType:[" + returnType.getName() + "] paramType:[" + getParametersString(parameterTypes) + "] in Class [" + clazz.getName() + "] by YukiHook#finder"); } } diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/utils/UtilsFactory.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/utils/UtilsFactory.kt new file mode 100644 index 00000000..1fd38ff8 --- /dev/null +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/utils/UtilsFactory.kt @@ -0,0 +1,52 @@ +/** + * MIT License + * + * Copyright (C) 2022 HighCapable + * + * This file is part of YukiHookAPI. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * This file is Created by fankes on 2022/2/5. + */ +package com.highcapable.yukihookapi.hook.utils + +/** + * 计算方法执行耗时 + * @param block 方法块 + * @return [RunBlockResult] + */ +inline fun runBlocking(block: () -> R): RunBlockResult { + val start = System.currentTimeMillis() + block() + return RunBlockResult(after = System.currentTimeMillis() - start) +} + +/** + * 构造耗时计算结果类 + * @param after 耗时 + */ +class RunBlockResult(private val after: Long) { + + /** + * 获取耗时计算结果 + * @param initiate 回调结果 - ([Long] 耗时) + */ + fun result(initiate: (Long) -> Unit) = initiate(after) +} \ No newline at end of file