Fun with Minecraft infostealer malware
Introduction
One day, a friend and I were bored one night, so we decided to look for some fun Minecraft mod malware floating around on the internet. We each dug around for our own samples, and I came across this video on YouTube:
This just looks like a traditional cheat for gaining experience in the large Minecraft server, Hypixel. So, I downloaded the file, decompiled the Java, and spent some time reversing its functionality.
There wasn’t anything immediately outright malicious, but maybe a little bit sketchy, and this led me down a rabbit hole until I found the final payload, which was an infostealer!
Infection sequence
This malware has four primary files, although I was only able to get ahold of three of them.
- Hikari-1.7.2.jar - Loaded and executed through Minecraft’s mod loader, downloads and executes a DLL file called
HikariDLL.dll
. - HikariDLL.dll - Upon execution, downloads and executes a .jar file called
discord.jar
. - discord.jar - The primary infostealer payload, downloads and executes a Jar file called
load.jar
. Has components to steal Discord tokens, browser credentials, sends screenshots of the victim machine, and more. - load.jar - Downloaded by
discord.jar
, but the file was unavailable.
With that, let’s get into the details of each!
Hikari-1.7.2.jar
MD5: c1de2f27030bca62ffbd70ace40d2052
Passing this into VirusTotal, there doesn’t seem to be anything inherently suspicious right off the bat.
This theme continues as I started looking at the decompiled Java. There really isn’t much going on here. Just some dependencies, one class called CustomGui.class
, and a text file with some Minecraft mod information called mcmod.info
.
Looking at mcmod.info
, we do get a Github link, which could come in handy later.
Looking at CustomGui.class
, we can see a DLL file is downloaded from the endpoint hxxp://84.19.3.61:2/HikariDll.dll
, written to a temporary file, and is called through System.load()
, a library used to load and run Windows DLLs.
We can see that it calls a DLL export called startHikari()
.
So, after Hikari-1.7.2.jar
is loaded and executed through Minecraft, it loads and executes a DLL file. Let’s look at that!
HikariDLL.dll
MD5: 3fe4976bada010959103f35ae92b53ce
Passing this into VirusTotal, we do receive a few hits, claiming this DLL is just generically malicious. Of course, false positives do exist depending on the behavior of the program, so I’ll do some manual analysis to see if anything sticks out.
Static analysis
Loading this into various PE analysis tools (CFF Explorer, pestudio, etc.) for some basic static analysis, we can extract some generic information like the language this was written in, as well as some of the imports and exports of the DLL.
Here, we can see this is a C++ program.
Taking a look at the DLL exports, we can see that Java_com_example_CustomGui_startHikari
is called, which seems to make sense given that the previous file, Hikari-1.7.2.jar
, calls this export.
Looking at the DLL imports, there’s not much going on here, and nothing seems outright malicious or immediately suspicious.
Looking at the specific imports called, there were a few flagged, but whether this is malicious or suspicious depends on the context of the functionality. I did make note of the network-related ones, such as InternetReadFile
, InternetOpenUrlA
, and more.
Running strings against the DLL file, however, did show some interesting information.
We can immediately see a URL pointing to hxxp://84.19.3.61:2/discord.jar
, as well as some components making up a web request (POST /apiv1/token HTTP/1.1
, Content-Type: application/json
, etc.) We can also see an explicit path for the DLL’s Program Database file, which does disclose a username!
I then immediately pulled down the discord.jar
file.
Dynamic analysis
Unfortunately, I couldn’t do any dynamic analysis, as HikariDLL.dll
needs to be directly loaded by Hikari-1.7.2.jar
, and Hikari-1.7.2.jar
needs to be directly loaded by Minecraft’s mod loader.
While I could configure an environment to properly execute this code, we have a relatively good idea of what’s going on given the limited amount of static analysis done.
discord.jar
MD5: 0da78f971d191d8f326f8f5fc0f27215
Here’s where things get fun!
Passing the MD5 hash VirusTotal revealed some interesting information. We receive much more hits than the previous two files, with results more specifically pointing to this potentially being some form of trojan malware.
Looking at the decompiled Java, there’s immediately a lot more going on, as we have more external dependencies and libraries called. Looking through most of them, really the only ones that seem to matter for the trojan functionality are here:
Main.class
Immediately, there are two variants of a function called addonLoad()
. Both of them check the public IP address of the victim host using Amazon AWS’ checkip endpoint, and further attempts to download a file called load.jar
from hxxp://188.225.35.18
.
If the download is successful, the program then executes a method inside load.jar
called huy()
on a class called dev.loveeev.Start
, which likely performs other forms of exfiltration.
Unfortunately, at the time I obtained the sample, the hxxp://188.225.35.18/load.jar
resource was unavailable and hasn’t come back online since.
Looking at the variants of addonLoad()
, the first variant, addonLoad(String session, String TEST)
, uses hardcoded placeholder strings such as “THIS STALCRAFT STEAL” and “TEST”, likely for testing/debugging purposes.
The second variant, addonLoad(Object minecraft, String hwid, String pizza)
, directly interacts with a minecraft
object, using reflection to extract session-related information such as the token, SSSID, username, UUID, and public IP address.
Lastly, both call the sendWebHook()
function which formats the gathered information as JSON, and uploads it to hxxp://188.225.35.18:5000/api
. This labels and formats the aforementioned token, username, UUID, and public IP address, but not the SSSID or the session.
Before we continue, it’s worth noting that the following files (Start.class
, Data.class
, MurderTasks.class
) are never directly referenced within Main.class
, despite being present in /org/examples
. Main.class
acts independently, interacting solely with the load.jar
file that was downloaded. Nonetheless, the following components still reveal lots of interesting behavior, so let’s keep going!
Start.class
After looking at the remaining code pieces, Start.class
is really the key program that orchestrates and handles most of the exfiltration functionality.
Looking at the main()
function, the following happens:
- Calls
getDriver
, which loads a jdbc.sqlite database driver - Calls
Data.class
, which obtains user browser config and credential locations (Local State, Login Data) - Calls
MurderTasks.class
to kill browser instances - Calls
sendData
to format and upload the exfiltrated data returned fromPasswords.class
,Huyna.class
, andAutofill.class
. - Calls
ScreenshotSender.class
to take a screenshot of the host and upload it
Before diving into the other classes, let’s take a closer look at the local functions, being getDriver
and sendData
.
getDriver
simply attempts to load the org.sqlite.JDBC
driver. jdbc:sqlite
is a common database used for storing browser credentials, and is used by many web browsers. If it cannot load the driver locally, it will attempt to download it directly from the Apache Maven repository at runtime, followed by storing it in %APPDATA%
in a file called sqlite-jdbc-3.43.0.0.jar
.
Next, sendData
has three hard-coded endpoints:
hxxp://84.19.3.61:1/apiv1/discord
hxxp://84.19.3.61:1/apiv1/autofills
hxxp://84.19.3.61:1/apiv1/passwords
With the information passed in from main()
, it will format this information accordingly and call sendPostRequest
to upload the information with the previously-initialized URL strings.
With that, let’s take a look at the immediate classes that were called, being Data.class
, MurderTasks.class
, Passwords.class
, Huyna.class
, Autofill.class
, and ScreenshotSender.class
!
Data.class & BrowserConfig.class
This is the first class called from Start.class
, and it’s relatively simple. It defines static file paths for the Local State
and Login Data
files used by various Chromium-based web browsers, relative to the user’s %APPDATA%
and %LOCALAPPDATA%
directories. These paths are stored in a hash map called BROWSERS
, with each entry tied to a specific browser.
The Local State
file contains browser-specific configuration data such as extensions, user preferences, and encrypted keys. These encoded encryption keys can be extracted and potentially used to decrypt saved credentials.
The Login Data
file stores website credentials stored in the browser. It primarily contains the the URL of the website saved, as well as the associated username and password.
For Chromium-based browsers, these passwords are typically encrypted using DPAPI (Windows Data Protection API), but decryption is theoretically possible using the key found in the Local State
file, especially if done on the same host where the credentials were initially created.
For each of the valid browsers found, the full paths for the Local State
and Login Data
files are essentially stored in BrowserConfig.class
.
MurderTasks.class
Like Data.class
, MurderTasks.class
is another relatively straightforward and simple program. When called, this will list the running processes, looking for chrome.exe
, browser.exe
, brave.exe
, msedge.exe
, opera.exe
, and vivaldi.exe
.
If any of the predefined browser names are found, it will invoke the Windows command line (cmd.exe /c
) to execute Powershell’s taskkill
command with the /IM
(Image Name) and /F
(Force) flags. The full command looks something like this:
cmd.exe /c taskkill /IM <browser name> /F
This is likely done to ensure the browser data files (Login Data
, Local State
) are not process locked or in use when the malware attempts to interact with them.
Passwords.class
This one is pretty neat. Given the data obtained and stored from Data.class
and BrowserConfig.class
, this program will attempt to extract and decrypt credentials in the Login Data database for each browser installed.
It will first loop through all of the paths defined in BrowserConfig.class
and ensure these paths exist. If they do, it will call extractAndWritePasswords()
with the current browser, Local State path, and Login Data path.
Following this, extractAndWritePasswords()
will attempt to extract the encryption key out of the Local State file and then decrypt it using Windows DPAPI. After this, the program will create a copy of the Login Data database and perform SQL queries against it to extract the origin_url
, username_value
, and password_value
.
It will ensure the password is not empty and is greater than 15 characters long, and if it is, it will call decryptPassword
to perform the operations necessary to decrypt the password.
Looking at the actual operations used to decrypt the passwords obtained, we can see that decryptPassword
utilizes AES/GSM, with some additional handling involved. Looking at Win32CryptUnprotectData
, this is previously used to call the DPAPI function to decrypt the encryption key on host.
Huyna.class
This is another interesting one! This will attempt to find various discord tokens from different paths on the system, extract and decrypt them, followed by verifying the tokens are valid.
Looking at the DiscordTypes
array, we can see that various locations relative to the user’s %APPDATA%
and %LOCALAPPDATA%
directories are specified, all pointing to a file called leveldb
, which contains information like usernames, tokens, and more.
When getDiscordToken
is called, the DiscordTypes
paths are verified to be associated with discord (as stated by the contains("iscord"))
). If a directory is found, it will read in the Local State
information, obtaining the Discord key.
However, if the path exists but does not contain "iscord"
, it will utilize regex to find the Discord token within the .ldb
file directory.
Then, for any Discord tokens discovered and decrypted, it will test them against Discord’s official validation endpoint (https://discord.com/api/v9/users/@me). If the token is valid, the program will then output the user’s ID as well as their username.
Autofill.class
This is another browser-based exfiltration program, but instead of targeting stored passwords, this one targets autofill information, such as name, addresses, and card information. It defines static file paths for the Local State
and Web Data
files used by various Chromium-based web browsers, relative to the user’s %APPDATA%
and %LOCALAPPDATA%
directories. These paths are stored in a hash map called BrowserPaths
, with each entry tied to a specific browser.
We’ve seen the Local State
file in the past, which contains browser-specific configuration data such as extensions, user preferences, and encrypted keys. However, this is the first time Web Data
has been seen, which contains information such as autofill or form data, which may contain credit card info, addresses, and more.
After setting these paths, extractAutofillData()
is called, which first validates the path exists. If it does, it will call extractBrowserAutofillData
, which connects to the Web Data
database using the jdbc:sqlite
driver, and specifically queries for autofill information. If the information is valid and not empty, it will organize the autofill field name, the value stored within it, and the browser it was extracted from.
ScreenshotSender.class
The last of the classes called from Start.class
is ScreenshotSender.class
. Simply put, this calls Robot
a native java.awt
package that allows a program to capture screen images among other things, to take a screenshot and write it to a temporary .png
file on disk.
After this, it will prepare the screenshot image for upload, sending it up to hxxp://202.181.188.121:1/apiv1/screen
.
Conclusion
This whole thing started with a seemingly harmless, although sketchy, Minecraft mod, and ended with a full-blown infostealing malware analysis project! I definitely wasn’t expecting this chain of events to unfold, and it was a fun rabbit hole to go down.
In traditional fashion, I did attempt to send funny messages and garbage data following the syntax to each endpoint, but sadly, I was unsuccessful! Just for fun, here’s a quick list of all of the IOCs:
hxxp://84.19.3.61:2/HikariDll.dll
hxxp://84.19.3.61:2/discord.jar
hxxp://188.225.35.18/load.jar
hxxp://188.225.35.18:5000/api
hxxp://84.19.3.61:1/apiv1/discord
hxxp://84.19.3.61:1/apiv1/autofills
hxxp://84.19.3.61:1/apiv1/passwords
hxxp://202.181.188.121:1/apiv1/screen
For my first blog post analyzing malware found in the wild, this was a lot of fun! I don’t have a ton of experience with Java, but this was a great little way to get somewhat familiar with some of the characteristics of the language. :)