This commit is contained in:
2022-02-05 05:21:43 +08:00
parent e6bfc181de
commit 6a6715268f
11 changed files with 175 additions and 28 deletions

View File

@@ -15,7 +15,7 @@
# Get Startted
- 暂定 1.0 版本,还有很多问题没有解决,暂时不要使用,敬请期待...
- 暂定 1.0 版本,可做学习参考但暂时不要 fork 也不要使用,还差 Wiki 没有撰写 demo 没有写完和 API 未提交至 maven 敬请期待...
# License

View File

@@ -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 {

View File

@@ -1 +1 @@
com.highcapable.yukihookapi.demo.InjectTest_YukiHookXposedInit
com.highcapable.yukihookapi.demo.hook.inject.MainInjecter_YukiHookXposedInit

View File

@@ -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() = "正常显示的一行文字"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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<KSClassDeclaration>().forEach {
if (injectOnce) {
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 = "@InjectYukiHookWithXposed only can be use in once times")
} 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
}

View File

@@ -52,6 +52,15 @@ object YukiHookAPI {
/** 全局标识 */
const val TAG = "YukiHookAPI"
/**
* 是否开启调试模式 - 默认启用
*
* 启用后将交由日志输出管理器打印详细 Hook 日志到控制台
*
* 请过滤 [TAG] (YukiHookAPI) 即可找到每条日志
*/
var isDebug = true
/**
* Xposed Hook API 绑定的模块包名 - 未写将自动生成
* - 你不应该设置此变量的名称 - 请使用 [encase] 装载模块包名

View File

@@ -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"
/**

View File

@@ -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 <name:" + methodName + " returnType:" + returnType.getName() + " paramType:" + getParametersString(parameterTypes) + "> 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");
}
}

View File

@@ -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 <R> 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)
}