Pentesting Java EE web applications with LAPSE+

Just a quick tip for anyone doing a code review of a Java EE web application: LAPSE+ is a very useful tool to have in the arsenal, whether you’ve got the original source or just the JAR/WAR file.

In my case, the client provided me with a single .WAR file which contained the application. As it was a large application, I didn’t really fancy digging through everything manually with JD-GUI, although it is an excellent Java decompiler. I decided to take the opportunity to give LAPSE+ a try.

Here’s what  you’ll need:

You can also grab a PDF instruction manual for LAPSE from the same site. However, be aware that I found some of the information in there to be a bit misleading, e.g. needing a specific version of Eclipse. Also, don’t worry if your client provided you a project for a different IDE, such as IntelliJ IDEA – it doesn’t really matter.

First step is to get Eclipse set up. Drop the .jar file from the LAPSE+ archive into the plugins directory of Eclipse. (Re)start Eclipse, then go to Window -> View -> Other… and select the items relating to LAPSE+. A little toolbar should appear on the right with blue spherical buttons. These are your LAPSE+ windows.

Next step is to load your code into a project. This is split up into two parts, but if you’ve already got an Eclipse project for the site’s source code, you can skip the first part. Otherwise, you’ll need to extract the code from your archive and make a project for it. Start by loading the JAR (rename the .WAR to .JAR if needs be) into JD-GUI. It should decompile the archive and let you browse the code. Go to File -> Export all Sources, and save the resulting ZIP file somewhere. This archive now contains all your decompiled source code, split into directories based on the namespace hierarchy.

Now, go back to Eclipse and create an empty Java project, filling the wizard out with whatever values suit you. Once that’s created, go into the project explorer tree and find the src directory, then right click it and select Import. Select your newly exported ZIP file, and Eclipse will populate your project with your reverse-engineered source. Now right-click the project and select Build. In all likelihood, it’ll throw a whole load of errors due to imperfect decompilation – don’t worry, we don’t really care, because LAPSE+ can still function with a broken build.

Once you’ve got your project set up, go to the individual LAPSE+ windows and browse through what they found. You might need to manually refresh them to run through the checking process. In my case, I found about a 10:1 ratio of false positives, which isn’t actually too bad for code scanning. Within an hour or so of digging through the results I’d found a couple of concrete XSS bugs that I’d not spotted yet, plus a whole bunch of potential XSS bugs that I couldn’t immediately find vectors for, and a whole variety of other interesting stuff to dig through. It’s a really nice way to cut down a 400kLoC project into manageable target points.

Preventing executable analysis – Part 1, Static Analysis

In this series of posts, I’m going to discuss executable analysis, the methods that are used and mechanisms to prevent them. There are three types of analysis that can be performed on executables:

  • Static – Analysis of the sample file on disk.
  • Emulated – Branch and stack analysis of the sample through an emulator.
  • Live – Analysis of the executing sample on a VM, usually using hooks.

I’m going to look at each type in detail, giving examples of techniques used in each and ways to make analysis difficult.

In this first post, I’ll look at static analysis. This type of analysis involves parsing the executable and disassembling the code and data contained within it, without ever running it. The benefit of this is that it’s safe, since it’s impossible for the code to cause any damage. The downside is that static analysis can’t really make assumptions about high-level behaviours.

Entry Point Check
The first method used to perform static analysis is simple header checks. If the entry point (EP) of the executable resides outside of a section marked as code, it is safe to assume that the application isn’t “normal”. In order to prevent recognising this from being a simple task, the executable should have its BaseOfCode header pointing at the same section the EP is in, even when packed.

Packing
Executables are often packed – i.e. their code is encrypted in some way. We can analyse this using entropy calculations on each section, to discover how “random” the data looks. It’s often tempting for authors to try to create a good cipher for encrypting packed sections, but this often leads to a few problems. Firstly, entropy calculations will very quickly spot sections that look too random to be normal code or data. Secondly, there are many applications out there that will look for sequences of data and instructions that match known cryptographic algorithms. It’s relatively easy to spot magic numbers and S-box arrays

In order to prevent this, a packing algorithm should be used that preserves the statistical signature of the original data. A good way to do this is to flip only the lowest two bits of each byte, or to simply shuffle the data rather than encrypting it with xor or a similar operation. By definition, a sample of data will have the same Shannon entropy regardless of how much you shuffle it. The usual way that analysis tools work is to split each section into blocks and compute an entropy graph across the file. By using a cipher that only shuffles bytes that are close, you can achieve an almost identical entropy graph:

Entropy Graph

Since instructions are multi-byte, shuffling completely destroys the code, making it impossible to read. It’s relatively simple to perform half-decent shuffling, given a reasonably large key:

for each byte k in key
{
	tmp = data[0]
	data[0] = data[k]
	data[k] = tmp
}

Simply loop the above over a sequence of data, you’ll get reasonable shuffling within each 256-byte block. OllyDbg doesn’t recognise this as packed, since it works on counts of particularly common bytes in code sections.

Jump Tables
Static analysis tools such as IDA Pro work by mapping sequences of jumps together. Some enhance this by performing heuristic analysis of jumps, for example turning jmp [file.exe+0x420c0] into an assumed jump based on the data at file offset 0x420c0. We can try to defeat this type of analysis by using jump tables. These are pointer tables generated at runtime, which are encrypted or obfuscated on disk. Jumps in the code are done by pointing to offsets in the jump table. Often this is further obfuscated by using jumps to register pointers, or stack jumps:

; ecx = function ID
mov eax, [ptrToTable+ecx*4] ; load the encrypted pointer into eax
xor eax, [ptrToKey+ecx*4]   ; xor with the key
push eax                    ; push address to stack
ret                         ; return (jump) to it, obfuscates the jump

Obviously there’s more we can do here – better encryption, values generated at runtime, more obfuscation, etc.

Control Flow Obfuscation
Some analysis tools focus on artifacts of compilers – i.e. the signatures of how common high level language constructs translate into assembly language. For example, some loops may be translated into a dec/jg loop, whereas some others might use rep mov. It all depends on the high level construct in use. By altering these constructs and using them in situations where they are unusual, this can confuse heuristics. One example for short loops is using a switch:

for(int i=0; i<5; i++)
{
	if(i%2==0) printf("%i is even\n", i);
	else printf("%i is odd\n", i);
	if(i==4) printf("done");
}

We can turn this into a switch statement that flattens out the flow, instead of being an obvious loop:

for(int i=0; i<5; i++)
{
	switch(i)
	{
		case 0: printf("%i is even\n", i); break;
		case 1: printf("%i is odd\n", i); break;
		case 2: printf("%i is even\n", i); break;
		case 3: printf("%i is odd\n", i); break;
		case 4: printf("%i is even\n", i); printf("done"); break;
	}
}

Since this uses a switch, we can use a jump table that is easy to obfuscate.

Conclusion
There are many ways to break static analysis, some of which are simple, some of which are more complex. By employing these, it makes it very difficult for any analyst to decode and understand. Such methods can also prevent automated tools from performing in-depth analysis of the code. Understanding these methods helps both implement them and circumvent them. In the next part, I’ll be looking at virtualised and emulated analysis, which uses virtual hardware to analyse and fingerprint software without actually executing the real application code live on a hardware processor.

Further Reading