Reverse Engineering and Debugging
Reverse engineering is a
critical skill for almost every penetration tester and security
engineer. Through reverse engineering, you can look at the hidden
secrets of the platform and applications to find out how they really
work and where the security flaws are. The goal is not to understand
every instruction, but rather to understand what data the application is
consuming and producing. After all, almost all security vulnerabilities
result from errors when producing and consuming data.
Just as we benefit from
being able to use the same development toolchain for JME, we benefit by
being able to use the same debugging and reverse-engineering toolchain.
Because JME code is Java, almost all of the standard Java security tools
work, and there are even a couple of JME-specific tools that make
reverse engineering and debugging easier.
Disassembly and Decompilation
Java application classes
are compiled into Java “.class” files. These files contain Java
bytecode, which is a machine-independent set of instructions for the
Java virtual machine to execute. Machine-independent bytecode is what
allows Java to run everywhere from ATMs to vending machines. When
developers want to run Java on a new hardware platform or operating
system, they create a new version of the Java virtual machine for that
platform. If this virtual machine properly conforms to the Java
specification, it will be able to execute Java bytecode without
problems. For security reasons, Java mandates that bytecode be easily
inspected at runtime. To support this, Java application bytecode must
follow some conventions.
Disassembling x86 assembly code
is a very painful process. Java instructions (a.k.a. opcodes) are each
one byte in size. Depending on the opcode, there may or may not be
instruction operands. Whether or not operands exist doesn’t matter.
Every instruction will always be a predictable size. This quality is
very important during runtime code inspection because it allows the
virtual machine to easily traverse the application in a predictable
manner. Additionally, instructions are not allowed to jump to invalid
memory locations, use uninitialized data, or access private methods and
data (see http://en.wikipedia.org/wiki/Java_virtual_machine). Contrast this to x86 programs, where these methods are often used to hide an application’s true behavior.
Just
like the runtime does, reverse engineers can take advantage of the
consistent size and variable rules to convert the program from its
compiled form back into instructions—a process known as disassembly.
Java is easy to disassemble—that is, it can be “decompiled” and turned
back into Java source code. The results aren’t always pretty, but it is
much easier to read messy Java source code than to read Java virtual
machine opcodes.
Decompiling Java Applications
There are many Java
decompilers to choose from. This author prefers the classic Java
application decompiler (Jad). Jad was written in 2001 by Pavel
Kouznetsov. It has not been changed since then, but still works
remarkably well. To install Jad, download your desktop operating
system’s version from the distribution source (http://www.varaneckas.com/jad) and decompress it to a folder.
To decompile code, follow these steps:
1. | JME
applications come packaged as Java archive files (JAR). Within a JAR
file can be lots of different file types, but we are interested in the
ones that contain code. These are the “.class” files. To extract them
from the JAR, simply change the JAR file’s extension to .zip and use
your favorite extraction utility to extract the “.class” files. For this
exercise, extract the files to the root folder \re_app\code. Keep the
directory structure intact when you extract the files.
|
2. | Create the folder \re_app\src. This is where the decompiled source files will go.
|
3. | Run the following command from the \re_app\ directory:
jad -o -r -sjava -dsrc code/**/*.class
|
4. | Jad
will decompile the application and generate the resultant Java files in
the \re_app\src directory. The -r option is for recursive directory
travel, and explicit output path (code/**/*.class) instructs Jad to
reconstruct the packages and directories as they appeared in the input.
|
If you want to make a
modification to the decompiled code and then recompile the application,
you may be able to do so by loading the project into NetBeans. This
doesn’t always work because the decompilation process is imperfect. For
certain applications, some decompilers work better than others. This
author has had limited success with JD-GUI (http://java.decompiler.free.fr/) and the DJ Java Decompiler (http://www.neshkov.com/dj.html).
Obfuscation
A
small hurdle for reverse engineers is that many Java applications are
intentionally made confusing through obfuscation. This process
intentionally changes instruction paths and removes symbolic
information, such as class and method names, from the compiled
application. For example, an obfuscator renames the class
com.isecpartners.test.MyApplication as a.a.a.C. This is not always done
to confuse reverse engineers. Shorter Java class names and compressed
code save memory, an important commodity on mobile devices. There are
many obfuscators available, but one of the most common is ProGuard (http://proguard.sourceforge.net/).
It is freely available and easy to use. Both NetBeans and the SDK
bundle it with their software. For more information on obfuscators and
how they work, visit the RCE forums (http://www.woodmann.com/forum/index.php).
Recovering original
symbolic names after obfuscation is impossible. When reviewing
obfuscated applications, look for references to core platform classes
and APIs. References to these cannot be obfuscated. Reviewing between
method calls can give you good insight into how the application works
and is much more efficient than attempting to piece the application’s
obfuscated code back together. At the very least, you will know which
parts are important to analyze.
The following code is a sample
decompilation of an obfuscated method. Notice that all symbolic
information has been lost, so the decompiler has used alphabetic letters
for class, method, variable, and parameter names. The mathematical
operations are not a result of the obfuscation process.
protected final void I(int ai[], int i, int j, int k, int l, int i1, int j1)
{
int k1 = (F >> 15) - 36;
int l1 = k1 - 32;
int i2 = 512;
if(l1 > 64)
i2 = 32768 / l1;
int j2 = l1;
if(j2 > j1)
j2 = j1;
for(int k2 = l; k2 < j2; k2++)
I(ai, i, j, k, k2, i1, k2 + 1,
C(0xff3399ff, 0xff005995, (l1 - k2) * i2));
Developers
often use obfuscation to hide cryptographic keys and other secrets in
their applications. While tempting, this is a fool’s errand. Obfuscation
will slow reverse engineers down, but will never stop them. Even
obfuscated code must be executed by the Java virtual machine and
therefore must always be reversible.
|
Debugging Applications
NetBeans includes a full
source debugger for stepping through application source code on real
devices or on emulators. Unfortunately, you are not able to single-step
through Java disassembly. To debug applications without source code, run
the application through a decompiler, build it, and then debug it using
NetBeans. Unfortunately, this is easier said than done because getting
decompiled applications to compile again and work properly can be a
challenge.
NetBeans communicates with the
JVM on the machine using the KVM Debug Wire Protocol (KDWP). A
specification for this protocol is available free from Sun (http://java.sun.com/javame/reference/docs/kdwp/KDWP.pdf).
KDWP enables NetBeans to communicate directly with the JVM running on
either the emulator or a real device. The protocol runs over a socket
connection to the actual device. Not all devices are KDWP enabled, and
many manufacturers require that you purchase KDWP devices directly from
them. The KDWP can be used for custom debugging and reverse-engineering
tasks where custom debug tools are required.
To debug an
application in NetBeans, load the application project, ensure that it
has no compile errors, and click the Debug Main Project button in the
toolbar. This will deploy the project to the appropriate device and
start the application. Breakpoints can be set up by pressing CTRL-F8 on
the target source code line.
Network Monitor
Decompiling and
debugging are effective tools for reverse-engineering applications, but
they can be time consuming and are not always the most efficient way to
approach a reversing problem. Monitoring input and output provides great
insight into an application’s behavior and is significantly easier. The
SDK includes a network monitor for monitoring network connections being
made by a device or emulator. The best part is that it doesn’t just
monitor HTTP or TCP traffic, it also monitors SMS traffic and shows SSL
in cleartext.
Unlike
debugging, which is enabled per invocation, network monitor logging is a
configuration property of the emulator. To enable the network monitor,
open a command prompt and run the following command:
c:\Java_ME_platform_SDK_3.0\bin\netmon-console.exe
This tool connects to
running JME emulators and starts recording network traffic. When the
netmon-console detects a new emulator, it logs a message (Output file:
C:\Users\bob\netmon-9.nms) to the console. This file contains the
network capture. You can also collect the capture from within NetBeans
by changing the emulator’s configuration and selecting Network Monitor.
To view the network capture, follow these steps:
1. | Open the JME 3.0 SDK development environment. NetBeans does not include an option for opening saved network capture files.
|
2. | Select Tools | Load Network Monitor Snapshot.
|
3. | Browse to the file containing the snapshot (for example, C:\Users\bob\netmon-9.nms).
|
4. | The snapshot will load in Network Monitor and display a view similar to Figure 2.
|
Now the fun begins. The topmost
pane contains the list of network connections made by or to the
application. The Protocol field lists the protocol used (arrows pointing
to the right indicate the client initiated the connection; arrows
pointing to the left are caused by server-generated traffic). You can
dig deeper into individual packets by clicking on the connection and
exploring the Hex View panels.
The Network Monitor is a great
tool and has vastly improved in version 3.0 of the SDK. Use it when
reverse engineering for its ease of setup and the depth of information
it provides.
Profiler
Another useful
application included with the SDK is the Application Profiler.
Developers use this tool to help them find performance problems—a real
concern for mobile applications. The Profiler records how much time is
spent in each application method. This information is valuable to
reverse engineers for finding the core methods of an application. It is
especially useful when the application is obfuscated and you are not
sure at which point to begin analysis.
Like the Network Monitor,
the Profiler is more cleanly integrated into the newest version of the
JME SDK than it is in NetBeans. There are some downsides: The profiler
uses lots of memory and will significantly slow down your application.
It also does not provide a
huge amount of detail. After all, it was built for performance analysis
with well-understood applications, not for hackers.
Follow these steps to capture data using the Profiler:
1. | Open the JME SDK and load your source project. Make sure it compiles.
|
2. | Right-click on the emulator profile in the Device Manager pane and select Properties.
|
3. | Choose
Enable Profiler. Record the Profiler filename listed in the properties
panel (the filename will have the extension .prof).
|
4. | Start the project by clicking the Run arrow.
|
5. | Exercise
the application. The goal is to figure out which code blocks are
executed the most often and which system APIs are being called.
|
6. | Terminate the application and close the emulator.
|
7. | Open the Profiler log by clicking on Tools | Import JME SDK Snapshot.
|
8. | Browse to the stored .prof file and click Okay.
|
9. | The result will appear similar to Figure 3. The call graph can be expanded by clicking the plus arrow.
|