A Deeper Look at RustImplant

A Deeper Look at RustImplant

We recently intercepted a malicious VSCode extension disguised as the vscode-icons package that utilized a sophisticated Rust-based downloader to compromise developer environments.

This sample notably contained a compiled Rust binary with anti-analysis functionality. Furthermore, the sample has been attributed by OSINT as pertaining to GLASSWORM.

This post details our rapid triage of the sample to offer additional insight into attribution and analysis within the supply chain security ecosystem.

The Initial Vector

The infection begins with a VSCode extension named icons-vscode, published by user bphpburn. The activate function in the extension’s entry point contains the trigger for the binary:

async function activate(context) {
  try {
    // 1. Break the Sandbox: Expose internal Node require to the global scope
    global.require = require; 

    const p = os.platform();
    // 2. Sideload the Native Binary
    if (p == 'win32') {
      const { run } = require('./os.node'); 
      await run(context);
    }
    if (p == 'darwin') {
      const { run } = require('./darwin.node'); 
      await run(context);
    }
  } ...
}

By loading a .node file, the attacker bypasses standard JavaScript static analysis tools since the malicious logic is in the binary.

Piercing the Binary

We opened the Mach-O binary (darwin.node) in our disassembler and immediately hunted for the N-API registration hook since this is a Node.js addon.

We found napi_register_module_v1. In Rust binaries, this function is usually a trampoline since it initializes the Rust runtime (handling panic hooks and thread-local storage) before passing control to the developer written logic.

// The Entry Point
__int64 __fastcall napi_register_module_v1(__int64 env, __int64 exports) {
  // ... Rust Boilerplate (std::sync::Once, Panic setup) ...
  
  // The Jump to Payload
  if ( sub_6C890() )
    off_255170(&env); // This offset pointed to the real malicious init
  return exports;
}

Following the pointer, we landed in a function responsible for exporting the native method rustImplantRun to the JavaScript layer. This confirmed that the JavaScript snippet require('./darwin.node').run() directly maps to the internal function we labeled Native_Callback_Payload.

Geofencing and Profiling

We encountered a hard-coded JavaScript Profiler embedded within the binary. The sample compiles and runs this script immediately upon execution:

(function(){
    const os=require('os');
    const si=[
        os.userInfo().username,
        process.env.LANG,
        // ... locale checks ...
    ];
    // Check 1: Cyrillic/Russian indicators
    const isRL=si.some(i=>i&&/ru_RU|ru-RU|Russian|russian/i.test(i));

    // Check 2: Timezone analysis (Moscow, Kaliningrad, etc.)
    const rtz=['Europe/Moscow','Europe/Kaliningrad', ... ,'MSK'];
    const isRT=ti.some(i=>i&&rtz.some(tz=>i.toLowerCase().includes(tz.toLowerCase())));

    // Returns TRUE if the user is in Russia/CIS
    return isRL&&(isRT||isRO);
})();

If this script returns true, the Rust binary terminates silently. This specific exclusion list is characteristic of eastern european cybercrime trying to avoid heat from local law enforcement.

Decrypting the Config

Rust binaries are notoriously difficult to reverse due to deep call stacks for simple operations. We hit several dead ends analyzing wrappers around Result::unwrap() and Vec::push.

Instead of fighting the compiler, we pivoted to a more data-centric analysis approach that helped identify unk_1FBBC7. We linked this blob of data to a loop performing a multi-byte XOR decryption uncovering a Solana Wallet Address associated with GLASSWORM: BjVeAjPrSKFiingBn4vZvghsGj9KCE8AJVtbc9S8o8SC.

Network Behavior

The sample handles HTTP states and parses network headers via the statically linked hyper Rust crate (or reqwest).

Our analysis indicates the sample did not modify this library to develop a custom C2 protocol, but rather leveraged it to read data stored at the Solana Wallet Address via an API. This behavior is similar to the variant analyzed in public reporting with SHA256 6ebeb188f3cc3b647c4460c0b8e41b75d057747c662f4cd7912d77deaccfd2f2.

The binary parses the data read from the network request using Regex found in the data section.

Conclusion & Attribution

We are looking at a Rust-based Downloader that we track as RustImplant.

Although the network behavior may not be apparent in sandbox runs, it can be triggered through bypassing the geofencing capabilities. The TTPs in this sample indicate it may be a variant associated with other GLASSWORM related samples.


Indicators of Compromise (IOCs)

Host-Based Indicators

  • Extension
    Filename: tailwind-nuxt.tailwindcss-for-react-0.14.29.vsix
    Sha256: 9e40d20dc3e3a8d4a394805dbebda3fc737f489ac997eb381cab285d85438ab5

  • Rust Sample Analyzed
    Filename: darwin.node
    Sha256: 026873b940176d103d45b41c9fba73f14cfcaca60e3117be81d2eadef85a4d17

  • Additional Rust Sample
    Filename: os.node
    Sha256: cbb3f830731fe2c9194f7fe5aa55479cffdae184039b0df078b1394209d7a49f

Network-Based Indicators

Solana Wallet Address: BjVeAjPrSKFiingBn4vZvghsGj9KCE8AJVtbc9S8o8SC

Remediation

We recommend adding alerts for new outbound connections to any blockchain network originating from a downloaded extension. Furthermore, ensure your endpoint detection and response (EDR) systems are not only updated with the indicators identified in this report, but also tuned to flag suspicious behaviors common to malicious extensions such as unusual network traffic, unauthorized credential access, or persistence mechanisms. Regular validation of detection rules and proactive threat hunting can help catch variants that evolve beyond known indicators.

Defense shouldn’t stop there — the dev-guard extension, built and maintained by the Yeeth Security team, adds a crucial layer of protection for VS Code and Cursor AI users. By continuously monitoring new campaigns and pushing live updates, dev-guard helps shut down these threats before they can spread across the developer community.