Post

WhiteSnake

WhiteSnake is an information-stealer/RAT that extracts a range of sensitive information from infected computers. It’s considered to be MaaS as it’s being sold on underground forums with a monthly fee of $120, an annual fee of $900, and a lifetime fee of $1,500.

It was first observed in early February of 2023. It has been developed in .NET language and can run on Windows and Linux as a cross-platform.

This sample I have is version "1.5.9.9" of WhiteSnake and it’s capable of extracting credentials from various Browsers and Apps, CryptoCurrency wallets, Syetem files as well as dropping (and executing) additional payloads in addition to Keylogging functionality.

What is pretty cool about this sample is that it uploads stolen data to Telegram and uses Tor (Onion Routing) as a channel to communicate with C&C Servers.

Sample Info

PropertyValue
File Type32-bit executable
SHA-256C219BEAECC91DF9265574EEA6E9D866C224549B7F41CDDA7E85015F4AE99B7C7
SSDEEP6144:H6Vo3IhHN5ya1R64TxT8jWHgf8YJkVHC++VeQPBZnq0LZYSwFxQx9tkD9bMLtttB:afhtHxpmWHgf8Y6/Qp1nLiDKyOLt

And a funny compiler stamp of Oct 20, 2071 :)

Anti-Analysis

WhiteSnake uses an Anti-VM technique, it queries the machine manufacturer and compares it with a list of blacklisted values :

1
virtual, vmbox, vmware, thinapp, VMXh, innotek gmbh, tpvcgateway, tpautoconnsvc, vbox, kvm, red hat, qemu

So those include VirtualBox, VMware, RHEV, Linux KVM, QEMU products.

You can change the manufacturer by editing the RegKey in HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\OEMInformation

In general, if you want to test your VM against similar checks you can use PaFish and VBox Cloak to “hide” your Vbox machine.

Or alternatively, you can just use the results from a Sandbox if the sample was already scanned.

It’s worth noting that it makes plenty of junk network traffic to Kz domains and other sites

network

Needless to say, the sample is obfuscated with a simple XOR routine and of course classes, methods and variable names are “scrambled”. As well as it has a fair amount of junk code including the code responsible for uploading random data to legitimate domains as we saw earlier.

code obfuscation

It’s implementing a custom obfuscator yet we can use de4dot to try cleaning up a bit.

We notice the method Izdaenrvjoeqtlyorjlrbo that is responsible for string decryption, it takes in the encrypted string and the key as parameters and returns the decrypted string. Just a simple XOR.

str_decryption

There are two ways we can use to bypass that :

  • Using de4dot or a PowerShell script to invoke the method by name/token, yet I don’t prefer this way as the method name could change and there could be more than one method responsible for string decryption.
  • We can use a more generic PowerShell script to invoke the decryption routine(s) by searching by its signature (the method takes in 2 string parameters and returns a string value). Good chance to practice powershell scripting.

Here’s the script I used, I commented on it explaining what it does.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# Load dnlib by Reflection
[System.Reflection.Assembly]::LoadFile("C:\Users\REM\Desktop\WhiteSnake\dnlib.dll") | Out-Null   

# Load the .NET assembly
$dot2Patch = "C:\Users\REM\Desktop\WhiteSnake\WhiteSnake_Stealer.bin"
$patchedDot = $dot2Patch + "_mod.bin"

# Looks for a method with 2 params (both string) and return value string - finds decryption method by signature
function FindStringDecryptionMethod($methods)
{
    foreach($method in $methods)
    {
        if(-not $method.HasBody){continue}
        if($method.Parameters.Count -eq 2 -and $method.Parameters[0].Type.FullName -eq "System.String" -and $method.Parameters[1].Type.FullName -eq "System.String" -and $method.ReturnType.FullName -eq "System.String")
        {
            return $method
        }
    }
    return $null   
}


$moduleRefl = [System.Reflection.Assembly]::LoadFile($dot2Patch).modules
$moduleDefMD = [dnlib.DotNet.ModuleDefMD]::Load($dot2Patch)

# Iterate all methods
$methods = $moduleDefMD.GetTypes().ForEach{$_.Methods}
$decryptionMethod = FindStringDecryptionMethod -methods $methods

$global:methodsToRemove = @($decryptionMethod)

foreach($method in $methods)
{
    if(-not $method.HasBody){continue}

    # Saves IL to array for easier parsing of method calls and finding its arguments
    foreach($instr in $method.MethodBody.Instructions.ToArray()) 
    {
        # Looks for call instruction
        if($instr.OpCode.Name -like "call" -and $instr.Operand -eq $decryptionMethod)
        {
            $indexDecryptionMethodInstr = $method.MethodBody.Instructions.IndexOf($instr) # index of the call
            $stateStr1 = $method.MethodBody.Instructions[$indexDecryptionMethodInstr-2].Operand # 1st argument - data
            $stateStr2 = $method.MethodBody.Instructions[$indexDecryptionMethodInstr-1].Operand # 2nd argument - key
            
            # Invoke method to decrypt
            $decryptedString = ($moduleRefl.ResolveMethod($instr.Operand.MDToken.ToInt32())).Invoke($null, @($stateStr1, $stateStr2))

            # Workaround to avoid patching of branch target (this way will refresh the branch target)
            $method.MethodBody.Instructions[$indexDecryptionMethodInstr-2].Opcode = [dnlib.DotNet.Emit.OpCodes]::Ldstr
            $method.MethodBody.Instructions[$indexDecryptionMethodInstr-2].Operand = $decryptedString

            # NOP out call instruction and 2nd argument 
            $method.MethodBody.Instructions.RemoveRange($indexDecryptionMethodInstr-1, 2)
        }
    }
    # corrects the instruction offsets
    $method.MethodBody.UpdateInstructionOffsets() | Out-Null
}

# Removes decryption methods
foreach($method in ($global:methodsToRemove | Sort-Object -Property MDToken -Unique))
{
    $method.DeclaringType.Remove($method)
}

# Writes modified file
$moduleWriterOptions = [dnlib.DotNet.Writer.ModuleWriterOptions]::new($moduleDefMD)
$moduleWriterOptions.MetadataOptions.Flags = $moduleWriterOptions.MetadataOptions.Flags -bor [dnlib.DotNet.Writer.MetadataFlags]::KeepOldMaxStack
$moduleDefMD.Write($patchedDot, $moduleWriterOptions)

I highly recommend taking a look at Dump-GUY <3

And voila :

decrypted

We got a persistence mechanism using the Startup folder.

Persistance and Spreading

persistance

Here’s the cleaned method for the startup persistence

persistance in startup

It also uses scheduled tasks, it will launch a hidden cmd window and run this command

schtasks /create /tn "WhiteSnake_Stealer" /sc MINUTE /tr "C:\Users\REM\AppData\Local\EsetSecurity\WhiteSnake_Stealer.exe" /rl HIGHEST /f && DEL /F /S /Q /A "C:\Users\REM\Desktop\WhiteSnake\WhiteSnake_Stealer.exe" &&START "" "C:\Users\REM\AppData\Local\EsetSecurity\WhiteSnake_Stealer.exe"

This runs a scheduled task called “WhiteSnake_Stealer” which runs the malware every minute. The command also deletes the malware from its original running directory. Note that it will check the priority it’s running on and change that accordingly additionally, it will create a folder called EsetSecurity in the C:\...\AppData\Local directory and copy itself there beforehand.

It will also create a mutex name mefr3hjdol

Spreading

WhiteSnake will check if there’s a removable device with a free space larger than 5GBs and copies itself there.

USB Spreading

Communication with C&C Servers

WhiteSnake uses Onion Routing (Tor) to communicate with C2, which is pretty cool and not so common I belive.

It would download and initialize Tor if it’s not present on the system and launches the “Beacon” process with the following config :

1
2
3
4
5
6
SOCKSPort Port
ControlPort Port+1
DataDirectory appdataLocal/76spawx7mu/data
HiddenServiceDir appdataLocal/76spawx7mu/host
HiddenServicePort 80 127.0.0.1:Port
HiddenServiceVersion 3

The port is read from the port.dat file or assigned a random value between 2000 and 7000.

You can read more about it here if you want.

After it finishes it will save the Onion Address to upload it with the report to Telegram but we will get to that later.

Next, it starts listening to hxxp://127.0.0.1:Port/ waiting for commands coming from the C2. Here’s a list of commands and what it does :

CommandFunctionality/Response
REFRESHGenerates and sends the report (base64 encoded) containing the stolen creditentials with additional system information (I’ll get through the details later).
COMPRESS <FilePath>Compress the file using tar indicating its compressed size
PINGReplies with ">> PONG <Currently active window title> <Key-logged Window Names>"
UNINSTALLKills the Tor Beacon process and deletes itself using cmd with command /C chcp 65001 && ping 127.0.0.1 && DEL /F /S /Q /A <Executable_Path> then exits
WEBCAMChecks for Camera/Image device using Object searcher SELECT * FROM Win32_PnPEntity WHERE (PNPClass = 'Image' OR PNPClass = 'Camera') , capture an image via webcam, bse64 encode it then sends it
KEYLOGGER STARTStart keylogging by setting a hook to the current active Window using SetWindowsHookExA and recording Keystrokes (I’ll get into how its using native/unmanaged code later)
KEYLOGGER STOPStops keylogging, removes the hook using UnhookWindowsHookEx
KEYLOGGER VIEW <Window>Views the keylogged keys for a Window
LIST_PROCESSESLists current processes names and IDs
GET_FILE <File_Path>Reads the file, base64 encode it and sends it as a response
SCREENSHOTCaptures a Screenshot, base64 encode it and sends it as a response
DECOMPRESS <Compressed_File>Unzips a tar archive. Returns decompressed file path (in current directory)
LOADER <File>Downloads a file into LocalAppData folder (Without executing it)
LIST_FILESLists all files and directory files in current directory aand their size in KBs
TRANSFER <File_Name>Uploads the specified file and returns its download URL. It uploads the file into one of the hardcoded IPs concatenated with the file name converted to hex. Example for "File_Name" : hxxp://140.238.218.94:8080/%46%69%6C%65%5F%4E%61%6D%65
DPAPI <File>Decodes file from base64 then uses CryptUnprotectData to decrypt file contents. (This API is used to decrypt protected data such as saved passwords in browsers as only a user with the same logon credential as the user who encrypted the data can decrypt the data)
LOADEXEC <File>Downloads a file into LocalAppData folder and executes it
cd <Path>Change directory
<Command>Executes command via hidden cmd window

Here’s a list of hardcoded IPs used to hold uploaded files :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public static readonly string[] IP_List = new string[]
 {
  "hxxp://140.238.218.94:8080",
  "hxxp://46.235.26.83:8080",
  "hxxp://168.119.121.16:8080",
  "hxxp://51.77.125.62:8080",
  "hxxp://185.189.159.121:8001",
  "hxxp://65.21.49.163:8080",
  "hxxp://167.86.115.218:9090",
  "hxxp://46.226.106.173:8080",
  "hxxps://89.46.80.136:443",
  "hxxp://77.240.38.138:8080",
  "hxxp://138.201.197.74:8080",
  "hxxps://164.90.185.9:443",
  "hxxp://92.63.70.131:8090",
  "hxxp://66.135.10.176:8080",
  "hxxp://107.161.20.142:8080",
  "hxxp://18.191.188.207:80",
  "hxxp://216.250.190.139:80",
  "hxxp://15.206.54.77:8080",
  "hxxp://39.96.33.40:8080",
  "hxxp://66.42.56.128:80",
  "hxxp://34.216.14.238:8080",
  "hxxp://116.196.97.232:8080",
  "hxxp://123.129.217.85:8080",
  "hxxp://139.224.8.231:8080",
  "hxxp://101.42.17.170:8080",
  "hxxp://47.100.180.12:80",
  "hxxp://115.231.163.168:8082",
  "hxxp://218.146.86.163:8888",
  "hxxp://85.8.181.218:80",
  "hxxp://121.63.250.132:88",
  "hxxp://124.223.67.212:5555",
  "hxxp://18.228.80.130:80",
  "hxxp://175.24.204.219:8080",
  "hxxp://106.52.28.126:8888",
  "hxxp://106.15.66.6:8080",
  "hxxp://47.110.140.182:8080",
  "hxxp://149.28.31.122:8080",
  "hxxps://192.99.196.191:443",
  "hxxps://44.228.161.50:443",
  "hxxp://189.115.56.226:8080",
  "hxxp://175.178.39.50:8080",
  "hxxps://52.68.125.8:443",
  "hxxps://138.2.92.67:443",
  "hxxps://54.178.235.46:443"
 };

Dynamic API Resolving

WhiteSnake is leveraging the capability of using Native Windows APIs (unmanaged code) in C# Applications (managed code). We can identify it by looking at the ImpMap we can see the following

imp_map

Here’s a list of the loaded APIs :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Kernel32.dll :
  GetModuleHandleA

User32.dll :
  GetForegroundWindow
  GetWindowTextLengthA
  GetWindowTextA
  GetWindowThreadProcessId
  SendMessageA
  SetWindowsHookExA
  UnhookWindowsHookEx
  CallNextHookEx
  GetKeyState
  GetKeyboardState
  GetKeyboardLayout
  ToUnicodeEx
  MapVirtualKeyA

avicap32.dll :
  capCreateCaptureWindowA

crypt32.dll :
  CryptUnprotectData

Another way is to use Get-PDInvokeImports to get the tokens of invoked modules and save it to a dnSpy bookmark file like so :

Get-PDInvokeImports

Here’s a cleaned version :

Get-PDInvokeImports_Cleaned

Parsing Data

WhiteSnake uses a large hardcoded XML file as a config file for the type and location of data to steal. In addition to exfiltrating desktop documents with extensions : *.txt, *.doc*, *.xls*, *.kbd*, *.pdf , It parses saved credentials and wallets in Gecko and Chromium-based browsers.

Parses and steals wallet addresses, including the public and private keys and transaction history.

Steals tokens, user ID and authentication hash from VPNs in addition to software credentials indicated below..

It steals data from the following :

  • Browsers :

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    Firefox (Gecko based)
    Thunderbird (Gecko based)
    Chrome
    YandexBrowser
    Vivaldi
    CocCoc
    CentBrowser
    Brave-Browser
    Edge
    Opera
    OperaGX
    
  • Wallets :

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    
    atomic 
    Wasabi 
    Binance 
    Guarda 
    Coinomi 
    Bitcoin 
    Electrum 
    Zcash 
    Exodus 
    JaxxLiberty 
    JaxxClassic 
    Metamask
    Ronin
    BinanceChain
    TronLink
    Phantom
    
  • VPNs :

    1
    2
    
    Windscribe 
    AzireVPN 
    
  • Software :

    SoftwareStolen Information
    Authy DesktopCredentials
    WinAuthCredentials
    The-Bat!Account information
    WinSCPHostName, UserName, Password and sessions
    CoreFTPHost, Port, User, Password and sessions
    FileZillaConfigured sites and FTP servers Credentials
    snowflakeSessions
    OBS StudioOBS connected services (authentication tokens to twitch, youtube, etc..)
    SteamConfigurations, friends Steam ID, Library, etc..
    DiscordCredentials
    OutlookEmails, Passwords, SMTP/EAS Servers and Credentials
    FoxmailAccount
    SignalContact list and chat history
    PidginAccount
    TelegramSecret chats

I highly recommend taking a look at this playlist <3 to get better insights on the dynamics of info stealers.

Data Exfiltration

Stolen and gathered data are populated in the dollwing struct :

1
2
3
4
5
6
7
8
9
10
[XmlType("report")]
[Serializable]
public struct report
{
 [XmlArray("files")]
 public file_info[] files { get; set; }

 [XmlArray("information")]
 public information[] information { get; set; }
}

file_info struct :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[XmlType("file")]
[Serializable]
public struct file_info
{
 [XmlAttribute("filename")]
 public string filename { get; set; }

 [XmlAttribute("filedata")]
 public byte[] filedata { get; set; }

 [XmlAttribute("filesize")]
 public long filesize { get; set; }

 [XmlAttribute("createdDate")]
 public long createdDate { get; set; }

 [XmlAttribute("modifiedDate")]
 public long modifiedDate { get; set; }
}

information struct :

1
2
3
4
5
6
7
8
9
10
[XmlType("information")]
[Serializable]
public struct information
{
 [XmlAttribute("key")]
 public string key { get; set; }

 [XmlAttribute("value")]
 public string value { get; set; }
}

Parsed data from the XML config is saved in the files field in addition to fingerpringint the system and populates the information field with the following :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Username
PC name
OS version
Tag (an embedded string "Newtest56")
IP
Screen size
CPU name
GPU name
RAM size
Hard drive size
PC Model
Manufacturer
Onion Address (To allow C2 Communication/Remote Access)
WhiteSnake stub version (1.5.9.9 for this sample)
Execution path
Execution timestamp
Screenshot taken on execution
Loaded DLLs
Running processes
Installed Software

The report is gzip compressed then encrypted using an RC4 Algorithm I believe, with a randomly generated 32 bytes key.

The key itself is encrypted with a RSA public key :

1
2
3
4
5
6
7
8
9
-----BEGIN RSA PRIVATE KEY-----
<RSAKeyValue><Modulus>uDshetz2ek4RVrjsP1dpASwqX0vSlWVvLDKhzZ3rXj
HXQLJsHN2J/1w9THSR4n0NGzeU1LbeJLFahvakZZs1OJXu+l+vp8oVLjfwszf+zn
feL6bxmkSj9nkNEqoT9kJCck+gR0DFAk8GyuQO9/+tgMhIBH0ZN/B9XpnUi42/+v
hpHMge4dOLaDLEVbXqGBaYNpErkOR9RURf+narhOCwm/zVYCl4PwXsBcVwbfKqIu
6XpIFjfttLWNXyyrVnlF9PtpxcSabe7ZhmgT9SfMcyb0DmdtwGk+iZ158KgRIN3m
BQo09WxTMSed110bM+SYHtNbdoTFtccJAu9qzs20/sGQ==</Modulus><Exponen
t>AQAB</Exponent></RSAKeyValue>
-----END RSA PRIVATE KEY-----

So the encrypted report blob will look like this: a "WS$" string converted to bytes + RC4_encrypted_report + RSA_encrypted_RC4_Key

The report name will look like this: W1bb3_admin@USER-PC_report.wsr. It generates 5 character ID randomly picked from [A-Za-z0-9] concatenated with Username and then PC name.

The file is then uploaded to one of the IPs from the previously mentioned IP list in the Communication with C&C Servers Section.

The report link and size will then be uploaded to a Telegram channel with ID: 2076277850 using Bot token: 6104192483:AAFCcnr4FR2XCO83zUSAWWZ9J3qw4tRYQoI. It will also include information about the victim such as OS Version, Country, Username, and Computer name as well as #Wallets and #Beacon tag indicating stolen wallets and the ability to access the victim’s PC and the WhiteSnake Tag (which is Newtest56 in our case).

IOCs

  • IPs :

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    
    "hxxp://140.238.218.94:8080",
    "hxxp://46.235.26.83:8080",
    "hxxp://168.119.121.16:8080",
    "hxxp://51.77.125.62:8080",
    "hxxp://185.189.159.121:8001",
    "hxxp://65.21.49.163:8080",
    "hxxp://167.86.115.218:9090",
    "hxxp://46.226.106.173:8080",
    "hxxps://89.46.80.136:443",
    "hxxp://77.240.38.138:8080",
    "hxxp://138.201.197.74:8080",
    "hxxps://164.90.185.9:443",
    "hxxp://92.63.70.131:8090",
    "hxxp://66.135.10.176:8080",
    "hxxp://107.161.20.142:8080",
    "hxxp://18.191.188.207:80",
    "hxxp://216.250.190.139:80",
    "hxxp://15.206.54.77:8080",
    "hxxp://39.96.33.40:8080",
    "hxxp://66.42.56.128:80",
    "hxxp://34.216.14.238:8080",
    "hxxp://116.196.97.232:8080",
    "hxxp://123.129.217.85:8080",
    "hxxp://139.224.8.231:8080",
    "hxxp://101.42.17.170:8080",
    "hxxp://47.100.180.12:80",
    "hxxp://115.231.163.168:8082",
    "hxxp://218.146.86.163:8888",
    "hxxp://85.8.181.218:80",
    "hxxp://121.63.250.132:88",
    "hxxp://124.223.67.212:5555",
    "hxxp://18.228.80.130:80",
    "hxxp://175.24.204.219:8080",
    "hxxp://106.52.28.126:8888",
    "hxxp://106.15.66.6:8080",
    "hxxp://47.110.140.182:8080",
    "hxxp://149.28.31.122:8080",
    "hxxps://192.99.196.191:443",
    "hxxps://44.228.161.50:443",
    "hxxp://189.115.56.226:8080",
    "hxxp://175.178.39.50:8080",
    "hxxps://52.68.125.8:443",
    "hxxps://138.2.92.67:443",
    "hxxps://54.178.235.46:443"
    
  • Telegram Link

    1
    
    hxxps://api.telegram[.]org/bot6104192483:AAFCcnr4FR2XCO83zUSAWWZ9J3qw4tRYQoI/sendMessage?chat_id=2076277850&text=
    
  • Files Created

    1
    2
    
    %localappdata%\76spawx7mu
    %localappdata%\EsetSecurity
    
  • Mutex

    1
    
    mefr3hjdol
    

Yara Rule

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
rule WhiteSnake {
   meta:
      author = "@3weSxZero"
      date = "2023-06-07"
      hash1 = "c219beaecc91df9265574eea6e9d866c224549b7f41cdda7e85015f4ae99b7c7"
   
   strings:
      $xor_key1 = {C5 9D 1C 81} 
      $xor_key2 = {93 01 00 01} 
      $str1 = "<ProcessCommand>b__11" fullword ascii
      $str2 = "<ProcessCommand>b__6_0" fullword ascii
      $str3 = "b77a5c561934e089"
   condition:
      uint16(0) == 0x5a4d and 2 of them
} 

Misc

Just two funny things I found, looks like the Authors decided to do a little tease after evading ESET static detection with the newer version :

eset

And a funny image in the resources :

LoL

This post is licensed under CC BY 4.0 by the author.