Reversing Java Apps - worked example CVE-2022-31491


Voltronic ViewPower Banner

TLDR;

A guide to show that ANYONE can compare an old version of software to a later version to reverse engineer and find details on CVE's or bugs that don't have any detailed disclosure. We use CVE-2022-31491 as a worked example. It has been possible to do this reverse engineering since at least 2024 if you where curious enough and discover the details behind CVE-2022-31491 for yourself.

LET US BE VERY CLEAR: It has been possible to do this reverse engineering and discover the information held here for several years since the PowerShield/Netguard software was patched if you were curious enough. You would've also discovered the details behind CVE-2022-31491 for yourself. Nothing here is new, it has been on the open internet for ANYONE to find for many years. Some threat actor may have already been using it... thats what Dave thinks.

Confused ? Thats not our fault... perhaps try here

Overview

The Voltonic Viewpower/ViewPowerPro software has bugs, and one example (of many) we found is CVE-2022-31491. We cover how to reverse the Java app and compare an old version to a new version to look for "fixes" and changes that the dev has put in. Ultimately arriving at the issue which is CVE-2022-31491, and the "fix" they attempted but failed at. Further details here

We actually found this bug originally (back in 2022 when we got the CVE assigned) by trawling through the code before any patch was issued (see other blog posts from the home page), but we're keen to show how easily others can use reversing to find details about bugs in software when something is "fixed/patched". It can be satisfying if the vendor just says things like "fixed security issue", but you want details that are otherwise not available.

We've tried to put in extra steps to explain things better, not assuming you're a leet pen tester. Yes, literally STEP-BY-STEP. So if you're a dev with zero bug hunting experience wondering how folks are pulling apart your apps... then you will find this useful, and simple to follow.

Strap in, crank up your Kali VM.


Let the shenanigans begin...

Caveats:

  • 🔥 We have absolutely no doubt there's a more efficient way of doing this... a fancy pipeline ? but we don't care
  • 🔥 It has been possible to do this reverse engineering for several years since the PowerShield/Netguard variant was patched.
  • 🔥 Nothing here is new, it has been on the open internet for ANYONE to find for many years.
  • 🔥 If you don't like this... go browse somewhere else, especially since our writing style isn't consistent. Blame Dave for that.
  • 🔥 Tell your cat we said pspsps.
  • We're going to start with an older version (24215) from www.power-software-download.com website, and a newer version (25210). We had a copies already, but there are some options over at Wayback Machine if you want grab them: ViewPower_win32.zip. You could also look at the current version of Powershield vs an old version of that from the Wayback Machine if you like (especially given they mentioned CVE-2022-31491 being fixed at a given time/version).

    The changelog for 25210 looks "interesting". Item 1 mentions "command injection". Item 4 has mention of "RCE", which could be especially nasty. Mind you, none of them reference CWE or CVE information. Lets get rolling, the team is keen to get started.

We used a Windows VM to install version 24215. It installs to c:\ViewPower by default. So take a zip of that entire folder as ViewPower_24215.zip. Move it off the VM as we need this for later.

We restored the snapshot of the windows VM and then repeated for version 25210, resulting in ViewPower_25210.zip

Righto, move the files to another machine (or just reuse the windows VM), into a working folder, something like c:\work... such imagination. Unzip into folders with names matching the versions. We're about to do the initial comparison.

Next we crank up a BeyondCompare... yeah, you'll need to get a trial of it or similar. Google is your friend. Use it to compare the version 24215 folder with 25210. Should start seeing a whole bunch of stuff right out of the gate.

Warning: We continue with quite basic commands here, we're keen not to lose you along the way... so perhaps you should grab another beer.

BeyondCompare does apparently allows a plugin to decompile .class files, but we're not going to use it. We will prefer jd-gui on Kali as you will see, coz you know... manual, it's the 'Try Harder' way.

Hit up the view to "Show Differences" and Filter out whats the same...

Looking at the top level items, not too many interesting things... (Edit: You didn't have to go this granular lolz)

Right clicking on many files and doing a "Binary Compare" will see that they're the same and just remove them from view...

Double clicking on others, we can quickly see other files off very little insight and are merely config related. Right click those items as you check and choose "Ignored" to remove them from view. These appear to extend to the language files and installer also, so boring stuff... out the door with you.

Lets go a step further, and hide the orphans (files that exist on one side, but not the other) to make things a bit neater on screen. We can always revisit if we find nothing in the "changed" files from one side to the other.

As one would expect from a Java app, we have multiple .jar, .jsp and .class files that are different, and which require further digging. We're not interested in the .DS_Store files, so we will "ignored" them off also. There is no point drilling into the .jar (zip compressed files) as we will decompile the contained class files on Kali.

The .jar files are just .zip files, but for the moment we're going to leave them as is. Move the entire structure across to Kali. We put This on Daves desktop (not a real person we swear).

Get jd-gui up and running on Kali. This is the tool we used to browse the source originally in 2022 initially to find the bugs. Again: We know there are automated tools to do this... go away, we're showing the hard way so peeps understand whats going on better, and Dave doesn't want to leave the air-con in the office.

Open up the first .jar file "ViewPowerConsole.jar" for version "24215", in our case from "/home/kalidave/Desktop/Work/24215/ViewPower/console/lib/". Thanks Dave (not his real name).

Then choose "File -> Save All Sources", and put the results into a folder like "24215_decompiled". This is the trick that is essentially going to decompile all the Java.

Repeat this for ALL the .jar files we flagged with BeyondCompare in the folder for version "24215". Given the structure of the .class files, you will only need to do one, as it will automatically pickup all the files. eg: Only choose "BaseAction.class" Dave (ok, maybe thats his real name) now has a nice collection on his Kali desktop...

Rinse and repeat for version 25210 files... daves Kali desktop :)

Move the files back to windows, where we can load it up again with beyond compare. 24215 on the left, 25210 on the right. Note: We have chosen "View -> Diffs No Orphans" again.

Looking at BaseAction_Classes... The differences in the language files look like nothing of interest, so will be right-click "Ignored" A Bunch of files have minor differences like the location. These are to be ignored. Some have extra "System.out.println" that appear to be related to tracing, so they're ignored too. Somewhere here Dave did point out that we could've done the initial selection of folders better... but you know, its too late DAVE.

So poking around further Dave said this is interesting and I agreed with him... "main" for "SystemConsoleTray.java. The 24215 code has a basic message to display, but 25210 looks to have something interesting... the word "exec" with a ping command in a string. That string is being sent off to a function "SystemTrayUDPclient". Does the name of and object with UDP mean network UDP or something else ? Lets not dismiss this. Worth keeping in mind and perhaps revisiting. Why did the developer have this code ? Something to do with a bugfix test ? \decomplied\ViewPowerConsole.jar.src.zip\cn\com\voltronic\console\windows\SystemConsoleTray.java

"main" in a UDPExecClient.java. Ok, this difference has "exec" again, in both versions. Looks to be connected to something related to UDP again "UDPExecClient". Could be related to SystemControlTray.java from above. Lets not dismiss this either, and maybe 43654 and 33654 are ports ? \decompiled\viewpowermonitor.jar.src.zip\cn\com\voltronicpower\utilities\UDPExecClient.java

Previous version clearly shows some code removed relating to "Runtime.getRuntime(execStr).exec" with a string being fed into it. Runtime exec is always something that can pique our interest. The code also clearly makes reference to the previously found "exec" with #'s. Very interesting. \decomplied\ViewPowerConsole.jar.src.zip\cn\com\voltronic\console\linux\LinuxMonitorConsole.java

"startRun" windows\MonitorConsole.class "startRun". Like the file above, code naming alludes to making the execStr execute. Structure a bit different to Linux, but still very interesting. \decomplied\ViewPowerConsole.jar.src.zip\cn\com\voltronic\console\windows\MonitorConsole.java

"startRun" mac\MacMonitorConsole.class "startRun". More similar to Linux, but still shows execution call to OS with a string. Top of function mentions. More references to a magic string "exec" and #'s \decomplied\ViewPowerConsole.jar.src.zip\cn\com\voltronic\console\mac\MacMonitorConsole.java

So for each OS type above, we have a "run" or "startRun" within a MonitorConsole.class with a sus looking execution removed relating to a variable called "message" that apparently has to start with "exec#". If we look at the top of those "run/startRun" functions, we see that "message" is populated from the "udpServerSocket" object. On both versions too... we're onto something here. If the system is taking raw data from a UDP socket (as the name of the object suggests), then just checking for a prefix of "exec###" before passing to the OS to run then we might be on a winner.

This grabbing of message from the UDP port appears to be the same for Linux and Mac also. Clearly also kicks off SystemTrayUDPServer.getServer(33654)

Back to Kali. All of the above have "SystemTrayUDPServer.getServer(33654)" in the old code. They also look search the "message" for "exec#". Lets search for that with jd-gui for the string "exec#". It may give us some into whats going on. We will look at the OLD version of the code.

We find "exec#" across the "MonitorClass" that we already knew about, but also: In UDPExecClient.class. Looks like some sort of test code... shows an entire command, but looks like its calling UDPExecClient. (Port 43654 looks to be a bit different... thats interesting)

In AbstractMonitorProcessor.class it looks like there is a string passed with "exec###" also

Ok, lets get Dave (3 beers deep) to install version 24215 back on windows and look around for UDP ports. Well... I need another beer (only 2 beers deep). This thing does appear to be listening on UDP port 33654, even better its 0.0.0.0 for ALL interfaces Even includes IPv6. Nice.

Righto, we're feeling lucky, and just want to throw some packets around already. The developer has kinda shown their hand here with Main in "UDPExecClient.java" having what looks to be a test string like "exec### D:\\Notepad++\\notepad++.exe". So we're getting itchy now and Dave requested (yet) another beer... Lets yolo something similar to the test code we found at the old version of the software... we already know the UDP port is listening, lets give it something simple.


echo -n "exec### calc.exe" >/dev/udp/172.16.25.146/33654
                    

Wowzers... looks like some RCE. Looks like we just found a simple PoC through reversing. Congratulations sports fans, better test the heart rate and blood pressure. Its getting exciting. This is literally CVE-2022-31491, and we've showed how to find it by wandering through a diff between versions. But you know we aint finished ;)

So... lets open up process explorer and see what is going on. Making the system run ip "cmd.exe" using the PoC we can see that "ViewPower.exe" and "upsTray.exe" is related to spawning the child process. For some reason the cmd.exe does not get a screen context like calc.exe did, but it still ran. Feel free to give it a shot with notepad.exe for fun.

Wowzers, why is this port listening anyway ? What on earth calls this in the software ? With this in mind, if we search around the code in jd-gui for exec#### again we find a reference to it in "AbstractMonitorProcessor.class". Within "executeFile(Shutdownconfigure shutdown) it calles out to the port on "localhost"

So, for some context the software normally allows a user (signed in via the web interface) to configure a command (.bat, .exe) to be run if/when the UPS is running low and the system needs to shutdown. This could be a backup script, notification, a script to shutdown some critical function. This is the sort of thing you expect UPS monitoring software to do... run something when it notices an impending power failure.

When the software detects the need to shutdown the UPS and system, it looks like it calls this function, which sends the required command (as configured by the system admin) to be run via the exposed UDP. Well... Thats what (tipsy) Dave reckons.

However, as we can see, the UDP port is not enforcing auth in any way, and not enforcing any sort of timing (eg: UPS shuting down)... It just you know... YOLO and run it. The best part... is its repeatable... Its raining packets that do RCE :)


The attempted encryption fix

Later versions implemented having the UDP packet encrypted. This is also fully visible using decompilation. We're not going to cover this in as much detail as we think you can tackle that on your own if you're keen... besides Dave has fallen asleep on the floor now.

We will however leave you with the following info, and the screensnaps below that (all of which you can discover for yourself with more reversing)

  • 🗑️ The attempted fix fails due to incorrect handling/creation of the Key and IV (Initialisation Vector)
  • ⚠️ This "encryption fix" and a related PoC is discussed further here

Patched version with some extra code with a "decryptWithoutPrivateKey", and then sending that to the normal getRuntime().exe

The module that contains the encryption routine.

Encryption and Decruption routines appear to use same function to get the key and the IV.

Oh dear... the function for the key and IV is using a hard coded value.

For added points, go find some more CVEs in this vendors software fleet.


v002