Skip to content

Instantly share code, notes, and snippets.

@shavitush
Created February 28, 2026 12:59
Show Gist options
  • Select an option

  • Save shavitush/9ae98e9ae70c4772c91d7bec1a138fb3 to your computer and use it in GitHub Desktop.

Select an option

Save shavitush/9ae98e9ae70c4772c91d7bec1a138fb3 to your computer and use it in GitHub Desktop.

From MawaStealer to Vidar: Sideloading via VLC

Introduction

The threat actor behind the MawaStealer campaign we saw a while ago is (probably) back. Instead of batch dropper -> PowerShell garbage disguised as a .lnk shortcut, they're actually using more sophisticated strategies now by leveraging libvlc.dll for DLL sideloading. They've been operating this campaign for roughly a month, since 2026-01-29. When I started analyzing this sample, it was delivered disguised as Jujutsu Kaisen episode 52, which I'm using as the base sample for this analysis. Ever since then, they've uploaded several fake highly-anticipated episodes for top-rated anime series (e.g., Sousou no Frieren and Oshi no Ko).

The latest attempt as of writing was performed through the Klyxar account which had been taken down.

Figure 0x00: The latest attempt
Figure 0x00: The latest attempt

The second wave of MawaStealer began on January 29th: a torrent titled [SubsPlease] Jujutsu Kaisen - 52 (2160p) appeared - before episode 52 had even aired, and at a resolution higher than what's typically expected. It had been uploaded through a stolen Nyaa account rather than the official SubsPlease one. The bait was a pirated copy of a Jujutsu Kaisen episode that hasn't actually aired yet. Upon extraction, it's not a simple .mkv file. You get a whole folder. The real video was actually episode 51, so I'm disappointed. They've been using stolen accounts often of late to spread the malware further. Anyway...

Figure 0x01: Screenshot of the archive's contents
Figure 0x01: Screenshot of the archive's contents

At first glance, it looks like a portable copy of VLC bundled with the video: the EXE is named convincingly enough, [SubsPlease] Jujutsu Kaisen - 52 (1080p) [23F441A4].exe, and there's a libvlc.dll and a Subs folder with video.mkv to sell it further. Considering so many users have been infected, it seems to deceive victims well enough.

The EXE is a legitimate signed distribution of VLC. The libvlc.dll is actually where the malicious code resides.

Analysis

$ file libvlc.dll
libvlc.dll: PE32+ executable (DLL) (GUI) x86-64, for MS Windows

Let's look at the metadata for libvlc.dll. While the EXE is digitally signed by VideoLAN, the DLL carries no signature. It has "CrestLeptonStellar" and "Max Distributed Bandwidth Timer" in the manifest... which are randomly generated.

Figure 0x02: File Version Information from VT
Figure 0x02: File Version Information from VT

When the victim runs the masquerading VLC, the Windows loader will load the malicious libvlc.dll from the same directory. The malicious DLL exports the standard libvlc_* functions to satisfy the EXE's import table. This is a well-known method, also described in MITRE ATT&CK T1574.001.

As soon as the libvlc_new export is called, the malicious code executes. It decrypts a few strings (AES-256-CBC, using key 01ae6068b2f43c85b17a92a8a6f16c9ec07dfe634208737df2f36a4c4bca2071 and IV 39b31e2fc557974d931a2a93432fa154), copies Subs\video.mkv to libvlc.mkv, and uses ShellExecuteW to open the video. The user gets to watch Jujutsu Kaisen, completely oblivious to the fact that their PC is being pillaged in the background.

Figure 0x03: AES (1) Figure 0x04: AES (2)
Figures 0x03 and 0x04: AES

I compiled a list with the encrypted strings for CyberChef, which also includes the various anti-debug related strings they use, e.g., AV vendors, debugger names and so on.

In libvlc.dll, after the string decryption takes place (for "open", "Subs", "video.mkv"), we can see CopyFileW being used and then ShellExecuteW runs it as a command so the victim actually sees a real video playing. Even though it's last week's episode...

Figure 0x05: Playback
Figure 0x05: Playback

While the video plays, the DLL concatenates several strings from its .rdata section to assemble a massive hex-encoded blob, decodes it, and decrypts the payload using AES-256-CBC.

What follows is a long unpacking process:

  1. The decrypted blob is AMD64 shellcode. Shoutout hrtng for making this easier
Figure 0x06: Shellcode entry
Figure 0x06: Shellcode entry
  1. It uses a custom ARX block cipher in CTR mode to decrypt the next stage
Figure 0x07: Block cipher
Figure 0x07: Block cipher
  1. XORs
Figure 0x08: XOR
Figure 0x08: XOR
  1. Unpacks a PE32 module using LZNT1 from an in-memory descriptor
Figure 0x09: LZNT1 decompression
Figure 0x09: LZNT1 decompression
  1. Manual maps the PE into memory
Figure 0x0A: Manual mapping
Figure 0x0A: Manual mapping
  1. The new PE (the one that was just manually mapped) decrypts another embedded blob from .rdata (xor -> subtract -> multiply -> chacha20 -> rotl8), spawns a suspended dllhost.exe, uses section-based injection (and APC queue) to run the final stage inside dllhost.exe
Figure 0x0B: Blob decryption Figure 0x0C: Blob decryption
Figures 0x0B and 0x0C: Blob decryption

If we extract that mapped PE and check its injection logic, we can spot the CreateProcessW call spawning dllhost.exe suspended, followed by the NtCreateSection, NtMapViewOfSection, and NtQueueApcThread dynamic syscalls:

Figure 0x0D: APC injection routine
Figure 0x0D: APC injection routine

The final payload is a grabber written in C (from strings: Build: grabber v1.0). It's Vidar Stealer v2. Unlike the previous MawaStealer sample that only checked for Avast and AVG, this one checks against a massive list of AV processes - pretty much the entire industry: Kaspersky, Avira, Avast, AVG, Bitdefender, Malwarebytes, McAfee, ESET, Dr.Web, 360 Security, Tencent, Norton, Sophos, F-Secure, Panda, Trend Micro, Comodo, ZoneAlarm, Fortinet, Cylance, CrowdStrike, SentinelOne, Carbon Black, and Windows Defender.

It targets an extensive list of information/credentials to harvest from the infected machine:

  • Chromium-based Browsers and Firefox: login data, cookies, autofill data, Google tokens, passwords, payment info, form history
  • Gaming: Steam, Minecraft, Battle.net, Origin, Ubisoft, GOG, Roblox
  • Social: Discord, Mailbird, Thunderbird
  • Wallets/keychains: Exodus, Jaxx, Bitpay, Bitwarden. Interestingly, also eo.finance (despite being shut down?)
  • Servers/networking: FileZilla, MobaXterm, WinSCP, Total Commander, mRemoteNG, Private Internet Access
  • Others: Screenshots, env vars, installed software, list of all running processes/services

They also have a dedicated bypass for Chromium's App-Bound Encryption. It's similar to ChromElevator. It spawns a hidden helper process of the browser and injects code into it via a named pipe (\\.\pipe\abe_%lu_%lu) to recover the ABE master key. The decompilation, is incredibly easy to follow, thanks to the total lack of obfuscation and the debug prints throughout.

Figure 0x0E: Chromium ABE
Figure 0x0E: Chromium ABE

All of that just to run an infostealer... lol, lmao even.

For exfiltration, it communicates with tester[.]technologytorg[.]com over HTTPS using WinHTTP.

When the infostealer payload runs, it registers the victim machine via /api/auth by sending a fingerprint of the host to get a session ID and an exfil config. It uses a payload like the following (their server happily accepted this JSON):

{
    "tag": "tf1",
    "gpu": "Intel(R) UHD Graphics 620",
    "wp": "e5da7baa684dc590f2b809a711f6b48f1c187d1b",
    "avm": 0,
    "hwid": "5230f828-8bdb-4d1b-aad9-154ff662a115",
    "user": "Public",
    "pc": "DESKTOP-DE28CCA",
    "av": "Kaspersky"
}

The server responds with a session_id and a config object like { "config": { "streaming": 1 } }. Based on this config, Vidar Stealer decides how to exfiltrate the data:

  • In streaming mode, it sends the data to /api/stream/file (with the decrypted payload gzip-compressed) and finishes with /api/stream/complete
  • Otherwise, it initiates the transfer at /api/upload/start to get an upload_id and resume_offset, sends the chunks to /api/upload/chunk, and finalizes at /api/upload/complete

I believe the server might decide streaming should be turned on or off depending on your data from the payload (depends on the AV/EDR perhaps?), but I couldn't figure out which vendor causes the server to decide data should be sent in chunks.

To authenticate to these endpoints, Vidar Stealer generates an X-Auth-Token header, which is a time-based HMAC-SHA256 hash derived from the C2 hostname and the current time (in sub_140017590).

The request bodies are further wrapped in Cha20 encryption and HMAC-SHA256 integrity checks. They generate a 12-byte nonce using CryptGenRandom, encrypt the payload using ChaCha20, and append an HMAC.

Figure 0x0F: JSON payload
Figure 0x0F: JSON payload
Figure 0x10: Auth token build (HMAC)
Figure 0x10: Auth token build (HMAC)
Figure 0x11: In-house ChaCha20
Figure 0x11: In-house ChaCha20

Indicators of Compromise (IoCs)

SHA256 sums:

5d695d8a0bb1d919cc77a2aa2488a61797bfa065238160278ee458120630aaf9  [SubsPlease] Jujutsu Kaisen - 52 (1080p) [23F441A4].exe (legitimate VLC binary, included in campaign)
4c78c753f8fc5b5bbc88838d951a7671de5114237b332bb12b3da685ff968654  libvlc.dll (sideloaded DLL)
d407f2737a44be3f1042fd4b72611a82389866146e4871f32cf2b38d9bb7ef9d  payload_decrypted.bin (Initial extracted shellcode)
f56d0c5ffb9795209afbbdfe34067140c0a924745e4bbad14a56476581779f60  MawaStealer_v2_Vidar_Stealer.bin

Domains

  • tester[.]technologytorg[.]com (C2)

Files/directories present on system

  • libvlc.mkv (the copied decoy video)
  • Named pipe: \\.\pipe\abe_<pid>_<tid>

YARA rules

rule MawaStealer_v2_libvlc
{
    meta:
        description = "Detects the sideloaded libvlc.dll from the new MawaStealer campaign."
        author = "shavitush"
        date = "2026-02-28"

    strings:
        $fake_vendor = "CrestLeptonStellar Communications" wide fullword
        $fake_product = "Max Distributed Bandwidth Timer" wide fullword
        $fake_dll = "schema_abso.dll" wide fullword
        $cbc = "ChainingModeCBC" wide fullword
        $export1 = "libvlc_new" ascii fullword
        $export2 = "AllocProcessEx_22AF" ascii fullword
        $export3 = "UpdateServiceA_22AF" ascii fullword

    condition:
        uint16(0) == 0x5A4D
        and filesize < 2MB
        and any of ($fake_*)
        and all of ($export*)
        and $cbc
}

rule MawaStealer_v2_vidar
{
    meta:
        description = "Detects Vidar Stealer v2's debug builds (as seen in the recent MawaStealer campaign)."
        author = "shavitush"
        date = "2026-02-28"

    strings:
        $ = "{\"filename\":\"%s\",\"size\":%u,\"hash\":\"%s\"}" ascii fullword
        $ = "{\"tag\":\"%s\",\"gpu\":\"%s\",\"wp\":\"%s\",\"avm\":%d,\"hwid\":\"%s\",\"user\":\"%s\",\"pc\":\"%s\",\"av\":\"%s\"}" ascii fullword
        $ = "[%s] [#] Build: grabber v" ascii

    condition:
        uint16(0) == 0x5A4D
        and filesize < 2MB
        and 2 of them
}

I suspect this is the same threat actor behind the original MawaStealer campaign, who happened to use a new infostealer (Vidar v2) for the final payload, given the similar delivery mechanism.

The earliest sample I could find for this variant of the infostealer is bacddaa7168afc28ae53a3cabb93becef60051b1250482ecd0c804e7d110c32b, from 2025-12-22, which was incorrectly attributed to conti_glassroom on VT. Other researchers have independently attributed this malware family to Vidar Stealer. There have been many similarities to Trend Micro's findings. This is likely the newest version of Vidar Stealer.

I was incredibly surprised to see that the actual final payload here lacked any sort of obfuscation, and there are debug strings all over the place which made the analysis piss easy... after the unpacking stages, of course.

Thanks for reading! Till next time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment