├── .gitignore ├── README.md ├── build.gradle ├── reflectionhooks-api ├── build.gradle └── src │ └── main │ └── java │ └── me │ └── xdark │ └── reflectionhooks │ └── api │ ├── BaseHook.java │ ├── FieldGetController.java │ ├── FieldSetController.java │ ├── FindType.java │ ├── Hook.java │ ├── HooksFactory.java │ ├── InvokeFieldController.java │ ├── InvokeMethodController.java │ ├── Invoker.java │ └── NonDirectReference.java ├── reflectionhooks-core ├── build.gradle └── src │ ├── main │ └── java │ │ ├── jdk │ │ └── internal │ │ │ └── reflect │ │ │ ├── ConstructorAccessor.java │ │ │ ├── FieldAccessor.java │ │ │ ├── MethodAccessor.java │ │ │ └── ReflectionFactory.java │ │ └── me │ │ └── xdark │ │ └── reflectionhooks │ │ └── core │ │ ├── DefaultHooksFactory.java │ │ ├── Environment.java │ │ ├── JavaAccess.java │ │ ├── JavaAccessNew.java │ │ ├── JavaAccessOld.java │ │ └── JavaInvokeInjector.java │ └── test │ └── java │ └── me │ └── xdark │ └── reflectionhooks │ └── core │ ├── TestConstructor.java │ ├── TestField.java │ └── TestMethod.java └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | build 3 | 4 | # Ignore Gradle GUI config 5 | gradle-app.setting 6 | 7 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 8 | !gradle-wrapper.jar 9 | 10 | # Cache of project 11 | .gradletasknamecache 12 | 13 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 14 | # gradle/wrapper/gradle-wrapper.properties 15 | 16 | # Intellij IDEA files 17 | .idea 18 | 19 | # Default build cache 20 | out 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # reflectionhooks 2 | WARNING: java.lang.invoke hooks can only be used if jar is appended into bootstrap class path 3 | ```Java 4 | public class Test { 5 | 6 | private static int val = 1; 7 | 8 | private Test(String str) { 9 | System.err.println(str); 10 | } 11 | 12 | public static void main(String[] a) throws Throwable { 13 | HooksFactory factory = new DefaultHooksFactory(); 14 | Hook hook = factory 15 | .createMethodHook(Void.class, Test.class.getDeclaredMethod("hi", String.class), 16 | (parent, handle, args) -> { 17 | System.out 18 | .println("Hello, World!, arg0 is: " 19 | + args[0] + " and my instance is " + handle); 20 | parent.invoke(null, handle, args); 21 | return null; 22 | }); 23 | hook.hook(); 24 | Test.class.getDeclaredMethod("hi", String.class) 25 | .invoke(null, "Hi!"); 26 | 27 | Hook hook1 = factory.createFieldHook(Test.class.getDeclaredField("val"), 28 | (parent, handle) -> { 29 | System.out.println("get called from " + handle); 30 | return parent.get(null, handle); 31 | }, (parent, handle, value) -> { 32 | System.out.println("set called from " + handle + ", to " + value); 33 | parent.set(null, handle, value); 34 | }); 35 | hook1.hook(); 36 | Field field = Test.class.getDeclaredField("val"); 37 | field.setInt(null, 5); 38 | 39 | Hook hook2 = factory 40 | .createConstructorHook(Test.class, Test.class.getDeclaredConstructor(String.class), 41 | (parent, handle, args) -> { 42 | System.err.println("Constructor called!"); 43 | args[0] = "World!"; 44 | return parent.invoke(null, handle, args); 45 | }); 46 | hook2.hook(); 47 | Test.class.getDeclaredConstructor(String.class).newInstance("Hello, "); 48 | } 49 | 50 | public static void hi(String str) { 51 | System.out.println(str); 52 | } 53 | } 54 | ``` 55 | 56 | ```Java 57 | public class Test { 58 | 59 | public static void main(String[] args) throws Throwable { 60 | JavaInvokeInjector.inject(); 61 | HooksFactory factory = new DefaultHooksFactory(); 62 | factory.createMethodInvokeHook((type, classRef, nameRef, typeRef) -> { 63 | System.out.println("Call: " + classRef.get() + ' ' + nameRef.get() + ' ' + typeRef.get()); 64 | nameRef.set("hooked"); 65 | }); 66 | MethodHandles.publicLookup().findStatic(Test.class, "first", MethodType.methodType(void.class)) 67 | .invokeExact(); 68 | } 69 | 70 | public static void first() { 71 | System.out.println("Hello, "); 72 | } 73 | 74 | public static void hooked() { 75 | System.out.println("World!"); 76 | } 77 | } 78 | ``` 79 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | } 4 | 5 | group 'me.xdark' 6 | version '1.0' 7 | 8 | sourceCompatibility = 1.8 9 | targetCompatibility = 1.8 -------------------------------------------------------------------------------- /reflectionhooks-api/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | } 4 | 5 | group 'me.xdark' 6 | version '1.0' 7 | 8 | sourceCompatibility = 1.8 9 | targetCompatibility = 1.8 -------------------------------------------------------------------------------- /reflectionhooks-api/src/main/java/me/xdark/reflectionhooks/api/BaseHook.java: -------------------------------------------------------------------------------- 1 | package me.xdark.reflectionhooks.api; 2 | 3 | public class BaseHook implements Hook { 4 | 5 | protected boolean hooked; 6 | 7 | @Override 8 | public void hook() { 9 | hooked = true; 10 | } 11 | 12 | @Override 13 | public boolean isHooked() { 14 | return hooked; 15 | } 16 | 17 | @Override 18 | public void unhook() { 19 | hooked = false; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /reflectionhooks-api/src/main/java/me/xdark/reflectionhooks/api/FieldGetController.java: -------------------------------------------------------------------------------- 1 | package me.xdark.reflectionhooks.api; 2 | 3 | @FunctionalInterface 4 | public interface FieldGetController { 5 | 6 | /** 7 | * Called when something tries to get field value 8 | * 9 | * @param parent parent controller, may be {@code null} 10 | * @param handle the field holder 11 | */ 12 | Object get(FieldGetController parent, Object handle); 13 | } 14 | -------------------------------------------------------------------------------- /reflectionhooks-api/src/main/java/me/xdark/reflectionhooks/api/FieldSetController.java: -------------------------------------------------------------------------------- 1 | package me.xdark.reflectionhooks.api; 2 | 3 | @FunctionalInterface 4 | public interface FieldSetController { 5 | 6 | /** 7 | * Sets a value of field 8 | * 9 | * @param parent parent controller, may be {@code null} 10 | * @param handle the field holder 11 | * @param value new value 12 | */ 13 | void set(FieldSetController parent, Object handle, Object value); 14 | } 15 | -------------------------------------------------------------------------------- /reflectionhooks-api/src/main/java/me/xdark/reflectionhooks/api/FindType.java: -------------------------------------------------------------------------------- 1 | package me.xdark.reflectionhooks.api; 2 | 3 | public enum FindType { 4 | 5 | VIRTUAL, STATIC, SPECIAL, CONSTRUCTOR, GETTER, SETTER, STATIC_GETTER, STATIC_SETTER 6 | } 7 | -------------------------------------------------------------------------------- /reflectionhooks-api/src/main/java/me/xdark/reflectionhooks/api/Hook.java: -------------------------------------------------------------------------------- 1 | package me.xdark.reflectionhooks.api; 2 | 3 | public interface Hook { 4 | 5 | /** 6 | * Hooks target {@link java.lang.reflect.Member} 7 | */ 8 | void hook(); 9 | 10 | /** 11 | * Returns {@code true} if hook is set 12 | */ 13 | boolean isHooked(); 14 | 15 | /** 16 | * Removes hook from target {@link java.lang.reflect.Member} 17 | */ 18 | void unhook(); 19 | } 20 | -------------------------------------------------------------------------------- /reflectionhooks-api/src/main/java/me/xdark/reflectionhooks/api/HooksFactory.java: -------------------------------------------------------------------------------- 1 | package me.xdark.reflectionhooks.api; 2 | 3 | import java.lang.reflect.Constructor; 4 | import java.lang.reflect.Field; 5 | import java.lang.reflect.Method; 6 | 7 | public interface HooksFactory { 8 | 9 | /** 10 | * Creates a hook for {@link java.lang.reflect.Method} 11 | * 12 | * @param rtype invocation return type 13 | * @param method the target 14 | * @param controller invocation controller 15 | * @return hook instance 16 | */ 17 | Hook createMethodHook(Class rtype, Method method, Invoker controller); 18 | 19 | /** 20 | * Creates a hook for {@link java.lang.reflect.Field} 21 | * 22 | * @return hook instance 23 | * @see FieldGetController 24 | * @see FieldSetController 25 | */ 26 | Hook createFieldHook(Field field, FieldGetController getController, 27 | FieldSetController setController); 28 | 29 | /** 30 | * Creates a hook for {@link java.lang.reflect.Constructor} 31 | * 32 | * @param rtype constructor return type 33 | * @param constructor the target 34 | * @param controller invocation controller 35 | * @return hook instance 36 | */ 37 | Hook createConstructorHook(Class rtype, Constructor constructor, Invoker controller); 38 | 39 | /** 40 | * Creates a method hook for {@link java.lang.invoke.MethodHandles.Lookup} 41 | * WARNING: this types of hook CANNOT be uninstalled 42 | */ 43 | void createMethodInvokeHook(InvokeMethodController controller); 44 | 45 | /** 46 | * Creates a method hook for {@link java.lang.invoke.MethodHandles.Lookup} 47 | * WARNING: this types of hook CANNOT be uninstalled 48 | */ 49 | void createConstructorInvokeHook(InvokeMethodController controller); 50 | 51 | /** 52 | * Creates a field hook for {@link java.lang.invoke.MethodHandles.Lookup} 53 | * WARNING: this types of hook CANNOT be uninstalled 54 | */ 55 | void createFieldInvokeHook(InvokeFieldController controller); 56 | } 57 | -------------------------------------------------------------------------------- /reflectionhooks-api/src/main/java/me/xdark/reflectionhooks/api/InvokeFieldController.java: -------------------------------------------------------------------------------- 1 | package me.xdark.reflectionhooks.api; 2 | 3 | @FunctionalInterface 4 | public interface InvokeFieldController { 5 | 6 | /** 7 | * Fired when find** is invoked 8 | * Fired on: findGetter, findSetter, findStaticGetter, findStaticSetter 9 | * 10 | * @param type the type of call 11 | * @param classRef the class reference 12 | * @param nameRef the name reference 13 | * @param typeRef the field type reference 14 | */ 15 | void onFindCalled(FindType type, NonDirectReference> classRef, NonDirectReference nameRef, NonDirectReference> typeRef); 16 | } 17 | -------------------------------------------------------------------------------- /reflectionhooks-api/src/main/java/me/xdark/reflectionhooks/api/InvokeMethodController.java: -------------------------------------------------------------------------------- 1 | package me.xdark.reflectionhooks.api; 2 | 3 | import java.lang.invoke.MethodType; 4 | 5 | @FunctionalInterface 6 | public interface InvokeMethodController { 7 | 8 | /** 9 | * Fired when find** is invoked 10 | * Fired on: findVirtual, findStatic, findSpecial, findConstructor 11 | * 12 | * @param type the type of call 13 | * @param classRef the class reference 14 | * @param nameRef the name reference 15 | * @param typeRef the descriptor reference 16 | */ 17 | void onFindCalled(FindType type, NonDirectReference> classRef, NonDirectReference nameRef, NonDirectReference typeRef); 18 | } 19 | -------------------------------------------------------------------------------- /reflectionhooks-api/src/main/java/me/xdark/reflectionhooks/api/Invoker.java: -------------------------------------------------------------------------------- 1 | package me.xdark.reflectionhooks.api; 2 | 3 | @FunctionalInterface 4 | public interface Invoker { 5 | 6 | /** 7 | * Invokes the method 8 | * 9 | * @param parent parent invoker, may be {@code null} 10 | * @param handle object instance 11 | * @param args arguments of method 12 | * @return invocation result 13 | */ 14 | R invoke(Invoker parent, Object handle, Object... args) throws Throwable; 15 | } 16 | -------------------------------------------------------------------------------- /reflectionhooks-api/src/main/java/me/xdark/reflectionhooks/api/NonDirectReference.java: -------------------------------------------------------------------------------- 1 | package me.xdark.reflectionhooks.api; 2 | 3 | public class NonDirectReference { 4 | 5 | private T value; 6 | 7 | public NonDirectReference(T referent) { 8 | this.value = referent; 9 | } 10 | 11 | /** 12 | * @retun value of reference 13 | */ 14 | public T get() { 15 | return value; 16 | } 17 | 18 | /** 19 | * Set new value of reference 20 | * 21 | * @param value new value 22 | */ 23 | public void set(T value) { 24 | this.value = value; 25 | } 26 | 27 | @Override 28 | public String toString() { 29 | return String.valueOf(value); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /reflectionhooks-core/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | } 4 | 5 | repositories { 6 | mavenLocal() 7 | mavenCentral() 8 | } 9 | 10 | group 'me.xdark' 11 | version '1.0' 12 | 13 | sourceCompatibility = 1.8 14 | targetCompatibility = 1.8 15 | 16 | jar { 17 | exclude('jdk/internal/reflect/*') 18 | from { 19 | configurations.runtimeClasspath.collect { 20 | it.isDirectory() ? it : zipTree(it) 21 | } 22 | } 23 | } 24 | 25 | dependencies { 26 | compile project(":reflectionhooks-api") 27 | testImplementation 'org.junit.jupiter:junit-jupiter-api:5.3.1' 28 | testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.3.1' 29 | compile 'org.ow2.asm:asm:7.0' 30 | compile 'org.ow2.asm:asm-tree:7.0' 31 | compile 'org.ow2.asm:asm-commons:7.0' 32 | compile 'org.ow2.asm:asm-analysis:7.0' 33 | compile 'org.ow2.asm:asm-util:7.0' 34 | } 35 | 36 | test { 37 | useJUnitPlatform() 38 | } 39 | -------------------------------------------------------------------------------- /reflectionhooks-core/src/main/java/jdk/internal/reflect/ConstructorAccessor.java: -------------------------------------------------------------------------------- 1 | package jdk.internal.reflect; 2 | 3 | import java.lang.reflect.InvocationTargetException; 4 | 5 | // Dummy JDK class 6 | public interface ConstructorAccessor { 7 | 8 | Object newInstance(Object[] var1) 9 | throws InstantiationException, IllegalArgumentException, InvocationTargetException; 10 | } 11 | -------------------------------------------------------------------------------- /reflectionhooks-core/src/main/java/jdk/internal/reflect/FieldAccessor.java: -------------------------------------------------------------------------------- 1 | package jdk.internal.reflect; 2 | 3 | // Dummy JDK class 4 | public interface FieldAccessor { 5 | 6 | Object get(Object var1) throws IllegalArgumentException; 7 | 8 | boolean getBoolean(Object var1) throws IllegalArgumentException; 9 | 10 | byte getByte(Object var1) throws IllegalArgumentException; 11 | 12 | char getChar(Object var1) throws IllegalArgumentException; 13 | 14 | short getShort(Object var1) throws IllegalArgumentException; 15 | 16 | int getInt(Object var1) throws IllegalArgumentException; 17 | 18 | long getLong(Object var1) throws IllegalArgumentException; 19 | 20 | float getFloat(Object var1) throws IllegalArgumentException; 21 | 22 | double getDouble(Object var1) throws IllegalArgumentException; 23 | 24 | void set(Object var1, Object var2) throws IllegalArgumentException, IllegalAccessException; 25 | 26 | void setBoolean(Object var1, boolean var2) 27 | throws IllegalArgumentException, IllegalAccessException; 28 | 29 | void setByte(Object var1, byte var2) throws IllegalArgumentException, IllegalAccessException; 30 | 31 | void setChar(Object var1, char var2) throws IllegalArgumentException, IllegalAccessException; 32 | 33 | void setShort(Object var1, short var2) throws IllegalArgumentException, IllegalAccessException; 34 | 35 | void setInt(Object var1, int var2) throws IllegalArgumentException, IllegalAccessException; 36 | 37 | void setLong(Object var1, long var2) throws IllegalArgumentException, IllegalAccessException; 38 | 39 | void setFloat(Object var1, float var2) throws IllegalArgumentException, IllegalAccessException; 40 | 41 | void setDouble(Object var1, double var2) throws IllegalArgumentException, IllegalAccessException; 42 | } 43 | -------------------------------------------------------------------------------- /reflectionhooks-core/src/main/java/jdk/internal/reflect/MethodAccessor.java: -------------------------------------------------------------------------------- 1 | package jdk.internal.reflect; 2 | 3 | import java.lang.reflect.InvocationTargetException; 4 | 5 | // Dummy JDK class 6 | public interface MethodAccessor { 7 | 8 | Object invoke(Object var1, Object[] var2) 9 | throws IllegalArgumentException, InvocationTargetException; 10 | } -------------------------------------------------------------------------------- /reflectionhooks-core/src/main/java/jdk/internal/reflect/ReflectionFactory.java: -------------------------------------------------------------------------------- 1 | package jdk.internal.reflect; 2 | 3 | import java.lang.reflect.Constructor; 4 | import java.lang.reflect.Field; 5 | import java.lang.reflect.Method; 6 | 7 | // Dummy JDK class 8 | public class ReflectionFactory { 9 | 10 | public static ReflectionFactory getReflectionFactory() { 11 | return null; 12 | } 13 | 14 | public MethodAccessor newMethodAccessor(Method method) { 15 | return null; 16 | } 17 | 18 | public ConstructorAccessor newConstructorAccessor(Constructor c) { 19 | return null; 20 | } 21 | 22 | public FieldAccessor newFieldAccessor(Field field, boolean override) { 23 | return null; 24 | } 25 | 26 | private static void checkInitted() { } 27 | } 28 | -------------------------------------------------------------------------------- /reflectionhooks-core/src/main/java/me/xdark/reflectionhooks/core/DefaultHooksFactory.java: -------------------------------------------------------------------------------- 1 | package me.xdark.reflectionhooks.core; 2 | 3 | import java.lang.reflect.Constructor; 4 | import java.lang.reflect.Field; 5 | import java.lang.reflect.Method; 6 | 7 | import me.xdark.reflectionhooks.api.*; 8 | 9 | public final class DefaultHooksFactory implements HooksFactory { 10 | 11 | @Override 12 | public Hook createMethodHook(Class rtype, Method method, Invoker controller) { 13 | return Environment.createMethodHook0(method, controller); 14 | } 15 | 16 | @Override 17 | public Hook createFieldHook(Field field, FieldGetController getController, 18 | FieldSetController setController) { 19 | return Environment.createFieldHook0(field, getController, setController); 20 | } 21 | 22 | @Override 23 | public Hook createConstructorHook(Class rtype, Constructor constructor, 24 | Invoker controller) { 25 | return Environment.createConstructorHook0(constructor, controller); 26 | } 27 | 28 | @Override 29 | public void createMethodInvokeHook(InvokeMethodController controller) { 30 | Environment.INVOKE_METHOD_CONTROLLERS.add(controller); 31 | } 32 | 33 | @Override 34 | public void createConstructorInvokeHook(InvokeMethodController controller) { 35 | Environment.INVOKE_CONSTRUCTOR_CONTROLLERS.add(controller); 36 | } 37 | 38 | @Override 39 | public void createFieldInvokeHook(InvokeFieldController controller) { 40 | Environment.INVOKE_FIELD_CONTROLLERS.add(controller); 41 | } 42 | 43 | static { 44 | Environment.prepare(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /reflectionhooks-core/src/main/java/me/xdark/reflectionhooks/core/Environment.java: -------------------------------------------------------------------------------- 1 | package me.xdark.reflectionhooks.core; 2 | 3 | import me.xdark.reflectionhooks.api.*; 4 | import sun.misc.Unsafe; 5 | 6 | import java.io.InputStream; 7 | import java.lang.invoke.MethodHandle; 8 | import java.lang.invoke.MethodHandles; 9 | import java.lang.invoke.MethodHandles.Lookup; 10 | import java.lang.invoke.MethodType; 11 | import java.lang.reflect.Constructor; 12 | import java.lang.reflect.Field; 13 | import java.lang.reflect.Method; 14 | import java.security.AccessController; 15 | import java.security.PrivilegedAction; 16 | import java.util.ArrayList; 17 | import java.util.List; 18 | 19 | public final class Environment { 20 | 21 | static final List INVOKE_METHOD_CONTROLLERS = new ArrayList<>(); 22 | static final List INVOKE_CONSTRUCTOR_CONTROLLERS = new ArrayList<>(); 23 | static final List INVOKE_FIELD_CONTROLLERS = new ArrayList<>(); 24 | 25 | static final Lookup LOOKUP; 26 | private static final Unsafe UNSAFE; 27 | private static final JavaAccess JAVA_ACCESS; 28 | 29 | private static final MethodHandle MH_METHOD_COPY; 30 | private static final MethodHandle MH_METHOD_PARENT_GET; 31 | private static final MethodHandle MH_METHOD_PARENT_SET; 32 | private static final MethodHandle MH_METHOD_ACCESSOR_SET; 33 | 34 | private static final MethodHandle MH_FIELD_COPY; 35 | private static final MethodHandle MH_FIELD_PARENT_GET; 36 | private static final MethodHandle MH_FIELD_PARENT_SET; 37 | private static final MethodHandle MH_FIELD_ACCESSOR_SET1; 38 | private static final MethodHandle MH_FIELD_ACCESSOR_SET2; 39 | 40 | private static final MethodHandle MH_CONST_COPY; 41 | private static final MethodHandle MH_CONST_PARENT_GET; 42 | private static final MethodHandle MH_CONST_PARENT_SET; 43 | private static final MethodHandle MH_CONST_ACCESSOR_SET; 44 | 45 | private Environment() { 46 | } 47 | 48 | static void prepare() { 49 | try { 50 | JAVA_ACCESS.init(); 51 | } catch (Throwable t) { 52 | sneakyThrow(t); 53 | } 54 | } 55 | 56 | static Hook createMethodHook0(Method method, Invoker hook) { 57 | assert method != null; 58 | try { 59 | // Where magic begins 60 | Method root = (Method) MH_METHOD_PARENT_GET.invokeExact(method); 61 | // If it is already a root one, then, what the heck? 62 | if (root == null) { 63 | root = method; 64 | } 65 | // Copy root method to allow to call original one 66 | Method copyRoot = (Method) MH_METHOD_COPY.invokeExact(root); 67 | wipeMethod(copyRoot); 68 | Hook delegate = new BaseHook(); 69 | Object hooked = JAVA_ACCESS.newMethodAccessor(copyRoot, hook, delegate); 70 | MH_METHOD_ACCESSOR_SET.invoke(root, hooked); 71 | wipeMethod(method); 72 | MH_METHOD_ACCESSOR_SET.invoke(method, hooked); 73 | return delegate; 74 | } catch (Throwable t) { 75 | return sneakyThrow(t); 76 | } 77 | } 78 | 79 | static Hook createFieldHook0(Field field, FieldGetController getController, 80 | FieldSetController setController) { 81 | assert field != null; 82 | try { 83 | // Where magic begins 84 | Field root = (Field) MH_FIELD_PARENT_GET.invokeExact(field); 85 | // If it is already a root one, then, what the heck? 86 | if (root == null) { 87 | root = field; 88 | } 89 | // Copy root field to allow to call original one 90 | Field copyRoot = (Field) MH_FIELD_COPY.invokeExact(root); 91 | wipeField(copyRoot); 92 | Hook delegate = new BaseHook(); 93 | Object hooked = JAVA_ACCESS 94 | .newFieldAccessor(copyRoot, getController, setController, delegate); 95 | MH_FIELD_ACCESSOR_SET1.invoke(root, hooked); 96 | MH_FIELD_ACCESSOR_SET2.invoke(root, hooked); 97 | wipeField(field); 98 | MH_FIELD_ACCESSOR_SET1.invoke(field, hooked); 99 | MH_FIELD_ACCESSOR_SET2.invoke(field, hooked); 100 | return delegate; 101 | } catch (Throwable t) { 102 | return sneakyThrow(t); 103 | } 104 | } 105 | 106 | static Hook createConstructorHook0(Constructor constructor, Invoker hook) { 107 | assert constructor != null; 108 | try { 109 | // Where magic begins 110 | Constructor root = (Constructor) MH_CONST_PARENT_GET.invokeExact(constructor); 111 | // If it is already a root one, then, what the heck? 112 | if (root == null) { 113 | root = constructor; 114 | } 115 | // Copy root method to allow to call original one 116 | Constructor copyRoot = (Constructor) MH_CONST_COPY.invokeExact(root); 117 | wipeConstructor(copyRoot); 118 | Hook delegate = new BaseHook(); 119 | Object hooked = JAVA_ACCESS.newConstructorAccessor(constructor, hook, delegate); 120 | MH_CONST_ACCESSOR_SET.invoke(root, hooked); 121 | wipeConstructor(constructor); 122 | MH_CONST_ACCESSOR_SET.invoke(constructor, hooked); 123 | return delegate; 124 | } catch (Throwable t) { 125 | return sneakyThrow(t); 126 | } 127 | } 128 | 129 | private static void wipeMethod(Method method) { 130 | try { 131 | MH_METHOD_PARENT_SET.invokeExact(method, (Method) null); 132 | MH_METHOD_ACCESSOR_SET.invoke(method, null); 133 | } catch (Throwable t) { 134 | sneakyThrow(t); 135 | } 136 | } 137 | 138 | private static void wipeField(Field field) { 139 | try { 140 | MH_FIELD_PARENT_SET.invokeExact(field, (Field) null); 141 | MH_FIELD_ACCESSOR_SET1.invoke(field, null); 142 | MH_FIELD_ACCESSOR_SET2.invoke(field, null); 143 | } catch (Throwable t) { 144 | sneakyThrow(t); 145 | } 146 | } 147 | 148 | private static void wipeConstructor(Constructor constructor) { 149 | try { 150 | MH_CONST_PARENT_SET.invokeExact(constructor, (Constructor) null); 151 | MH_CONST_ACCESSOR_SET.invoke(constructor, null); 152 | } catch (Throwable t) { 153 | sneakyThrow(t); 154 | } 155 | } 156 | 157 | static T sneakyThrow(Throwable t) { 158 | UNSAFE.throwException(t); 159 | // We throw exception, but compiler does not know about it 160 | return null; 161 | } 162 | 163 | public static void onMethodHook(NonDirectReference> classRef, NonDirectReference nameRef, NonDirectReference typeRef) { 164 | for (int i = 0; i < INVOKE_METHOD_CONTROLLERS.size(); i++) { 165 | INVOKE_METHOD_CONTROLLERS.get(i).onFindCalled(null, classRef, nameRef, typeRef); 166 | } 167 | } 168 | 169 | public static void onConstructorHook(NonDirectReference> classRef, NonDirectReference nameRef, NonDirectReference typeRef) { 170 | for (int i = 0; i < INVOKE_CONSTRUCTOR_CONTROLLERS.size(); i++) { 171 | INVOKE_CONSTRUCTOR_CONTROLLERS.get(i).onFindCalled(null, classRef, nameRef, typeRef); 172 | } 173 | } 174 | 175 | static { 176 | Object maybeUnsafe = AccessController.doPrivileged((PrivilegedAction) () -> { 177 | try { 178 | Field field = Unsafe.class.getDeclaredField("theUnsafe"); 179 | field.setAccessible(true); 180 | return field.get(null); 181 | } catch (Throwable t) { 182 | return t; 183 | } 184 | }); 185 | if (maybeUnsafe instanceof Throwable) { 186 | throw new AssertionError("sun.misc.Unsafe is not available!", (Throwable) maybeUnsafe); 187 | } else { 188 | UNSAFE = (Unsafe) maybeUnsafe; 189 | } 190 | Object maybeLookup = AccessController.doPrivileged((PrivilegedAction) () -> { 191 | try { 192 | MethodHandles.publicLookup(); 193 | Field implLookupField = Lookup.class.getDeclaredField("IMPL_LOOKUP"); 194 | return UNSAFE.getObject(UNSAFE.staticFieldBase(implLookupField), 195 | UNSAFE.staticFieldOffset(implLookupField)); 196 | } catch (Throwable t) { 197 | return t; 198 | } 199 | }); 200 | if (maybeLookup instanceof Throwable) { 201 | throw new AssertionError("java.lang.invoke.Lookup#IMPL_LOOKUP not available!", 202 | (Throwable) maybeLookup); 203 | } else { 204 | LOOKUP = (Lookup) maybeLookup; 205 | } 206 | try { 207 | 208 | // I hate Oracle, if they change classfile format, im gonna kill myself 209 | int version; 210 | try (InputStream in = ClassLoader.getSystemResourceAsStream("java/lang/ClassLoader.class")) { 211 | in.skip(6); 212 | version = (in.read() << 8) + in.read() - 44; 213 | } 214 | if (version <= 8) { 215 | JAVA_ACCESS = new JavaAccessOld(); 216 | } else { 217 | JAVA_ACCESS = new JavaAccessNew(); 218 | } 219 | 220 | MH_METHOD_COPY = LOOKUP 221 | .findVirtual(Method.class, "copy", MethodType.methodType(Method.class)); 222 | MH_METHOD_PARENT_GET = LOOKUP.findGetter(Method.class, "root", Method.class); 223 | MH_METHOD_PARENT_SET = LOOKUP.findSetter(Method.class, "root", Method.class); 224 | MH_METHOD_ACCESSOR_SET = LOOKUP 225 | .findSetter(Method.class, "methodAccessor", JAVA_ACCESS.resolve("MethodAccessor")); 226 | 227 | MH_FIELD_COPY = LOOKUP 228 | .findVirtual(Field.class, "copy", MethodType.methodType(Field.class)); 229 | MH_FIELD_PARENT_GET = LOOKUP.findGetter(Field.class, "root", Field.class); 230 | MH_FIELD_PARENT_SET = LOOKUP.findSetter(Field.class, "root", Field.class); 231 | MH_FIELD_ACCESSOR_SET1 = LOOKUP 232 | .findSetter(Field.class, "overrideFieldAccessor", JAVA_ACCESS.resolve("FieldAccessor")); 233 | MH_FIELD_ACCESSOR_SET2 = LOOKUP 234 | .findSetter(Field.class, "fieldAccessor", JAVA_ACCESS.resolve("FieldAccessor")); 235 | 236 | MH_CONST_COPY = LOOKUP 237 | .findVirtual(Constructor.class, "copy", MethodType.methodType(Constructor.class)); 238 | MH_CONST_PARENT_GET = LOOKUP.findGetter(Constructor.class, "root", Constructor.class); 239 | MH_CONST_PARENT_SET = LOOKUP.findSetter(Constructor.class, "root", Constructor.class); 240 | MH_CONST_ACCESSOR_SET = LOOKUP 241 | .findSetter(Constructor.class, "constructorAccessor", 242 | JAVA_ACCESS.resolve("ConstructorAccessor")); 243 | } catch (Throwable t) { 244 | throw new AssertionError("Initial setup failed!", t); 245 | } 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /reflectionhooks-core/src/main/java/me/xdark/reflectionhooks/core/JavaAccess.java: -------------------------------------------------------------------------------- 1 | package me.xdark.reflectionhooks.core; 2 | 3 | import java.lang.reflect.Constructor; 4 | import java.lang.reflect.Field; 5 | import java.lang.reflect.Method; 6 | import me.xdark.reflectionhooks.api.FieldGetController; 7 | import me.xdark.reflectionhooks.api.FieldSetController; 8 | import me.xdark.reflectionhooks.api.Hook; 9 | import me.xdark.reflectionhooks.api.Invoker; 10 | 11 | interface JavaAccess { 12 | 13 | default void init() throws Throwable { } 14 | 15 | Object newMethodAccessor(Method method, Invoker invoker, Hook hook); 16 | 17 | Object newFieldAccessor(Field field, FieldGetController getController, 18 | FieldSetController setController, Hook hook); 19 | 20 | Object newConstructorAccessor(Constructor constructor, Invoker invoker, Hook hook); 21 | 22 | Class resolve(String target); 23 | } 24 | -------------------------------------------------------------------------------- /reflectionhooks-core/src/main/java/me/xdark/reflectionhooks/core/JavaAccessNew.java: -------------------------------------------------------------------------------- 1 | package me.xdark.reflectionhooks.core; 2 | 3 | import java.lang.invoke.MethodHandle; 4 | import java.lang.invoke.MethodHandles.Lookup; 5 | import java.lang.invoke.MethodType; 6 | import java.lang.reflect.Constructor; 7 | import java.lang.reflect.Field; 8 | import java.lang.reflect.InvocationTargetException; 9 | import java.lang.reflect.Method; 10 | import java.util.HashSet; 11 | import java.util.Set; 12 | import jdk.internal.reflect.ConstructorAccessor; 13 | import jdk.internal.reflect.FieldAccessor; 14 | import jdk.internal.reflect.MethodAccessor; 15 | import jdk.internal.reflect.ReflectionFactory; 16 | import me.xdark.reflectionhooks.api.FieldGetController; 17 | import me.xdark.reflectionhooks.api.FieldSetController; 18 | import me.xdark.reflectionhooks.api.Hook; 19 | import me.xdark.reflectionhooks.api.Invoker; 20 | 21 | @SuppressWarnings("Duplicates") 22 | final class JavaAccessNew implements JavaAccess { 23 | 24 | private final ReflectionFactory factory = ReflectionFactory.getReflectionFactory(); 25 | 26 | @Override 27 | public void init() throws Throwable { 28 | Environment.LOOKUP 29 | .findStatic(ReflectionFactory.class, "checkInitted", 30 | MethodType.methodType(void.class)) 31 | .invokeExact(); 32 | } 33 | 34 | 35 | @Override 36 | public Object newMethodAccessor(Method method, Invoker invoker, Hook hook) { 37 | MethodAccessor accessor = factory.newMethodAccessor(method); 38 | Invoker parent = (parent1, handle, args) -> (R) accessor.invoke(handle, args); 39 | return (MethodAccessor) (handle, args) -> { 40 | try { 41 | if (!hook.isHooked()) { 42 | return parent.invoke(null, handle, args); 43 | } 44 | return invoker.invoke(parent, handle, args); 45 | } catch (Throwable t) { 46 | throw new InvocationTargetException(t); 47 | } 48 | }; 49 | } 50 | 51 | @Override 52 | public Object newFieldAccessor(Field field, FieldGetController getController, 53 | FieldSetController setController, Hook hook) { 54 | FieldAccessor accessor = factory.newFieldAccessor(field, true); 55 | FieldGetController parentGet = (parent, handle) -> accessor.get(handle); 56 | FieldSetController parentSet = (parent, handle, value) -> { 57 | try { 58 | accessor.set(handle, value); 59 | } catch (IllegalAccessException ignored) { 60 | } 61 | }; 62 | return new FieldAccessor() { 63 | @Override 64 | public Object get(Object o) throws IllegalArgumentException { 65 | return getController == null || !hook.isHooked() ? accessor.get(o) 66 | : getController.get(parentGet, o); 67 | } 68 | 69 | @Override 70 | public boolean getBoolean(Object o) throws IllegalArgumentException { 71 | return getController == null || !hook.isHooked() ? accessor.getBoolean(o) 72 | : (boolean) getController.get(parentGet, o); 73 | } 74 | 75 | @Override 76 | public byte getByte(Object o) throws IllegalArgumentException { 77 | return getController == null || !hook.isHooked() ? accessor.getByte(o) 78 | : (byte) getController.get(parentGet, o); 79 | } 80 | 81 | @Override 82 | public char getChar(Object o) throws IllegalArgumentException { 83 | return getController == null || !hook.isHooked() ? accessor.getChar(o) 84 | : (char) getController.get(parentGet, o); 85 | } 86 | 87 | @Override 88 | public short getShort(Object o) throws IllegalArgumentException { 89 | return getController == null || !hook.isHooked() ? accessor.getShort(o) 90 | : (short) getController.get(parentGet, o); 91 | } 92 | 93 | @Override 94 | public int getInt(Object o) throws IllegalArgumentException { 95 | return getController == null || !hook.isHooked() ? accessor.getInt(o) 96 | : (int) getController.get(parentGet, o); 97 | } 98 | 99 | @Override 100 | public long getLong(Object o) throws IllegalArgumentException { 101 | return getController == null || !hook.isHooked() ? accessor.getLong(o) 102 | : (long) getController.get(parentGet, o); 103 | } 104 | 105 | @Override 106 | public float getFloat(Object o) throws IllegalArgumentException { 107 | return getController == null || !hook.isHooked() ? accessor.getFloat(o) 108 | : (float) getController.get(parentGet, o); 109 | } 110 | 111 | @Override 112 | public double getDouble(Object o) throws IllegalArgumentException { 113 | return getController == null || !hook.isHooked() ? accessor.getDouble(o) 114 | : (double) getController.get(parentGet, o); 115 | } 116 | 117 | @Override 118 | public void set(Object o, Object o1) 119 | throws IllegalArgumentException, IllegalAccessException { 120 | if (setController == null || !hook.isHooked()) { 121 | accessor.set(o, o1); 122 | } else { 123 | setController.set(parentSet, o, o1); 124 | } 125 | } 126 | 127 | @Override 128 | public void setBoolean(Object o, boolean b) 129 | throws IllegalArgumentException, IllegalAccessException { 130 | if (setController == null || !hook.isHooked()) { 131 | accessor.setBoolean(o, b); 132 | } else { 133 | setController.set(parentSet, o, b); 134 | } 135 | } 136 | 137 | @Override 138 | public void setByte(Object o, byte b) 139 | throws IllegalArgumentException, IllegalAccessException { 140 | if (setController == null || !hook.isHooked()) { 141 | accessor.setByte(o, b); 142 | } else { 143 | setController.set(parentSet, o, b); 144 | } 145 | } 146 | 147 | @Override 148 | public void setChar(Object o, char c) 149 | throws IllegalArgumentException, IllegalAccessException { 150 | if (setController == null || !hook.isHooked()) { 151 | accessor.setChar(o, c); 152 | } else { 153 | setController.set(parentSet, o, c); 154 | } 155 | } 156 | 157 | @Override 158 | public void setShort(Object o, short i) 159 | throws IllegalArgumentException, IllegalAccessException { 160 | if (setController == null || !hook.isHooked()) { 161 | accessor.setShort(o, i); 162 | } else { 163 | setController.set(parentSet, o, i); 164 | } 165 | } 166 | 167 | @Override 168 | public void setInt(Object o, int i) 169 | throws IllegalArgumentException, IllegalAccessException { 170 | if (setController == null || !hook.isHooked()) { 171 | accessor.setInt(o, i); 172 | } else { 173 | setController.set(parentSet, o, i); 174 | } 175 | } 176 | 177 | @Override 178 | public void setLong(Object o, long l) 179 | throws IllegalArgumentException, IllegalAccessException { 180 | if (setController == null || !hook.isHooked()) { 181 | accessor.setLong(o, l); 182 | } else { 183 | setController.set(parentSet, o, l); 184 | } 185 | } 186 | 187 | @Override 188 | public void setFloat(Object o, float v) 189 | throws IllegalArgumentException, IllegalAccessException { 190 | if (setController == null || !hook.isHooked()) { 191 | accessor.setFloat(o, v); 192 | } else { 193 | setController.set(parentSet, o, v); 194 | } 195 | } 196 | 197 | @Override 198 | public void setDouble(Object o, double v) 199 | throws IllegalArgumentException, IllegalAccessException { 200 | if (setController == null || !hook.isHooked()) { 201 | accessor.setDouble(o, v); 202 | } else { 203 | setController.set(parentSet, o, v); 204 | } 205 | } 206 | }; 207 | } 208 | 209 | @Override 210 | public Object newConstructorAccessor(Constructor constructor, Invoker invoker, 211 | Hook hook) { 212 | ConstructorAccessor accessor = factory.newConstructorAccessor(constructor); 213 | Invoker parent = (parent1, handle, args) -> (R) accessor.newInstance(args); 214 | return (ConstructorAccessor) args -> { 215 | try { 216 | if (!hook.isHooked()) { 217 | return parent.invoke(null, null, args); 218 | } 219 | return invoker.invoke(parent, null, args); 220 | } catch (Throwable t) { 221 | throw new InvocationTargetException(t); 222 | } 223 | }; 224 | } 225 | 226 | @Override 227 | public Class resolve(String target) { 228 | try { 229 | return Class.forName("jdk.internal.reflect." + target); 230 | } catch (ClassNotFoundException ex) { 231 | return Environment.sneakyThrow(ex); 232 | } 233 | } 234 | 235 | static { 236 | // Once again, I hate Oracle 237 | // Try to bypass Jigsaw module system 238 | Lookup lookup = Environment.LOOKUP; 239 | try { 240 | Class module = Class.forName("java.lang.Module"); 241 | Class layer = Class.forName("java.lang.ModuleLayer"); 242 | MethodHandle export = lookup 243 | .findVirtual(module, "implAddOpens", MethodType.methodType(void.class, String.class)); 244 | MethodHandle getPackages = lookup 245 | .findVirtual(module, "getPackages", MethodType.methodType(Set.class)); 246 | MethodHandle getModule = lookup 247 | .findVirtual(Class.class, "getModule", MethodType.methodType(module)); 248 | MethodHandle getLayer = lookup 249 | .findVirtual(module, "getLayer", MethodType.methodType(layer)); 250 | MethodHandle layerModules = lookup 251 | .findVirtual(layer, "modules", MethodType.methodType(Set.class)); 252 | MethodHandle unnamedModule = lookup 253 | .findVirtual(ClassLoader.class, "getUnnamedModule", MethodType.methodType(module)); 254 | Set modules = new HashSet(); 255 | 256 | Object ourModule = getModule.invoke(JavaAccessNew.class); 257 | Object ourLayer = getLayer.invoke(ourModule); 258 | if (ourLayer != null) { 259 | modules.addAll((Set) layerModules.invoke(ourLayer)); 260 | } 261 | modules.addAll( 262 | (Set) layerModules 263 | .invoke(lookup.findStatic(layer, "boot", MethodType.methodType(layer)) 264 | .invoke())); 265 | for (ClassLoader c = JavaAccessNew.class.getClassLoader(); c != null; c = c.getParent()) { 266 | modules.add(unnamedModule.invoke(c)); 267 | } 268 | 269 | for (Object impl : modules) { 270 | for (String name : (Set) getPackages.invoke(impl)) { 271 | export.invoke(impl, name); 272 | } 273 | } 274 | 275 | } catch (Throwable t) { 276 | t.printStackTrace(); 277 | } 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /reflectionhooks-core/src/main/java/me/xdark/reflectionhooks/core/JavaAccessOld.java: -------------------------------------------------------------------------------- 1 | package me.xdark.reflectionhooks.core; 2 | 3 | import java.lang.invoke.MethodType; 4 | import java.lang.reflect.Constructor; 5 | import java.lang.reflect.Field; 6 | import java.lang.reflect.InvocationTargetException; 7 | import java.lang.reflect.Method; 8 | import me.xdark.reflectionhooks.api.FieldGetController; 9 | import me.xdark.reflectionhooks.api.FieldSetController; 10 | import me.xdark.reflectionhooks.api.Hook; 11 | import me.xdark.reflectionhooks.api.Invoker; 12 | import sun.reflect.ConstructorAccessor; 13 | import sun.reflect.FieldAccessor; 14 | import sun.reflect.MethodAccessor; 15 | import sun.reflect.ReflectionFactory; 16 | 17 | @SuppressWarnings("Duplicates") 18 | final class JavaAccessOld implements JavaAccess { 19 | 20 | private final ReflectionFactory factory = ReflectionFactory.getReflectionFactory(); 21 | 22 | @Override 23 | public void init() throws Throwable { 24 | Environment.LOOKUP.findStaticSetter(Class.class, "useCaches", boolean.class) 25 | .invokeExact(true); 26 | Environment.LOOKUP 27 | .findStatic(ReflectionFactory.class, "checkInitted", MethodType.methodType(void.class)) 28 | .invokeExact(); 29 | } 30 | 31 | @Override 32 | public Object newMethodAccessor(Method method, Invoker invoker, Hook hook) { 33 | MethodAccessor accessor = factory.newMethodAccessor(method); 34 | Invoker parent = (parent1, handle, args) -> (R) accessor.invoke(handle, args); 35 | return (MethodAccessor) (handle, args) -> { 36 | try { 37 | if (!hook.isHooked()) { 38 | return parent.invoke(null, handle, args); 39 | } 40 | return invoker.invoke(parent, handle, args); 41 | } catch (Throwable t) { 42 | throw new InvocationTargetException(t); 43 | } 44 | }; 45 | } 46 | 47 | @Override 48 | public Object newFieldAccessor(Field field, FieldGetController getController, 49 | FieldSetController setController, Hook hook) { 50 | FieldAccessor accessor = factory.newFieldAccessor(field, true); 51 | FieldGetController parentGet = (parent, handle) -> accessor.get(handle); 52 | FieldSetController parentSet = (parent, handle, value) -> { 53 | try { 54 | accessor.set(handle, value); 55 | } catch (IllegalAccessException ignored) { 56 | } 57 | }; 58 | return new FieldAccessor() { 59 | @Override 60 | public Object get(Object o) throws IllegalArgumentException { 61 | return getController == null || !hook.isHooked() ? accessor.get(o) 62 | : getController.get(parentGet, o); 63 | } 64 | 65 | @Override 66 | public boolean getBoolean(Object o) throws IllegalArgumentException { 67 | return getController == null || !hook.isHooked() ? accessor.getBoolean(o) 68 | : (boolean) getController.get(parentGet, o); 69 | } 70 | 71 | @Override 72 | public byte getByte(Object o) throws IllegalArgumentException { 73 | return getController == null || !hook.isHooked() ? accessor.getByte(o) 74 | : (byte) getController.get(parentGet, o); 75 | } 76 | 77 | @Override 78 | public char getChar(Object o) throws IllegalArgumentException { 79 | return getController == null || !hook.isHooked() ? accessor.getChar(o) 80 | : (char) getController.get(parentGet, o); 81 | } 82 | 83 | @Override 84 | public short getShort(Object o) throws IllegalArgumentException { 85 | return getController == null || !hook.isHooked() ? accessor.getShort(o) 86 | : (short) getController.get(parentGet, o); 87 | } 88 | 89 | @Override 90 | public int getInt(Object o) throws IllegalArgumentException { 91 | return getController == null || !hook.isHooked() ? accessor.getInt(o) 92 | : (int) getController.get(parentGet, o); 93 | } 94 | 95 | @Override 96 | public long getLong(Object o) throws IllegalArgumentException { 97 | return getController == null || !hook.isHooked() ? accessor.getLong(o) 98 | : (long) getController.get(parentGet, o); 99 | } 100 | 101 | @Override 102 | public float getFloat(Object o) throws IllegalArgumentException { 103 | return getController == null || !hook.isHooked() ? accessor.getFloat(o) 104 | : (float) getController.get(parentGet, o); 105 | } 106 | 107 | @Override 108 | public double getDouble(Object o) throws IllegalArgumentException { 109 | return getController == null || !hook.isHooked() ? accessor.getDouble(o) 110 | : (double) getController.get(parentGet, o); 111 | } 112 | 113 | @Override 114 | public void set(Object o, Object o1) 115 | throws IllegalArgumentException, IllegalAccessException { 116 | if (setController == null || !hook.isHooked()) { 117 | accessor.set(o, o1); 118 | } else { 119 | setController.set(parentSet, o, o1); 120 | } 121 | } 122 | 123 | @Override 124 | public void setBoolean(Object o, boolean b) 125 | throws IllegalArgumentException, IllegalAccessException { 126 | if (setController == null || !hook.isHooked()) { 127 | accessor.setBoolean(o, b); 128 | } else { 129 | setController.set(parentSet, o, b); 130 | } 131 | } 132 | 133 | @Override 134 | public void setByte(Object o, byte b) 135 | throws IllegalArgumentException, IllegalAccessException { 136 | if (setController == null || !hook.isHooked()) { 137 | accessor.setByte(o, b); 138 | } else { 139 | setController.set(parentSet, o, b); 140 | } 141 | } 142 | 143 | @Override 144 | public void setChar(Object o, char c) 145 | throws IllegalArgumentException, IllegalAccessException { 146 | if (setController == null || !hook.isHooked()) { 147 | accessor.setChar(o, c); 148 | } else { 149 | setController.set(parentSet, o, c); 150 | } 151 | } 152 | 153 | @Override 154 | public void setShort(Object o, short i) 155 | throws IllegalArgumentException, IllegalAccessException { 156 | if (setController == null || !hook.isHooked()) { 157 | accessor.setShort(o, i); 158 | } else { 159 | setController.set(parentSet, o, i); 160 | } 161 | } 162 | 163 | @Override 164 | public void setInt(Object o, int i) 165 | throws IllegalArgumentException, IllegalAccessException { 166 | if (setController == null || !hook.isHooked()) { 167 | accessor.setInt(o, i); 168 | } else { 169 | setController.set(parentSet, o, i); 170 | } 171 | } 172 | 173 | @Override 174 | public void setLong(Object o, long l) 175 | throws IllegalArgumentException, IllegalAccessException { 176 | if (setController == null || !hook.isHooked()) { 177 | accessor.setLong(o, l); 178 | } else { 179 | setController.set(parentSet, o, l); 180 | } 181 | } 182 | 183 | @Override 184 | public void setFloat(Object o, float v) 185 | throws IllegalArgumentException, IllegalAccessException { 186 | if (setController == null || !hook.isHooked()) { 187 | accessor.setFloat(o, v); 188 | } else { 189 | setController.set(parentSet, o, v); 190 | } 191 | } 192 | 193 | @Override 194 | public void setDouble(Object o, double v) 195 | throws IllegalArgumentException, IllegalAccessException { 196 | if (setController == null || !hook.isHooked()) { 197 | accessor.setDouble(o, v); 198 | } else { 199 | setController.set(parentSet, o, v); 200 | } 201 | } 202 | }; 203 | } 204 | 205 | @Override 206 | public Object newConstructorAccessor(Constructor constructor, Invoker invoker, 207 | Hook hook) { 208 | ConstructorAccessor accessor = factory.newConstructorAccessor(constructor); 209 | Invoker parent = (parent1, handle, args) -> (R) accessor.newInstance(args); 210 | return (ConstructorAccessor) args -> { 211 | try { 212 | if (!hook.isHooked()) { 213 | return parent.invoke(null, null, args); 214 | } 215 | return invoker.invoke(parent, null, args); 216 | } catch (Throwable t) { 217 | throw new InvocationTargetException(t); 218 | } 219 | }; 220 | } 221 | 222 | @Override 223 | public Class resolve(String target) { 224 | try { 225 | return Class.forName("sun.reflect." + target); 226 | } catch (ClassNotFoundException ex) { 227 | return Environment.sneakyThrow(ex); 228 | } 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /reflectionhooks-core/src/main/java/me/xdark/reflectionhooks/core/JavaInvokeInjector.java: -------------------------------------------------------------------------------- 1 | package me.xdark.reflectionhooks.core; 2 | 3 | import org.objectweb.asm.ClassReader; 4 | import org.objectweb.asm.ClassWriter; 5 | import org.objectweb.asm.Opcodes; 6 | import org.objectweb.asm.tree.*; 7 | import sun.misc.Unsafe; 8 | 9 | import java.io.IOException; 10 | import java.io.InputStream; 11 | import java.lang.reflect.Field; 12 | import java.nio.file.Files; 13 | import java.nio.file.Paths; 14 | import java.nio.file.StandardOpenOption; 15 | import java.util.List; 16 | import java.util.function.Consumer; 17 | 18 | public final class JavaInvokeInjector { 19 | 20 | // A bit explanation here: 21 | // We can't use lambdas there because it will trigger java.lang.invoke classes initialization 22 | public static void inject() throws Throwable { 23 | // Let's hope that no one access java.lang.invoke.* before us. 24 | Field field = Unsafe.class.getDeclaredField("theUnsafe"); 25 | field.setAccessible(true); 26 | Unsafe unsafe = (Unsafe) field.get(null); 27 | { 28 | byte[] transformed = transform("java/lang/invoke/MethodHandles.class", 29 | new Consumer() { 30 | @Override 31 | public void accept(ClassNode cw) { 32 | cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "java/lang/invoke/MethodHandles", null, 33 | "java/lang/Object", null); 34 | } 35 | }); 36 | defineClass(unsafe, "java.lang.invoke.MethodHandles", transformed, null); 37 | } 38 | { 39 | byte[] transformed = transform("java/lang/invoke/MethodHandles$Lookup.class", 40 | new Consumer() { 41 | @Override 42 | public void accept(ClassNode cw) { 43 | // We can't clear final bit here, so we directly inject into 44 | // findVirtual, findStatic & so on 45 | injectMethod(findMethodByName(cw.methods, "findVirtual")); 46 | injectMethod(findMethodByName(cw.methods, "findStatic")); 47 | injectUnreflectMethod(findMethodByNameAndDesc(cw.methods, "unreflect", "(Ljava/lang/reflect/Method;)Ljava/lang/invoke/MethodHandle;")); 48 | } 49 | }); 50 | Files.write(Paths.get(".").resolve("Test.class"), transformed, StandardOpenOption.CREATE); 51 | defineClass(unsafe, "java.lang.invoke.MethodHandles$Lookup", transformed, null); 52 | } 53 | } 54 | 55 | private static MethodNode findMethodByName(List nodes, String name) { 56 | for (MethodNode mn : nodes) { 57 | if (name.equals(mn.name)) { 58 | return mn; 59 | } 60 | } 61 | throw new RuntimeException("MethodNode " + name + " was not found!"); 62 | } 63 | 64 | private static MethodNode findMethodByNameAndDesc(List nodes, String name, String desc) { 65 | for (MethodNode mn : nodes) { 66 | if (name.equals(mn.name) && desc.equals(mn.desc)) { 67 | return mn; 68 | } 69 | } 70 | throw new RuntimeException("MethodNode " + name + " was not found!"); 71 | } 72 | 73 | private static void injectMethod(MethodNode mn) { 74 | InsnList list = mn.instructions; 75 | AbstractInsnNode first = list.getFirst(); 76 | createRef(list, first, 1, 4); 77 | createRef(list, first, 2, 5); 78 | createRef(list, first, 3, 6); 79 | list.insertBefore(first, new VarInsnNode(Opcodes.ALOAD, 4)); 80 | list.insertBefore(first, new VarInsnNode(Opcodes.ALOAD, 5)); 81 | list.insertBefore(first, new VarInsnNode(Opcodes.ALOAD, 6)); 82 | list.insertBefore(first, new MethodInsnNode(Opcodes.INVOKESTATIC, "me/xdark/reflectionhooks/core/Environment", "onMethodHook", "(Lme/xdark/reflectionhooks/api/NonDirectReference;Lme/xdark/reflectionhooks/api/NonDirectReference;Lme/xdark/reflectionhooks/api/NonDirectReference;)V", false)); 83 | getAndSet(list, first, 4, 1); 84 | getAndSet(list, first, 5, 2); 85 | getAndSet(list, first, 6, 3); 86 | } 87 | 88 | private static void injectUnreflectMethod(MethodNode mn) { 89 | InsnList list = mn.instructions; 90 | AbstractInsnNode first = list.getFirst(); 91 | list.insertBefore(first, new VarInsnNode(Opcodes.ALOAD, 1)); 92 | list.insertBefore(first, new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/lang/reflect/Method", "getReturnType", "()Ljava/lang/Class;", false)); 93 | list.insertBefore(first, new VarInsnNode(Opcodes.ALOAD, 1)); 94 | list.insertBefore(first, new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/lang/reflect/Method", "getParameterTypes", "()[Ljava/lang/Class;", false)); 95 | list.insertBefore(first, new MethodInsnNode(Opcodes.INVOKESTATIC, "java/lang/invoke/MethodType", "methodType", "(Ljava/lang/Class;[Ljava/lang/Class;)Ljava/lang/invoke/MethodType;", false)); 96 | list.insertBefore(first, new VarInsnNode(Opcodes.ASTORE, 2)); 97 | list.insertBefore(first, new VarInsnNode(Opcodes.ALOAD, 1)); 98 | list.insertBefore(first, new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/lang/reflect/Method", "getDeclaringClass", "()Ljava/lang/Class;", false)); 99 | list.insertBefore(first, new VarInsnNode(Opcodes.ASTORE, 3)); 100 | list.insertBefore(first, new VarInsnNode(Opcodes.ALOAD, 1)); 101 | list.insertBefore(first, new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/lang/reflect/Method", "getName", "()Ljava/lang/String;", false)); 102 | list.insertBefore(first, new VarInsnNode(Opcodes.ASTORE, 4)); 103 | // TODO replace 104 | } 105 | 106 | private static void getAndSet(InsnList list, AbstractInsnNode first, int aload, int astore) { 107 | list.insertBefore(first, new VarInsnNode(Opcodes.ALOAD, aload)); 108 | list.insertBefore(first, new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "me/xdark/reflectionhooks/api/NonDirectReference", "get", "()Ljava/lang/Object;", false)); 109 | list.insertBefore(first, new VarInsnNode(Opcodes.ASTORE, astore)); 110 | } 111 | 112 | private static void createRef(InsnList list, AbstractInsnNode first, int aload, int astore) { 113 | list.insertBefore(first, new TypeInsnNode(Opcodes.NEW, "me/xdark/reflectionhooks/api/NonDirectReference")); 114 | list.insertBefore(first, new InsnNode(Opcodes.DUP)); 115 | list.insertBefore(first, new VarInsnNode(Opcodes.ALOAD, aload)); 116 | list.insertBefore(first, new MethodInsnNode(Opcodes.INVOKESPECIAL, "me/xdark/reflectionhooks/api/NonDirectReference", "", "(Ljava/lang/Object;)V", false)); 117 | list.insertBefore(first, new VarInsnNode(Opcodes.ASTORE, astore)); 118 | } 119 | 120 | private static void defineClass(Unsafe unsafe, String className, byte[] code, Class root) { 121 | if (root == null) { 122 | unsafe.defineClass(className, code, 0, code.length, null, null); 123 | } else { 124 | unsafe.defineAnonymousClass(root, code, null); 125 | } 126 | } 127 | 128 | private static byte[] transform(String resource, Consumer consumer) 129 | throws IOException { 130 | ClassReader cr; 131 | try (InputStream in = ClassLoader.getSystemResourceAsStream(resource)) { 132 | cr = new ClassReader(in); 133 | } 134 | ClassNode cn = new ClassNode(); 135 | cr.accept(cn, 0); 136 | consumer.accept(cn); 137 | ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); 138 | cn.accept(cw); 139 | return cw.toByteArray(); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /reflectionhooks-core/src/test/java/me/xdark/reflectionhooks/core/TestConstructor.java: -------------------------------------------------------------------------------- 1 | package me.xdark.reflectionhooks.core; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import java.lang.reflect.Constructor; 6 | import java.lang.reflect.InvocationTargetException; 7 | import me.xdark.reflectionhooks.api.Hook; 8 | import me.xdark.reflectionhooks.api.HooksFactory; 9 | import org.junit.jupiter.api.Test; 10 | 11 | public class TestConstructor { 12 | 13 | 14 | @Test 15 | public void testNewInstance() 16 | throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { 17 | HooksFactory factory = new DefaultHooksFactory(); 18 | Constructor constructor = Klass.class.getDeclaredConstructor(String.class); 19 | constructor.setAccessible(true); 20 | Hook hook = factory.createConstructorHook(Klass.class, constructor, (parent, handle, args) -> { 21 | args[0] = "World!"; 22 | return parent.invoke(null, null, args); 23 | }); 24 | hook.hook(); 25 | String value = constructor.newInstance("Hello").value; 26 | assertEquals(value, "World!"); 27 | hook.unhook(); 28 | value = constructor.newInstance("Hello").value; 29 | assertEquals(value, "Hello"); 30 | } 31 | 32 | private static class Klass { 33 | 34 | final String value; 35 | 36 | private Klass(String value) { 37 | this.value = value; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /reflectionhooks-core/src/test/java/me/xdark/reflectionhooks/core/TestField.java: -------------------------------------------------------------------------------- 1 | package me.xdark.reflectionhooks.core; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import java.lang.reflect.Field; 6 | import me.xdark.reflectionhooks.api.Hook; 7 | import me.xdark.reflectionhooks.api.HooksFactory; 8 | import org.junit.jupiter.api.Assertions; 9 | import org.junit.jupiter.api.Test; 10 | 11 | public class TestField { 12 | 13 | private int value = 0; 14 | 15 | @Test 16 | public void testField() throws NoSuchFieldException, IllegalAccessException { 17 | HooksFactory factory = new DefaultHooksFactory(); 18 | Field field = TestField.class.getDeclaredField("value"); 19 | Hook hook = factory.createFieldHook(field, (parent, handle) -> 13, 20 | (parent, handle, value1) -> parent.set(null, handle, 5)); 21 | hook.hook(); 22 | field.set(this, 3); 23 | assertEquals(value, 5); 24 | int get = field.getInt(this); 25 | Assertions 26 | .assertEquals(get, 13); 27 | hook.unhook(); 28 | field.set(this, 3); 29 | assertEquals(value, 3); 30 | get = field.getInt(this); 31 | assertEquals(get, 3); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /reflectionhooks-core/src/test/java/me/xdark/reflectionhooks/core/TestMethod.java: -------------------------------------------------------------------------------- 1 | package me.xdark.reflectionhooks.core; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import java.lang.reflect.InvocationTargetException; 6 | import java.lang.reflect.Method; 7 | import me.xdark.reflectionhooks.api.Hook; 8 | import me.xdark.reflectionhooks.api.HooksFactory; 9 | import org.junit.jupiter.api.Test; 10 | 11 | public class TestMethod { 12 | 13 | @Test 14 | public void testReturn() 15 | throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { 16 | HooksFactory factory = new DefaultHooksFactory(); 17 | Method method = TestMethod.class.getDeclaredMethod("someMethod"); 18 | Hook hook = factory.createMethodHook(String.class, method, (parent, handle, args) -> "World!"); 19 | hook.hook(); 20 | String returnment = (String) method.invoke(this); 21 | assertEquals(returnment, "World!"); 22 | hook.unhook(); 23 | returnment = (String) method.invoke(this); 24 | assertEquals(returnment, "Hello, "); 25 | } 26 | 27 | @Test 28 | public void testArguments() 29 | throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { 30 | HooksFactory factory = new DefaultHooksFactory(); 31 | Method method = TestMethod.class.getDeclaredMethod("self", String.class); 32 | Hook hook = factory.createMethodHook(String.class, method, (parent, handle, args) -> { 33 | args[0] = "World!"; 34 | return parent.invoke(null, handle, args); 35 | }); 36 | hook.hook(); 37 | String returnment = (String) method.invoke(this, "Hello, "); 38 | assertEquals("World!", returnment); 39 | hook.unhook(); 40 | returnment = (String) method.invoke(this, "Hello, "); 41 | assertEquals("Hello, ", returnment); 42 | } 43 | 44 | public String someMethod() { 45 | return "Hello, "; 46 | } 47 | 48 | public String self(String str) { 49 | return str; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'reflectionhooks' 2 | include 'reflectionhooks-api' 3 | include 'reflectionhooks-core' 4 | 5 | --------------------------------------------------------------------------------