Monday, December 13, 2010

Use agents to screw with non-protected JVMs

The title is mostly for shock value, but it is true that you can do some pretty awesome stuff to any un-protected JVM.

I ran across this little piece of information as I was reading the source code for JConsole to see how it managed to monitor any JVM running on your box (assuming you have user permissions).  It's an extension of the java.lang.instrument agent functionality added in Java 1.5.  NOTE: This example is specifically for Java 6 and later.

So in Java 6, the Attach API was introduced, and it provided a new way to add agents to JVM: after the JVM already started running.

So let's start by creating a simple little app:
public class SomeThreadedApp {
public static void main(String[] args) {
new Thread(new BoringRunner(), "bRunner1").start();
new Thread(new BoringRunner(), "bRunner2").start();
new Thread(new BoringRunner(), "bRunner3").start();
}
static class BoringRunner implements Runnable {
public void run() {
int count = 1;
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException ie) {}
System.out.println("Thread named " + Thread.currentThread().getName()
+ " and count is: " + count);
count++;
}
}
}
}

And let's start this app with: java SomeThreadedApp  No special arguments, just typical start.

Now, the JVM is running.  But now we want to modify the JVM in flight: I wanted to kill some threads in the running JVM.  With the agent Attach API, we can inject any code we want into a running JVM that isn't protected by a SecurityManager:

import java.lang.instrument.Instrumentation;
public class ThreadAssassin {
public static void agentmain(String agentArgs, Instrumentation inst) {
ThreadGroup root = Thread.currentThread().getThreadGroup();
while (root.getParent() != null) {
root = root.getParent();
}
Thread[] allThreads = new Thread[root.activeCount()];
root.enumerate(allThreads);
System.out.println("The assassin takes aim!");
for (Thread t : allThreads) {
if (t.getName().startsWith("bRunner")) {
System.out.println("Thread named: " + t.getName() + " is gonna die!");
t.stop(); // I know this is deprecated and bad; just for effect.
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {}
}
}
}

The agent code needs to be in a JAR with a MANIFEST.MF entry for "Agent-Class" set to the class that defines the public static void agentmain method.  More info can be found on the Oracle Java Instrumentation page.

And the last step to actually get the agent code into the target JVM, so the agent code actually run on the target JVM, I use a little JRuby:

# JRuby
require 'java'
VirtualMachine = com.sun.tools.attach.VirtualMachine
vm = VirtualMachine.attach("4567") # target JVM process ID
vm.loadAgent("/tmp/ThreadAssassin.jar") # path to JAR with Agent code.
vm.detach


No comments:

Post a Comment