Page 3 of 6
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.
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.
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.
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.
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.
Archived Discussions (Read only)