Thick client applications are an uncertain target for pentesters; the scope of testing can vary wildly for applications of the same size, depending largely on how they communicate with the end server. Standard HTTP GET and POST requests are all well and good, but what happens when you start seeing serialized Java or binary protocols? HTTP proxies such as Burp and Fiddler will only get you so far.
On a recent engagement, Context were contracted to test a suite of Java applications used for financial trading which communicated using a number of separate protocols, only some of which could be manipulated easily using Burp Suite. While it is possible to decompile, edit and recompile Java applications, doing so is awkward and not conducive to common testing methods which usually involve sending hundreds if not thousands of differently-formatted requests. So we needed an easier way.
The setup we used was based on Netbeans. By running the applications using a shared memory process, it is possible to attach Netbeans to the process and, assuming it decompiles neatly, set breakpoints within the application code. Once these breakpoints are hit, the process will be paused and Netbeans will display a list of in-memory variables and allow you to change their values before continuing.
Going for Gold
To demonstrate this technique, let's use the Java-based application FreeCol, an open-source clone of the game "Sid Meier's Colonization". The game is started by running the file "FreeCol.jar", and requires Java 8. To begin with we'll need to get all this set up in Netbeans. We first create a project using the "Java Project with Existing Sources" option, and specify the game install folder as the project folder. We then right-click the "Libraries" option on the left-hand panel in Netbeans under our project, select "Add JAR/Folder", and select the FreeCol.jar file.
We then need to add the sources by right-clicking the JAR, selecting Edit, and specifying a .zip file containing the source within the "Sources" field. We could acquire the source by decompiling the file in a Java decompiler such as "jd-gui" and exporting the results as a .zip file, but since the developers have been kind enough to release the source, we'll just use theirs.
We now need to launch the Java file "FreeCol.jar" using a shared memory address. For this we create a batch file with some specific flags, that reads:
java -jar -agentlib:jdwp=transport=dt_shmem,address=jdbconn,server=y,suspend=n FreeCol.jar
Running this batch file will start the process using shared memory with the name "jdbconn". A description of the flags used can be found on the Oracle website.
Next, we attach Netbeans to the running process by selecting "Debug", then "Attach Debugger", and setting the options as shown here:
If all goes well, we'll now see the "Debugging" tab in the middle left-hand panel in Netbeans. Our general setup will look like this:
Next up, we'll need to start setting breakpoints. To force the process to stop so we can change the variables, we'll need to set a breakpoint somewhere that we know we can trigger. The easiest way to do this is to find a string that you can search for in the code, and then follow it through to where it gets called. Let's take a look in the game:
We can see a list of Reports we can run. Let's take "Trade Advisor" and grep through the source code. We could search using Netbeans, but Netbeans is already working pretty hard and this whole process already likes to freeze on occasion.
Ok, so our string "Trade Advisor" gets set as the name for "reportTradeAction". Where is this called?
So it looks like we need to set breakpoints in "ReportTradeAction.java", using the "ReportTradeAction.class" file inside "net.sf.freecol.client.gui.action". Let's shove a few in there by clicking the line number next to each method we see, and see what gets triggered when we click the option in-game:
Success! Now we can edit variables. Let's browse through and give ourselves a big pile of gold:
And it works!
We can now enter arbitrary values into these fields and attempt attacks such as authorisation bypasses, SQL injection, remote code execution and numerous others. Of course I've skipped out the trial-and-error process of finding a breakpoint that actually gets hit, simplified the searching through variables, and neglected to show the part where I accidentally gave that gold to France instead. A real-world application test will likely require a bit more grepping and fiddling around to find breakpoints that allow you to set the values you need.
To wrap up, this technique is useful when testing a thick client app that communicates using non-HTTP protocols, signed requests or serialized Java requests which are otherwise difficult to edit in transit. This technique can also be useful if the client-side application is not letting you create certain requests, as it believes you are not an administrator, or the application is running in "demo mode", or similar.
In order to prevent debugging as described above, Java application code should be obfuscated so that variable names cannot easily be determined. However, a safer approach is to assume that anything client-side can be manipulated easily, and to implement all security server-side.
Some general notes:
- Netbeans attaches to the running Java process and the connection is closed whenever the process stops. You'll need to reattach every time you restart the app.
- Breakpoints are saved and will be automatically reapplied when Netbeans is reattached.
- FreeCol includes a "Debug" option which would let you achieve all of this anyway.
- This was all done in Windows, though only out of preference. It probably works mostly the same in Linux, though you might have to use different Java flags, as per this page.
- I've not tried this technique against obfuscated code, but strongly suspect that it won't work well, if at all. This might be a future project.
Contact and Follow-Up
Lewis works in Context's Assurance team in our London office on a variety of projects, with a focus on application testing. See the contact page for ways to get in touch.