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 :
Sample Analysis
First Stage
Property | Value |
---|---|
File Type | 32-bit .NET executable |
SHA-256 | D1FE4CF589C8852FBC60FDCDC39BF944E27CCC2AE634B98EE841D9A9C4C6F55F |
SSDEEP | 6144:5rwSOSu/CmfQnbol39kCUBDyTic+fZuWOuSzI:izD/Cmybobk5BeTic+BuWOuSzI |
Original Filename | TransportSecurityBindingElement.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.
Calling RGB
to decrypt and launch the final payload :
Second Stage
Property | Value |
---|---|
File Type | 32-bit .NET dll |
SHA-256 | 97CDDE692FADBB6D04DC59BA21E3F41B186810EE8962C6D3D2D33292EF4085D7 |
SSDEEP | 3072:Sj3pekbcdxXzc+/O6PN7GCm1tmjaabJvfJHxACl:o3pHcdxzcK7K1tmjLbJ9xAC |
Original Filename | RGBCore.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 :
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:
The payload is decrypted and then launched as a new process using these APIs for process hollowing
:
Final payload captured in memory :
Final Payload
Property | Value |
---|---|
File Type | 32-bit .NET executable |
SHA-256 | DBA3833B916F0D5029A74BD1CA979088B9194EF92D57826B9626405B4B5E7927 |
SSDEEP | 1536:XHB+zRmEOxAuUuH3WiitPZptmMJE9se8xaumbf9Bo3IHTPyH8doO3XtxbIj8EvG7:XwzRmEnuUIWiitPftmMJE9sRxa71UIHl |
Original Filename | Meint.exe |
The final payload is an information stealer. The executable is not stripped nor obfuscated.
It has the following configuration :
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 anMD5
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)
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.
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)
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
andProtonVPN
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
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
ID | Name | Use |
---|---|---|
T1083 | File and Directory Discovery | Enumerate system files |
T1055.012 | Process Injection: Process Hollowing | Process injection to write the RedLine payload into memory |
T1087.002 | Account Discovery: Domain Account | Gets user domain name to be used in system fingerprinting |
T1087.001 | Account Discovery: Local Account | Gets user name to be used in system fingerprinting |
T1217 | Browser Information Discovery | Enumerates browsers to steal stored data and credentials |
T1622 | Debugger Evasion | Detecting debugger presence |
T1057 | Process Discovery | Pareses a list of running processes and information regarding each process |
T1012 | Query Registry | Queries registeries gathering more system information (includes installed software, OS version) |
T1518.001 | Software Discovery: Security Software Discovery | Queries installed security software (firewalls, antivirus, antispyware) |
T1518 | Software Discovery | Queries installed software gathering version and path information |
T1082 | System Information Discovery | Gathering information about operating system and hardware, including version, service packs, and architecture |
T1614.001 | System Location Discovery: System Language Discovery | Gather information about the system language of a victim |
T1614 | System Location Discovery | Gather information in an attempt to calculate the geographical location of a victim host |
T1016 | System Network Configuration Discovery | Gets victim IP |
T1033 | System Owner/User Discovery | Gets currently logged in user and gets user’s apps credentials |
T1124 | System Time Discovery | Gather the time zone |
T1113 | Screen Capture | Take screen captures of the desktop to gather information |
T1005 | Data from Local System | search 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.003 | Archive Collected Data: Archive via Custom Method | Gathered data are written in Json format then encrypted |
T1132.002 | Data Encoding: Non-Standard Encoding | Data to be exfiltrated are base64 encoded, XOR-ed with the provided key then base64 encoded again |
T1020 | Automated Exfiltration | Attempts to contact C2 for data exfiltration and to receive new configurations/updates after setting a delay functionality |
T1041 | Exfiltration Over C2 Channel | Connects to C2 for data exfiltration |
T1059.003 | Command and Scripting Interpreter: Windows Command Shell | |
T1047 | Windows Management Instrumentation | Gather information about the infected host including AV products installed, hardware specefications and device disk serial number |