diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 248fc390..25ec9e94 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -10,6 +10,21 @@ android:supportsRtl="true" android:theme="@style/Theme.YukiHookAPI"> + + + + + + + + + diff --git a/app/src/main/java/com/highcapable/yukihookapi/demo/Yuu.kt b/app/src/main/java/com/highcapable/yukihookapi/demo/Yuu.kt new file mode 100644 index 00000000..3107dcad --- /dev/null +++ b/app/src/main/java/com/highcapable/yukihookapi/demo/Yuu.kt @@ -0,0 +1,9 @@ +package com.highcapable.yukihookapi.demo + +import com.highcapable.yukihookapi.hook.init.YukiHookLoadPackage + +class Yuu : YukiHookLoadPackage() { + + override fun onHookStart() { + } +} \ No newline at end of file diff --git a/yukihookapi/build.gradle b/yukihookapi/build.gradle index f89d6544..da80ad4e 100644 --- a/yukihookapi/build.gradle +++ b/yukihookapi/build.gradle @@ -32,6 +32,7 @@ android { dependencies { // Used 82 API Version compileOnly fileTree(include: ['api-82.jar'], dir: 'libs') + implementation 'androidx.appcompat:appcompat:1.4.1' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' diff --git a/yukihookapi/src/main/assets/xposed_init b/yukihookapi/src/main/assets/xposed_init new file mode 100644 index 00000000..1d697fdd --- /dev/null +++ b/yukihookapi/src/main/assets/xposed_init @@ -0,0 +1 @@ +com.highcapable.yukihookapi.hook.init.YukiHookLoadPackage \ No newline at end of file diff --git a/yukihookapi/src/main/java/com/highcapable/yukihookapi/hook/YukiHookCreater.kt b/yukihookapi/src/main/java/com/highcapable/yukihookapi/hook/YukiHookCreater.kt new file mode 100644 index 00000000..0fdb6a9c --- /dev/null +++ b/yukihookapi/src/main/java/com/highcapable/yukihookapi/hook/YukiHookCreater.kt @@ -0,0 +1,63 @@ +/** + * 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/2. + */ +package com.highcapable.yukihookapi.hook + +import com.highcapable.yukihookapi.param.HookParam +import com.highcapable.yukihookapi.param.PackageParam +import java.lang.reflect.Constructor +import java.lang.reflect.Method + +class YukiHookCreater(private val param: PackageParam) { + + var grabMethod: Method? = null + var grabConstructor: Constructor<*>? = null + + fun beforeHook(initiate: HookParam.() -> Unit) { + + } + + fun afterHook(initiate: HookParam.() -> Unit) { + + } + + fun replaceHook(initiate: HookParam.() -> Any?) { + + } + + fun replaceTo(any: Any?) { + + } + + fun intercept() { + + } + + fun hook() { + + } +} \ No newline at end of file diff --git a/yukihookapi/src/main/java/com/highcapable/yukihookapi/hook/factory/ReflectFactory.kt b/yukihookapi/src/main/java/com/highcapable/yukihookapi/hook/factory/ReflectFactory.kt new file mode 100644 index 00000000..2bfc85e5 --- /dev/null +++ b/yukihookapi/src/main/java/com/highcapable/yukihookapi/hook/factory/ReflectFactory.kt @@ -0,0 +1,32 @@ +/** + * 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/2. + */ +package com.highcapable.yukihookapi.hook.factory + +import com.highcapable.yukihookapi.hook.YukiHookCreater + +fun Class<*>.hook(it: YukiHookCreater.() -> Unit) = YukiHookCreater().apply(it).hook() \ No newline at end of file diff --git a/yukihookapi/src/main/java/com/highcapable/yukihookapi/hook/helper/YukiHookHelper.kt b/yukihookapi/src/main/java/com/highcapable/yukihookapi/hook/helper/YukiHookHelper.kt new file mode 100644 index 00000000..dc694b21 --- /dev/null +++ b/yukihookapi/src/main/java/com/highcapable/yukihookapi/hook/helper/YukiHookHelper.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/2. + */ +package com.highcapable.yukihookapi.hook.helper + +import com.highcapable.yukihookapi.param.PackageParam + +/** + * YukiHook 的装载 API 调用类 + * 可以实现作为模块装载和 Hook 自身 APP 两种方式 + */ +object YukiHookHelper { + + /** + * 自身作为模块装载调用入口方法 + * @param initiate Hook 方法体 + */ + fun onHookApp(initiate: PackageParam.() -> Unit) { + + } + + /** + * 作为侵入式 Hook 自身装载调用入口方法 + * 正在开发敬请期待。 + * @param initiate Hook 方法体 + */ + fun onHookSelf(initiate: PackageParam.() -> Unit) {} +} \ No newline at end of file diff --git a/yukihookapi/src/main/java/com/highcapable/yukihookapi/hook/init/YukiHookLoadPackage.kt b/yukihookapi/src/main/java/com/highcapable/yukihookapi/hook/init/YukiHookLoadPackage.kt new file mode 100644 index 00000000..be8b86af --- /dev/null +++ b/yukihookapi/src/main/java/com/highcapable/yukihookapi/hook/init/YukiHookLoadPackage.kt @@ -0,0 +1,46 @@ +/** + * 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/2. + */ +package com.highcapable.yukihookapi.hook.init + +import androidx.annotation.Keep +import com.highcapable.yukihookapi.hook.helper.YukiHookHelper +import de.robv.android.xposed.IXposedHookLoadPackage +import de.robv.android.xposed.callbacks.XC_LoadPackage + +/** + * 接管 Xposed 的 [IXposedHookLoadPackage] 入口 + * 你可以使用 [YukiHookHelper] 来监听模块开始装载 + */ +@Keep +class YukiHookLoadPackage : IXposedHookLoadPackage { + + override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam?) { + if (lpparam == null) return + + } +} \ No newline at end of file diff --git a/yukihookapi/src/main/java/com/highcapable/yukihookapi/param/HookParam.kt b/yukihookapi/src/main/java/com/highcapable/yukihookapi/param/HookParam.kt index 64b5684c..3ecd686b 100644 --- a/yukihookapi/src/main/java/com/highcapable/yukihookapi/param/HookParam.kt +++ b/yukihookapi/src/main/java/com/highcapable/yukihookapi/param/HookParam.kt @@ -25,7 +25,132 @@ * * This file is Created by fankes on 2022/2/2. */ +@file:Suppress("unused", "MemberVisibilityCanBePrivate") + package com.highcapable.yukihookapi.param -class HookParam { +import de.robv.android.xposed.XC_MethodHook +import java.lang.reflect.Constructor +import java.lang.reflect.Method + +/** + * Hook 方法、构造类的目标对象实现类 + * @param instance 对接 Xposed API 的 [XC_MethodHook.MethodHookParam] + */ +class HookParam(private val instance: XC_MethodHook.MethodHookParam) { + + /** + * 获取 Hook 方法的参数对象数组 + * @return [Array] + */ + val args get() = instance.args ?: arrayOf(0) + + /** + * 获取 Hook 方法的参数对象数组第一位 + * @return [Array] + * @throws IllegalStateException 如果数组为空或对象为空 + */ + val firstArgs get() = args[0] ?: error("HookParam args[0] with a non-null object") + + /** + * 获取 Hook 方法的参数对象数组最后一位 + * @return [Array] + * @throws IllegalStateException 如果数组为空或对象为空 + */ + val lastArgs get() = args[args.lastIndex] ?: error("HookParam args[lastIndex] with a non-null object") + + /** + * 获取 Hook 实例的 Class + * @return [Class] + */ + val thisClass get() = instance.thisObject.javaClass + + /** + * 获取 Hook 实例的对象 + * @return [Any] + * @throws IllegalStateException 如果对象为空 + */ + val thisAny get() = instance.thisObject ?: error("HookParam must with a non-null object") + + /** + * 获取 Hook 当前普通方法 + * @return [Method] + * @throws IllegalStateException 如果方法为空或方法类型不是 [Method] + */ + val method get() = instance.method as? Method? ?: error("Current hook method type is wrong or null") + + /** + * 获取 Hook 当前构造方法 + * @return [Constructor] + * @throws IllegalStateException 如果方法为空或方法类型不是 [Constructor] + */ + val constructor get() = instance.method as? Constructor<*>? ?: error("Current hook constructor type is wrong or null") + + /** + * 获取、设置 Hook 方法的返回值 + * @return [Any] or null + */ + var result: Any? + get() = instance.result + set(value) { + instance.result = value + } + + /** + * 获取 Hook 实例的对象 [T] + * @return [Any] + * @throws IllegalStateException 如果对象为空或对象类型不是 [T] + */ + inline fun thisAny() = thisAny as? T? ?: error("HookParam object cannot cast to ${T::class.java.name}") + + /** + * 获取 Hook 方法的参数实例化对象类 + * @param index 参数对象数组下标 - 默认是 0 + * @return [Array] + */ + fun args(index: Int = 0) = ArgsModifyer(index) + + /** + * 拦截整个方法体 + * 此方法将强制设置方法体的 [result] 为 null + */ + fun intercept() { + result = null + } + + /** + * 对方法参数的修改进行实例化类 + * @param index 参数对象数组下标 + */ + inner class ArgsModifyer(private val index: Int) { + + /** + * 设置方法参数的实例对象 + * @param any 实例对象 + * @throws IllegalStateException 如果目标方法参数对象数组为空或 [index] 下标不存在 + */ + fun set(any: T?) { + if (args.isEmpty()) error("HookParam method args is empty,mabe not has args") + if (index > args.lastIndex) error("HookParam method args index out of bounds,max is ${args.lastIndex}") + instance.args[index] = any + } + + /** + * 设置方法参数的实例对象为 null + * 此方法可以将任何被 Hook 的目标对象设置为空 + */ + fun setNull() = set(null) + + /** + * 设置方法参数的实例对象为 true + * 请确保目标对象的类型是 [Boolean] 不然会出错 + */ + fun setTrue() = set(true) + + /** + * 设置方法参数的实例对象为 false + * 请确保目标对象的类型是 [Boolean] 不然会出错 + */ + fun setFalse() = set(false) + } } \ No newline at end of file diff --git a/yukihookapi/src/main/java/com/highcapable/yukihookapi/param/PackageParam.kt b/yukihookapi/src/main/java/com/highcapable/yukihookapi/param/PackageParam.kt new file mode 100644 index 00000000..b9d6f03d --- /dev/null +++ b/yukihookapi/src/main/java/com/highcapable/yukihookapi/param/PackageParam.kt @@ -0,0 +1,52 @@ +@file:Suppress("unused", "MemberVisibilityCanBePrivate") + +package com.highcapable.yukihookapi.param + +import android.content.pm.ApplicationInfo +import de.robv.android.xposed.callbacks.XC_LoadPackage + +/** + * 装载 Hook 的目标 APP 入口对象实现类 + * 如果是侵入式 Hook 自身 APP 可将参数 [instance] 置空获得当前类的 [ClassLoader] + * @param instance 对接 Xposed API 的 [XC_LoadPackage.LoadPackageParam] + */ +class PackageParam(private val instance: XC_LoadPackage.LoadPackageParam? = null) { + + /** 当前 classLoader */ + private var privateClassLoader = instance?.classLoader ?: javaClass.classLoader + + /** + * 获取、设置当前 APP 的 [ClassLoader] + * @return [ClassLoader] + */ + var appClassLoader: ClassLoader + get() = privateClassLoader + set(value) { + privateClassLoader = value + } + + /** + * 获取当前 APP 的 [ApplicationInfo] + * @return [ApplicationInfo] + */ + val appInfo get() = instance?.appInfo ?: ApplicationInfo() + + /** + * 获取当前 APP 的进程名称 + * 默认的进程名称是 [packageName] + * @return [String] + */ + val processName get() = instance?.processName ?: "" + + /** + * 获取当前 APP 的包名 + * @return [String] + */ + val packageName get() = instance?.packageName ?: "" + + /** + * 获取当前 APP 是否为第一个 Application + * @return [Boolean] + */ + val isFirstApplication get() = instance?.isFirstApplication ?: true +} \ No newline at end of file