Search code examples
javainstrumentationjavassist

Instrumenting loaded classes after modification with Javassist


I have the following Javassist code to modify a logging method to make it only log certain cases (in an attempt to detect hackers transferring large sums of money in a game, for context):

CtClass ctClass = ClassPool.getDefault().getCtClass(Trade.class.getName());
CtMethod commandMethod = ctClass.getDeclaredMethod("log");
commandMethod.setBody("if (/* conditions */) {"
                    + "    fw.write(sender + \" [\" + senderUser.getMoney() + \"/\" + senderOldBal + \"] sent \" + pay.getMoney() + \" to \" + receiver + \"[\" + receiverOldBal + \"/\" + receiverUser.getMoney() + \"]\");"
                    + "}");

I understand that simply calling ctClass.toClass() will not replace the currently loaded class, and that in order to achieve this I need to make use of the Instrumentation API in Java. However I have not been able to find much in the way of explanations of how to use the Instrumentation API.

Any advice on how to do this would be appreciated.


Solution

  • Sample javaagent project: java-agent-asm-javassist-sample (found in google, not my code).

    In order to benefit from Instrumentation API you need to build your own javaagent:

    public class Agent {
    
        public static void premain(String agentArgs, Instrumentation inst) {
            inst.addTransformer(new ClassFileTransformer() {
                @Override
                public byte[] transform(ClassLoader classLoader, String s, Class<?> aClass, ProtectionDomain protectionDomain, byte[] bytes) throws IllegalClassFormatException {
                    if ("your/package/Trade".equals(s)) {
                        try {
                            ClassPool cp = ClassPool.getDefault();
                            CtClass ctClass = cp.get("your.package.Trade");
                            CtMethod commandMethod = ctClass.getDeclaredMethod("log");
                            commandMethod.setBody("if (/* conditions */) {"
                                + "    fw.write(sender + \" [\" + senderUser.getMoney() + \"/\" + senderOldBal + \"] sent \" + pay.getMoney() + \" to \" + receiver + \"[\" + receiverOldBal + \"/\" + receiverUser.getMoney() + \"]\");"
                                + "}");
                            byte[] byteCode = ctClass.toBytecode();
                            ctClass.detach();
                            return byteCode;
                        } catch (Exception ex) {
                            ex.printStackTrace();
                        }
                    }
                    return null;
                }
            });
        }
    
    }
    

    Compile it and package into agent.jar with 'Premain-Class' in manifest.

    Pass your javagent via JVM argument: java -javaagent:some/path/agent.jar -jar your-main-app.jar