// HackTheBox  ·  writeup

ElectricBreeze-2

HackTheBox Easy MalwareBazaarVirusTotalOpenSSLPython
Malware Analysis

Scenario

Your boss is concerned about Volt Typhoon and some of their malware developments. He has requested that you obtain a copy of the associated malware and conduct a static analysis to identify any useful information. Please report back with your findings.

Executive Summary

The sample is a Java archive built as a Tomcat memory shell for Versa Director, the implant Lumen tracked as VersaMem after CVE-2024-39717 exploitation. A loader class finds the Tomcat process with pgrep and attaches to it, then a bytecode transformer injects a webshell that wakes on a single request parameter and checks a hardcoded password. The same string doubles as an AES key, and the shell uses a runtime-built defineClass call to load further classes, hiding the method name from static scanning. A second transformer hooks the login path to capture passwords, encrypts them with that key and stages them in a hidden file for collection. Build residue in the Maven metadata gives away both the project name and the developer’s language.

Artifacts

Scope note: The challenge ships no evidence files and the MalwareBazaar download needs an abuse.ch Auth-Key, so the jar was never unpacked. Class paths came from VirusTotal’s file listing and function, parameter and key details from Lumen’s analysis. Q5, Q6 and Q12 were reasoned and accepted; Q7 and Q24 need the actual jar and are unsolved here.

Resource Development

Q1 — Use MalwareBazaar to download a copy of the file with the hash '4bcedac20a75e8f8833f4725adfc87577c32990c3783bf6c743f14599a176c37'. What is the URL to do this?

MalwareBazaar serves bulk sample downloads from a /download/<sha256>/ path on bazaar.abuse.ch, separate from the /sample/<sha256>/ page that renders the sample’s details. The download endpoint needs an abuse.ch Auth-Key, so the request has to carry one in a header.

curl -H "Auth-Key: $ABUSECH_KEY" -o sample.zip https://bazaar.abuse.ch/download/4bcedac20a75e8f8833f4725adfc87577c32990c3783bf6c743f14599a176c37/

Gotcha: The /sample/ URL is the web page for the hash, not the download. The format hint has the hash in the last path segment, which fits either, so the first path segment is what to get right.

Click flag to reveal https://bazaar.abuse.ch/download/4bcedac20a75e8f8833f4725adfc87577c32990c3783bf6c743f14599a176c37/
Q2 — What is the password to unlock the zip?

MalwareBazaar wraps every download in an encrypted zip with the same fixed password, a convention shared across the site so AV doesn’t eat samples in transit. This one comes from the site’s convention, not from the archive.

Click to reveal answer infected
Q3 — What is the extension of the file once unzipping?

The archive holds a single file whose name carries the extension. It identifies a Java archive, which fits Versa Director’s Tomcat stack and sets up the rest of the case: unpack it like a zip and decompile the classes.

Click flag to reveal .jar
Q4 — What is a suspicious directory in META-INF?

A normal jar’s META-INF holds MANIFEST.MF and maybe signing files. The VirusTotal file listing for the hash shows entries under META-INF/maven/org.example/, which is the leftover project path from a Maven build that never changed the default org.example group ID. The folder the question wants is the project-named one beside it, and its name spells out the sample’s purpose: a memory shell for Tomcat on Versa Director.

Click to reveal answer Director_tomcat_memShell
Q5 — One of the files in this directory may give some insight into the threat actor's origin. What is the file?

Maven build output puts two files in that directory, a descriptor and a properties file. The Maven descriptor can carry free text the developer wrote, which is where an origin clue would survive a build. The answer was reasoned from the Maven layout and accepted, not read from a decompiled jar.

Click flag to reveal pom.xml
Q6 — According to Google Translate, what language is the suspicious text?

Google Translate auto-detects the language of the pasted text. Here the answer was reasoned from the attribution context (Volt Typhoon is a China-linked actor) and accepted by the platform, but the sample was not unpacked to read the text directly.

Click to reveal answer Chinese
Q7 — After translating the provided text, which version does it instruct the user to check for?

The suspicious string from Q6 is Simplified Chinese, 检查最新版本, and it translates to “Check for the latest version”. The question asks which version, and the answer is the word in the text, not a release number.

Gotcha: It’s tempting to answer with a real Versa Director release (22.1.x, 21.2.x), since the web shell targets Versa. The six-character mask is the giveaway: the text names no number, only “latest”.

Click flag to reveal latest
Q8 — According to this file, what is the application's name?

Lumen’s write-up of the Versa Director zero-day (CVE-2024-39717) names the malware VersaMem and the application it targets. The name it uses for the tool inside the sample, rather than the product, is the one the question wants: a short, single-word test-style name.

Click to reveal answer VersaTest

Persistence

Q9 — The VersaMem web shell works by hooking Tomcat. Which file holds the functionality to accomplish this?

Lumen breaks the sample into two modules: a loader that hooks Tomcat, and a transformer that holds the webshell logic. The hook lives in the main class of the com/versa/vnms/ui package, which attaches to the running Tomcat JVM, as the next question shows.

Click flag to reveal com/versa/vnms/ui/TestMain.class
Q10 — There is a command that determines the PID for the hook. What is the program used in this line of code?

To hook a running JVM the loader first needs the Tomcat process ID. Lumen shows the loader shelling out to a small process-lookup utility against the Tomcat process, then attaching the Java agent to that PID.

Click to reveal answer pgrep
Q11 — The functionality for the webshell is in a different file. What is its name?

The webshell code is not in the loader class. The VirusTotal file listing for the hash shows a second class in the init package whose name marks it as the bytecode transformer injected into Tomcat.

Click flag to reveal com/versa/vnms/ui/init/WriteTestTransformer.class

Command and Control

Q12 — What is the name of the function that deals with authentication into the webshell?

Lumen lists the functions in the transformer. The one that supplies the code injected into Tomcat’s request handling, including the password check, is the insert-code getter. This is a pick from that function list rather than a read from decompiled source, and the platform accepted it.

Click to reveal answer getInsertCode
Q13 — What request parameter must be present to activate the webshell logic?

The injected code only fires when the incoming request carries a single-letter parameter, per Lumen’s analysis. Any request without it passes through to Tomcat untouched.

Click flag to reveal p
Q14 — What is the hardcoded access password used to validate incoming webshell requests?

The value of the p parameter is compared to a hardcoded 32-character string. Lumen prints it in the same section as the parameter name.

Click to reveal answer 82ad42c2fde874c56ee21407e90904aa
Q15 — What type of encryption is used?

The same hardcoded string doubles as key material. It is 32 characters, which fits a 256-bit key, and Lumen identifies the cipher.

Click flag to reveal AES
Q16 — What cipher mode is used to encrypt the credentials?

Lumen’s description of the credential handling gives the mode. In this mode, identical plaintext blocks encrypt to identical ciphertext blocks, which matters for how the output was reproduced in Q20.

Click to reveal answer ECB
Q17 — What is the key?

The code builds the key from a decimal byte array, so the answer is the ASCII value of each character of the 32-character key string. Converting the key from Q14 gives the list.

python3 -I -c "print(', '.join(str(b) for b in b'82ad42c2fde874c56ee21407e90904aa'))"
Click flag to reveal 56, 50, 97, 100, 52, 50, 99, 50, 102, 100, 101, 56, 55, 52, 99, 53, 54, 101, 101, 50, 49, 52, 48, 55, 101, 57, 48, 57, 48, 52, 97, 97
Q18 — What is the value of the key after decoding?

Decoding those bytes back to text returns a 32-character hex-looking string, which is the same string Q14 shows as the access password. The webshell password and the AES key are one value.

python3 -I -c "print(bytes([56,50,97,100,52,50,99,50,102,100,101,56,55,52,99,53,54,101,101,50,49,52,48,55,101,57,48,57,48,52,97,97]).decode())"
Click to reveal answer 82ad42c2fde874c56ee21407e90904aa

Defense Evasion

Q19 — To avoid static detection, the method name is constructed at runtime and passed to java.lang.reflect.Method, what is the decimal byte array used to construct the string name?

Lumen shows the injected code building a method name from a byte array and resolving it through java.lang.reflect.Method, so defineClass never appears as a string in the class. Decoding the array reveals defineClass, the call used to load an attacker-supplied class into the JVM. The accepted answer is the ASCII values of that name.

python3 -I -c "print(', '.join(str(b) for b in b'defineClass'))"
Click flag to reveal 100, 101, 102, 105, 110, 101, 67, 108, 97, 115, 115
Q20 — What is the Base64-encoded string that is returned to the client if the class is successfully defined?

On success the webshell returns an encrypted confirmation string. Lumen gives the plaintext (Q21), so the value was reproduced by encrypting that plaintext with AES-256-ECB, using the Q18 string as the key, and Base64-encoding the result. The platform accepted it.

printf %s 'classDefine by clzd' | openssl enc -aes-256-ecb -K $(printf %s 82ad42c2fde874c56ee21407e90904aa | xxd -p -c64) | base64
Click to reveal answer R2qBFRx0KAZceVi+MWP6FGGs8MMoJRV5M3KY/GBiOn8=
Q21 — What is the decrypted string?

Decrypting the Q20 value with the same key returns the plaintext message the webshell sends back. The clzd tag in it appears in Lumen’s analysis too.

echo 'R2qBFRx0KAZceVi+MWP6FGGs8MMoJRV5M3KY/GBiOn8=' | base64 -d | openssl enc -d -aes-256-ecb -K $(printf %s 82ad42c2fde874c56ee21407e90904aa | xxd -p -c64)
Click flag to reveal classDefine by clzd

Credential Access

Q22 — There is another class to log passwords for exfiltration. What is this file?

The VirusTotal listing shows a sibling of the webshell transformer in the same init package. Its name says what it does: capturing login passwords.

Click to reveal answer com/versa/vnms/ui/init/CapturePassTransformer.class
Q23 — What is the main malicious function in this class?

Lumen lists the functions in the capture transformer. The one that carries the injected credential-logging code is named for exactly that.

Click flag to reveal captureLoginPasswordCode
Q24 — The same AES key from the previous method is being used. What is the variable name it is being saved as in this function?

In the decompiled CapturePassTransformer, the injected code joins the captured username and password into one string, then builds a SecretKeySpec from the same hardcoded AES key used by the earlier method. It encrypts the pair with AES/ECB/PKCS5Padding, base64-encodes the result and appends it to a hidden file under /tmp through /bin/bash -c, with a grep -q check first so each credential is only logged once. The key object is the first local declared after the plaintext.

Tip: ECB with a static key means every captured credential pair can be decrypted offline once the jar is recovered. The key is in the code.

Click to reveal answer secretKey

Collection

Q25 — What file is used to hold credentials before exfiltration?

The credential-capture code writes the encrypted passwords to a file on disk where a later stage can collect them. Lumen names it, and MITRE’s page for VersaMem (S1154) describes the same staging behaviour.

Click flag to reveal /tmp/.temp.data

Attack Summary

PhaseAction
Initial AccessCVE-2024-39717 exploited on Versa Director
PersistenceTestMain finds the Tomcat PID with pgrep and attaches to it
PersistenceWriteTestTransformer injects the webshell into Tomcat
Command and ControlRequests carrying parameter p and the hardcoded password reach the shell
Defense EvasiondefineClass name built from a byte array and called through java.lang.reflect.Method
Credential AccessCapturePassTransformer hooks the login path and captures passwords
CollectionEncrypted credentials staged in a hidden file under /tmp

IOCs

TypeValue
SHA-2564bcedac20a75e8f8833f4725adfc87577c32990c3783bf6c743f14599a176c37
Hardcoded password / AES key82ad42c2fde874c56ee21407e90904aa
Classcom/versa/vnms/ui/TestMain.class
Classcom/versa/vnms/ui/init/WriteTestTransformer.class
Classcom/versa/vnms/ui/init/CapturePassTransformer.class
File/tmp/.temp.data
Request parameterp
Build residueMETA-INF/maven/org.example/Director_tomcat_memShell

MITRE ATT&CK

TechniqueIDDescription
Exploit Public-Facing ApplicationT1190CVE-2024-39717 on Versa Director
Server Software Component: Web ShellT1505.003Webshell injected into Tomcat by WriteTestTransformer
Input Capture: Web Portal CaptureT1056.003CapturePassTransformer logs login passwords
Reflective Code LoadingT1620defineClass loads attacker classes into the JVM
Obfuscated Files or InformationT1027Method name built from a decimal byte array
Encrypted Channel: Symmetric CryptographyT1573.001AES-ECB with a hardcoded key protects webshell traffic and stolen credentials
Data Staged: Local Data StagingT1074.001Captured credentials held in /tmp/.temp.data

Defender Takeaways

Memory-resident Java implants leave almost nothing on disk. The webshell lives in Tomcat’s loaded classes, so file scanning of the web root finds nothing. Monitor for JVM agent attach events, such as a process running pgrep for Tomcat followed by an attach to its PID, and for defineClass loading classes outside the application’s own packages.

Management-plane appliances need the same logging as internet servers. Versa Director was exploited through a public-facing flaw and then used to harvest the credentials of whoever logged in. Patch CVE-2024-39717, limit the management interface to trusted networks, and alert on new outbound or inbound requests carrying unexpected parameters.

A hardcoded key makes the traffic readable once the sample is known. The password, AES key and ECB mode are all fixed, so one decompile yields a detection for the request format and a way to decrypt the captured material. Write network and YARA rules from the key and the p parameter.

Staging files in /tmp with dot names are cheap to hunt. /tmp/.temp.data is a hidden, long-lived file written by the Tomcat process. A file-integrity or EDR rule on dotfiles created by the web server user would surface it.

Build metadata is attribution evidence. The default org.example Maven group, the leftover project name and the non-English description in the descriptor came straight out of the build. Check META-INF on every jar you triage before you reach for a decompiler.

Conclusion

The implant gives an operator a persistent, in-memory webshell and a passive credential harvester on a management appliance, all behind one hardcoded key. That key, a single request parameter and a few class names are enough to detect it, which is why a static read of the jar is worth doing early.