diff --git a/library/build.gradle.kts b/library/build.gradle.kts index bd43f8c..58c7a14 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -1,4 +1,12 @@ import com.android.build.api.artifact.SingleArtifact +import com.android.build.api.instrumentation.AsmClassVisitorFactory +import com.android.build.api.instrumentation.ClassContext +import com.android.build.api.instrumentation.ClassData +import com.android.build.api.instrumentation.InstrumentationParameters +import com.android.build.api.instrumentation.InstrumentationScope +import org.objectweb.asm.ClassVisitor +import org.objectweb.asm.commons.ClassRemapper +import org.objectweb.asm.commons.Remapper plugins { alias(libs.plugins.agp.lib) @@ -30,8 +38,8 @@ android { } } compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 } packaging { resources { @@ -55,6 +63,32 @@ dependencies { androidTestCompileOnly(projects.stub) } +androidComponents.onVariants { variant -> + variant.instrumentation.transformClassesWith( + ClassVisitorFactory::class.java, InstrumentationScope.PROJECT + ) {} +} + +abstract class ClassVisitorFactory : AsmClassVisitorFactory { + override fun createClassVisitor( + classContext: ClassContext, + nextClassVisitor: ClassVisitor + ): ClassVisitor { + return ClassRemapper(nextClassVisitor, object : Remapper() { + override fun map(name: String): String { + if (name.startsWith("stub/")) { + return name.substring(name.indexOf('/') + 1) + } + return name + } + }) + } + + override fun isInstrumentable(classData: ClassData): Boolean { + return classData.className.endsWith("ass") + } +} + @CacheableTask abstract class ManifestUpdater : DefaultTask() { @get:InputFile diff --git a/library/consumer-rules.pro b/library/consumer-rules.pro index b1bd364..1737e0b 100644 --- a/library/consumer-rules.pro +++ b/library/consumer-rules.pro @@ -1,2 +1,8 @@ -dontwarn dalvik.system.VMRuntime + +-if class org.lsposed.hiddenapibypass.HiddenApiBypass -keepclassmembers class org.lsposed.hiddenapibypass.Helper$* { *; } + +-assumenosideeffects class android.util.Property{ + public static *** of(...); +} diff --git a/library/src/androidTest/java/org/lsposed/hiddenapibypass/HiddenApiBypassTest.java b/library/src/androidTest/java/org/lsposed/hiddenapibypass/HiddenApiBypassTest.java index 5f58ba9..c108c43 100644 --- a/library/src/androidTest/java/org/lsposed/hiddenapibypass/HiddenApiBypassTest.java +++ b/library/src/androidTest/java/org/lsposed/hiddenapibypass/HiddenApiBypassTest.java @@ -25,20 +25,23 @@ import java.util.List; import java.util.Optional; -import dalvik.system.VMRuntime; - @SuppressWarnings("JavaReflectionMemberAccess") @FixMethodOrder(MethodSorters.NAME_ASCENDING) @SdkSuppress(minSdkVersion = Build.VERSION_CODES.P) @RunWith(AndroidJUnit4.class) public class HiddenApiBypassTest { + private final Class runtime = Class.forName("dalvik.system.VMRuntime"); + @Rule public ExpectedException exception = ExpectedException.none(); + public HiddenApiBypassTest() throws ClassNotFoundException { + } + @Test public void AgetDeclaredMethods() { - List methods = HiddenApiBypass.getDeclaredMethods(VMRuntime.class); + List methods = HiddenApiBypass.getDeclaredMethods(runtime); Optional getRuntime = methods.stream().filter(it -> it.getName().equals("getRuntime")).findFirst(); assertTrue(getRuntime.isPresent()); Optional setHiddenApiExemptions = methods.stream().filter(it -> it.getName().equals("setHiddenApiExemptions")).findFirst(); @@ -52,7 +55,7 @@ public void BusesNonSdkApiIsHiddenApi() throws NoSuchMethodException { @Test(expected = NoSuchMethodException.class) public void CsetHiddenApiExemptionsIsHiddenApi() throws NoSuchMethodException { - VMRuntime.class.getMethod("setHiddenApiExemptions", String[].class); + runtime.getMethod("setHiddenApiExemptions", String[].class); } @Test(expected = NoSuchMethodException.class) @@ -69,6 +72,7 @@ public void ElongVersionCodeIsHiddenApi() throws NoSuchFieldException { public void FHiddenApiEnforcementDefaultIsHiddenApi() throws NoSuchFieldException { ApplicationInfo.class.getDeclaredField("HIDDEN_API_ENFORCEMENT_DEFAULT"); } + @Test public void GtestGetInstanceFields() { assertTrue(HiddenApiBypass.getInstanceFields(ApplicationInfo.class).stream().anyMatch(i -> i.getName().equals("longVersionCode"))); @@ -112,7 +116,7 @@ public void MclearHiddenApiExemptions() throws NoSuchMethodException { assertTrue(HiddenApiBypass.setHiddenApiExemptions("L")); ApplicationInfo.class.getMethod("getHiddenApiEnforcementPolicy"); assertTrue(HiddenApiBypass.clearHiddenApiExemptions()); - VMRuntime.class.getMethod("setHiddenApiExemptions", String[].class); + runtime.getMethod("setHiddenApiExemptions", String[].class); } @Test @@ -120,22 +124,22 @@ public void NaddHiddenApiExemptionsTest() throws NoSuchMethodException { assertTrue(HiddenApiBypass.addHiddenApiExemptions("Landroid/content/pm/ApplicationInfo;")); ApplicationInfo.class.getMethod("getHiddenApiEnforcementPolicy"); assertTrue(HiddenApiBypass.addHiddenApiExemptions("Ldalvik/system/VMRuntime;")); - VMRuntime.class.getMethod("setHiddenApiExemptions", String[].class); + runtime.getMethod("setHiddenApiExemptions", String[].class); } @Test public void OtestCheckArgsForInvokeMethod() { class X { } - assertFalse(HiddenApiBypass.checkArgsForInvokeMethod(new Class[]{}, new Object[]{new Object()})); - assertTrue(HiddenApiBypass.checkArgsForInvokeMethod(new Class[]{int.class}, new Object[]{1})); - assertFalse(HiddenApiBypass.checkArgsForInvokeMethod(new Class[]{int.class}, new Object[]{1.0})); - assertFalse(HiddenApiBypass.checkArgsForInvokeMethod(new Class[]{int.class}, new Object[]{null})); - assertTrue(HiddenApiBypass.checkArgsForInvokeMethod(new Class[]{Integer.class}, new Object[]{1})); - assertTrue(HiddenApiBypass.checkArgsForInvokeMethod(new Class[]{Integer.class}, new Object[]{null})); - assertTrue(HiddenApiBypass.checkArgsForInvokeMethod(new Class[]{Object.class}, new Object[]{new X()})); - assertFalse(HiddenApiBypass.checkArgsForInvokeMethod(new Class[]{X.class}, new Object[]{new Object()})); - assertTrue(HiddenApiBypass.checkArgsForInvokeMethod(new Class[]{Object.class, int.class, byte.class, short.class, char.class, double.class, float.class, boolean.class, long.class}, new Object[]{new X(), 1, (byte) 0, (short) 2, 'c', 1.1, 1.2f, false, 114514L})); + assertFalse(Helper.checkArgsForInvokeMethod(new Class[]{}, new Object[]{new Object()})); + assertTrue(Helper.checkArgsForInvokeMethod(new Class[]{int.class}, new Object[]{1})); + assertFalse(Helper.checkArgsForInvokeMethod(new Class[]{int.class}, new Object[]{1.0})); + assertFalse(Helper.checkArgsForInvokeMethod(new Class[]{int.class}, new Object[]{null})); + assertTrue(Helper.checkArgsForInvokeMethod(new Class[]{Integer.class}, new Object[]{1})); + assertTrue(Helper.checkArgsForInvokeMethod(new Class[]{Integer.class}, new Object[]{null})); + assertTrue(Helper.checkArgsForInvokeMethod(new Class[]{Object.class}, new Object[]{new X()})); + assertFalse(Helper.checkArgsForInvokeMethod(new Class[]{X.class}, new Object[]{new Object()})); + assertTrue(Helper.checkArgsForInvokeMethod(new Class[]{Object.class, int.class, byte.class, short.class, char.class, double.class, float.class, boolean.class, long.class}, new Object[]{new X(), 1, (byte) 0, (short) 2, 'c', 1.1, 1.2f, false, 114514L})); } } diff --git a/library/src/androidTest/java/org/lsposed/lspass/LSPassTest.java b/library/src/androidTest/java/org/lsposed/hiddenapibypass/LSPassTest.java similarity index 58% rename from library/src/androidTest/java/org/lsposed/lspass/LSPassTest.java rename to library/src/androidTest/java/org/lsposed/hiddenapibypass/LSPassTest.java index ed7615f..9914de2 100644 --- a/library/src/androidTest/java/org/lsposed/lspass/LSPassTest.java +++ b/library/src/androidTest/java/org/lsposed/hiddenapibypass/LSPassTest.java @@ -1,5 +1,6 @@ -package org.lsposed.lspass; +package org.lsposed.hiddenapibypass; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertSame; @@ -17,7 +18,6 @@ import org.junit.runners.MethodSorters; import java.lang.reflect.InvocationTargetException; -import java.util.Arrays; @SuppressWarnings("JavaReflectionMemberAccess") @FixMethodOrder(MethodSorters.NAME_ASCENDING) @@ -41,12 +41,18 @@ public void CmHeightIsHiddenApi() throws NoSuchFieldException { } @Test - public void DtestgetDeclaredFields() { - assertTrue(Arrays.stream(LSPass.getDeclaredFields(ActivityOptions.class)).anyMatch(i -> i.getName().equals("mHeight"))); + public void DgetDeclaredFieldsTest() { + var fields = LSPass.getDeclaredFields(ActivityOptions.class); + assertTrue(fields.stream().anyMatch(i -> i.getName().equals("mHeight"))); + var instance = LSPass.getInstanceFields(ActivityOptions.class); + assertTrue(instance.stream().anyMatch(i -> i.getName().equals("mHeight"))); + var staticFields = LSPass.getStaticFields(ActivityOptions.class); + assertTrue(staticFields.stream().anyMatch(i -> i.getName().equals("ANIM_NONE"))); + assertEquals(fields.size(), instance.size() + staticFields.size()); } @Test - public void EtestgetDeclaredFields() throws NoSuchMethodException { + public void EgetDeclaredMethodTest() throws NoSuchMethodException { assertNotNull(LSPass.getDeclaredMethod(ActivityOptions.class, "getHeight")); } @@ -56,4 +62,18 @@ public void FnewActivityOptionsWithoutExemption() throws NoSuchMethodException, Object instance = LSPass.newInstance(ActivityOptions.class); assertSame(ActivityOptions.class, instance.getClass()); } + + @Test(expected = NoSuchFieldException.class) + public void GclearHiddenApiExemptionsTest() throws NoSuchFieldException { + assertTrue(LSPass.addHiddenApiExemptions("L")); + assertTrue(LSPass.clearHiddenApiExemptions()); + ActivityOptions.class.getDeclaredField("mHeight"); + } + + @Test + public void HaddHiddenApiExemptionsTest() throws NoSuchMethodException { + assertTrue(LSPass.addHiddenApiExemptions("L")); + assertTrue(LSPass.addHiddenApiExemptions("xx")); + ActivityOptions.class.getDeclaredMethod("getHeight"); + } } diff --git a/library/src/main/java/org/lsposed/hiddenapibypass/CoreOjClassLoader.java b/library/src/main/java/org/lsposed/hiddenapibypass/CoreOjClassLoader.java new file mode 100644 index 0000000..da304ad --- /dev/null +++ b/library/src/main/java/org/lsposed/hiddenapibypass/CoreOjClassLoader.java @@ -0,0 +1,43 @@ +package org.lsposed.hiddenapibypass; + +import android.os.Build; + +import androidx.annotation.RequiresApi; + +import java.lang.invoke.MethodHandle; +import java.lang.reflect.Executable; + +import dalvik.system.PathClassLoader; + +@RequiresApi(Build.VERSION_CODES.P) +final class CoreOjClassLoader extends PathClassLoader { + private static String getCoreOjPath() { + String bootClassPath = System.getProperty("java.boot.class.path", ""); + assert bootClassPath != null; + return bootClassPath.split(":", 2)[0]; + } + + CoreOjClassLoader() { + super(getCoreOjPath(), null); + } + + @Override + public Class loadClass(String name) throws ClassNotFoundException { + if (Object.class.getName().equals(name)) { + return Object.class; + } + try { + return findClass(name); + } catch (ClassNotFoundException ignored) { + // no class file in jar before art moved to apex. + } + if (Executable.class.getName().equals(name)) { + return Helper.Executable.class; + } else if (MethodHandle.class.getName().equals(name)) { + return Helper.MethodHandle.class; + } else if (Class.class.getName().equals(name)) { + return Helper.Class.class; + } + return super.loadClass(name); + } +} diff --git a/library/src/main/java/org/lsposed/hiddenapibypass/Helper.java b/library/src/main/java/org/lsposed/hiddenapibypass/Helper.java index 52c3d6b..1fb7fd2 100644 --- a/library/src/main/java/org/lsposed/hiddenapibypass/Helper.java +++ b/library/src/main/java/org/lsposed/hiddenapibypass/Helper.java @@ -17,9 +17,30 @@ package org.lsposed.hiddenapibypass; import java.lang.invoke.MethodType; +import java.util.HashSet; +import java.util.Set; @SuppressWarnings("unused") public class Helper { + static final Set signaturePrefixes = new HashSet<>(); + + static boolean checkArgsForInvokeMethod(java.lang.Class[] params, Object[] args) { + if (params.length != args.length) return false; + for (int i = 0; i < params.length; ++i) { + if (params[i].isPrimitive()) { + if (params[i] == int.class && !(args[i] instanceof Integer)) return false; + else if (params[i] == byte.class && !(args[i] instanceof Byte)) return false; + else if (params[i] == char.class && !(args[i] instanceof Character)) return false; + else if (params[i] == boolean.class && !(args[i] instanceof Boolean)) return false; + else if (params[i] == double.class && !(args[i] instanceof Double)) return false; + else if (params[i] == float.class && !(args[i] instanceof Float)) return false; + else if (params[i] == long.class && !(args[i] instanceof Long)) return false; + else if (params[i] == short.class && !(args[i] instanceof Short)) return false; + } else if (args[i] != null && !params[i].isInstance(args[i])) return false; + } + return true; + } + static public class MethodHandle { private final MethodType type = null; private MethodType nominalType; diff --git a/library/src/main/java/org/lsposed/hiddenapibypass/HiddenApiBypass.java b/library/src/main/java/org/lsposed/hiddenapibypass/HiddenApiBypass.java index 4310a12..cc8f7ce 100644 --- a/library/src/main/java/org/lsposed/hiddenapibypass/HiddenApiBypass.java +++ b/library/src/main/java/org/lsposed/hiddenapibypass/HiddenApiBypass.java @@ -22,7 +22,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; -import androidx.annotation.VisibleForTesting; import org.lsposed.hiddenapibypass.library.BuildConfig; @@ -37,47 +36,11 @@ import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Arrays; -import java.util.HashSet; import java.util.List; -import java.util.Set; import java.util.stream.Collectors; -import dalvik.system.PathClassLoader; -import dalvik.system.VMRuntime; -import sun.misc.Unsafe; - -@RequiresApi(Build.VERSION_CODES.P) -class CoreOjClassLoader extends PathClassLoader { - private static String getCoreOjPath() { - String bootClassPath = System.getProperty("java.boot.class.path", ""); - assert bootClassPath != null; - return bootClassPath.split(":", 2)[0]; - } - - public CoreOjClassLoader() { - super(getCoreOjPath(), null); - } - - @Override - public Class loadClass(String name) throws ClassNotFoundException { - if (Object.class.getName().equals(name)) { - return Object.class; - } - try { - return findClass(name); - } catch (Throwable ignored) { - // no class file in jar before art moved to apex. - } - if (Executable.class.getName().equals(name)) { - return Helper.Executable.class; - } else if (MethodHandle.class.getName().equals(name)) { - return Helper.MethodHandle.class; - } else if (Class.class.getName().equals(name)) { - return Helper.Class.class; - } - return super.loadClass(name); - } -} +import stub.dalvik.system.VMRuntime; +import stub.sun.misc.Unsafe; @RequiresApi(Build.VERSION_CODES.P) public final class HiddenApiBypass { @@ -93,7 +56,6 @@ public final class HiddenApiBypass { private static final long artMethodBias; private static final long artFieldSize; private static final long artFieldBias; - private static final Set signaturePrefixes = new HashSet<>(); static { try { @@ -107,13 +69,17 @@ public final class HiddenApiBypass { methodOffset = unsafe.objectFieldOffset(executableClass.getDeclaredField("artMethod")); classOffset = unsafe.objectFieldOffset(executableClass.getDeclaredField("declaringClass")); artOffset = unsafe.objectFieldOffset(methodHandleClass.getDeclaredField("artFieldOrMethod")); - long fieldOffset; + long iField; + long sField; try { - fieldOffset = unsafe.objectFieldOffset(classClass.getDeclaredField("fields")); + iField = unsafe.objectFieldOffset(classClass.getDeclaredField("fields")); + sField = iField; } catch (NoSuchFieldException e) { - fieldOffset = unsafe.objectFieldOffset(classClass.getDeclaredField("iFields")); + iField = unsafe.objectFieldOffset(classClass.getDeclaredField("iFields")); + sField = unsafe.objectFieldOffset(classClass.getDeclaredField("sFields")); } - iFieldOffset = fieldOffset; + iFieldOffset = iField; + sFieldOffset = sField; methodsOffset = unsafe.objectFieldOffset(classClass.getDeclaredField("methods")); Method mA = Helper.NeverCall.class.getDeclaredMethod("a"); Method mB = Helper.NeverCall.class.getDeclaredMethod("b"); @@ -145,35 +111,12 @@ public final class HiddenApiBypass { Long.toString(jAddr, 16) + ", " + Long.toString(iFields, 16)); artFieldBias = iAddr - iFields; - if (unsafe.getInt(unsafe.getLong(Helper.NeverCall.class, iFieldOffset)) == 4) { - sFieldOffset = iFieldOffset; - } else { - sFieldOffset = unsafe.objectFieldOffset(classClass.getDeclaredField("sFields")); - } } catch (ReflectiveOperationException e) { Log.e(TAG, "Initialize error", e); throw new ExceptionInInitializerError(e); } } - @VisibleForTesting - static boolean checkArgsForInvokeMethod(Class[] params, Object[] args) { - if (params.length != args.length) return false; - for (int i = 0; i < params.length; ++i) { - if (params[i].isPrimitive()) { - if (params[i] == int.class && !(args[i] instanceof Integer)) return false; - else if (params[i] == byte.class && !(args[i] instanceof Byte)) return false; - else if (params[i] == char.class && !(args[i] instanceof Character)) return false; - else if (params[i] == boolean.class && !(args[i] instanceof Boolean)) return false; - else if (params[i] == double.class && !(args[i] instanceof Double)) return false; - else if (params[i] == float.class && !(args[i] instanceof Float)) return false; - else if (params[i] == long.class && !(args[i] instanceof Long)) return false; - else if (params[i] == short.class && !(args[i] instanceof Short)) return false; - } else if (args[i] != null && !params[i].isInstance(args[i])) return false; - } - return true; - } - /** * create an instance of the given class {@code clazz} calling the restricted constructor with arguments {@code args} * @@ -199,7 +142,7 @@ public static Object newInstance(@NonNull Class clazz, Object... initargs) th unsafe.putLong(ctor, methodOffset, method); unsafe.putObject(ctor, classOffset, clazz); Class[] params = ctor.getParameterTypes(); - if (checkArgsForInvokeMethod(params, initargs)) + if (Helper.checkArgsForInvokeMethod(params, initargs)) return ctor.newInstance(initargs); } } @@ -233,7 +176,7 @@ public static Object invoke(@NonNull Class clazz, @Nullable Object thiz, @Non "(" + Arrays.stream(stub.getParameterTypes()).map(Type::getTypeName).collect(Collectors.joining()) + ")"); if (methodName.equals(stub.getName())) { Class[] params = stub.getParameterTypes(); - if (checkArgsForInvokeMethod(params, args)) + if (Helper.checkArgsForInvokeMethod(params, args)) return stub.invoke(thiz, args); } } @@ -248,20 +191,20 @@ public static Object invoke(@NonNull Class clazz, @Nullable Object thiz, @Non */ @NonNull public static List getDeclaredMethods(@NonNull Class clazz) { - ArrayList list = new ArrayList<>(); - if (clazz.isPrimitive() || clazz.isArray()) return list; + if (clazz.isPrimitive() || clazz.isArray()) return List.of(); MethodHandle mh; try { Method mA = Helper.NeverCall.class.getDeclaredMethod("a"); mA.setAccessible(true); mh = MethodHandles.lookup().unreflect(mA); } catch (NoSuchMethodException | IllegalAccessException e) { - return list; + return List.of(); } long methods = unsafe.getLong(clazz, methodsOffset); - if (methods == 0) return list; + if (methods == 0) return List.of(); int numMethods = unsafe.getInt(methods); if (BuildConfig.DEBUG) Log.d(TAG, clazz + " has " + numMethods + " methods"); + List list = new ArrayList<>(numMethods); for (int i = 0; i < numMethods; i++) { long method = methods + i * artMethodSize + artMethodBias; unsafe.putLong(mh, artOffset, method); @@ -335,20 +278,20 @@ public static Constructor getDeclaredConstructor(@NonNull Class clazz, @No */ @NonNull public static List getInstanceFields(@NonNull Class clazz) { - ArrayList list = new ArrayList<>(); - if (clazz.isPrimitive() || clazz.isArray()) return list; + if (clazz.isPrimitive() || clazz.isArray()) return List.of(); MethodHandle mh; try { Field fI = Helper.NeverCall.class.getDeclaredField("i"); fI.setAccessible(true); mh = MethodHandles.lookup().unreflectGetter(fI); } catch (IllegalAccessException | NoSuchFieldException e) { - return list; + return List.of(); } long fields = unsafe.getLong(clazz, iFieldOffset); - if (fields == 0) return list; + if (fields == 0) return List.of(); int numFields = unsafe.getInt(fields); if (BuildConfig.DEBUG) Log.d(TAG, clazz + " has " + numFields + " fields"); + List list = new ArrayList<>(numFields); for (int i = 0; i < numFields; i++) { long field = fields + i * artFieldSize + artFieldBias; unsafe.putLong(mh, artOffset, field); @@ -369,20 +312,20 @@ public static List getInstanceFields(@NonNull Class clazz) { */ @NonNull public static List getStaticFields(@NonNull Class clazz) { - ArrayList list = new ArrayList<>(); - if (clazz.isPrimitive() || clazz.isArray()) return list; + if (clazz.isPrimitive() || clazz.isArray()) return List.of(); MethodHandle mh; try { Field fS = Helper.NeverCall.class.getDeclaredField("s"); fS.setAccessible(true); mh = MethodHandles.lookup().unreflectGetter(fS); } catch (IllegalAccessException | NoSuchFieldException e) { - return list; + return List.of(); } long fields = unsafe.getLong(clazz, sFieldOffset); - if (fields == 0) return list; + if (fields == 0) return List.of(); int numFields = unsafe.getInt(fields); if (BuildConfig.DEBUG) Log.d(TAG, clazz + " has " + numFields + " fields"); + List list = new ArrayList<>(numFields); for (int i = 0; i < numFields; i++) { long field = fields + i * artFieldSize + artFieldBias; unsafe.putLong(mh, artOffset, field); @@ -423,9 +366,9 @@ public static boolean setHiddenApiExemptions(@NonNull String... signaturePrefixe * @return whether the operation is successful */ public static boolean addHiddenApiExemptions(String... signaturePrefixes) { - HiddenApiBypass.signaturePrefixes.addAll(Arrays.asList(signaturePrefixes)); - String[] strings = new String[HiddenApiBypass.signaturePrefixes.size()]; - HiddenApiBypass.signaturePrefixes.toArray(strings); + Helper.signaturePrefixes.addAll(Arrays.asList(signaturePrefixes)); + String[] strings = new String[Helper.signaturePrefixes.size()]; + Helper.signaturePrefixes.toArray(strings); return setHiddenApiExemptions(strings); } @@ -437,7 +380,7 @@ public static boolean addHiddenApiExemptions(String... signaturePrefixes) { * @return whether the operation is successful */ public static boolean clearHiddenApiExemptions() { - HiddenApiBypass.signaturePrefixes.clear(); + Helper.signaturePrefixes.clear(); return setHiddenApiExemptions(); } } diff --git a/library/src/main/java/org/lsposed/lspass/LSPass.java b/library/src/main/java/org/lsposed/hiddenapibypass/LSPass.java similarity index 70% rename from library/src/main/java/org/lsposed/lspass/LSPass.java rename to library/src/main/java/org/lsposed/hiddenapibypass/LSPass.java index fe82819..383dd8d 100644 --- a/library/src/main/java/org/lsposed/lspass/LSPass.java +++ b/library/src/main/java/org/lsposed/hiddenapibypass/LSPass.java @@ -1,4 +1,4 @@ -package org.lsposed.lspass; +package org.lsposed.hiddenapibypass; import android.os.Build; import android.util.Log; @@ -12,19 +12,20 @@ import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; +import java.util.List; -import dalvik.system.VMRuntime; +import stub.dalvik.system.VMRuntime; +@SuppressWarnings("rawtypes") @RequiresApi(Build.VERSION_CODES.P) public final class LSPass { private static final String TAG = "LSPass"; private static final Property methods; private static final Property constructors; private static final Property fields; - private static final Set signaturePrefixes = new HashSet<>(); static { methods = Property.of(Class.class, Method[].class, "DeclaredMethods"); @@ -32,51 +33,66 @@ public final class LSPass { fields = Property.of(Class.class, Field[].class, "DeclaredFields"); } - private static boolean checkArgsForInvokeMethod(Class[] params, Object[] args) { - if (params.length != args.length) return false; - for (int i = 0; i < params.length; ++i) { - if (params[i].isPrimitive()) { - if (params[i] == int.class && !(args[i] instanceof Integer)) return false; - else if (params[i] == byte.class && !(args[i] instanceof Byte)) return false; - else if (params[i] == char.class && !(args[i] instanceof Character)) return false; - else if (params[i] == boolean.class && !(args[i] instanceof Boolean)) return false; - else if (params[i] == double.class && !(args[i] instanceof Double)) return false; - else if (params[i] == float.class && !(args[i] instanceof Float)) return false; - else if (params[i] == long.class && !(args[i] instanceof Long)) return false; - else if (params[i] == short.class && !(args[i] instanceof Short)) return false; - } else if (args[i] != null && !params[i].isInstance(args[i])) return false; - } - return true; - } - /** * get declared methods of given class without hidden api restriction * * @param clazz the class to fetch declared methods - * @return array of declared methods of {@code clazz} + * @return list of declared methods of {@code clazz} */ - public static Method[] getDeclaredMethods(@NonNull Class clazz) { - return methods.get(clazz); + public static List getDeclaredMethods(@NonNull Class clazz) { + return Arrays.asList(methods.get(clazz)); } /** * get declared constructors of given class without hidden api restriction * * @param clazz the class to fetch declared constructors - * @return array of declared constructors of {@code clazz} + * @return list of declared constructors of {@code clazz} */ - public static Constructor[] getDeclaredConstructors(@NonNull Class clazz) { - return constructors.get(clazz); + public static List> getDeclaredConstructors(@NonNull Class clazz) { + return Arrays.>asList(constructors.get(clazz)); } /** * get declared fields of given class without hidden api restriction * * @param clazz the class to fetch declared methods - * @return array of declared fields of {@code clazz} + * @return list of declared fields of {@code clazz} + */ + public static List getDeclaredFields(@NonNull Class clazz) { + return Arrays.asList(fields.get(clazz)); + } + + /** + * get declared non-static fields of given class without hidden api restriction + * + * @param clazz the class to fetch declared methods + * @return list of declared non-static fields of {@code clazz} */ - public static Field[] getDeclaredFields(@NonNull Class clazz) { - return fields.get(clazz); + @NonNull + public static List getInstanceFields(@NonNull Class clazz) { + var list = new ArrayList(); + for (var member : getDeclaredFields(clazz)) { + if (!Modifier.isStatic(member.getModifiers())) + list.add(member); + } + return list; + } + + /** + * get declared static fields of given class without hidden api restriction + * + * @param clazz the class to fetch declared methods + * @return list of declared static fields of {@code clazz} + */ + @NonNull + public static List getStaticFields(@NonNull Class clazz) { + var list = new ArrayList(); + for (var member : getDeclaredFields(clazz)) { + if (Modifier.isStatic(member.getModifiers())) + list.add(member); + } + return list; } /** @@ -91,11 +107,11 @@ public static Field[] getDeclaredFields(@NonNull Class clazz) { */ @NonNull public static Method getDeclaredMethod(@NonNull Class clazz, @NonNull String methodName, @NonNull Class... parameterTypes) throws NoSuchMethodException { - Method[] methods = getDeclaredMethods(clazz); + var methods = getDeclaredMethods(clazz); all: - for (Method method : methods) { + for (var method : methods) { if (!method.getName().equals(methodName)) continue; - Class[] expectedTypes = method.getParameterTypes(); + var expectedTypes = method.getParameterTypes(); if (expectedTypes.length != parameterTypes.length) continue; for (int i = 0; i < parameterTypes.length; ++i) { if (parameterTypes[i] != expectedTypes[i]) continue all; @@ -116,10 +132,10 @@ public static Method getDeclaredMethod(@NonNull Class clazz, @NonNull String */ @NonNull public static Constructor getDeclaredConstructor(@NonNull Class clazz, @NonNull Class... parameterTypes) throws NoSuchMethodException { - Constructor[] constructors = getDeclaredConstructors(clazz); + var constructors = getDeclaredConstructors(clazz); all: - for (Constructor constructor : constructors) { - Class[] expectedTypes = constructor.getParameterTypes(); + for (var constructor : constructors) { + var expectedTypes = constructor.getParameterTypes(); if (expectedTypes.length != parameterTypes.length) continue; for (int i = 0; i < parameterTypes.length; ++i) { if (parameterTypes[i] != expectedTypes[i]) continue all; @@ -138,10 +154,10 @@ public static Constructor getDeclaredConstructor(@NonNull Class clazz, @No * @see Constructor#newInstance(Object...) */ public static Object newInstance(@NonNull Class clazz, Object... initargs) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { - Constructor[] constructors = getDeclaredConstructors(clazz); - for (Constructor constructor : constructors) { - Class[] params = constructor.getParameterTypes(); - if (!checkArgsForInvokeMethod(params, initargs)) continue; + var constructors = getDeclaredConstructors(clazz); + for (var constructor : constructors) { + var params = constructor.getParameterTypes(); + if (!Helper.checkArgsForInvokeMethod(params, initargs)) continue; constructor.setAccessible(true); return constructor.newInstance(initargs); } @@ -159,11 +175,11 @@ public static Object newInstance(@NonNull Class clazz, Object... initargs) th * @see Method#invoke(Object, Object...) */ public static Object invoke(@NonNull Class clazz, @Nullable Object thiz, @NonNull String methodName, Object... args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { - Method[] methods = getDeclaredMethods(clazz); - for (Method method : methods) { + var methods = getDeclaredMethods(clazz); + for (var method : methods) { if (!method.getName().equals(methodName)) continue; - Class[] params = method.getParameterTypes(); - if (!checkArgsForInvokeMethod(params, args)) continue; + var params = method.getParameterTypes(); + if (!Helper.checkArgsForInvokeMethod(params, args)) continue; method.setAccessible(true); return method.invoke(thiz, args); } @@ -180,7 +196,7 @@ public static Object invoke(@NonNull Class clazz, @Nullable Object thiz, @Non */ public static boolean setHiddenApiExemptions(@NonNull String... signaturePrefixes) { try { - Object runtime = invoke(VMRuntime.class, null, "getRuntime"); + var runtime = invoke(VMRuntime.class, null, "getRuntime"); invoke(VMRuntime.class, runtime, "setHiddenApiExemptions", (Object) signaturePrefixes); return true; } catch (ReflectiveOperationException e) { @@ -198,9 +214,9 @@ public static boolean setHiddenApiExemptions(@NonNull String... signaturePrefixe * @return whether the operation is successful */ public static boolean addHiddenApiExemptions(String... signaturePrefixes) { - LSPass.signaturePrefixes.addAll(Arrays.asList(signaturePrefixes)); - String[] strings = new String[LSPass.signaturePrefixes.size()]; - LSPass.signaturePrefixes.toArray(strings); + Helper.signaturePrefixes.addAll(Arrays.asList(signaturePrefixes)); + var strings = new String[Helper.signaturePrefixes.size()]; + Helper.signaturePrefixes.toArray(strings); return setHiddenApiExemptions(strings); } @@ -212,7 +228,7 @@ public static boolean addHiddenApiExemptions(String... signaturePrefixes) { * @return whether the operation is successful */ public static boolean clearHiddenApiExemptions() { - LSPass.signaturePrefixes.clear(); + Helper.signaturePrefixes.clear(); return setHiddenApiExemptions(); } } diff --git a/stub/build.gradle.kts b/stub/build.gradle.kts index 9f31fb5..0de0654 100644 --- a/stub/build.gradle.kts +++ b/stub/build.gradle.kts @@ -3,6 +3,6 @@ plugins { } java { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 } diff --git a/stub/src/main/java/dalvik/system/VMRuntime.java b/stub/src/main/java/stub/dalvik/system/VMRuntime.java similarity index 89% rename from stub/src/main/java/dalvik/system/VMRuntime.java rename to stub/src/main/java/stub/dalvik/system/VMRuntime.java index 87db1ec..8492a1a 100644 --- a/stub/src/main/java/dalvik/system/VMRuntime.java +++ b/stub/src/main/java/stub/dalvik/system/VMRuntime.java @@ -1,4 +1,4 @@ -package dalvik.system; +package stub.dalvik.system; @SuppressWarnings("unused") public class VMRuntime { diff --git a/stub/src/main/java/sun/misc/Unsafe.java b/stub/src/main/java/stub/sun/misc/Unsafe.java similarity index 97% rename from stub/src/main/java/sun/misc/Unsafe.java rename to stub/src/main/java/stub/sun/misc/Unsafe.java index c69f961..34a4e0b 100644 --- a/stub/src/main/java/sun/misc/Unsafe.java +++ b/stub/src/main/java/stub/sun/misc/Unsafe.java @@ -1,4 +1,4 @@ -package sun.misc; +package stub.sun.misc; @SuppressWarnings({"unused", "rawtypes"}) public class Unsafe {