A very powerful hack - use with caution!
Here's a very quick tutorial on how you can use Byteman to change a running application on the fly. You can insert arbitrary code into a running application server. Caveat CodeMonkeytor!
Let's say we have a JEE 6 application that's misbehaving. You can easily attach Byteman to the app server, then inject the code you want-- without bringing down the server!
I like to use 2 scripts with Byteman, one to attach to the server (since this should be done only once), and one to check and install the Byteman rules I want to run. This way, you can incrementally add to your Byteman rules.
There's an example of a misbehaving Servlet at the bottom of this post. It's bad code. Let's pick on a single fault. The user is asked to provide two numbers, the servlet is supposed to divide them and provide the result. But if the user enters a zero for the second argument, a divide by zero exception results! Please install the servlet and try for yourself.
Making JEE apps is trivial now that JEE 6 is here. I'd recommend using JBoss AS 7, and building with Maven. (I used to hate Maven, but since I've started using it, I hate it a little less. Still, it seems better than Ant in easy cases.) So please find the Maven pom.xml at the bottom of this post, with the Servlet code.
But this is a post about Byteman, so I'm going to put that code first. Here are the scripts:
# InstallByteman.sh - run this only once, after JBoss is already running. It doesn't matter if JBoss has been running 7 months or 7 seconds.
# There should be 4 lines to this script-- in case your browser line-wraps.....
#!/bin/bash
export JBOSS_HOME=/home/rick/Tools/JBoss/AS7/jboss-as-7.1.0.Beta1b
export BYTEMAN_HOME=/home/rick/Tools/Byteman/byteman-2.0
export BYTEMAN_BIN=$BYTEMAN_HOME/bin
$BYTEMAN_BIN/bminstall.sh -b -Dorg.jboss.byteman.transform.all $JBOSS_HOME/jboss-modules.jar
# InstallRules.sh - This script validates and installs your Rules. You can run this as many times as you need, to incrementally build your rules up.
# There should be 10 lines to this script-- in case your browser line-wraps.....
#!/bin/bash
export JBOSS_HOME=/home/rick/Tools/JBoss/AS7/jboss-as-7.1.0.Beta1b
export BYTEMAN_HOME=/home/rick/Tools/Byteman/byteman-2.0
export BYTEMAN_BIN=$BYTEMAN_HOME/bin
# Export this so Byteman can validate your classes. This is wherever you have compiled your classes to.
export APP_TGT=/home/rick/Blog_Temp/Byteman2/simpleappservlet30/target/classes
# check it
$BYTEMAN_BIN/bmcheck.sh -cp $APP_TGT/HackFix.btm
# add the rule
$BYTEMAN_BIN/bmsubmit.sh HackFix.btm
So we can see in the above Install script that we have a Byteman rule named 'HackFix.btm'. Here it is.
(BTW, what this script is doing is changing the second argument passed to the method 'makeDivision' into a 1 if it is a zero. To prevent divide-by-zero...)
RULE Hack Fix for Divide Servlet
CLASS com.flyingdog.SimpleServlet
METHOD makeDivision
AT ENTRY
IF 0 == $2
DO $2 = 1;
ENDRULE
--------------------------------------------------------------------------------------------
So that's all the Byteman stuff above. You can use those samples to attach to a running application server (ANY application server, not just JBoss) and inject whatever arbitrary code you want. Cool (and dangerous), huh?
Ok, if you're like me, you'd love a quick way to try that out. So here's the pom.xml for the SimpleServlet...
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.flyingdog</groupId>
<artifactId>notsogoodapp</artifactId>
<packaging>war</packaging>
<version>1.0</version>
<name>notsogoodapp</name>
<url>http://maven.apache.org</url>
<build>
<finalName>notsogoodapp</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.1-beta-1</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>java.net</id>
<url>http://download.java.net/maven/2</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<version>6.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
Now the bad Servlet.. Install it, have it divide a few numbers. Especially give it a zero to divide by, and notice the ugly blowup! Then use the above Byteman script to 'fix' the problem.
package com.flyingdog;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.StringBuffer;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet(urlPatterns = {"/simpleservlet", "*.foo"})
public class SimpleServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request,
HttpServletResponse response) {
doGet(request, response);
}
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response) {
try {
response.setContentType("text/html");
PrintWriter printWriter = response.getWriter();
printWriter.println("<h2>");
printWriter.println("</h2>");
// if there is a value there, try to provide an answer
if (null != request.getParameter("firstValue")){
printWriter.println(makeAnswer(request));
}
// print the form that requests numbers
printWriter.println(makeForm());
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
private String makeAnswer(HttpServletRequest request){
String first = request.getParameter("firstValue");
String second = request.getParameter("secondValue");
int iFirst = Integer.parseInt(first);
int iSecond = Integer.parseInt(second);
int answer = makeDivision(iFirst, iSecond);
String response = first + " divided by " + second + " equals " + answer;
return response;
}
private int makeDivision(int first, int second){
return first / second;
}
private String makeForm(){
StringBuffer sb = new StringBuffer();
sb.append("<form method=\"post\" action=\"simpleservlet\">\n");
sb.append("<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\">\n");
sb.append(" <tr>\n");
sb.append(" <td>Please enter two numbers to divide:</td>\n");
sb.append(" <td><input type=\"text\" name=\"firstValue\" /></td>\n");
sb.append(" <td><input type=\"text\" name=\"secondValue\" /></td>\n");
sb.append(" </tr>\n");
sb.append(" <tr>\n");
sb.append(" <td></td>\n");
sb.append(" <td><input type=\"submit\" value=\"Submit\"></td>\n");
sb.append(" </tr>\n");
sb.append("</table>\n");
sb.append("</form>\n");
return sb.toString();
}
}
So there you have it. To recap:
- Use the pom.xml and the Servlet code to make the .war
- Deploy the .war to a JEE 6 app server, like JBoss AS 7.
- Access the servlet at http://localhost:8080/notsogoodapp/simpleservlet
- Observe how it divides two numbers. Let it try to divide by zero, see blowup.
- Run the Byteman scripts, see how blowup stops.
Happy Hacking!
3 comments:
This is one of the powerful and good scripting .I like your blog effort.Thanks for share with us.
Thank you very much for posting and sharing this great article. It is so interesting. I want to know some other information about this site. So please give me this news quickly. I always will be aware of you.
I'm a bankruptcy attorney in Ogden Utah and I like your blog.
Post a Comment