Newsletter sign-up
View all newsletters

Sign up for our technology specific newsletters.

Enterprise Java
Email Address:

Hacking Java libraries

Learn how to replace or patch application classes

  • Digg
  • Reddit
  • SlashDot
  • Stumble
  • del.icio.us
  • Technorati
  • dzone

Page 3 of 6

Working with obfuscated code

A worse scenario is when you have to deal with the obfuscated code. A good obfuscator renames packages, classes, methods, and variables. The best products on the market even encode Java strings, so searching for a trace message can yield no results. This turns your task into a hellish toil of understanding the application piece by piece. Here you have to use a more creative approach; otherwise, it is like trying to find a needle in a haystack. Knowing the principles of obfuscation can help you in the navigation. Although the obfuscator has the freedom to change the application class and method names, it cannot do so for system classes.

For example, if a library checks for the presence of a file and throws an exception if the file is not there, doing a binary search on the exception string might yield no results if the obfuscator was smart enough to encode it. However, doing a search on File or FileInputStream can lead you to the related code. Similarly, if the application incorrectly reads the system date or time, you can search for the java.util.Date or getTime() method of the Calendar class. The biggest problem is that obfuscated classes cannot always be recompiled after decompilation.

A sample scenario that requires patching

We are going to modify the Chat application to show the username and hostname instead of just the hostname in the conversation window. The original application displays the hostname followed by a colon for each message that is received, as shown in Figure 1.

Figure 1. The main window of the original chat

This makes the implementation of the utility easy, but users will certainly prefer to see from which person they are getting messages, rather than which computer is used to send the messages. Chat is free and open for enhancements, but no source code exists for it.

As is common with Java applications, the bytecode is shipped in one or several jar files, so the first task is to create a working directory and unjar all the libraries into it. This allows easy navigation and direct access to the .class files, which are the target of our research. After creating a working directory executing jar xf chat.jar, we see the following files:

images
AboutDialog.class
ChatApplication.class
ChatServer.class
ChatServerRemote.class
MainFrame.class
MainFrame.class
MessageInfo.class
MessageListener.class


Let's try all the approaches of locating the starting point presented earlier and see which one works best for this application.

Using the class name

Luckily, the bytecode is not obfuscated, so we can look at the class names and see whether we can pick the winner. A 5-second examination should lead to the conclusion that MainFrame is the best candidate for a first look. Browsing through the decompiled code, we see that all recording of the conversation is done via the appendMessage() method that looks like this:

    void appendMessage(String message, MessageInfo messageInfo) {
        if (messageInfo == null) {
            this.conversation.append("<font color=\"red\">");
            this.conversation.append("You");
        }
        else {
            this.conversation.append("<font color=\"blue\">");
            this.conversation.append(messageInfo.getDisplayName());
        }
        this.conversation.append(": ");
        this.conversation.append("</font>");
        this.conversation.append(message);
        this.conversation.append("<br>");
        this.txtConversation.setText(this.conversation.toString() +
                          "</BODY></HTML>");
    } 


The implementation of the method uses the getDisplayName() method of the MessageInfo class to obtain the name of the sender. This leads us to decompiling the MessageInfo class to obtain the implementation of getDisplayName(), shown here:

    public String getDisplayName() {
        return getHostName();
    } 


Bingo! We have found out that the Chat user interface relies on MessageInfo and that the current implementation uses just the hostname. Our task is therefore to patch MessageInfo.getDisplayName() to use both the hostname and username.

Searching for text strings

Let's pretend that Chat is a large application with more than 500 classes in many different packages. Hoping to guess the right class based on its name is like hoping your code will run correctly after the first compile. You need to use a more reliable method to obtain a starting point. The Chat utility writes pretty decent log messages, so let's try to use it. After starting it, we send a message to another user, get a reply, and get the following output on the Java console:

Initializing the chat server... 
Trying to get the registry on port 1149 
Registry was not running, trying to create one... 
ChatApplication server initialized 
Sending message to host JAMAICA: test 
Received message from host JAMAICA


It is not hard to guess that appending a new message to the conversation history occurs when a message is sent or received. It is also fairly obvious that information such as the host that sent, or was a destination for, a message would not be a part of a static string. Therefore, we will use Received message from host as a search criteria for all the .class files in the working directory. The search produces one file, ChatServer.class, which we promptly decompile to get ChatServer.jad. Searching for the string inside the decompiled source code leads us to the receiveMessage() method, which is as follows:

public void receiveMessage(String message, MessageInfo messageInfo)
    throws RemoteException
{
    System.out.println("Received message from host " + messageInfo.getHostName());
    if(messageListener != null)
        messageListener.messageReceived(message, messageInfo);
}


Searching ChatServer.jad for messageListener, we get to know that it is an interface, and a method called setMessageListener() sets the listener instance. Now we have two options: One is to find the classes that implement MessageListener and see which one (if several exist) is associated with ChatServer. Another approach is based on the fact that Java method names are stored as text inside the bytecode. Because the code is not obfuscated, we can search for setMessageListener() in all the classfiles. We will use the latter method and run the search. In our case, it returns two classes, ChatServer and MainFrame. We conclude that only MainFrame acts as a listener on ChatServer and proceed to decompile it.

The rest of the investigation is performed exactly as in the previous section, where we used the class name to find MainFrame. In our sample application, guessing the starting point from the class name proved to be faster, but a certain factor of luck is involved. Using log messages is a more reliable approach that works better for most real-life applications.

Using the call stack to navigate application logic

Many problems in Java manifest themselves through exceptions. Exceptions can be thrown by Java runtime classes or by the application itself, and of course the error message provided by the exception together with the exception type is usually sufficient to solve the problem. But the reason this book exists is because not all things are simple in life. You can get a NullPointerException or an exception with no error message, and if you are dealing with third-party code, you will have no clue as to how to work around it. As long as the license does not prevent you from decompiling the source code, or if you have the source code itself, you can embark on a search using a much less painful method.

  • Digg
  • Reddit
  • SlashDot
  • Stumble
  • del.icio.us
  • Technorati
  • dzone
Comment
Login
Forgot your account info?
Add comment
Anonymous comments subject to approval. Register here for member benefits.
Have a JavaWorld account? Log in here. Register now for a free account.
Resources