A Java Agent Example (-javaagent)

Since Java 5 developers have the possibility to define so called pre-main hooks to manipulate the execution of a Java program at runtime with Java agents. An agent as part of the classpath is triggered before execution of the main method and therefor can be used to either filter calls to or even manipulate the underlying Java code. A tool for code manipulation is javassists. Apache Ranger for example is using both java agents and javassits to override the authorization mechanism of components of the Hadoop stack. This together with Ranger Stacks could also be used to secure existing code unchanged during runtime.

In this post we are going to look a very basic example of using Java agents to manipulate existing code.

The Name Checker

For this example a simple name checker is manipulated to throw an exception when “Dr.No” is entered. The name checker itself does not distinguish between any names. Just the injection of NameCheckerAgent changes the behavior of an existing jar as demonstrated here:

$ java -cp target/JavaAgentTest-1.0-SNAPSHOT.jar 
  javaagenttest.CheckName Dr.No
AgentTestMain.check allowed: Dr.No

$ java -cp target/JavaAgentTest-1.0-SNAPSHOT.jar 
  -javaagent:target/JavaAgentTest-1.0-SNAPSHOT.jar=testagent 
  javaagenttest.CheckName Dr.No
NameCheckerAgent.check : Dr.No
Exception in thread "main" java.lang.Exception: Wrong Name! Dr.No
  at javaagenttest.NameCheckerAgent.check(NameCheckerAgent.java:27)
  at javaagenttest.NameChecker.check(NameChecker.java)
  at javaagenttest.NameChecker.<init>(NameChecker.java:15)
  at javaagenttest.CheckName.main(CheckName.java:15)

The CheckName application has two basic classes responsible for taking the name as an argument. The pseudo NameChecker simply returns true. Only the NameCheckerAgent is responsible for throwing an exception when the given argument is “Dr.No”.

public class CheckName {  
  public static void main(String ... args){
    NameChecker app = new NameChecker(args[0]);
  }
}

public class NameChecker {
  public NameChecker(String name) {
    this.check(name);
  }

  public boolean check(String name) {
    System.out.println("AgentTestMain.check allowed: "+ name);
    return true;
  }   
}

NameCheckerAgent

The NameCheckerAgent.class is the point of entry for the the pre-main hook which is executed before the main method. In the MANIFEST.MF the class with the pre-main method is set by applying the Premain-Class parameter.

Premain-Class: javaagenttest.NameCheckerAgent

The pre-main method checks for given arguments being equal to the defined initialization parameter. If both are equal the Instrumentation API is being used to manipulate the existing code base. A so called Transformer, in this case the CheckNameTransformer is being injected. An agent provides an implementation of this interface in order to transform class files. The transformation occurs before the class is defined by the JVM.

When there are multiple transformers, transformations are composed by chaining the transform calls. That is, the byte array returned by one call to transform becomes the input (via the classfileBuffer parameter) to the next call.

public static final String AUTHORIZATION_AGENT_PARAM = "testagent";

public static void premain(String agentArgs, Instrumentation inst) {
  if (agentArgs != null && AUTHORIZATION_AGENT_PARAM.equalsIgnoreCase(agentArgs.trim())) {
    inst.addTransformer(new CheckNameTransformer());
  }
}

The CheckNameTransformer is an implementation of the ClassFileTransformer interface, where class file is being used as defined in the Java Virtual Machine Specification to mean a sequence of bytes in class file format, whether or not they reside in a file. The transform method is called during processing, before the class file bytes have been verified or applied.

With the help of javassits the check method of NameChecker is altered in that way, that the call of the check method of the NameCheckerAgent is executed before it. Besides the insertBefore there is insertAt to change the code at a specific line of the method or insertAfter to define a function being executed after the check method.

public class CheckNameTransformer implements ClassFileTransformer {
  byte[] transformedClassByteCode = null;
  
  @Override
  public byte[] transform(ClassLoader classLoader, String className,
      Class<?> classBeingRedefined,
      ProtectionDomain protectionDomain,
      byte[] classFileBuffer) throws IllegalClassFormatException {
    
    byte[] byteCode = classFileBuffer;
    
    if (className.equals("javaagenttest/NameChecker")) {
      try {
        ClassPool cp = ClassPool.getDefault();
        String curClassName = className.replaceAll("/", ".");

        CtClass curClass = cp.get(curClassName);
        CtMethod checkMethod = null ;
        CtClass strClass = cp.get(String.class.getCanonicalName()) ;
        CtClass[] paramArgs  = new CtClass[] { strClass } ;
                
        checkMethod = curClass.getDeclaredMethod("check", paramArgs);
        checkMethod.insertBefore("{if (javaagenttest.NameCheckerAgent.check($1) ){ /*System.out.println("Transformer");*/ return false; } }");
        
        return curClass.toBytecode();
      } catch (NotFoundException | CannotCompileException | IOException ex) {
        Logger.getLogger(CheckNameTransformer.class.getName()).log(Level.SEVERE, null, ex);
      }
    }
    return byteCode;
  }
}

In the NameCheckerAgent we check for Dr.No and throw an exception when the argument given matches.

public class NameCheckerAgent {
  public static final String AUTHORIZATION_AGENT_PARAM = "testagent";
  
  public static void premain(String agentArgs, Instrumentation inst) {
    if (agentArgs != null && 
        AUTHORIZATION_AGENT_PARAM.equalsIgnoreCase(agentArgs.trim())) {
      inst.addTransformer(new CheckNameTransformer());
    }
  }

  static public boolean check(String name) throws Exception {
    System.out.println("Agent.check : "+ name);
    if(name.equals("Dr.No")) throw new Exception("Wrong Name! "+ name);
    return true;
  }
}

Further Readings

One thought on “A Java Agent Example (-javaagent)

Leave a comment