Oh No Cleo! Malichus Implant Malware Analysis

Glitch effectGlitch effectGlitch effect
Glitch banner

Summary - CVE-2024-55956

Huntress previously reported on malicious activity from the exploitation of a 0-day vulnerability in Cleo software. The malware being delivered through this exploitation has now been analyzed and a technical breakdown of a new family of malware we’ve named Malichus is included in this post. The name is a play on the word Cleopatra and comes from Malichus I, who is noted to have burned Cleopatra’s navy fleet in revenge for his losses throughout a war that Cleopatra had initiated.

Figure 1: Overview of the attack chain

Technical Analysis

Stage 1: Powershell Downloader

The malware makes use of a small PowerShell loader that sets up the host for further exploitation. It is stored as a base64 blob and gets decoded and is used to execute a Java Archive that gets deployed to the system with the name cleo.[numerical-identifier]

The loader creates a TCP connection to a C2 IP address to retrieve a second stage payload. The formatted loader is shown below:

Figure 2: Formatted Stage 1 PowerShell Downloader script

The loader also sets a variable called Query which is used for retrieving the C2 address used in a subsequent Java backdoor and the victim IP address identifier. The C2 IP address and second stage dropper are different for each identified payload by Huntress.

Stage 2: Java Downloader

The downloaded second stage archive is decrypted by the loader using a unique AES key per payload. Upon decrypting and decompiling the second stage downloader, this contained a manifest file indicating it would run the start class upon execution.

Figure 3: Java Downloader MANIFEST.MF file

Starting from the main method, the backdoor will get the environment variable query and repair it to make it a valid base64 string. That decoded value contains the AES key, a unique value required to download stage 3, and the IPs of the C2 server and the victim host. A request is then sent to the C2 using that base64 encoded value appended with TLS v3.

Figure 4: Annotated Decompilation of main method in stage 2

A CyberChef recipe showing the output of this is as follows:

Loading Gist...

Figure 5: View of CyberChef with decoded IP addresses (victim’s address redacted)

Using the allocated space we’ve renamed as recvBuf, the dropper then keeps accepting bytes until the full stage 3 payload has been downloaded. Then using the AES key and hardcoded IV, it decrypts the downloaded data which will yield an intentionally corrupted zip. They then repair the header by cutting off the first two bytes (cC in the example we analyzed), and move onto extraction and loading. 

Figure 6: Annotated decompilation of the decryption routine

A CyberChef recipe showing the output of this is as follows:

Figure 7: View of decrypted zip file within CyberChef

Finally, the backdoor will unzip the downloaded archive using the provided helper method, and dynamically resolve the classes it contains.

Figure 8: Decompilation of start and findClass methods

Figure 9: Decompilation showing calls to start and dynamic loading of class files

Stage 3: Java Backdoor / Post Exploitation Framework

The final stage is a modular Java based post exploitation framework which contains a significant amount of functionality. It is composed of 9 class files, with the primary driver being the Cli class loaded by the previous stage. The framework supports both Linux and Windows however Huntress only observed usage on Windows.

Cli

This begins with a public class called Cli that takes three parameters passed to it from the stage 2 downloader that are stored in the variables host, cliid, and stage1fn.

Figure 10: Decompilation of Cli class assigning variables before executing run method

The passed parameters from stage 2 correspond to: 

  1. The C2 IP address retrieved from the query parameter 
  2. The exploited system IP address and associated port retrieved from the query parameter (used as a unique victim identifier) 
  3. The file name stored in the environment variable f from the downloader (in this case cleo.2607)

The class has a number of static variables defined that are used throughout the payload including one that identifies if it is operating on a Windows operating system or not.

Figure 11: Decompilation of class instance variables used by Cli

Figure 12: Decompilation of Cli class retrieving hostname and determining if it is running on Windows

This runs the main run() method and subsequent runDelFileCmd() method to delete the first stage payload (downloader) from disk (cleo.2607) using either PowerShell or Bash depending on whether the system was identified as Windows or not.

Figure 13: Decompilation of runDelFileCmd method showing deletion of stage1 payload from disk

The run method is responsible for using the SrvSlot class to queue up connections to the C2 IP address retrieved from the previous query parameter on port 443. This class extends the class Slot allowing it to access the static variable st which is used to send custom commands to the implant from the connected C2 server and is part of a custom C2 protocol. These connections will continue until the number of connections is 5 or more, and the variable st is set to something other than 200 or 500.

Figure 14: Decompilation of run method showing ishell and prs-conf commands

This class also contains two helper methods: l (logging) and dmp (hex dump). The first is used throughout the implant to log various data to a log buffer. The second allows the malware to create a valid hexdump from a provided byte array, which is then logged using the logging helper function. The hexdump function is likely for debugging purposes as it isn’t called anywhere in the implant.

Figure 15: Decompilation of logging function

Figure 16: Decompilation of annotated hex-dump function which is never used.

Dwn

A zip file management class for uploading, packaging, and handling zip files for the operators to read and collect files or directories. A number of variables are used in this class in addition to a method named Dwn responsible for logging how much time has passed since the last update and assigning a queue of files to be uploaded to the C2 server stored in an arraylist called lvs.

Figure 17: Decompilation of Dwn class

The class contains a tick method used for keeping track of time passed and sending a status update to the C2 server if more than 5 seconds had passed since the last update.

Figure 18: Annotated decompilation of tick method

The setStat method called by tick will set fields of the newly created tick packet with information about the current state of the Dwn class.

Figure 19: Annotated decompilation of the setStat method

Interestingly this class also has a check called tick2 responsible for tracking the state of the files being exfiltrated, and managing the queue of files to be exfiltrated.

Figure 20: Decompilation of tick2 method

The addFile method is used to add the files to the queue for exfiltration.

Figure 21: Decompilation of addFile method

This is supported by the readFile and write method to retrieve the file from disk.

Figure 22: Decompilation of the readFile method

Figure 23: Decompilation of write method

Other methods zipOpen, zipClose, and getCurrZipSize use a new instance of the Mos class but other than that are fairly self-explanatory.

Figure 24: Decompilation of zipOpen method

Figure 25: Decompilation of zipClose method

Figure 26: Decompilation of getCurrZipSize

DwnLevel

A simple class for managing an array of files preparing for zip archival and uploading to the attacker. It is used primarily in the Dwn class and is just for keeping state.

Figure 27: Decompilation of DwnLevel class

Mos

A small class for handling multi-archive zip files. In order to optimize zip file sending, zip files are split up for every 262154 bytes and then archived with ZipIDs during exfiltration.

This appears to be equivalent to 2 of their custom protocol packet lengths of 131072 bytes.

Figure 28: Decompiled Mos class, including getSize, write, and close methods

Proc

Proc contains implementations of the tasks that can be performed by the malware as well as a few reconnaissance capabilities on the victim’s machine. These commands are parsed in the SlotSrv class. 

The Proc class allows the remote attackers to primarily issue execution commands and read from configuration files using the run function. The ishell command (interactive shell) is a command that uses the pipeMode function to handle interactive shells indicating the malware has capability to allow an attacker to go ‘hands-on-keyboard’ and interactively run commands through a terminal. 

This aligns to activity identified by the Huntress SOC when a nltest command was seen to be interactively run by a threat actor. The run function is also the primary method used to retrieve Cleo software configuration via the prs-conf command.

Figure 29: Decompilation of run method

The confParser method will retrieve elements from within a configuration file located at conf/Top.xml on disk and retrieve all the host values from within this. For each host identified it will attempt to retrieve the corresponding xml file within hosts/<hostname.xml> and parse this to be used in a function loadUserDirs.

Figure 30: Decompilation of confParser method

The function loadUserDirs is used to parse any Host or Mailbox nodes within the host xml file and extract the aliases defined within this file. In addition this will also parse the file for references to several directories which are sent alongside the alias they are paired with.

  • Default Home Directory
  • FTP Root Path
  • Inbox
  • Sentbox
  • Outbox
Figure 31: Decompilation of loadUserDirs method

Figure 32: Decompilation of mailbox parsing portion of loadUserEnv method

This includes a method to replace any custom variables named Homedirectory and Homedirectorytype that may have been defined in the application instance with their corresponding value.

Figure 33: Decompilation of replCustomVars method

This shows that the malware author had an understanding of specific configuration files used within the Cleo software, and was interested in understanding what types of trading relationships a Cleo Harmony, VLTrader, or LexiCom instance had been configured with in addition to where payload files such as files sent or received between these businesses resided on disk.

Interestingly this contained a method called checkDir that appears to be looking for if a directory is readable or not; however, this was never called in the code so may have been used for debugging purposes or it is a feature which has not yet been implemented.

Figure 34: Decompilation of checkDir method

SFile

This class allows the attackers to perform basic read and write operations on the filesystem using FileOutputStream and FileInputStream classes. This is referenced in file handling procedures within the SvrSlot class.

Figure 35: Decompilation of SFile class

There are also a number of helper functions that help SFile read, write, and manage files:

Figure 36: Decompilation of SFile helper methods

ScSlot

This is a multi-channel communication link with the channel used for handling asynchronous communication, each ScSlot acts as a unique pipeline for data using the SvrSlot class.

Figure 37: Decompilation of ScSlot class with evConnect, evRead, and tick methods

Slot

This class is responsible for establishing and managing the connection to the C2 server. It initializes two buffers for sending and receiving data:

Figure 38: Decompilation of Slot class with connect method

SrvSlot

This class is mainly involved with receiving and sending responses from the C2 server. Upon connection to a C2 server this class will log the packet number using the method l within the Cli class for debugging purposes before setting st to 2 indicating the connection has been successfully established and was ready to receive a hello packet. This overrides a method with the same name in Slot used to determine if connection has been established or not.

Figure 39: Decompilation of evConnect method

The hello packet sent to the C2 has a 16 byte header, the structure is as follows:

Byte OffsetValue
0 - 9Math.random() * 256.0D
102
11119
1233
13-1
141
15Cli.fIsWin ? 0 : 1

An interesting bit to note is the 10 junk bytes that are added to the front of the packet. This is likely an attempt to bypass network fingerprinting techniques, however the other bytes in the packet are static making that relatively trivial.

The first packet sent to the C2 by the malware is a “hello” packet which contains an identifier paired with a hostname if it was previously retrieved in Cli (cliid + “\t” + hostname), as well as synchronization required for encrypting further packets.

Figure 40: Decompiled and annotated pktHello method

After that hello packet is sent, the implant awaits a hello response from the server which gets parsed in the prsHelloPkt method.

Figure 41: Annotated decompilation of prsHelloPkt method

SvrSlot also contains an option to record statistics on each compromised endpoint which appears to be used for health tracking of their own C2 and endpoint.

There are three debugging options used for collecting information, the primary command is #dbg# which will collect information on how many zip file errors, how many times they encountered a server queue status, and the availability of free memory on the host.

Figure 42: Decompiled #dbg# command

The command #ll# displays how many files have been added to zips referencing the Dwn Class mentioned above.

Figure 43: Decompiled #ll# command

The command #lsz# performs information about zip file gathering, detailing the last zip file size, the ID of the zip, the zip file number, and the offset of the zip file - this is likely used to debug their multi-zip Archival process performed within the Mos class mentioned above.

Figure 44: Decompiled #lsz# command

C2 Protocol Analysis

Malichus makes use of a fully custom C2 protocol. Each incoming and outgoing packet is processed by the function in SrvSlot called pkt0. It takes in a complete packet, and calculates its CRC32 value to confirm the packet's integrity. Bytes 3 and 4 are then set to the high and low bytes of that CRC32 value before it is encrypted.

Figure 45: Annotated Decompilation of pkt0 method

After the packet’s CRC32 has been updated, pkt0 calls addEncr which is responsible for encrypting the packet. There are a few global variables that are used to manage both encryption and decryption state:

Loading Gist...

The C2 and the client must stay in sync for the encryption/decryption to work as the mutation of the state and index variables are updated by the value of the previously encrypted/decrypted byte.

Figure 46: Decompilation of the encryption routine

A similar method exists for decrypting packets but it makes use of a different set of variables to keep track of the state:

Figure 47: Decompilation of the decryption routine

As we mentioned before, the pkt0 method is the final packet constructor used by all other 6 packet types, most of which are overloaded methods of the name pkt. These each have slightly different uses but share the commonality that the first byte is the packet identifier. There are 4 generic packet types, and 2 specialized ones. Of the generic packets:

Figure 48: Annotated decompilation of pkt1

Figure 49: Annotated decompilation of pkt

Figure 50: Annotated decompilation of pkt

Figure 51: Decompilation of pkt helper

The two special packets that are used are helloPkt and zipPkt, with the helloPkt being detailed earlier:

Figure 52: Decompilation of zipdata special packet

After the packets are created by pkt0, they are put into the outbuf queue and are sent to the C2 using the evWrite method in the Slot class:

Figure 53: Decompilation of evWrite method used to send packets to the C2

Conclusion

Whilst the full extent of who created this malware and why still remains under speculation, this blog hopes to shine a light on a new family of malware targeting Cleo software, its functionality, and gives potential insight into what the malware author was hoping to achieve with this custom piece of malware.

This blog post was independently created in tandem with other industry vendors, and mostly aligns with analysis also independently performed by:

Valid C2 Packet Identifiers

Packet NumberValueExplanation
1HelloHello packet to initialize connections
2ChannelGet C2 channel identifier
3DataData Packet
4Prepare FilePrepares a file for upload
5Close ChannelKill C2 channel and any open handles
6EchoReturn received data to C2
7HaltResets input buffers
8CommandRun received command
9StopStop process, channel, or file transfer
10ResumeRestart process, channel, or file transfer
11AckConfirm synchronization of processed commands
12File I/O StatusConfirms that implant is able to receive or process zip data
13Zipdata ReassemblyReassemble zip chunks and remove them from a queue

IOCs

FilenameSHA256
cleo.26076705eea898ef1155417361fa71b1078b7aaab61e7597d2a080aa38df4ad87b1c
Cli0c57b317b572d071afd8ccdb844dd6f117e20f818c6031d7ba8adcbd32be0617
Dwn429d24e3f30c7e999033c91f32b108db48d669fde1c3fa62eff9da2697ed078e
DwnLevelf80634ce187ad4834d8f68ac7c93500d9da69ee0a7c964df1ffc8db1b6fff5a9
Mos0b7b1b24f85a0107829781b10d08432db260421a7727230f1d3caa854370cb81
Proc1ba95af21bac45db43ebf02f87ecedde802c7de4d472f33e74ee0a5b5015a726
SFile57ec6d8891c95a259636380f7d8b8f4f8ac209bc245d602bfa9014a4efd2c740
ScSlot87f7627e98c27620dd947e8dd60e5a124fdd3bb7c0f5957f0d8f7da6d0f90dee
Slot1e351bb7f6e105a3eaa1a0840140ae397e0e79c2bdc69d5e1197393fbeefc29b
SrvSlotf4e5a6027b25ede93b10e132d5f861ed7cca1df7e36402978936019930e52a16

YARA Rules

https://github.com/huntresslabs/threat-intel/tree/main/2024/2024-12/Cleo

Share

Sign Up for Huntress Updates

Get insider access to Huntress tradecraft, killer events, and the freshest blog updates.

By submitting this form, you accept our Terms of Service & Privacy Policy
Oops! Something went wrong while submitting the form.
Huntress at work