Jar Files: Analysis and Modifications

I recently ran into a challenge where I was given a Java Jar file that I needed to analyze and patch to exploit. I didn’t find many good tutorials on how to do this, so I wanted to get my notes down. For now it’s just a cheat sheet table of commands. Updated 8 Aug 2020: Now that Fatty from HackTheBox has retired, I’ve updated this post to reflect some examples.
Jar File Structure
A Jar (Java Archive) file is just a ZIP file that contains Java class (compiled) files and the necessary metadata and resources (text, images). That said, they are very picky, and it can be important how to interact with them when pulling stuff in and out.
The Jar will have a META-INF/
folder at the root, which contains metadata such as signatures, license, and the MANIFEST.MF
file. For example, in fatty-client.jar
:
root@kali# ls META-INF/
1.RSA LICENSE MANIFEST.MF NOTICE services spring-context.kotlin_module spring.factories spring.handlers spring.tld spring-web.kotlin_module
1.SF license.txt maven notice.txt spring-beans.kotlin_module spring-core.kotlin_module spring-form.tld spring.schemas spring.tooling web-fragment.xml
The MANIFEST.MF
file provides the Jdk version, as well as optionally a Main-Class
attribute. If Main-Class
is present, this Jar can be executed without providing a class name, which makes it an executable Jar. The manifest file also contains base64 format hashes of the other files in the archive.
Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Built-By: root
Sealed: True
Created-By: Apache Maven 3.3.9
Build-Jdk: 1.8.0_232
Main-Class: htb.fatty.client.run.Starter
Name: META-INF/maven/org.slf4j/slf4j-log4j12/pom.properties
SHA-256-Digest: miPHJ+Y50c4aqIcmsko7Z/hdj03XNhHx3C/pZbEp4Cw=
Name: org/springframework/jmx/export/metadata/ManagedOperationParameter.class
SHA-256-Digest: h+JmFJqj0MnFbvd+LoFffOtcKcpbf/FD9h2AMOntcgw=
...[snip]...
Initial Work
Unpack / Repack
Before taking on something like unpack, modify, repack, I’ll start with unpack then repack to make sure I can do that without errors. I’ll make a directory, unzipped
, and then unzip the Jar into it:
root@kali# unzip -d unzipped/ fatty-client.jar
Archive: fatty-client.jar
inflating: unzipped/META-INF/MANIFEST.MF
inflating: unzipped/META-INF/1.SF
...[snip]...
Now, from within that directory, I’ll run jar -cmf META-INF/MANIFEST.MF ../new.jar *
:
-c
- Create new jar file.-m
- include a preexisting manifest; I had issues with the manifest coming out blank without this flag.-f
- specifies the jar file to be created.
Now I can run java -jar new.jar
and it works:

Signatures
Updating the Manifest
One of the first things I had to do in Fatty was to modify a text file at the root of the Jar, beans.xml
, to get the client to connect to the right port. I can edit that in my unzipped folder, and then recreate the Jar just like above. I can run it, but when I try to submit something to the server, it errors out:
root@kali# java -jar new.jar
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
Exception in thread "AWT-EventQueue-0" org.springframework.beans.factory.BeanDefinitionStoreException: Unexpected exception parsing XML document from class path resource [beans.xml]; nested excep
tion is java.lang.SecurityException: SHA-256 digest error for beans.xml
at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions(XmlBeanDefinitionReader.java:419)
...[snip]...
This is the important part : java.lang.SecurityException: SHA-256 digest error for beans.xml
.
I need to update the hash in the manifest. If I look at the first file in the manifest after the header info, I can see the hash format:
Name: META-INF/maven/org.slf4j/slf4j-log4j12/pom.properties
SHA-256-Digest: miPHJ+Y50c4aqIcmsko7Z/hdj03XNhHx3C/pZbEp4Cw=
That has format is different that I typically see. It looks base64 encoded. I’ll use the following command to replicate it:
root@kali# sha256sum META-INF/maven/org.slf4j/slf4j-log4j12/pom.properties | cut -d' ' -f1 | xxd -r -p | base64
miPHJ+Y50c4aqIcmsko7Z/hdj03XNhHx3C/pZbEp4Cw=
That takes the standard hex hash output, uses cut
to isolate it, xxd
to convert it to binary, and base64
to encode it. I’ll calculate for the new beans.xml
:
root@kali# sha256sum beans.xml | cut -d' ' -f1 | xxd -r -p | base64
f/D4a+DZ53lRwjwkcYauGCDdJ5AJT0bZ9wsIBzqDdJ8=
Removing Signing
After updating and re-Jar-ing, a new error on running:
root@kali# java -jar new2.jar
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
Error: A JNI error has occurred, please check your installation and try again
Exception in thread "main" java.lang.SecurityException: invalid SHA-256 signature file digest for beans.xml
...[snip]...
There are also signatures for the Jar, and I want to remove them. The easiest way to do that is just to remove the signature files, as this StackOverflow post suggests.
root@kali# ls META-INF/*.SF META-INF/*.DSA META-INF/*.RSA 2>/dev/null
META-INF/1.RSA META-INF/1.SF
root@kali# rm META-INF/1.SF META-INF/1.RSA
Now if I build the Jar, it runs fine.
Modifying Compiled Classes
Decompiling
Install Procyon
To decompile, I used the Procyon Java decompiler. To “install” it, I just downloaded the latest Jar from that link, and dropped a link into /usr/local/bin/
so I could just type procyron
to use it:
root@kali# ln -s /opt/procyron/procyon-decompiler-0.5.36.jar /usr/local/bin/procyon
Single File
With procyon
, its simplest to operate from the root directory of the unzipped Jar, at least if you’re going to output files. If I run procyon [path to class file]
, the decompiled Java will output to STDOUT, to the terminal.
procyon -o . [path to class]
will output that Java to a file, with a directory structure to match how the classes are set up. For the Fatty example, from the root of the unzipped Jar, it creates
root@kali# find . -name *.java
root@kali# procyon -o . htb/fatty/client/gui/ClientGuiTest.class
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
Decompiling htb/fatty/client/gui/ClientGuiTest.class...
root@kali# find . -name *.java
./htb/fatty/client/gui/ClientGuiTest.java
The directory structure is the same, even if the output directory isn’t the local one:
root@kali# procyon htb/fatty/client/gui/ClientGuiTest.class -o /tmp
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
Decompiling htb/fatty/client/gui/ClientGuiTest.class...
root@kali# find /tmp -name *.java
/tmp/htb/fatty/client/gui/ClientGuiTest.java
Entire Jar
I can also decompile an entire Jar, with the syntax procyon -jar [jar] -o [output directory]
. Sites like javadecompiles.com will do this for you, and provide back a zip with all the source. One note of caution - This option returns all the .java
source files. It does not return the metadata, so to recompile, or repackage, you’ll need to work from a unzipped version.
ReCompiling
I’ll make some small change to the .java
source I decompiled, and now I need to recompile it to put it back into a Jar to run. I’ll use javac
, which comes in the JDK I downloaded. I’ll first run update-alternatives
to make sure I’m using the version that matches what was in the MANIFEST.MF
file.
The example I’m working with from fatty first presents a login form when run. I’ll set it so that when that form loads, the username “qtc” is already in the username field, by adding "qtc"
in this line in the decompiled ClientGuiTest.java
:
(this.tfUsername = new JTextField("qtc")).setBounds(294, 218, 396, 27);
I’ll want to compile from the root of the unzipped Jar. It is not uncommon to get errors compiling from decompiled source:
root@kali# javac htb/fatty/client/gui/ClientGuiTest.java
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
htb/fatty/client/gui/ClientGuiTest.java:276: error: variable ex might not have been initialized
final Exception e2 = ex;
^
htb/fatty/client/gui/ClientGuiTest.java:294: error: variable ex might not have been initialized
final Exception e2 = ex;
^
htb/fatty/client/gui/ClientGuiTest.java:313: error: variable ex might not have been initialized
final Exception e2 = ex;
^
htb/fatty/client/gui/ClientGuiTest.java:332: error: variable ex might not have been initialized
final Exception e2 = ex;
^
htb/fatty/client/gui/ClientGuiTest.java:351: error: variable ex might not have been initialized
final Exception e2 = ex;
^
htb/fatty/client/gui/ClientGuiTest.java:375: error: variable ex might not have been initialized
final Exception e2 = ex;
^
htb/fatty/client/gui/ClientGuiTest.java:393: error: variable ex might not have been initialized
final Exception e2 = ex;
^
htb/fatty/client/gui/ClientGuiTest.java:411: error: variable ex might not have been initialized
final Exception e2 = ex;
^
htb/fatty/client/gui/ClientGuiTest.java:429: error: variable ex might not have been initialized
final Exception e2 = ex;
^
htb/fatty/client/gui/ClientGuiTest.java:447: error: variable ex might not have been initialized
final Exception e2 = ex;
^
10 errors
Looking in the code, there are 10 places where exception handling looks like:
catch (MessageBuildException | MessageParseException ex2) {
final Exception ex;
final Exception e2 = ex;
JOptionPane.showMessageDialog(controlPanel, "Failure during message building/parsing.", "Error", 0);
}
Neither ex
nor e2
are ever referenced after this, so I’ll just remove those two lines.
Now compiling works fine:
root@kali# javac htb/fatty/client/gui/ClientGuiTest.java
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
This is working from the unzipped Jar file structure, and the compilation just replaced htb/fatty/client/gui/ClientGuiTest.class
. Now I can re-package the Jar just like before:
root@kali# jar -cmf META-INF/MANIFEST.MF ../mod.jar *
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
And on running the new Jar, the username field is filled in:

Cheat Sheet
Here’s all the commands from this post (and a few extras that are useful with Jars):
Task | Command |
---|---|
Run Jar | java -jar [jar] |
Unzip Jar | unzip -d [output directory] [jar] |
Create Jar | jar -cmf META-INF/MANIFEST.MF [output jar] * |
Base64 SHA256 | sha256sum [file] | cut -d' ' -f1 | xxd -r -p | base64 |
Remove Signing | rm META-INF/*.SF META-INF/*.RSA META-INF/*.DSA |
Delete from Jar | zip -d [jar] [file to remove] |
Decompile class | procyon -o . [path to class] |
Decompile Jar | procyon -jar [jar] -o [output directory] |
Compile class | javac [path to .java file] |
It’s always important to track file structure and your relative directory. I’ve found it’s easiest to work out of the root directory of the unzipped Jar.
Procyon “installed” by downloading Jar and creating a symlink: ln -s /opt/procyron/procyon-decompiler-0.5.36.jar /usr/local/bin/procyon
.