Remote Access Trojan · Worm · Dropper
VioletWorm - game.exe
- Author
- Moise Medici
- Updated
- 07 Sept 2026 · Completed
- Difficulty
- Easy
- Platform
- Capabilities
- Tags
Class FCTJQlq
As before, this class does not appear to rely on other classes, so it is a good candidate for analysis. It contains several encoded strings, which can be renamed as before.
| Obfuscated Name | Renamed as | Content |
|---|---|---|
ds9rgn5MhzCuV7xsVI8tGoogle | PasteUrl | %PasteUrl% |
c54QTcBcmf6rGgyNXjL6Google | windowsUpdateURL | https://windowsupdate.microsoft.com |
yKv0YAEQkb99PUiIYS7DGoogle | windowsDefenderURL | https://winatp-gw-cus.microsoft.com |
ls0B3Lof8k5bwCv19DrTGoogle | microsoftWatsonURL | https://watson.microsoft.com |
7ksKGxclxqpEMHkGktxNGoogle | msEdgeURL | https://msedge.api.cdp.microsoft.com |
LZhAGxcMZQ9U1O5AAPv5Google | windowsFwLinkURL | https://go.microsoft.com/fwlink/ |
9Dhiv0ZpRphfFeHIDrEIGoogle | microsoftActivationURL | https://activation.sls.microsoft.com |
1yGgUQDMNWiX2IFEFpmLGoogle | XSXSXS | XSXSXSX |
N3fGJvjHrDlZqOhGoH9JGoogle | Violet | <Violet> |
ofuoTiOcboEgTpGyTz7ZGoogle | USBexe | USB.exe |
7vUJbT6N0zPxQdW88rm0Google | randomString | QakjxWa1r8Oh6UTB |
gH6CzFFBIjnoaKcsUlMzGoogle | mutexVar | None |
MAMc9HZf28Z1NGiZZSK5Google | boolVar | None |
N4FgM0WGB4NnemOgvt3rGoogle | currentProcessName | The output of Process.GetCurrentProcess().MainModule.FileName |
The other strings in the module are base64 encoded, but they are also passed as an argument to the method fZ8aUVb31M2Cahmhu5ZI:
public static string 8owwMuJjqLlo9pPUhCrsGoogle = Encoding.UTF8.GetString(Convert.FromBase64String(Encoding.UTF8.GetString(Convert.FromBase64String("WkVkT01XWklXbkpSU0ZKdlpFaDRkMkZSUFQwPQ=="))));
// Token: 0x0400001A RID: 26public static string DQSEYDpOuwltw1eNOARKGoogle = FCTJQlq.fZ8aUVb31M2Cahmhu5ZI(FCTJQlq.8owwMuJjqLlo9pPUhCrsGoogle);Looking at that method, the code is:
public static string fZ8aUVb31M2Cahmhu5ZI(string encryptedText){ string @string = Encoding.UTF8.GetString(Convert.FromBase64String(Encoding.UTF8.GetString(Convert.FromBase64String("UlZCRlVrZFpiZz09")))); List<char> list = new List<char>(); int num = 0; byte[] array = Convert.FromBase64String(encryptedText); checked { foreach (byte b in array) { char c = Strings.Chr((int)((byte)((int)b ^ Strings.Asc(@string[num])))); list.Add(c); num = (num + 1) % @string.Length; } return new string(list.ToArray()); }}It starts by creating a variable called string, which is the double decoded value of UlZCRlVrZFpiZz09. It then decodes the argument it received, and for each of the bytes in the decoded string it converts the byte to a number and XORs it with the integer value of the character at the index of the current iteration. For example, if the content of string is abcdef and the content of encryptedText is moise, then on the first iteration, where num is 0, it takes a, converts it to an integer (97) and XORs it with the integer of m (109). The result is added to list and num gets incremented by 1.
The instruction:
num = (num + 1) % @string.Length;This increments num while also keeping it inside the bounds of string. If the input string is 30 characters long while string is 10 characters long, at the 11th iteration there would be no characters left to extract from string, resulting in an index error. Using the modulo operator (%), num always grows up to the length of string and then restarts from 0.
To get the strings without executing the code, we can run them through the following script:
import base64import sys
def decrypt(encoded_constant: str) -> str: key = base64.b64decode(base64.b64decode("UlZCRlVrZFpiZz09").decode("utf-8")).decode( "utf-8" )
encrypted_text = base64.b64decode( base64.b64decode(encoded_constant).decode("utf-8") ).decode("utf-8")
encrypted_bytes = base64.b64decode(encrypted_text)
result = [] key_index = 0
for b in encrypted_bytes: result.append(chr(b ^ ord(key[key_index]))) key_index = (key_index + 1) % len(key)
return "".join(result)
def main() -> None: encoded_constant = sys.argv[1] result = decrypt(encoded_constant)
print(result)
if __name__ == "__main__": main()The script is a direct translation of fZ8aUVb31M2Cahmhu5ZI, with the two decoding steps of the field initializer folded into it so that a base64 literal can be copied straight out of the decompiled source and passed on the command line.
decrypt starts by rebuilding the key: UlZCRlVrZFpiZz09 decoded twice from base64 gives EPERGYn, the seven character key used for every constant in the class. The argument then goes through the same two decodes, which is what the field initializer does before calling the method, followed by a third one, which is the Convert.FromBase64String(encryptedText) inside the method itself. Three decodes in total, and only then is there anything to XOR.
The loop is the foreach from the C#: every byte is XORed with the key character at key_index, and key_index = (key_index + 1) % len(key) is the same wrap-around described above, so a seven byte key covers an input of any length. Running it against the first constant of the table returns the IP address:
$ python3 string_decryptor.py WkVkT01XWklXbkpSU0ZKdlpFaDRkMkZSUFQwPQ==130.12.181.70This allows us to understand the content of some variables:
| Obfuscated Name | Renamed as | Content |
|---|---|---|
8owwMuJjqLlo9pPUhCrsGoogle | EncryptedIpAddress | Encrypted value of 130.12.181.70 |
DQSEYDpOuwltw1eNOARKGoogle | IpAddress | 130.12.181.70 |
ztkMDEbqIOb4pkOR5r8JGoogle | EncryptedPort | Encrypted value of 7000 |
Fuou0srIKBFgkhtK6GATGoogle | Port | 7000 |
The assumption that 7000 is a port number is based on the previous variable being an IP address; if it turns out not to be used as a port number, it can be renamed.
So for now the function has been renamed to stringDecryptor and the class to EncodedConstants.
The longer strings do not appear to be passed to the stringDecryptor function. Decoding them with Base64 twice returns another very long string that does not look like Base64 either. For now, they can be skipped while their usage is investigated.