├── .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 |
4 |
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 |
5 |
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 |
5 |
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 |
--------------------------------------------------------------------------------