Skip to main content

Plux Steam Campaign targets European entities

Executive summary
#

Another PlugX malware campaign was identified by @malwarehunterteam on 9 January 2026. The attack initially exploits a critical Windows LNK vulnerability (CVE-2025-9491), followed by multi-stage payload delivery via DLL sideloading techniques using legitimate Steam application files. The campaign has employed geopolitical themes (the US-Adriatic Charter Partnership Commission) as a social engineering lure in order to allegedly target European entities. The delivery and triggering method of PlugX, along with other additional obfuscation layer of its configuration, suggest a possible variant of the malware.

Identification
#

On the 9th of January 26, malwarehunterteam shared a post on about a zip file dubbed “Post-Meeting_Report_US-Adriatic_Charter_Partnership_Commission.zip”

@malwarehunterteam post
X post by @malwarehunterteam post

The zip file, which contains a lnk file with the same name, was uploaded to Malware Bazar by @simca83 and attributed to Mustang Panda.

Attack Chain Overview
#

I started looking at the .lnk file and observed this chain.

Step 1:
  └─→ LNK file
       └─→ PowerShell script execution
            └─→ Downloads nested ZIP archive
                 └─→ Extracts TAR archive to %APPDATA%

This was already reported in a previous campaign, where TA exploited CVE-2025-9491 to deploy PlugX. The extracted files resulted in:

step 2: DLL Sideloading
  └─→ Legitimate steam_monitor.exe (signed binary)
  └─→ Malicious crashhandler.dll (sideloaded DLL)
  └─→ crashlog.dat (encrypted payload + shellcode + decoy PDF)

The shellcode embedded in the data file was responsible for decrypting the XOR-encrypted PlugX payload and activating the malware via RtlRegisterWait native api.


crashlog.dat
#

crashlog.dat present a custom format which includes shellcode, encrypted PE and the PDF (decoy) as per the following layout

Layout:
┌────────────────────────────────────────────────────────┐
│ 0x00000 - 0x95205  │ ENCRYPTED PE + Initial Stub       │
│                    │ (XORed with 0x7a)                 │
├────────────────────────────────────────────────────────┤
│ 0x95206 - 0x9524E  │ DECRYPTION STUB (plaintext!)      │
│                    │ ~75 bytes of x86 code             │
├────────────────────────────────────────────────────────┤
│ 0x9524F - 0xB0C22  │ APPENDED PDF (plaintext)          │
│                    │ Decoy/stealth data                │
└────────────────────────────────────────────────────────┘

crashhandler.dll
#

crashhandler.dll is the malicious dll sideloaded by legit steam exe. The function CreateInterface loads dat file and prepares its execution. In details it resolves API via hashing function, allocates memory space, decrypts xored dat name, retrives and loads dat file in memory, change allocated memory protection, prepare shellcode execution via RegisterWait callback

api hash
api resolution

the function sub_100013E2 implements a simple API hashing algorithm identified by HashDB as add1501_shl5.

rtlregisterwait native api
rtlregisterwait native api

The shellcode is executed via RtlRegisterWait and SetEvent API

Plugx
#

Looking at the plugx payload, i noticed it employs multiple XOR-based string encryption algorithms to obfuscate API names and configuration and other values. The following image shows the code where CreateThread API name is decrypted, resolved via sub_100038DE and than used to start core main thread (sub_10001290)

plugx string CreateThread decryption
plugx string CreateThread decryption

As already documented, plugx store its encrypted RC4 configuration into .data section. In this sample, i noticed another obfuscation layer consisting by a positional XOR.

rc4 and postional-xor for configuration decryption
rc4 and postional-xor for configuration decryption

Basically for each string block from the RC4 data decryption process, an initial accumulator is computed as follow a3_init = i3 + count where i3 is the length of the RC4 key and count is the value read from the block’s QWORD header. Than for each byte i in the encrypted payload:

   plaintext[i] = encrypted_payload[i] ^ ((count + a3_init) & 0xFF)
   a3_init = a3_init + count

The keystream is therefore a linear sequence stepping by count each byte, with an initial offset determined jointly by the string’s own length and the global seed.

extracted configuration

Lure filenamePost-Meeting_Report_US-Adriatic_Charter_Partnership_Commission.pdf
Campaign tagHOyvWAfnD
Persist path“%public%\Steam”
Staging path“%temp%”
C2 domainmajicbus.org:443

IoCs
#

filesha256
27122025_0228_Post-Meeting_Report_US-Adriatic_Charter_Partnership_Commission.lnk.zipb1606ca49aa15eadb039f33d438697973b203693d0003e467e1f33b36d10a530
Post-Meeting_Report_US-Adriatic_Charter_Partnership_Commission.lnk87929c8f53341a5e413950d33c7946c64e1d4b2eba6d1a8b2d08ef56f7065052
steam_monitor.exe (legit)8c0051a83b3611ff2b669b670aa005633f3d9e844454a112b31d2a4bc944a234
crashhandler.dll843b22df66f87a587be77145da163f9615fe8164a5ea17f9e33562ff43894fbf
crashlog.dat6788365386ccd34d1db681c61ef07ef4d2faea5672571b77a76dc48f327afaa9
  • C2 domain:majicbus.org
  • C2 port: 443

sample shared on malware bazar