├── .idea ├── .name ├── codeStyles │ ├── codeStyleConfig.xml │ └── Project.xml ├── vcs.xml ├── .gitignore ├── encodings.xml ├── misc.xml └── uiDesigner.xml ├── .gitignore ├── src ├── main │ └── java │ │ └── com │ │ └── debug │ │ ├── Agent.java │ │ ├── Config.java │ │ └── HookTransformer.java └── test │ └── java │ └── com │ └── debug │ └── AppTest.java ├── README.md └── pom.xml /.idea/.name: -------------------------------------------------------------------------------- 1 | DemoAgent -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | !**/src/main/**/target/ 4 | !**/src/test/**/target/ 5 | 6 | ### IntelliJ IDEA ### 7 | .idea/modules.xml 8 | .idea/jarRepositories.xml 9 | .idea/compiler.xml 10 | .idea/libraries/ 11 | *.iws 12 | *.iml 13 | *.ipr 14 | 15 | ### Eclipse ### 16 | .apt_generated 17 | .classpath 18 | .factorypath 19 | .project 20 | .settings 21 | .springBeans 22 | .sts4-cache 23 | 24 | ### NetBeans ### 25 | /nbproject/private/ 26 | /nbbuild/ 27 | /dist/ 28 | /nbdist/ 29 | /.nb-gradle/ 30 | build/ 31 | !**/src/main/**/build/ 32 | !**/src/test/**/build/ 33 | 34 | ### VS Code ### 35 | .vscode/ 36 | 37 | ### Mac OS ### 38 | .DS_Store -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/main/java/com/debug/Agent.java: -------------------------------------------------------------------------------- 1 | package com.debug; 2 | 3 | import com.sun.source.doctree.SystemPropertyTree; 4 | 5 | import java.lang.instrument.Instrumentation; 6 | 7 | public class Agent { 8 | 9 | public static void log(String fmt, Object ...args) { 10 | System.out.printf("[agent] " + fmt + "\n", args); 11 | } 12 | 13 | public static void premain(String agentArgs, Instrumentation inst) { 14 | Agent.log("enter premain, agentArgs=%s", agentArgs); 15 | if (!inst.isRetransformClassesSupported()) { 16 | Agent.log("Class retransformation is not supported."); 17 | return; 18 | } 19 | 20 | Config conf = new Config(agentArgs); 21 | HookTransformer.replace(inst, conf.className, conf.methodName, conf.methodImpl); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HookAgent 2 | 3 | A simple Java Agent template with javassist support. 4 | 5 | Command line example: 6 | ```shell 7 | # pass hook arguments via agentArgs. 8 | java -Xmx16G --add-opens java.base/java.lang=ALL-UNNAMED -Xverify:none \ 9 | -javaagent:HookAgent.jar=className=com.example.Foo;methodName=bar;methodImplFile=hook.js \ 10 | target.jar 11 | # or pass hook arguments via Properties. 12 | java -Xmx16G --add-opens java.base/java.lang=ALL-UNNAMED -Xverify:none \ 13 | -javaagent:HookAgent.jar \ 14 | -Dhook.className=com.example.Foo \ 15 | -Dhook.methodName=bar \ 16 | -Dhook.methodImplFile=hook.js \ 17 | target.jar 18 | ``` 19 | 20 | The `hook.js` example: 21 | ```js 22 | { 23 | System.out.println("[agent-hook] skip check: " + new java.util.Date()); 24 | return 0; 25 | } 26 | ``` -------------------------------------------------------------------------------- /src/test/java/com/debug/AppTest.java: -------------------------------------------------------------------------------- 1 | package com.debug; 2 | 3 | import junit.framework.Test; 4 | import junit.framework.TestCase; 5 | import junit.framework.TestSuite; 6 | 7 | /** 8 | * Unit test for simple App. 9 | */ 10 | public class AppTest 11 | extends TestCase 12 | { 13 | /** 14 | * Create the test case 15 | * 16 | * @param testName name of the test case 17 | */ 18 | public AppTest( String testName ) 19 | { 20 | super( testName ); 21 | } 22 | 23 | /** 24 | * @return the suite of tests being tested 25 | */ 26 | public static Test suite() 27 | { 28 | return new TestSuite( AppTest.class ); 29 | } 30 | 31 | /** 32 | * Rigourous Test :-) 33 | */ 34 | public void testApp() 35 | { 36 | assertTrue( true ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/debug/Config.java: -------------------------------------------------------------------------------- 1 | package com.debug; 2 | 3 | import java.io.IOException; 4 | import java.nio.file.Files; 5 | import java.nio.file.Path; 6 | 7 | public class Config { 8 | 9 | public String className = ""; 10 | public String methodName = ""; 11 | public String methodImpl = ""; 12 | public String methodImplFile = ""; 13 | 14 | Config(String agentArgs) throws RuntimeException { 15 | if (agentArgs != null) { 16 | Agent.log("Parsing hook options from agentArgs: %s", agentArgs); 17 | for (String arg: agentArgs.split(";")) { 18 | String[] parts = arg.split("="); 19 | switch (parts[0]) { 20 | case "className": 21 | this.className = parts[1]; 22 | break; 23 | case "methodName": 24 | this.methodName = parts[1]; 25 | break; 26 | case "methodImpl": 27 | this.methodImpl = parts[1]; 28 | break; 29 | case "methodImplFile": 30 | this.methodImplFile = parts[1]; 31 | break; 32 | default: 33 | break; 34 | } 35 | } 36 | } else { 37 | Agent.log("Parsing hook options from properties."); 38 | this.className = System.getProperty("hook.className", ""); 39 | this.methodName = System.getProperty("hook.methodName", ""); 40 | this.methodImpl = System.getProperty("hook.methodImpl", ""); 41 | this.methodImplFile = System.getProperty("hook.methodImplFile", ""); 42 | } 43 | 44 | if (isEmpty(methodImpl) && !isEmpty(methodImplFile)) { 45 | try { 46 | Agent.log("Reading methodImpl from %s", methodImplFile); 47 | this.methodImpl = Files.readString(Path.of(this.methodImplFile)); 48 | } catch (IOException e) { 49 | Agent.log("Error reading %s: %s", this.methodImplFile, e); 50 | } 51 | } 52 | Agent.log("Loaded %s->%s: %s", this.className, this.methodName, this.methodImpl); 53 | if (isEmpty(className) || isEmpty(methodName) || isEmpty(methodImpl)) { 54 | throw new RuntimeException("Invalid arguments"); 55 | } 56 | } 57 | 58 | private static boolean isEmpty(String str) { 59 | return str == null || str.isEmpty(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com.debug 6 | HookAgent 7 | 1.0-SNAPSHOT 8 | jar 9 | 10 | HookAgent 11 | http://maven.apache.org 12 | 13 | 14 | UTF-8 15 | 17 16 | 17 17 | 18 | 19 | 20 | 21 | junit 22 | junit 23 | 3.8.1 24 | test 25 | 26 | 27 | 28 | org.javassist 29 | javassist 30 | 3.30.2-GA 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | org.apache.maven.plugins 39 | maven-jar-plugin 40 | 41 | 42 | 43 | 44 | true 45 | 46 | 47 | com.debug.Agent 48 | true 49 | true 50 | 51 | 52 | 53 | 54 | 55 | 56 | org.apache.maven.plugins 57 | maven-assembly-plugin 58 | 59 | 60 | jar-with-dependencies 61 | 62 | 63 | 64 | true 65 | 66 | 67 | com.debug.Agent 68 | true 69 | true 70 | 71 | 72 | 73 | 74 | 75 | make-assembly 76 | package 77 | 78 | single 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /src/main/java/com/debug/HookTransformer.java: -------------------------------------------------------------------------------- 1 | package com.debug; 2 | 3 | import javassist.*; 4 | 5 | import java.lang.instrument.ClassFileTransformer; 6 | import java.lang.instrument.Instrumentation; 7 | import java.lang.reflect.Method; 8 | import java.security.ProtectionDomain; 9 | 10 | public class HookTransformer implements ClassFileTransformer { 11 | 12 | private final CtClass ctClass; 13 | private final CtMethod ctMethod; 14 | private final byte[] byteCode; 15 | 16 | private final String qualifiedClassName; 17 | 18 | public static void replace(Instrumentation inst, String className, String methodName, String implementation) { 19 | try { 20 | ClassPool cp = ClassPool.getDefault(); 21 | // cp.insertClassPath(new LoaderClassPath(loader)); 22 | CtClass cc = cp.get(className); 23 | CtMethod cm = cc.getDeclaredMethod(methodName); 24 | Agent.log("CtMethod: %s", cm); 25 | // cm.insertBefore("System.out.println(\"[Hook] skip check.\"); return 0;"); 26 | cm.setBody(implementation); 27 | byte[] byteCode = cc.toBytecode(); 28 | Agent.log("byteCode size: %d", byteCode.length); 29 | cc.detach(); 30 | 31 | HookTransformer ht = new HookTransformer(cc, cm, byteCode); 32 | inst.addTransformer(ht, true); 33 | inst.retransformClasses(cc.getClass()); 34 | Agent.log("Hooked: %s->%s", cc.getName(), cm.getName()); 35 | } catch (Exception e) { 36 | Agent.log("Failed to hook %s->%s: %s", className, methodName, e); 37 | } 38 | } 39 | 40 | public static void preCheck(String className, String methodName, Instrumentation instrumentation) { 41 | Class targetClass = null; 42 | Method targetMethod = null; 43 | try { 44 | targetClass = Class.forName(className); 45 | } catch (Exception ex) { 46 | Agent.log("Class not found with Class.forName: %s", className); 47 | } 48 | if (targetClass == null) { 49 | for (Class clazz : instrumentation.getAllLoadedClasses()) { 50 | if (clazz.getName().equals(className)) { 51 | targetClass = clazz; 52 | break; 53 | } 54 | } 55 | } 56 | 57 | if (targetClass == null) { 58 | throw new RuntimeException("Failed to find class [" + className + "]"); 59 | } 60 | for (Method method : targetClass.getDeclaredMethods()) { 61 | if (method.getName().equals(methodName)) { 62 | targetMethod = method; 63 | break; 64 | } 65 | } 66 | if (targetMethod == null) { 67 | throw new RuntimeException("Failed to find Method: [" + methodName + "]"); 68 | } 69 | } 70 | 71 | HookTransformer(CtClass ctClass, CtMethod ctMethod, byte[] byteCode) { 72 | this.ctClass = ctClass; 73 | this.ctMethod = ctMethod; 74 | this.byteCode = byteCode; 75 | this.qualifiedClassName = ctClass.getName().replace(".", "/"); 76 | } 77 | 78 | @Override 79 | public byte[] transform(ClassLoader loader, String className, 80 | Class classBeingRedefined, ProtectionDomain protectionDomain, 81 | byte[] classfileBuffer) { 82 | if (!this.qualifiedClassName.equals(className)) { 83 | return classfileBuffer; 84 | } 85 | Agent.log("transform: %s->%s", ctClass.getName(), ctMethod.getName()); 86 | return byteCode; 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /.idea/uiDesigner.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | --------------------------------------------------------------------------------