diff --git a/yukihookapi/build.gradle b/yukihookapi/build.gradle index 436618bb..69d2df94 100644 --- a/yukihookapi/build.gradle +++ b/yukihookapi/build.gradle @@ -41,7 +41,6 @@ dependencies { // Used 82 API Version compileOnly 'de.robv.android.xposed:api:82' compileOnly fileTree(include: ['android-stub.jar', 'module-injector.jar'], dir: 'libs') - implementation fileTree(include: ['free-reflection.jar'], dir: 'libs') implementation 'androidx.annotation:annotation:1.3.0' } diff --git a/yukihookapi/libs/free-reflection.jar b/yukihookapi/libs/free-reflection.jar deleted file mode 100644 index e21de47a..00000000 Binary files a/yukihookapi/libs/free-reflection.jar and /dev/null differ diff --git a/yukihookapi/src/api/kotlin/me/weishu/reflection/BootstrapClass.java b/yukihookapi/src/api/kotlin/me/weishu/reflection/BootstrapClass.java new file mode 100644 index 00000000..e3abdc97 --- /dev/null +++ b/yukihookapi/src/api/kotlin/me/weishu/reflection/BootstrapClass.java @@ -0,0 +1,78 @@ +package me.weishu.reflection; + +import static android.os.Build.VERSION.SDK_INT; + +import android.os.Build; +import android.util.Log; + +import androidx.annotation.Keep; + +import java.lang.reflect.Method; + +/** + * @author weishu + * @date 2020/7/13. + */ +@SuppressWarnings("ALL") +@Keep +public final class BootstrapClass { + + private static final String TAG = "BootstrapClass"; + + private static Object sVmRuntime; + private static Method setHiddenApiExemptions; + + static { + if (SDK_INT >= Build.VERSION_CODES.P) { + try { + Method forName = Class.class.getDeclaredMethod("forName", String.class); + Method getDeclaredMethod = Class.class.getDeclaredMethod("getDeclaredMethod", String.class, Class[].class); + + Class vmRuntimeClass = (Class) forName.invoke(null, "dalvik.system.VMRuntime"); + Method getRuntime = (Method) getDeclaredMethod.invoke(vmRuntimeClass, "getRuntime", null); + setHiddenApiExemptions = (Method) getDeclaredMethod.invoke(vmRuntimeClass, "setHiddenApiExemptions", new Class[]{String[].class}); + sVmRuntime = getRuntime.invoke(null); + } catch (Throwable e) { + Log.w(TAG, "reflect bootstrap failed:", e); + } + } + } + + /** + * make the method exempted from hidden API check. + * + * @param method the method signature prefix. + * @return true if success. + */ + public static boolean exempt(String method) { + return exempt(new String[]{method}); + } + + /** + * make specific methods exempted from hidden API check. + * + * @param methods the method signature prefix, such as "Ldalvik/system", "Landroid" or even "L" + * @return true if success + */ + public static boolean exempt(String... methods) { + if (sVmRuntime == null || setHiddenApiExemptions == null) { + return false; + } + + try { + setHiddenApiExemptions.invoke(sVmRuntime, new Object[]{methods}); + return true; + } catch (Throwable e) { + return false; + } + } + + /** + * Make all hidden API exempted. + * + * @return true if success. + */ + public static boolean exemptAll() { + return exempt(new String[]{"L"}); + } +} \ No newline at end of file diff --git a/yukihookapi/src/api/kotlin/me/weishu/reflection/Reflection.java b/yukihookapi/src/api/kotlin/me/weishu/reflection/Reflection.java new file mode 100644 index 00000000..30c122dd --- /dev/null +++ b/yukihookapi/src/api/kotlin/me/weishu/reflection/Reflection.java @@ -0,0 +1,97 @@ +package me.weishu.reflection; + +import static android.os.Build.VERSION.SDK_INT; +import static me.weishu.reflection.BootstrapClass.exemptAll; + +import android.content.Context; +import android.text.TextUtils; +import android.util.Base64; + +import androidx.annotation.Keep; + +import java.io.File; +import java.io.FileOutputStream; +import java.lang.reflect.Method; + +import dalvik.system.DexFile; + +/** + * @author weishu + * @date 2018/6/7. + */ +@SuppressWarnings("ALL") +@Keep +public class Reflection { + private static final String TAG = "Reflection"; + + private static final String DEX = "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TGFuZHJvaWQvdXRpbC9Mb2c7ABFMamF2YS9sYW5nL0NsYXNzOwAUTGphdmEvbGFuZy9DbGFzczwqPjsAEkxqYXZhL2xhbmcvT2JqZWN0OwASTGphdmEvbGFuZy9TdHJpbmc7ABJMamF2YS9sYW5nL1N5c3RlbTsAFUxqYXZhL2xhbmcvVGhyb3dhYmxlOwAaTGphdmEvbGFuZy9yZWZsZWN0L01ldGhvZDsAJkxtZS93ZWlzaHUvZnJlZXJlZmxlY3Rpb24vQnVpbGRDb25maWc7ACVMbWUvd2Vpc2h1L3JlZmxlY3Rpb24vQm9vdHN0cmFwQ2xhc3M7ACFMbWUvd2Vpc2h1L3JlZmxlY3Rpb24vUmVmbGVjdGlvbjsAClJlZmxlY3Rpb24AD1JlZmxlY3Rpb24uamF2YQAHU0RLX0lOVAADVEFHAAFWAAxWRVJTSU9OX0NPREUADFZFUlNJT05fTkFNRQACVkwAAVoAAlpMABJbTGphdmEvbGFuZy9DbGFzczsAE1tMamF2YS9sYW5nL09iamVjdDsAE1tMamF2YS9sYW5nL1N0cmluZzsAB2NvbnRleHQAF2RhbHZpay5zeXN0ZW0uVk1SdW50aW1lAAFlAAZleGVtcHQACWV4ZW1wdEFsbAAHZm9yTmFtZQAPZnJlZS1yZWZsZWN0aW9uABJnZXRBcHBsaWNhdGlvbkluZm8AEWdldERlY2xhcmVkTWV0aG9kAApnZXRSdW50aW1lAAZpbnZva2UAC2xvYWRMaWJyYXJ5ABhtZS53ZWlzaHUuZnJlZXJlZmxlY3Rpb24ABm1ldGhvZAAHbWV0aG9kcwAZcmVmbGVjdCBib290c3RyYXAgZmFpbGVkOgAHcmVsZWFzZQAKc1ZtUnVudGltZQAWc2V0SGlkZGVuQXBpRXhlbXB0aW9ucwAQdGFyZ2V0U2RrVmVyc2lvbgAEdGhpcwAGdW5zZWFsAAx1bnNlYWxOYXRpdmUADnZtUnVudGltZUNsYXNzAAYABw4AFgAHDmr/AwEyCwEVEAMCNQvwBAREBhcBEg8DAzYLARsPqQUCBQMFBBkeAwAvCgAOAAcOACwBOgcOADYBOwcsnRriAQEDAC8KHgBIAAcOAA0ABw4AEwEtBx1yGWtaAAYXOBc8HxcABAEXAQEXBgEXHwYAAQACGQEZARkBGQEZARkGgYAEiAcDAAUACBoBCgEKB4iABKAHAYGABLQJAQnMCQGJAfQJAQnMCgEAAwALGgyBgAT4CgEJkAsBigIAAAAADgAAAAAAAAABAAAAAAAAAAEAAABEAAAAcAAAAAIAAAATAAAAgAEAAAMAAAALAAAAzAEAAAQAAAAMAAAAUAIAAAUAAAAPAAAAsAIAAAYAAAADAAAAKAMAAAEgAAAIAAAAiAMAAAEQAAAHAAAA3AUAAAIgAABEAAAAFgYAAAMgAAAIAAAAAwoAAAUgAAADAAAAZgoAAAAgAAADAAAAeAoAAAAQAAABAAAAyAoAAA=="; + + private static native int unsealNative(int targetSdkVersion); + + /** + * Begin + * + * @param context The Base Context + */ + public static int unseal(Context context) { + if (SDK_INT < 28) { + // Below Android P, ignore + return 0; + } + + // try exempt API first. + if (exemptAll()) { + return 0; + } + if (unsealByDexFile(context)) { + return 0; + } + + return -1; + } + + private static boolean unsealByDexFile(Context context) { + byte[] bytes = Base64.decode(DEX, Base64.NO_WRAP); + File codeCacheDir = getCodeCacheDir(context); + if (codeCacheDir == null) { + return false; + } + File code = new File(codeCacheDir, System.currentTimeMillis() + ".dex"); + try { + + try (FileOutputStream fos = new FileOutputStream(code)) { + fos.write(bytes); + } + + DexFile dexFile = new DexFile(code); + // This class is hardcoded in the dex, Don't use BootstrapClass.class to reference it + // it maybe obfuscated!! + Class bootstrapClass = dexFile.loadClass("me.weishu.reflection.BootstrapClass", null); + Method exemptAll = bootstrapClass.getDeclaredMethod("exemptAll"); + return (boolean) exemptAll.invoke(null); + } catch (Throwable e) { + e.printStackTrace(); + return false; + } finally { + if (code.exists()) { + //noinspection ResultOfMethodCallIgnored + code.delete(); + } + } + } + + private static File getCodeCacheDir(Context context) { + if (context != null) { + return context.getCodeCacheDir(); + } + String tmpDir = System.getProperty("java.io.tmpdir"); + if (TextUtils.isEmpty(tmpDir)) { + return null; + } + File tmp = new File(tmpDir); + if (!tmp.exists()) { + return null; + } + return tmp; + } +} \ No newline at end of file