__                 _ _                _
  / _| ___   ___   __| | |__   __ _ _ __| | __
 | |_ / _ \ / _ \ / _` | '_ \ / _` | '__| |/ /
 |  _| (_) | (_) | (_| | |_) | (_| | |  |   <
 |_|  \___/ \___/ \__,_|_.__/ \__,_|_|  |_|\_\

A personal BBS  ·  Missoula, Montana  ·  formerly NaClCON BBS

Connect from your terminal: telnet naclconbbs.net 23  |  ssh naclconbbs.net -p 2222

  • goro lives!

    From foodbark to All on Mon Mar 23 00:00:00 2026
    Goro Lives!

    ANSI art: goro-lives-1



    i was digging around on reddit a couple of weeks ago and came across a post
    by a developer u/eugneussou on r/github titled, '"null" committed to most of
    my repos adding suspicious code' with the following screenshot:

    ANSI art: goro-lives-2



    at first glance it doesn't look like much, a commit, a few lines of code, a
    few blank lines. but these few lines of code were popping up as a null
    commit on most of u/eugneussou's repos, and they had no memory of committing them.

    if you have been following my blog you know that i have been writing about a series of supply chain attacks on the npm infrastructure over the last eight months (sha1-hulud, react/next.js rce, etc) and this one, with the blank
    lines, has been on my radar for the last couple of weeks but it has been
    hard to get it down on paper because this is very sophisticated and right on the upper level of my limited ability to understand malware analysis, but
    let's dig into it anyway, starting with that bit of mysteriously committed code.

    the null commit

    here is the code that was added to u/eugneussou's repository, supposedly by them, but without their knowledge or approval:

    const s = v => [...v].map(w => ( w = w.codePointAt(0), w >= 0xFE00 && w <= 0xFE0F ? w - 0xFE00 : w >= 0xE0100 && w <= 0xE01EF ? w - 0xE0100 + 16 :
    null )).filter(n => n !== null);

    eval(Buffer.from(s(``)).toString('utf-8'));

    on the last line between the backticks there is what looks like an empty string. the thing is it isn't empty. between those two backticks there are thousands of characters of code written in 'invisible' unicode. characters
    your eyes don't see, that VSCode doesn't see, that github's diff viewer
    doesn't see, but once those couple of lines of code above them run the javascript runtime can see them, and when it does see them that invisible
    code becomes a malware payload that will reach out and turn your computer
    into a bot that will steal from you everything it can while simultaneously reaching out to turn other computers into bots, and it does so in an
    absolutely ingenious way.

    this is glassworm, and in the first half of this month, march 2026, security teams had collectively reported on nearly 500 repositories, packages, and extensions as victims and vectors in a supply chain attack that spanned four separate software ecosystems as part of one single coordinated four-armed campaign, a campaign i like to call goro.

    what is glassworm

    glassworm is self-propagating malware. it targets the tools that developers
    use to make software: their supply chain. it was first identified by koi security in october 2025 when it showed up in seven openvsx extensions with
    a combined 35,800 downloads. openvsx publishes open source plugins for
    VSCode, codium, cursor, and other code editors. the name glassworm comes
    from the hidden code technique: like glass glassworm is clear. you can be staring directly at the code and not even notice that it is there.

    what makes it a "worm" is that it self propagates. last october, in its original form, glassworm propagated all on its own. but in its current iteration as goro it uses its foothold on your computer to download a RAT, a remote access trojan. when that RAT lands on your machine it steals your npm tokens, your github credentials, your openvsx publishing access, and then
    uses all of those to compromise more packages and extensions with the worm under your identity by making commits to your software projects like it did
    to u/eugneussou. each new victim becomes a new vector for infection, automatically.

    by the first week of march of 2026 koi security, aikido, socket, and stepsecurity had collectively identified 433 components compromised by glassworm. in the second week of march three of those research teams
    published reports almost simultaneously each believing they had found
    separate campaigns in three different ecosystems. aikido had found over 150 compromised github repositories using invisible unicode. socket had found 72 malicious vscode extensions in open-vsx. stepsecurity had found hundreds of python repos being silently rewritten via stolen github tokens.

    and then the team at opensourcemalware.com pulled it all together. one
    solana address appeared in all three reports (and in fact appeared in the
    code i was looking at over on reddit):

    BjVeAjPrSKFiingBn4vZvghsGj9KCE8AJVtbc9S8o8SC

    these three teams (and us redditors) were looking at the same monster and it had a hand in four different ecosystems. a four armed monster engaged in
    mortal kombat: goro.

    the invisible code trick

    so how do we make code invisible? we make code invisible by writing it in invisible characters.

    unicode is a universal character encoding standard that exists to make sure that every letter, number, symbol, or emoji is represented consistently
    across different platforms, languages, and applications. and in unicode
    there are certain code points that exist only to modify the appearance of
    the characters immediately before them. these are called variation
    selectors. they are what tells your computer to render # as a color emoji instead of a plain text glyph: #. follow the biohazard character with the variation selector 16 (U+FE0F) and you get the version with color, but
    U+FE0F produces no visible output of its own. it doesn't display anything,
    it just augments: it is invisible by design.

    glassworm uses two specific ranges: U+FE00 through U+FE0F and U+E0100
    through U+E01EF. glassworm's loader reads each invisible character as 4 bits
    of data (two characters per byte) and if you string enough of them together
    you can hide a payload that no editor, terminal, linter, or diff tool will
    ever display. as idan dardikman of koi security put it when they first documented the technique in october: "this is a fundamental break in our security model. we've built entire systems around the assumption that humans can review code. glassworm just proved that assumption wrong."1

    goro: the four armed monster

    ANSI art: goro-lives-3



    this new, as of march 2026, campaign is what opensourcemalware.com has
    framed as a "quad-platform"2 operation or a "four-armed monster". it's the
    same operator using four simultaneous attack vectors, each tailored to its ecosystem, ultimately converging on the same payload linked by the shared solana wallet address above from which the larger piece of malware is downloaded.

    in npm and github the invisible code wasn't always injected with null
    commits like we saw on reddit either, in some cases reported by aikido3 they were committed with bespoke, probably llm-generated messages, to help hide
    them from dev teams.

    in the vscode plugin marketplace and in open-vsx, socket4 found that the operators had gotten creative with the extensionPack and
    extensionDependencies fields to create a transitive delivery system where
    the malicious extension became a dependency of something normal and benign.

    meanwhile, according stepsecurity's5 report, python github repositories were being hit by a different obfuscation method: base64, then zlib compression, then XOR with key 134. the malicious code would be appended to legitimate
    files like setup.py, main.py, and app.py. and then used tokens stolen from already-infected developer machines to force-push the malicious commits, preserving the original author, commit message, and author date. only the committer date changed, and the committer email showed up as null, similar
    to what i had seen posted on reddit.

    so far we have only talked about how goro spreads, now let's get a little
    into the weeds on how it works. luckily u/eugneussou posted the decoded invisible stage 1 loader to pastebin for us to look at.

    the stage 1 loader

    so you have unwittingly downloaded the 'invisible' unicode as part of a git repository and your computer decodes the stage 1 loader and starts to run
    it. the first thing the loader does is wait:

    new Promise((resolve) => setTimeout(resolve, 10 * 1e3)).then((_) => {

    it waits for 10 seconds before doing anything. in those critical ten seconds
    it sneaks past 80% of automated analysis tools that are watching for
    immediate malicious activity after execution. next it tries to decide if the machine it's on is russian.

    if (_isRussianSystem()) return;

    it goes through a number of checks: username, system locale, system
    language, and 13 russian specific timezones. if it finds both an indication
    of russian-language locale and a russian matching timezone, it doesn't run.
    it tries as hard as it can not to run on russian machines.

    the next sneaky thing it does is it only runs once every 48 hours:

    if (!(check?.date && check.date + 2 * 24 * 60 * 60 * 1e3 < Date.now()))
    return;

    this prolonged cool down is insane. lots of malware gets flagged because it beacons constantly and at a completely inhuman rate, hundreds, thousands of times a second, this thing is more than happy to just sit there and wait.

    when it does decide to do something it checks the local operating system and then reaches out to the solana wallet for the current rolling command and control (C2) url, from there it fetches the encrypted payload specific to
    your operating system, and receives an aes-256-cbc decryption key and iv separately in the http response headers. the encrypted payload and the keys never travel together so that even if the payload is intercepted, you need
    to make a second request through the solana-resolved C2 to get the current
    keys to decrypt it.

    interestingly, during my analysis i pasted a section of the decoded stage 1 loader into a markdown file on my windows laptop and windows defender immediately started screaming. this was a markdown file, not an executable,
    but defender flagged it instantly. meanwhile in its encoded 'invisible' form
    my computer had seen nothing at all. it's a pretty good illustration of
    exactly what the invisible unicode technique is doing: the malware is only visible to the things that can execute it, and is invisible to everything designed to catch it.

    the unkillable RAT: 3 layers of C2

    so now that the initial invisible worm has downloaded a remote access trojan (RAT) via the solana wallet, our computer can be commanded and controlled
    (C2) remotely (koi security, and it seems maybe they creators of it, called this module ZOMBI). in most malware C2 is easy to disrupt: you find the
    server maliciously controlling other computers, you report that server, and that server gets taken down and your computer is yours again. but goro's RAT module ZOMBI has three layers of C2 redundancy and each of them is set up in
    a way to be almost impossible to disrupt.

    layer 1: the solana blockchain.

    goro uses the solana wallet as described above to resolve a base64-encoded
    url pointing to the current payload server. blockchain transactions are immutable (you can't delete them), the wallet is pseudonymous, it is distributed, there's no hosting provider to contact, no domain to seize, no registrar to pressure. the operator can update their C2 url for less than a penny by posting a new transaction.

    layer 2: bittorrent DHT.

    goro also uses bittorrent's distributed hash table (DHT) to look up a configuration record containing the current C2 ip, payload urls, encryption keys, and binary hashes. here's the clever part: every infected machine that reads the config immediately republishes it back to the DHT. the entire
    botnet becomes a content delivery network for its own command
    infrastructure. even if the operator's own DHT node goes dark, the configuration is replicated across every infected machine worldwide and
    keeps circulating. once again distributed and impossible to take down.

    layer 3: google calendar.

    now this one is so clever and simple that it is stupid. as a back up if the other two methods fail the goro infected machine will reach out to a public google calendar event with a title that contains a base64-encoded url to
    obtain command and control. it's free, no one is going to block google, no authorization or stolen credentials required. if it gets flagged they can always make another anonymous google account and a new google calendar
    event.

    what it does to your machine

    so now we are on stage 3, where goro actually starts trying to steal from
    you.

    first the ZOMBI module tries to silently harvest credentials from your
    browser and if that fails it will pop-up a fake system dialog box asking for your password. and what is it stealing? everything your browser has ever touched:

    all browser data from 10 chromium variants (chrome, brave, edge, vivaldi, opera, and more): cookies, login data, autofill, and all browser extension local storage

    150 hardcoded chromium cryptocurrency wallet extension ids

    firefox profiles, with metamask specifically targeted by parsing prefs.js
    for the extension UUID

    15 desktop wallet directories: electrum, exodus, ledger live, wasabi,
    bitcoin core, monero, tonkeeper, trezor suite, and more

    ssh keys , with validation, it checks for BEGIN PRIVATE KEY before
    exfiltrating

    aws credentials

    vpn configurations

    documents under 10mb matching common office and archive extensions
    (essentially every potentially business-sensitive document on your machine, from spreadsheets to contracts to NDAs)

    at the same time the node.js credential theft is running alongside it,
    taking github tokens through four fallback methods, npm tokens through
    three, and a native keychain extractor downloaded from the C2.

    and all of it goes to two separate exfiltration servers with campaign
    metadata sent as http headers.

    the wallet trojanization

    this is the nastiest part. if you have a crypto wallet on your machine, in particular if ledger live or trezor suite is installed, goro kills the
    running process, deletes the app, downloads a trojanized version from the
    C2, and strips the quarantine flags twice (once after extraction and once
    after install) to prevent any security warnings.

    hardware wallets are trusted precisely because signing happens on a separate physical device. a compromised companion app can intercept seed phrases
    during recovery flows or silently manipulate transactions without you ever knowing. this thing doesn't just steal your wallet, it replaces it
    completely with its own and keeps anything in it or added to it.

    the zombi rat

    so now the RAT is installed and running. it connects to the C2 via socket.io
    on port 4789 and it has four modes of operation.

    socks5 proxy. your developer workstation, the one that is sitting inside
    your network behind firewalls and security controls, becomes an exit node
    for criminal traffic. the attackers route their activity through your ip address. your machine can reach internal resources that no external proxy
    ever could.

    webrtc peer-to-peer. direct control channels via NAT traversal. the operator can talk directly to your machine without making a connection to a central server which might be flagged by network monitoring software as suspicious. this is the same technology your web browser uses to make peer-to-peer video calls and thus looks like normal traffic.

    bittorrent DHT command distribution. the same DHT network used to find the
    C2 is also used to distribute commands. there is no central node to take offline.

    HVNC , hidden virtual network computing. this is the scary one. complete invisible remote desktop access. it runs in a virtual desktop that produces
    no visible windows and doesn't show up in your process monitor. the operator can use your browser with your logged-in sessions, read your source code,
    get into your email and messaging apps, and pivot to other machines on your network as though it was you. you see nothing, nothing looks wrong, nothing
    is slow but they are in there pretending to be you.

    the RAT is very good at persisting. it catches every termination signal (sigint, sigterm, sigquit), every flavor of crash, every unhandled exception and reports back to the C2, and if necessary, restarts itself after 21
    seconds. you cannot kill it through normal means.

    attribution

    so who is doing this? evidence points toward russian-speaking operators
    though attribution in these situations is always uncertain and worth being careful not to overstate.

    the stage 1 loader checks thoroughly for russian locale, querying language settings cross referenced with russian timezones and UTC offsets. russian- language error strings show up in the npm credential theft module: "">:5= =5 =0945=" (token not found), "520;84=K9 B>:5=" (invalid token). the C2 infrastructure is hosted on aeza group, a russian hosting provider. the
    solana wallet that funds the whole operation holds over 1,000 SOL and is clearly not a throwaway address.

    opensourcemalware.com compared this campaign to polinrider and tasksjacker,
    two north korean campaigns that also use blockchain C2, and concluded it's probably not the same people. the russian geofencing, the mix of traditional server infrastructure alongside blockchain, and the payload format are all different from what north korea typically runs. "probably russian in origin, not necessarily state-run but not not state-adjacent either"2 is about as
    far as anyone is willing to go.

    so what this means for npm

    well, the invisible unicode technique breaks the assumption that code review means anything at the package level. you cannot catch what you cannot see,
    and no linter, no diff tool, no amount of staring at a pull request is going
    to flag those invisible unicode variation selector characters.

    pinned versions and lockfiles don't matter when the upstream package is compromised and published under a legitimate maintainer account. you pinned
    to a clean hash and now that clean hash points to malware. that's how supply chain attacks work.

    the same goes for auto-updated vscode extensions and the implicit trust most
    of us extend to npm install. you installed extensions months ago that were clean and now are suddenly infected without having actively done anything.

    the python forcememo vector means that git clone followed by npm install and pip install from a github source are suddenly risky behaviors. you could
    even be visually checking the full commit history of every piece of code you are running and could still miss it because 'invisible'.

    detection & iocs

    on a potentially infected macos machine:

    # persistence ls ~/Library/LaunchAgents/com.user.nodestart.plist

    # hidden node.js ls ~/.config/system/.data/.nodejs/

    # rat state file cat ~/init.json

    # phished password stored in keychain security find-generic-password -s "pass_users_for_script" 2>/dev/null

    # error log artifacts find ~ -name "error*XkrnQlLAX*" 2>/dev/null

    # stolen data staging directory ls /tmp/ijewf/

    in your codebase:

    # python forcememo marker variable grep -r "lzcdrtfxyqiplpd" .

    # unicode decoder fingerprint grep -r "w>=0xFE00" .

    # the solana address grep -r "BjVeAjPrSKFiingBn4vZvghsGj9KCE8AJVtbc9S8o8SC"
    .

    # force-push null committer fingerprint git log --all --format="%cn|%ce" |
    grep "null"

    network iocs:

    217.69.11.99:4789 , socket.io C2

    208.85.20.124:80/wall , exfiltration endpoint

    solana wallet: BjVeAjPrSKFiingBn4vZvghsGj9KCE8AJVtbc9S8o8SC

    DHT public key: ea1b4260a83348243387d6cdfda3cd287e323958

    tools that might help you not get got:

    aikido safechain , wraps npm/npx/yarn and scans installs before they run

    aikido intel , these packages are flagged at 100/100 severity

    github code search for the decoder fingerprint: 0xFE00&&w<=0xFE0F?w-0xFE00:w>=0xE0100&&w<=0xE01EF

    the kill switch that doesn't work

    one last thing worth mentioning. if you go back and read that original
    reddit thread you can watch u/willing_monitor5855 (also known as tip-o- deincognito on codeberg) show up in the comments. i basically watched this person do live malware analysis in real time, coming back to the thread with increasingly detailed breakdowns.

    it was one of the coolest things i have seen on the internet in a long time:
    a stranger on reddit spending their evening reverse engineering an active botnet for someone who posted in r/github that they had some weird commits.
    i guess the internet is not entirely dead, even if it might be dying there
    are still a few of us out here. u/willing_monitor5855's 57 hours of live monitoring is documented in their codeberg writeup which i definitely
    suggest checking out and from which i learned a lot in the writing of this post.

    at some point during that window u/willing_monitor5855 noticed that the operator appeared to be trying to shut things down. every payload endpoint started going dark one by one. and the main stealer endpoint started
    returning a single value:

    cHJvY2Vzcy5leGl0KDAp

    decode that from base64 and you get process.exit(0). the operator was trying
    to remotely kill their own botnet.

    it didn't work, for three completely independent reasons.

    first: process.exit(0) encodes to exactly 20 characters in base64. and the
    RAT has this check:

    if (_script.length == 20) { return; }

    so the kill command is immediately and silently tossed out. why i don't
    know. in their report u/willing_monitor5855 thought maybe this was a red herring, inserted to make researchers think that the operator of the RAT was shutting it down when they were really just letting it go dormant.

    second: even if that check wasn't there, the payload fetching function only runs once every seven days. most bots wouldn't even try to pick up the kill command for days.

    third: even if process.exit(0) somehow executed, the persistence mechanism
    is configured to restart the process immediately on any exit, so it's persistent even if that command was successful. it is also possible that the operator didn't write the RAT and doesn't understand how it works.

    strangely the stage 1 loader, the invisible unicode one that runs on a fresh infection, handles 20 character payloads correctly and would have eval'd the kill command just fine. the kill switch works perfectly on machines that haven't been fully infected yet and does absolutely nothing to the ones that are already completely compromised. maybe a kill switch only in case of accidental initial infection? that still seems like a small window in which
    to respond.

    u/willing_monitor5855 watched the C2 continue running bot inventory,
    checking which machines were still phoning home, for the entire 57 hours
    even as servers that were receiving the stolen data went down. as they put
    it on reddit, "the campaign is over in intent but the botnet will persist indefinitely on infected machines."6

    sources

    a lot of people did the actual hard work that made this post possible:

    koi security team , original discovery, ZOMBI RAT analysis, wave 4 macos
    pivot

    aikido security team , march 2026 wave, scale reporting, safechain tool

    socket team , transitive extension delivery, open-vsx coverage

    stepsecurity team , forcememo, python repo analysis, blockchain forensics

    opensourcemalware.com team , cross-campaign correlation, the four armed
    monster framing that tied all three reports together

    u/willing_monitor5855 / tip-o-deincognito , codeberg writeup, 57 hours of
    live infrastructure monitoring, kill switch postmortem, ioc submissions to threatfox urlhaus and malwarebazaar

    u/eugneussou , author of the reddit thread and the pastebin that started my spiral into malware analysis

    GlassWorm: First Self-Propagating Worm Using Invisible Code Hits OpenVSX Marketplace ©

    Four Arms, One Monster: GlassWorm Invades GitHub, NPM, Open VSX and VS Code
    © ©

    Glassworm Returns: Invisible Unicode Malware Found in 150+ GitHub
    Repositories ©

    72 Malicious Open VSX Extensions Linked to GlassWorm Campaign ©

    ForceMemo: Hundreds of GitHub Python Repos Compromised via Account Takeover
    and Force-Push ©

    "null" committed to most of my repos adding suspicious code ©
    --
    https://foodbark.io/posts/goro-lives/