RubyGems Ghost Dev: 42 Malicious Crypto-Themed Gems That Open a Delayed Shell at Install

Account reqthrottle_3474 published 42 gems in about an hour on 2026-10-05. A native-extension hook runs at gem install, waits 20 to 40 minutes, then opens a reverse shell or downloads a wallet-theft kit. It refuses to run in CI and sandboxes.
Threat
reqthrottle_3474 ("Ghost Dev" profile), 42 RubyGems
Severity
HIGH
Type
Software supply chain, reverse shell, crypto-wallet stealer
Access
gem install / bundle install native-extension hook
Version
v0.1 (Draft) · 2026-10-12
Author
HuntPack
Confidence
High (gems, loader, C2), Medium (kit files, hashes, wallets)
01

Executive Summary

On 2026-10-05 the RubyGems account reqthrottle_3474, whose profile calls itself "Ghost Dev", published 42 malicious gems in about one hour. Twenty-six carry crypto-themed or generic utility names and sixteen are typosquats of real gems such as bitcoin, keccak and solana-ruby. Every gem declares a native extension, and RubyGems runs that extension's extconf.rb during gem install or bundle install. The Ruby in lib/ is a decoy.

The loader checks that it is on a real developer workstation (no CI variables, no sandbox-like hostname, user or path, uptime over 30 minutes, and developer files such as .ssh or .gem/credentials present), decodes a Base64 payload, forks twice, detaches, sleeps 20 to 40 minutes and then runs it. Eleven gems (Template A) open a reverse /bin/sh to 45.138.12.177 on TCP 8089 or 8090. Thirty-one (Template B) download wgkit.tar.gz from the same host on TCP 8092 and run wg_install.sh. OpenSourceMalware analysed that kit: a local TLS-intercepting proxy, a clipboard hijacker and a wallet grabber that swap withdrawal addresses and send seed phrases, private keys and wallet files to a hard-coded collector.

Highest-value defensive angle. Hunt the install, not the payload. The 20 to 40 minute sleep and a double fork cut the process link between gem install and the shell (Q8 rebuilds it by host), and the campaign's own gating means a sandbox or CI scan of the gems shows nothing. Name-based hunting of the 42 gems (Q1, native hunt N1) and the one hard-coded address (Q5) are the two checks that do not depend on catching the payload in the act.

Defender priority. Treat any developer workstation that installed one of the 42 gems as compromised, as SafeDep advises: the shell runs with the installing user's rights. Rotate every credential that user could read, check wallets and exchange accounts from a clean device, and block 45.138.12.177 at the network edge.

What this pack does not claim. No actor or group name is attributed: the only designators in the sources are the account name and the profile label "Ghost Dev". On the sources' own code excerpt the payload reads /proc/uptime and treats a read failure as 0.0, so on macOS (no /proc/uptime) the uptime check should fail and the payload should not run. That is an inference from the quoted code, not a statement in the reports, so macOS hits are treated as exposure of the install, not proof of compromise. The tools in the chain (ruby, gem, bundle, sh, tar, curl) are stock software and nothing here blocks them.

02

Source Review & Web Hunter Notes

TierSourceKey findingCarry forward
T1SafeDep, 42 Malicious RubyGems Open a Reverse Shell at Install Time (2026-10-05)Primary analysis. Account, 42-gem list with versions, extconf.rb loader code, environment checks, both payload templates, C2 address and ports, dropper command, IOC tableyes. Snapshot 01-safedep.txt
T2OpenSourceMalware, Malicious Crypto Shell Packages Target RubyGems (2026-10-05)Independent detection and analysis. "Ghost Dev" profile name, the same account and C2, the Wallet Guard kit contents, persistence, collector endpoints, wallets and SHA-256 hashesyes. Snapshot 04-opensourcemalware.txt. The kit analysis is single-source
T2The Hacker News, ThreatsDay bulletinOne paragraph restating the account, the 42 gems, the two payload counts and the C2 address, quoting SafeDeppartial. Snapshot 03-thehackernews.txt. Not independent of SafeDep
T3Cyber Security News, Malicious RubyGems Turn Developer Machines Into Monero MinersReviewed and rejected. It describes a different RubyGems campaign (Monero mining, SSH spread, other accounts and mining pools). It contains no reqthrottle_3474 or 45.138.12.177 indicatorno. Snapshot 02-cybersecuritynews.txt kept for the record; nothing from it is in this pack

Decisions

  • Independence. SafeDep and OpenSourceMalware reached the account and the C2 address separately on the same day, so 45.138.12.177 has two independent reporters and is the only indicator that ships at prevent. The Hacker News repeats SafeDep. The kit hashes, collector URLs, file paths and wallet addresses come from OpenSourceMalware alone and ship detect.
  • Source 2 is a different campaign. The brief listed it as a source for this pack. Its content is the Monero cryptojacking and SSH-worm cluster, so its wallets, mining pools, emails and accounts are not used. Mixing them in would be the related-cluster borrowing the duplicate gate exists to stop.
  • Naming. Only the designators in the sources are used: the account reqthrottle_3474 and the profile name "Ghost Dev". The kit's own label "Wallet Guard" is the operator's disguise, quoted from OpenSourceMalware. No group attribution is made.
  • Disagreements. SafeDep reports the reverse shell on 8089 (four named gems) and 8090 (the other seven); OpenSourceMalware reports 8090 only. Hunts cover both ports. SafeDep states that what wg_install.sh does is not known; OpenSourceMalware describes it, so the kit-behavior queries (Q4, Q9, Q10) are medium or low confidence.
  • Snapshots. The four sources were saved as plain text extracted from the fetched pages, not as model summaries, so that every indicator can be traced mechanically.
03

Hunt Brief & Attack Chain

Hunt hypotheses (highest fidelity first)

#HypothesisMITRETelemetryExpected FPConf
H1A host contacted the collector address 45.138.12.177 on any port.T1095, T1105, T1041Outbound network connection (CrowdStrike: NetworkConnectIP4. SentinelOne: IP Connect. Defender: DeviceNetworkEvents)Scanners that probe it on purposeHIGH
H2A developer ran a gem command naming one of the 42 gems.T1195.001, T1204.005Process creation with command line (CrowdStrike: ProcessRollup2. SentinelOne: Process Creation. Defender: DeviceProcessEvents)Tools that name a gem without installingHIGH
H3A shell command carried the dropper strings .w1.tgz, wgkit.tar.gz or wg_install.sh.T1105, T1059.004, T1070.004Process creation with command line (as H2)Analysts quoting the namesHIGH
H4A ruby process built the req_throttle_mini extension.T1195.001Process creation with command line (as H2)None expectedMEDIUM
H5A ruby process started a bare interactive shell or connected out on 8089, 8090 or 8092.T1059.004, T1095, T1571Process creation with parent (as H2) and outbound network connection (as H1)Ruby console helpers, apps using those portsMEDIUM
H6The same host shows a gem install and a ruby callback (sleep-gap correlation).T1497.003, T1195.001Process creation and outbound network connection, joined by hostDevelopers who run Ruby apps on those portsMEDIUM
H7The Wallet Guard kit is installed: kit file names, a loopback proxy on 8899, a rogue root CA.T1547.013, T1553.004, T1557Process creation with command line (as H2) and listening-port telemetry (CrowdStrike: NetworkListenIP4)Local dev servers on 8899MEDIUM
H8A Python process polls the clipboard through xclip or xsel.T1115Process creation with parent (as H2)Clipboard managersLOW

Attack chain

StepBehaviorTelemetryHunt angle
1Developer runs gem install or bundle install for a crypto-themed or typosquatted gem.Process creation with command lineQ1, N1
2RubyGems runs ext/req_throttle_mini/extconf.rb from the gemspec extensions: entry. The Ruby in lib/ is a decoy.Process creation with command lineQ2
3The loader exits quietly unless the environment looks like a workstation: CI variables, sandbox-like hostname, user or path, uptime under 30 minutes or no developer files all stop it.None. The checks are inside one Ruby processGap by design. See detection challenge 2
4Base64 payload decoded; fork twice and Process.setsid; standard streams to /dev/null; sleep 1200 to 2400 seconds; eval. The parent detaches and gem install finishes.Process creation (lineage is broken here)Q8, N2. See detection challenge 1
5ATemplate A (11 gems): TCPSocket to 45.138.12.177 on 8089 or 8090, /bin/sh wired to the socket, retry every 30 seconds.Outbound network connection; process creation with parentQ5, Q6, Q7
5BTemplate B (31 gems): after the delay, curl to /tmp/.w1.tgz, tar into /tmp/.w1, bash wg_install.sh, then rm -rf of both.Process creation with command line; outbound network connectionQ3, Q5
6Wallet Guard kit (OpenSourceMalware): copied to $HOME/.cache/.wg, root CA trusted, proxy variables added to shell profiles, two autostart entries, loopback proxy on 8899.Process creation; listening portQ4, Q9; file writes are a gap
7Collection and theft: wallet extension storage, seed phrase scan, clipboard swap about every 800 ms, withdrawal-address rewrite in proxied pages, upload to the collector on TCP 8080.Process creation with parent; outbound network connectionQ10, Q5; file reads and in-page rewrites are gaps

The two detection challenges

Challenge 1: the sleep breaks the lineage. The shell or downloader runs 20 to 40 minutes after gem install returned, in a process that was forked twice, given its own session and re-parented once its parent exited. A rule that waits for gem to be the ancestor of sh will never fire. This pack therefore hunts the install by name (Q1, N1), the payload by what it does regardless of ancestry (Q3, Q5, Q6, Q7), and joins the two by host (Q8). A ps check for an orphaned extconf.rb process during the sleep window is in N2. Whether a forked child that never calls exec produces its own process event on the sensor is not documented in the sources; validate it on a lab host.

Challenge 2: a detonation lab shows nothing. The campaign checks for CI variables, sandbox-style hostnames and users, scratch install paths such as /tmp/, short uptime and missing developer files, so an automated scanner, a CI job or a fresh container records a clean install with no network traffic. A "clean" verdict from a sandbox, a registry scanner that detonates, or a CI dependency check is not evidence a gem is safe. Hunt by name and by endpoint behavior on real developer machines, and do not try to reproduce the payload against the live C2 to prove the detection.

04

Consolidated IOC Table

TypeValueConfActionSource snapshotContext
RubyGems accountreqthrottle_3474 (profile name "Ghost Dev")HIGHdetect01, 03, 0448 gems hosted, 42 malicious, published 2026-10-05
IPv4 / C245.138.12.177HIGHprevent01, 03, 04Reverse shell TCP 8089 and 8090; kit download TCP 8092; collector TCP 8080. Two independent reporters (SafeDep, OpenSourceMalware). The only prevent row
URLhttp://45.138.12.177:8092/wgkit.tar.gzHIGHdetect01, 04Second-stage kit download (Template B, 31 gems). The IP above already carries the block
URLhttp://45.138.12.177:8080/wi/grabMEDIUMdetect04Grabber upload endpoint decoded by OpenSourceMalware. Single source
URLhttp://45.138.12.177:8080/wMEDIUMdetect04Browser-script fallback URL. Single source
Extension pathext/req_throttle_mini/extconf.rbHIGHdetect01, 04Declared in the gemspec of all 42 gems; the payload file
Dropped files/tmp/.w1.tgz, /tmp/.w1/, /tmp/.w1/wg_install.shHIGHdetect01Template B working files, deleted by the dropper after the run
Environment variablesWG_KIT_URL, WG_FASTMEDIUMhunt01Read by the Template B payload (override URL, 2 second delay). Not visible in process telemetry; for code review
Persistence$HOME/.cache/.wg, $HOME/.config/autostart/wg.desktop, wg-grab.desktopMEDIUMdetect04Kit install location and autostart entries. Single source
Root CA"Wallet Guard Root CA" (O=Wallet Guard LLC), valid 2026-10-05 to 2036-10-02MEDIUMdetect04Self-signed root the installer trusts. Single source
Local listener127.0.0.1:8899MEDIUMhunt04Interception proxy. Port can be used by other tools
SHA-256387a778a6376b6c060516ce5c1191a61e075acfe3fb073e6e5cd78eeb96a95b3MEDIUMdetect04_grab.py. Single source
SHA-256549d35217f1e884634993ba6f13dd8e82091dd9f995a9ed0cb575958e63f073dMEDIUMdetect04inject_proxy.py. Single source
SHA-25657a56ab47fa7f33147d4924b07e77d773515b4a06ca9ac090874209a9950f30bMEDIUMdetect04ext/content.js. Single source
SHA-256b857fdf59db6948ea01643d935742904e9275572e63cd23331a9405483c6379cMEDIUMdetect04wg_install.sh. Single source
SHA-25633276fedf0632be39b4e8a646c520bfa081bb7e67dce52042da46bbbda16d440MEDIUMdetect04wgkit.tar.gz. Single source
SHA-256075b6ac656aa414e2d21b9d1bc8b0fd030902202222e9d06a326fd80d7827ae6MEDIUMdetect04wg-ca.crt. Single source
Gems42 gem names (full list in the Section 10 gem block)HIGHdetect01, 0426 crypto-themed or generic names plus 16 typosquats. Block by name in the gem mirror; not a Falcon IOC type
Wallet addressesBTC, ETH, TRX, SOL, TON (Section 10 wallet block)MEDIUMhunt04Operator wallets embedded in the injected script. For chain analytics and exchange notification, not endpoint blocking

Of the 42 gems, 31 are Template B (kit download) and 11 are Template A (reverse shell). SafeDep names four Template A gems (req-throttle-mini, bitcoin-rpc-lite, wallet-crypto-utils, web3-eth-utils, all on port 8089) and does not enumerate the other seven, so hunts treat all 42 names the same.

05

Affected Surface & Telemetry Matrix

SurfaceTelemetryPriorityGap risk
Linux developer workstations and WSL distributionsProcess creation with command line and parent (CrowdStrike: ProcessRollup2. SentinelOne: Process Creation. Defender: DeviceProcessEvents); outbound network connection (CrowdStrike: NetworkConnectIP4. SentinelOne: IP Connect. Defender: DeviceNetworkEvents); listening port (CrowdStrike: NetworkListenIP4)HIGHFile writes under /tmp and $HOME, wallet-file reads and clipboard reads have no event used here (outside endpoint telemetry as built). WSL guest activity depends on sensor coverage of the distribution.
macOS developer workstationsProcess creation with command line (as above)MEDIUMThe install hook runs. By the quoted code the uptime check reads /proc/uptime, which macOS lacks, so the payload should be skipped. A hit proves exposure of the gem, not compromise. Validate on a macOS test host before downgrading.
CI runners and build containersProcess creation (as above)LOWThe payload refuses to run when CI variables are set or the path is under /tmp or /workspace, so a runner shows an install and no callback. Still check the lockfiles: the gem reached the build.
Gem mirrors, lockfiles and GemfilesRepository and artifact-manager inventory; Gemfile.lock contentHIGHOutside endpoint telemetry. bundle install reads names from a Gemfile, so the command line does not carry them (Q1 misses it); use N1.
Developer secrets and wallets in $HOMENone used hereHIGHThe gate itself looks for .ssh, .gitconfig, .npmrc, .gem/credentials and .bundle, so the hosts the payload runs on are the ones holding developer secrets. Wallet and browser-extension storage reads are a gap.
Network edge and DNSFirewall and proxy logs for 45.138.12.177 on 8080, 8089, 8090, 8092MEDIUMThe C2 is a bare IP, so there is no domain to hunt in DNS. Raw TCP on 8089 and 8090 is not HTTP and may not appear in proxy logs.
06

ATT&CK Mapping

TacticTechniqueObserved behaviorQuery or control
Initial AccessT1195.001 Compromise Software Dependencies and Development Tools42 malicious gems on RubyGems, 16 of them typosquatsQ1, Q2; PB1
ExecutionT1204.005 User Execution: Malicious LibraryDeveloper installs the gem; the gemspec native extension runs extconf.rbQ1, Q2
ExecutionT1059.004 Unix ShellReverse /bin/sh (Template A); sh -c dropper and bash wg_install.sh (Template B)Q3, Q7
Defense EvasionT1497.001 System ChecksCI variables, hostname, user, path, uptime and developer-file checksGap by design (challenge 2)
Defense EvasionT1497.003 Time Based EvasionSleep 1200 to 2400 seconds before eval or downloadQ8; N2
Defense EvasionT1027 Obfuscated Files or Information; T1140 Deobfuscate/Decode Files or InformationBase64 payload; hex URL decoded with a 4-byte XOR keyQ3 (post-decode behavior)
Defense EvasionT1070.004 File DeletionDropper removes /tmp/.w1.tgz and /tmp/.w1 after runningQ3; evidence is gone, hunt the command
Command and ControlT1095 Non-Application Layer ProtocolRaw TCP reverse shell to 45.138.12.177 on 8089 or 8090, 30 second retryQ5, Q6, Q7; PB2
Command and ControlT1105 Ingress Tool Transfercurl of wgkit.tar.gz from port 8092Q3, Q5; PB2
Command and ControlT1571 Non-Standard Port8089, 8090, 8092 and 8080 on the same hostQ6
PersistenceT1547.013 XDG Autostart Entrieswg.desktop and wg-grab.desktop in $HOME/.config/autostart (OpenSourceMalware)Q4; PB4
PersistenceT1546.004 Unix Shell Configuration ModificationProxy variables added to .bashrc and .zshrc (OpenSourceMalware)N4; PB4
Defense EvasionT1553.004 Install Root CertificateWallet Guard Root CA trusted by the installerQ4; N4; PB4
CollectionT1557 Adversary-in-the-MiddleLocal HTTP/HTTPS interception proxy on 127.0.0.1:8899 for 28 named exchange hostsQ9
CollectionT1115 Clipboard DataClipboard read about every 800 ms and address replacementQ10
CollectionT1005 Data from Local SystemBrowser wallet extension storage, Exodus and Electrum files, seed phrase, xprv, WIF and keystore scanGap (no file-read event used)
ImpactT1565.002 Transmitted Data ManipulationWithdrawal destination replaced in page forms and requests while the page still shows the originalGap (in-browser)
ExfiltrationT1041 Exfiltration Over C2 ChannelXOR-obfuscated, base64 data POSTed to the collector /grab endpointQ5
07

Native Audit-Log Hunts

Non-CQL checks for what endpoint telemetry does not show. Replace the angle-bracket placeholders before running. These commands only read; run them as the affected developer account, then as root for system paths.

N1 Lockfile, Gemfile and installed-gem sweep

# Repositories, build caches and shared mirrors (matches the 42 names; -w treats a hyphen as a word break)
grep -rEwl --include=Gemfile --include=Gemfile.lock --include='*.gemspec' -e '(req-throttle-mini|throttle-requests|rate-limit-mini|request-guard|bitcoin-rpc-lite|web3-eth-utils|wallet-crypto-utils|eth-keystore-utils|btc-wallet-tools|eth-address-utils|crypto-mnemonic-tools|wallet-backup-tool|bip39-wordlist-utils|hdkey-derive-helper|blockchain-sync-utils|tx-broadcast-utils|electrum-protocol-lite|coinmarket-utils|web3-sign-helper|crypto-key-utils|bitcoin-address-utils|ethereum-tx-helper|merkle-proof-lite|utxo-set-utils|lightning-invoice-utils|base58-check-helper|bitcion-ruby|bitcoij-ruby|bitcion|bitciin|etheremu\.rb|etherdum\.rb|tron-rb|lightinng-invoice|ligbtning-invoice|lighthing-invoice|crypti-toolbox|crylto-toolbox|cryoto-toolbox|kecack|keccka|solaan-ruby)' /srv /opt /home 2>/dev/null
# Gems already installed for the current user and system
gem list --local 2>/dev/null | grep -E -w '(req-throttle-mini|throttle-requests|rate-limit-mini|request-guard|bitcoin-rpc-lite|web3-eth-utils|wallet-crypto-utils|eth-keystore-utils|btc-wallet-tools|eth-address-utils|crypto-mnemonic-tools|wallet-backup-tool|bip39-wordlist-utils|hdkey-derive-helper|blockchain-sync-utils|tx-broadcast-utils|electrum-protocol-lite|coinmarket-utils|web3-sign-helper|crypto-key-utils|bitcoin-address-utils|ethereum-tx-helper|merkle-proof-lite|utxo-set-utils|lightning-invoice-utils|base58-check-helper|bitcion-ruby|bitcoij-ruby|bitcion|bitciin|etheremu\.rb|etherdum\.rb|tron-rb|lightinng-invoice|ligbtning-invoice|lighthing-invoice|crypti-toolbox|crylto-toolbox|cryoto-toolbox|kecack|keccka|solaan-ruby)'
# Review each hit. A name such as 'bitcion' can sit inside a longer legitimate name only if the hyphen word break matches.

N2 Orphaned extconf.rb process during the sleep window

# The forked child keeps the extconf.rb command line and is re-parented to PID 1 (or a user systemd/launchd)
ps -eo pid,ppid,etime,user,args | awk '$2==1 || $2 ~ /^[0-9]+$/' | grep -E '[e]xtconf\.rb'
# A ruby extconf.rb process that is still alive 20+ minutes after install is the sleeping payload
# Where it exists, list its open sockets
lsof -nP -p <PID> 2>/dev/null | grep -E 'TCP|IPv4'

N3 Template A implant: established connections

ss -tnp 2>/dev/null | grep -E '45\.138\.12\.177'      # Linux
lsof -nP -iTCP -sTCP:ESTABLISHED 2>/dev/null | grep -E '45\.138\.12\.177'   # macOS or Linux
# Template A reconnects every 30 seconds, so a live implant shows SYN-SENT or ESTABLISHED to 8089 or 8090

N4 Wallet Guard kit residue (OpenSourceMalware)

ls -la ~/.cache/.wg ~/.config/autostart/wg.desktop ~/.config/autostart/wg-grab.desktop 2>/dev/null
grep -nEi 'proxy.*8899|8899' ~/.bashrc ~/.zshrc ~/.profile 2>/dev/null
ss -ltnp 2>/dev/null | grep ':8899'                     # Linux
lsof -nP -iTCP:8899 -sTCP:LISTEN 2>/dev/null            # macOS or Linux
# Rogue root CA (Linux trust stores, then macOS keychains)
grep -rl 'Wallet Guard' /etc/ssl/certs /usr/local/share/ca-certificates /etc/pki/ca-trust 2>/dev/null
security find-certificate -a -c 'Wallet Guard Root CA' /Library/Keychains/System.keychain ~/Library/Keychains/login.keychain-db 2>/dev/null

N5 Install history and gem metadata

# Where a gem was installed from and when (adjust the gem directory to your Ruby)
ls -ld "$(gem env gemdir)"/gems/* 2>/dev/null | grep -E -w '(req-throttle-mini|throttle-requests|rate-limit-mini|request-guard|bitcoin-rpc-lite|web3-eth-utils|wallet-crypto-utils|eth-keystore-utils|btc-wallet-tools|eth-address-utils|crypto-mnemonic-tools|wallet-backup-tool|bip39-wordlist-utils|hdkey-derive-helper|blockchain-sync-utils|tx-broadcast-utils|electrum-protocol-lite|coinmarket-utils|web3-sign-helper|crypto-key-utils|bitcoin-address-utils|ethereum-tx-helper|merkle-proof-lite|utxo-set-utils|lightning-invoice-utils|base58-check-helper|bitcion-ruby|bitcoij-ruby|bitcion|bitciin|etheremu\.rb|etherdum\.rb|tron-rb|lightinng-invoice|ligbtning-invoice|lighthing-invoice|crypti-toolbox|crylto-toolbox|cryoto-toolbox|kecack|keccka|solaan-ruby)'
find "$(gem env gemdir)" -path '*/ext/req_throttle_mini/extconf.rb' 2>/dev/null
# Mirror or proxy logs: search downloads of the 42 names from 2026-10-05 onward

N6 Network edge

# Firewall, flow or proxy logs: destination 45.138.12.177, TCP 8080, 8089, 8090, 8092, from 2026-10-05
# Raw TCP on 8089 and 8090 will not appear in HTTP proxy logs; use flow or firewall records
08

Hunt Queries: CrowdStrike CQL, SentinelOne PowerQuery and Defender XDR KQL

Show all queries in
Pick your tenant's cloud first: every "Open in Falcon" button below uses this selection.
Q1 · Gem or Bundler command line naming one of the 42 malicious gems
CONF HIGHFP LOWCOST LOW

Looks for: A gem, bundle, bundler or ruby process whose command line names any of the 42 gem names from the SafeDep list, as an install, add, update or even a query. The delimiter group stops bitcion matching inside bitcoin. A bundle install that reads a Gemfile does not put gem names on the command line, so this query finds explicit installs only; use native hunt N1 for lockfiles.

Validation: schema-aligned, static-reviewedAutomated: static-passedEquivalence: equivalent-with-schema-assumptionsProfiles: microsoft-defender-xdr, sentinelone-endpoint-ocsf
Known limitations and deployment notes
  • PowerQuery: A name passed through a file such as a Gemfile rather than the command line is not captured.
  • PowerQuery assumes: The new process name is stored without a directory, so a versioned ruby such as ruby3.2 matches the numeric suffix pattern.
  • KQL – Defender XDR: A gem fetched by a process the sensor did not observe starting, or a name passed through a file such as a Gemfile rather than the command line, is not captured.
  • KQL – Defender XDR assumes: FileName holds the bare image name of the created process, so a versioned ruby such as ruby3.2 matches the numeric suffix pattern.
  • KQL – Defender XDR assumes: The two quote characters are matched by hex escape inside a character class so the verbatim string needs no quote doubling.
// HUNT: Ghost Dev malicious gem named on a gem or bundle command line
// MITRE: T1195.001, T1204.005
// CONF: high  FP: low  COST: low
// REQUIRES: process creation with command line (Falcon ProcessRollup2) on developer workstations
// FALSE POSITIVES: a security tool, ticket search or analyst that passes a gem name on a command line without installing it.
// LOOKBACK: 30d (set with the console time picker)
#event_simpleName=/ProcessRollup2|SyntheticProcessRollup2/
| FileName=/^(gem|bundle|bundler|ruby)[0-9.]*$/i
| CommandLine=/(^|[\s'"=,@])(req-throttle-mini|throttle-requests|rate-limit-mini|request-guard|bitcoin-rpc-lite|web3-eth-utils|wallet-crypto-utils|eth-keystore-utils|btc-wallet-tools|eth-address-utils|crypto-mnemonic-tools|wallet-backup-tool|bip39-wordlist-utils|hdkey-derive-helper|blockchain-sync-utils|tx-broadcast-utils|electrum-protocol-lite|coinmarket-utils|web3-sign-helper|crypto-key-utils|bitcoin-address-utils|ethereum-tx-helper|merkle-proof-lite|utxo-set-utils|lightning-invoice-utils|base58-check-helper|bitcion-ruby|bitcoij-ruby|bitcion|bitciin|etheremu\.rb|etherdum\.rb|tron-rb|lightinng-invoice|ligbtning-invoice|lighthing-invoice|crypti-toolbox|crylto-toolbox|cryoto-toolbox|kecack|keccka|solaan-ruby)([\s'",@:=]|-[0-9]|$)/i
| table([@timestamp, aid, ComputerName, UserName, event_platform, FileName, ParentBaseFileName, CommandLine])
Q2 · ruby building the req_throttle_mini native extension
CONF MEDIUMFP LOWCOST LOW

Looks for: A ruby process running extconf.rb from the req_throttle_mini extension directory. SafeDep shows the same ext/req_throttle_mini/extconf.rb declaration in the gem specification, and OpenSourceMalware names it as the payload file. Whether the directory appears in the sensor's command line depends on how RubyGems invokes the build, so confidence is medium until you confirm it on a tenant host. An infected host will often show no hit here because the build step is brief; its value is as an install-time anchor.

Validation: schema-aligned, static-reviewedAutomated: static-passedEquivalence: equivalent-with-schema-assumptionsProfiles: microsoft-defender-xdr, sentinelone-endpoint-ocsf
Known limitations and deployment notes
  • PowerQuery: If the build runs with the directory name only in the working directory and not on the command line, the row is not produced.
  • PowerQuery assumes: The extension directory name appears in the command line of the ruby process for a gem native-extension build; validate this in the tenant.
  • KQL – Defender XDR: If the build runs with the directory name only in the working directory and not on the command line, the row is not produced.
  • KQL – Defender XDR assumes: The extension directory name appears in the command line of the ruby process for a gem native-extension build; validate this in the tenant.
// HUNT: Ghost Dev req_throttle_mini extconf.rb build
// MITRE: T1195.001, T1059
// CONF: medium  FP: low  COST: low
// REQUIRES: process creation with command line (Falcon ProcessRollup2); ⚠ validate in tenant that the extension directory name appears in CommandLine for gem builds
// FALSE POSITIVES: none expected; the directory name is specific to this campaign.
// LOOKBACK: 30d (set with the console time picker)
#event_simpleName=/ProcessRollup2|SyntheticProcessRollup2/
| FileName=/^ruby[0-9.]*$/i
| CommandLine=/extconf\.rb/i
| CommandLine=/req_throttle_mini/i
| table([@timestamp, aid, ComputerName, UserName, event_platform, FileName, ParentBaseFileName, CommandLine])
Q3 · Second-stage dropper shell: .w1.tgz, wgkit.tar.gz or wg_install.sh
CONF HIGHFP LOWCOST LOW

Looks for: Any process, normally sh -c, curl, tar or bash, whose command line carries the dropper's own strings. SafeDep shows the exact command Ruby hands to the shell: curl to /tmp/.w1.tgz, tar into /tmp/.w1, bash on wg_install.sh, then rm -rf. The strings are matched by basename (.w1.tgz, wgkit.tar.gz, wg_install.sh) so the regex needs no path slash. Template B only: 31 of the 42 gems.

Validation: schema-aligned, static-reviewedAutomated: static-passedEquivalence: equivalent-with-schema-assumptionsProfiles: microsoft-defender-xdr, sentinelone-endpoint-ocsf
Known limitations and deployment notes
  • PowerQuery: A dropper run from a script file that does not repeat the artifact names on a command line is not captured.
  • KQL – Defender XDR: A dropper run from a script file that does not repeat the artifact names on a command line is not captured.
  • KQL – Defender XDR assumes: Linux and macOS process-creation events carry the full command line in ProcessCommandLine.
// HUNT: Ghost Dev second-stage dropper command (Template B)
// MITRE: T1105, T1059.004, T1070.004
// CONF: high  FP: low  COST: low
// REQUIRES: process creation with command line (Falcon ProcessRollup2) on Linux and macOS developer workstations
// FALSE POSITIVES: an analyst or tool that echoes the filenames without running the dropper.
// LOOKBACK: 30d (set with the console time picker)
#event_simpleName=/ProcessRollup2|SyntheticProcessRollup2/
| CommandLine=/\.w1\.tgz|wgkit\.tar\.gz|wg_install\.sh/i
| table([@timestamp, aid, ComputerName, UserName, event_platform, FileName, ParentBaseFileName, CommandLine])
Q4 · Wallet Guard kit artifacts: proxy, grabber, autostart and root CA
CONF MEDIUMFP LOWCOST LOW

Looks for: Process command lines naming the kit's own files: inject_proxy.py, _grab.py, wg-grab.desktop, wg.desktop, the $HOME/.cache/.wg directory, wg-ca.crt or the Wallet Guard Root CA subject. These names come from the OpenSourceMalware analysis only (SafeDep did not recover wg_install.sh), so the query ships at medium confidence and the paths are matched with escaped slashes.

Validation: schema-aligned, static-reviewedAutomated: static-passedEquivalence: equivalent-with-schema-assumptionsProfiles: microsoft-defender-xdr, sentinelone-endpoint-ocsf
Known limitations and deployment notes
  • PowerQuery: Kit files touched only by file operations, with no process command line naming them, are not captured.
  • KQL – Defender XDR: Kit files touched only by file operations, with no process command line naming them, are not captured.
  • KQL – Defender XDR assumes: Linux process-creation events carry the full command line in ProcessCommandLine.
// HUNT: Ghost Dev Wallet Guard kit files on a command line
// MITRE: T1547.013, T1553.004, T1557, T1115
// CONF: medium  FP: low  COST: low
// REQUIRES: process creation with command line (Falcon ProcessRollup2) on Linux developer workstations
// FALSE POSITIVES: none expected; every name is specific to the kit. Single source (OpenSourceMalware).
// LOOKBACK: 30d (set with the console time picker)
#event_simpleName=/ProcessRollup2|SyntheticProcessRollup2/
| CommandLine=/inject_proxy\.py|_grab\.py|wg-grab|wg-ca\.crt|Wallet Guard Root CA|\.cache\/\.wg/i
| table([@timestamp, aid, ComputerName, UserName, event_platform, FileName, ParentBaseFileName, CommandLine])
Q5 · Any connection to the 45.138.12.177 collector, any port
CONF HIGHFP LOWCOST LOW

Looks for: Any outbound IPv4 connection to 45.138.12.177. SafeDep and OpenSourceMalware, analysing independently, both name this address: SafeDep for the reverse shell (TCP 8089 or 8090) and the kit download (TCP 8092), OpenSourceMalware for the /wi/grab and /w collector paths on TCP 8080. All ports are matched on purpose. Falcon logs connection attempts, so a blocked attempt still fires.

Validation: schema-aligned, static-reviewedAutomated: static-passedEquivalence: equivalent-with-schema-assumptionsProfiles: microsoft-defender-xdr, sentinelone-endpoint-ocsf
Known limitations and deployment notes
  • PowerQuery: Retention on the tenant may be shorter than the 90d CQL window.
  • KQL – Defender XDR: Advanced Hunting retention is 30 days, so connections between 30 and 90 days old are visible only on the CQL tab.
  • KQL – Defender XDR assumes: DeviceNetworkEvents records the connection attempt for the process that made it.
// HUNT: Ghost Dev connection to the 45.138.12.177 C2 and collector
// MITRE: T1095, T1105, T1041
// CONF: high  FP: low  COST: low
// REQUIRES: outbound network connection telemetry (Falcon NetworkConnectIP4)
// FALSE POSITIVES: a scanner or threat-intelligence tool that probes the address on purpose.
// LOOKBACK: 90d (set with the console time picker)
#event_simpleName=NetworkConnectIP4
| RemoteAddressIP4=45.138.12.177
| table([@timestamp, aid, ComputerName, event_platform, ContextBaseFileName, LocalAddressIP4, LocalPort, RemoteAddressIP4, RemotePort])
Q6 · ruby connecting out on the campaign reverse-shell and kit ports
CONF MEDIUMFP MEDIUMCOST MEDIUM

Looks for: A ruby process connecting to a public address on TCP 8089, 8090 or 8092, the ports SafeDep reports for the reverse shell (8089 for four gems, 8090 for the other seven) and the kit download. This is the address-independent version of Q5: it still fires if the operator moves the C2 to a new IP and keeps the template. The reconnect loop retries every 30 seconds, so a live implant produces a steady count. FP: Ruby web apps and test servers that legitimately talk to a public service on 8089, 8090 or 8092.

Validation: schema-aligned, static-reviewedAutomated: static-passedEquivalence: equivalent-with-schema-assumptionsProfiles: microsoft-defender-xdr, sentinelone-endpoint-ocsf
Known limitations and deployment notes
  • PowerQuery: The 100.64.0.0/10 shared range is not excluded in either language.
  • PowerQuery assumes: dst.port.number is stored as a number, so the in list compares numerically.
  • KQL – Defender XDR: The CQL excludes four private and special ranges; the 100.64.0.0/10 shared range is not excluded in either language.
  • KQL – Defender XDR assumes: IPv6 destinations are not evaluated by the address-range tests, matching the IPv4-only CQL.
// HUNT: Ghost Dev ruby outbound on campaign ports to a public address
// MITRE: T1095, T1571
// CONF: medium  FP: medium  COST: medium
// REQUIRES: outbound network connection telemetry (Falcon NetworkConnectIP4) with the connecting image name
// FALSE POSITIVES: Ruby applications that call a public service on 8089, 8090 or 8092.
// TUNING: exclude approved destinations by RemoteAddressIP4 and approved application hosts by ComputerName; a developer workstation with a count that repeats every 30 seconds is the signal.
// LOOKBACK: 14d (set with the console time picker)
#event_simpleName=NetworkConnectIP4
| ContextBaseFileName=/^ruby[0-9.]*$/i
| RemotePort=/^(8089|8090|8092)$/
| !cidr(RemoteAddressIP4, subnet=["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "127.0.0.0/8", "169.254.0.0/16"])
| groupBy([aid, ComputerName, ContextBaseFileName, RemoteAddressIP4, RemotePort], function=[count(as=connections), min(@timestamp, as=first_seen), max(@timestamp, as=last_seen)], limit=1000)
| sort(connections, order=desc, limit=200)
Q7 · ruby spawning a bare interactive shell
CONF MEDIUMFP MEDIUMCOST LOW

Looks for: A ruby parent starting sh, bash or dash with no arguments. The Template A payload calls IO.popen(['/bin/sh'], 'r+') and wires the shell to a socket, so the shell has no -c string. Ordinary Ruby tooling nearly always passes -c or a script. This is the post-sleep behavior that does not depend on the parent-child link back to gem install. FP: Ruby tools that open an interactive subshell (some console and REPL helpers).

Validation: schema-aligned, static-reviewedAutomated: static-passedEquivalence: equivalent-with-schema-assumptionsProfiles: microsoft-defender-xdr, sentinelone-endpoint-ocsf
Known limitations and deployment notes
  • PowerQuery: If the double fork reparents the shell away from ruby, the creator is not ruby and the row is not produced.
  • PowerQuery assumes: After the double fork of the reverse shell the creator still resolves to ruby; validate this in the tenant.
  • KQL – Defender XDR: If the double fork reparents the shell away from ruby, the initiating process is not ruby and the row is not produced.
  • KQL – Defender XDR assumes: After the double fork of the reverse shell the initiating process still resolves to ruby; validate this in the tenant.
// HUNT: Ghost Dev ruby spawns a bare /bin/sh (Template A reverse shell)
// MITRE: T1059.004, T1095
// CONF: medium  FP: medium  COST: low
// REQUIRES: process creation with command line and parent name (Falcon ProcessRollup2); ⚠ validate in tenant that the parent resolves to ruby after the double fork
// FALSE POSITIVES: Ruby console helpers that start an interactive subshell.
// TUNING: exclude known console helpers by ComputerName or UserName; a hit with an established outbound connection from the same host (Q5, Q6) is the high-confidence case.
// LOOKBACK: 14d (set with the console time picker)
#event_simpleName=/ProcessRollup2|SyntheticProcessRollup2/
| ParentBaseFileName=/^ruby[0-9.]*$/i
| FileName=/^(sh|bash|dash)$/i
| CommandLine=/^(\/bin\/|\/usr\/bin\/)?(sh|bash|dash)\s*$/i
| table([@timestamp, aid, ComputerName, UserName, event_platform, FileName, ParentBaseFileName, CommandLine])
Q8 · Host-level link: a gem or bundle install and a ruby callback on the same host
CONF MEDIUMFP MEDIUMCOST MEDIUM

Looks for: Rebuilds the link the 20 to 40 minute sleep breaks. The parent-child chain from gem install to the shell is gone by the time the payload runs (the parent exits and the twice-forked child is re-parented), so this query matches on host instead of lineage: a host that ran a gem or bundle install and, in the same window, had ruby connect to 8089, 8090 or 8092. Narrow the time picker to a 60 to 90 minute slice after a suspect install for the cleanest read. FP: A developer who installs gems and also runs a Ruby app that uses one of those ports.

Validation: static-reviewedEquivalence: equivalent-with-schema-assumptions, unsupported-without-redesignProfiles: microsoft-defender-xdr, sentinelone-endpoint-ocsf
Known limitations and deployment notes
  • PowerQuery: The callback half does not exclude private destinations, matching the CQL.
  • PowerQuery assumes: dst.port.number is stored as a number, so the in list compares numerically.
  • KQL – Defender XDR: The same-host correlation of an install and a ruby callback is not expressed in Defender XDR here.
// HUNT: Ghost Dev gem install and ruby callback on one host (sleep-gap correlation)
// MITRE: T1195.001, T1497.003, T1095
// CONF: medium  FP: medium  COST: medium
// REQUIRES: process creation (Falcon ProcessRollup2) and outbound network connection (NetworkConnectIP4) from the same host; ⚠ validate in tenant that evt collects both event names
// FALSE POSITIVES: a developer who installs gems and runs a Ruby app that uses 8089, 8090 or 8092.
// TUNING: run with a 90 minute time picker slice after a suspect install; exclude approved application hosts by ComputerName.
// LOOKBACK: 7d (set with the console time picker)
#event_simpleName=/ProcessRollup2|SyntheticProcessRollup2|NetworkConnectIP4/
| (FileName=/^(gem|bundle|bundler|ruby)[0-9.]*$/i CommandLine=/\b(gem|bundle|bundler)\b.*\binstall\b/i) or (ContextBaseFileName=/^ruby[0-9.]*$/i RemotePort=/^(8089|8090|8092)$/)
| evt := #event_simpleName
| groupBy([aid, ComputerName], function=[count(as=events), collect([evt]), min(@timestamp, as=first_seen), max(@timestamp, as=last_seen)], limit=1000)
| evt=/NetworkConnectIP4/
| evt=/ProcessRollup2/
| sort(last_seen, order=desc, limit=200)
Q9 · Local listener on 127.0.0.1:8899 (interception proxy)
CONF MEDIUMFP MEDIUMCOST LOW

Looks for: A process listening on loopback TCP 8899. OpenSourceMalware reports that inject_proxy.py listens on 127.0.0.1:8899 for HTTP and HTTPS CONNECT traffic and that the installer writes proxy variables to .bashrc and .zshrc. A single source, so medium confidence, and a port that other developer tools can use. FP: Local development servers, debuggers and proxies that bind 8899.

Validation: schema-aligned, static-reviewedAutomated: static-passedEquivalence: approximate-analytic, equivalent-with-schema-assumptionsProfiles: microsoft-defender-xdr, sentinelone-endpoint-ocsf
Known limitations and deployment notes
  • PowerQuery: The listening process is not identified, only clients that connect to it; a listener with no client in the window is missed.
  • PowerQuery assumes: Loopback connections are recorded as IP Connect events on this tenant.
  • PowerQuery assumes: dst.port.number is stored as a number.
  • KQL – Defender XDR: If the tenant does not record listening events, the query returns nothing, which reads as a clean estate.
  • KQL – Defender XDR assumes: The ListeningConnectionCreated action type is populated on this tenant; it is attested in Microsoft-owned hunting repositories, not on the Learn table page.
// HUNT: Ghost Dev Wallet Guard loopback proxy listener
// MITRE: T1557, T1090.001
// CONF: medium  FP: medium  COST: low
// REQUIRES: listening-port telemetry (Falcon NetworkListenIP4)
// FALSE POSITIVES: local development servers and debuggers that bind 8899.
// TUNING: exclude known dev tools by ContextBaseFileName once baselined; a python listener on 8899 on a host that also hit Q3 or Q4 is the strong case.
// LOOKBACK: 14d (set with the console time picker)
#event_simpleName=NetworkListenIP4
| LocalPort=8899
| LocalAddressIP4=127.0.0.1
| table([@timestamp, aid, ComputerName, event_platform, ContextBaseFileName, LocalAddressIP4, LocalPort])
Q10 · Clipboard polling by a Python parent (xclip or xsel)
CONF LOWFP MEDIUMCOST MEDIUM

Looks for: Many xclip or xsel launches whose parent is a Python interpreter. OpenSourceMalware reports that the grabber reads the clipboard about every 800 milliseconds on Unix-like systems using xclip and xsel, which would produce thousands of launches an hour. This is a behavior hunt, not an indicator, so confidence is low. FP: Clipboard managers and desktop automation written in Python.

Validation: schema-aligned, static-reviewedAutomated: static-passedEquivalence: equivalent-with-schema-assumptionsProfiles: microsoft-defender-xdr, sentinelone-endpoint-ocsf
Known limitations and deployment notes
  • PowerQuery: Clipboard access that does not spawn xclip or xsel, such as an in-process library call, is not captured.
  • PowerQuery assumes: The creator process name is python or a versioned python such as python3.11.
  • KQL – Defender XDR: Clipboard access that does not spawn xclip or xsel, such as an in-process library call, is not captured.
  • KQL – Defender XDR assumes: Linux sensors record every xclip and xsel start as a process-creation event.
// HUNT: Ghost Dev clipboard polling pattern (Wallet Guard grabber)
// MITRE: T1115
// CONF: low  FP: medium  COST: medium
// REQUIRES: process creation with parent name (Falcon ProcessRollup2) on Linux desktop developer workstations
// FALSE POSITIVES: clipboard managers and desktop automation scripts written in Python.
// TUNING: exclude approved clipboard tools by ComputerName or by the ParentBaseFileName value you baseline; raise the threshold above your busiest legitimate tool.
// LOOKBACK: 7d (set with the console time picker)
#event_simpleName=/ProcessRollup2|SyntheticProcessRollup2/
| FileName=/^(xclip|xsel)$/i
| ParentBaseFileName=/^python[0-9.]*$/i
| groupBy([aid, ComputerName, ParentBaseFileName], function=[count(as=clipboard_reads), min(@timestamp, as=first_seen), max(@timestamp, as=last_seen)], limit=1000)
| test(clipboard_reads >= 500)
| sort(clipboard_reads, order=desc, limit=200)
09

Detection Rules by Platform

CrowdStrike: Custom IOA

IOA namePlatform / typePatternExclusionsDeployment
IOA-1 Ghost Dev dropper command. Promote Q3Linux and macOS, Process CreationCommand Line .*(\.w1\.tgz|wgkit\.tar\.gz|wg_install\.sh).*None needed. The strings are specific to this campaign.Detect. The pattern is a campaign string, not a tool, and the IOA does not block sh, curl or tar.
IOA-2 ruby spawns a bare shell. Promote Q7Linux, Process CreationParent Image File Name .*/ruby[0-9.]*$ and Command Line ^(/bin/|/usr/bin/)?(sh|bash|dash)\s*$Approved Ruby console helpers by host group.Detect only. Behavioral, medium false-positive risk, and it matches a stock binary.
IOA-3 Connection to the collector. Promote Q5Linux and macOS, Network ConnectionRemote IP 45.138.12.177, any portThreat-intelligence scanners by host.Detect. The same address is in the Falcon IOC CSV at prevent; the IOA adds the connecting process to the alert.
IOA-4 ruby callback on campaign ports. Promote Q6Linux, Network ConnectionImage .*/ruby[0-9.]*$, remote port 8089, 8090 or 8092, remote address not privateApproved application hosts and destinations.Detect only after a 14 day baseline. Behavioral, not an indicator.

No IOA is proposed for ruby, gem, bundle, sh, tar or curl on their own. They are stock binaries and every rule above is scoped to a campaign string, a campaign address or a behavior, at detect.

SentinelOne: custom detection rules

RuleSource queryRun asNotes
IOA-1 Ghost Dev dropper command. Promote Q3Q3, PowerQuery tabScheduled custom detection ruleBind entities so the alert names the host instead of "Unknown Device"
IOA-2 ruby spawns a bare shell. Promote Q7Q7, PowerQuery tabScheduled custom detection ruleBind entities so the alert names the host instead of "Unknown Device"
IOA-3 Connection to the collector. Promote Q5Q5, PowerQuery tabScheduled custom detection ruleBind entities so the alert names the host instead of "Unknown Device"
IOA-4 ruby callback on campaign ports. Promote Q6Q6, PowerQuery tabScheduled custom detection ruleBind entities so the alert names the host instead of "Unknown Device". This pack's own text mentions a baseline or pilot; follow it as written

Create the rule with POST /web/api/v2.1/cloud-detection/rules, queryType: "scheduled" and queryLang: "2.0", with the query in data.scheduledParams.query. Use 2.0 operator spellings: a rule written with the 1.0 token ContainsCIS was accepted and never fired on a tenant. The API does not warn, so lint the body before deploying.

Microsoft Defender: custom detection rules

RuleSource queryRun asNotes
IOA-1 Ghost Dev dropper command. Promote Q3Q3, KQL – Defender XDR tabCustom detection rule, alert only, every hourThe query returns Timestamp, ReportId and DeviceId, which Microsoft recommends for Defender for Endpoint tables so alerts carry the device group scope and the process tree. Remove the Timestamp filter and the final sort and take before saving the rule: the service applies the rule's lookback itself
IOA-2 ruby spawns a bare shell. Promote Q7Q7, KQL – Defender XDR tabCustom detection rule, alert only, every hourThe query returns Timestamp, ReportId and DeviceId, which Microsoft recommends for Defender for Endpoint tables so alerts carry the device group scope and the process tree. Remove the Timestamp filter and the final sort and take before saving the rule: the service applies the rule's lookback itself
IOA-3 Connection to the collector. Promote Q5Q5, KQL – Defender XDR tabCustom detection rule, alert only, every hourThe query returns Timestamp, ReportId and DeviceId, which Microsoft recommends for Defender for Endpoint tables so alerts carry the device group scope and the process tree. Remove the Timestamp filter and the final sort and take before saving the rule: the service applies the rule's lookback itself
IOA-4 ruby callback on campaign ports. Promote Q6Q6, KQL – Defender XDR tabCustom detection rule, alert only, every hourMake the final result include Timestamp, ReportId and DeviceId: Microsoft recommends them for Defender for Endpoint tables so alerts carry the device group scope and the process tree. Remove the Timestamp filter and the final sort and take before saving the rule: the service applies the rule's lookback itself. This pack's own text mentions a baseline or pilot; follow it as written

Q1, Q2, Q4, Q8, Q9 and Q10 stay a hunt in this pack, not a rule. A rule can generate only 150 alerts per run. Continuous (NRT) frequency needs a single table and no comment lines, so delete the generated comment header from the query first.

10

Machine-Readable IOC Appendix

Falcon IOC Management CSVbulk import, 1 prevent, 9 detect
type,value,action,severity,expiration,description,tags
ipv4,45.138.12.177,prevent,critical,2027-04-12,Ghost Dev reverse shell C2 8089/8090 and kit host 8092/8080 (SafeDep and OpenSourceMalware independently),campaign:GhostDev-RubyGems
url,http://45.138.12.177:8092/wgkit.tar.gz,detect,high,2027-04-12,Ghost Dev second-stage kit download (SafeDep and OpenSourceMalware),campaign:GhostDev-RubyGems
url,http://45.138.12.177:8080/wi/grab,detect,medium,2027-04-12,Ghost Dev grabber upload endpoint (single source: OpenSourceMalware),campaign:GhostDev-RubyGems
url,http://45.138.12.177:8080/w,detect,medium,2027-04-12,Ghost Dev browser-script fallback URL (single source: OpenSourceMalware),campaign:GhostDev-RubyGems
sha256,387a778a6376b6c060516ce5c1191a61e075acfe3fb073e6e5cd78eeb96a95b3,detect,medium,2027-04-12,Ghost Dev Wallet Guard kit file _grab.py (single source: OpenSourceMalware),campaign:GhostDev-RubyGems
sha256,549d35217f1e884634993ba6f13dd8e82091dd9f995a9ed0cb575958e63f073d,detect,medium,2027-04-12,Ghost Dev Wallet Guard kit file inject_proxy.py (single source: OpenSourceMalware),campaign:GhostDev-RubyGems
sha256,57a56ab47fa7f33147d4924b07e77d773515b4a06ca9ac090874209a9950f30b,detect,medium,2027-04-12,Ghost Dev Wallet Guard kit file ext/content.js (single source: OpenSourceMalware),campaign:GhostDev-RubyGems
sha256,b857fdf59db6948ea01643d935742904e9275572e63cd23331a9405483c6379c,detect,medium,2027-04-12,Ghost Dev Wallet Guard kit file wg_install.sh (single source: OpenSourceMalware),campaign:GhostDev-RubyGems
sha256,33276fedf0632be39b4e8a646c520bfa081bb7e67dce52042da46bbbda16d440,detect,medium,2027-04-12,Ghost Dev Wallet Guard kit file wgkit.tar.gz (single source: OpenSourceMalware),campaign:GhostDev-RubyGems
sha256,075b6ac656aa414e2d21b9d1bc8b0fd030902202222e9d06a326fd80d7827ae6,detect,medium,2027-04-12,Ghost Dev Wallet Guard kit file wg-ca.crt (single source: OpenSourceMalware),campaign:GhostDev-RubyGems
Microsoft Defender indicators CSVSettings, Endpoints, Indicators, Import: audit and block
indicatorType,indicatorValue,action,title,description,expirationTime,severity,recommendedActions,rbacGroups,category,mitretechniques,GenerateAlert
IpAddress,45.138.12.177,Block,Ghost Dev reverse shell C2 8089/8090 and kit host 8092/8080,Ghost Dev reverse shell C2 8089/8090 and kit host 8092/8080 (SafeDep and OpenSourceMalware independently),2027-04-12T00:00:00.0Z,High,Investigate the device and pivot into the pack's queries,,,,True
Url,http://45.138.12.177:8092/wgkit.tar.gz,Audit,Ghost Dev second-stage kit download,Ghost Dev second-stage kit download (SafeDep and OpenSourceMalware),2027-04-12T00:00:00.0Z,High,Investigate the device and pivot into the pack's queries,,,,True
Url,http://45.138.12.177:8080/wi/grab,Audit,Ghost Dev grabber upload endpoint,Ghost Dev grabber upload endpoint (single source: OpenSourceMalware),2027-04-12T00:00:00.0Z,Medium,Investigate the device and pivot into the pack's queries,,,,True
Url,http://45.138.12.177:8080/w,Audit,Ghost Dev browser-script fallback URL,Ghost Dev browser-script fallback URL (single source: OpenSourceMalware),2027-04-12T00:00:00.0Z,Medium,Investigate the device and pivot into the pack's queries,,,,True
FileSha256,387a778a6376b6c060516ce5c1191a61e075acfe3fb073e6e5cd78eeb96a95b3,Audit,Ghost Dev Wallet Guard kit file _grab.py,Ghost Dev Wallet Guard kit file _grab.py (single source: OpenSourceMalware),2027-04-12T00:00:00.0Z,Medium,Investigate the device and pivot into the pack's queries,,,,True
FileSha256,549d35217f1e884634993ba6f13dd8e82091dd9f995a9ed0cb575958e63f073d,Audit,Ghost Dev Wallet Guard kit file inject_proxy.py,Ghost Dev Wallet Guard kit file inject_proxy.py (single source: OpenSourceMalware),2027-04-12T00:00:00.0Z,Medium,Investigate the device and pivot into the pack's queries,,,,True
FileSha256,57a56ab47fa7f33147d4924b07e77d773515b4a06ca9ac090874209a9950f30b,Audit,Ghost Dev Wallet Guard kit file ext/content.js,Ghost Dev Wallet Guard kit file ext/content.js (single source: OpenSourceMalware),2027-04-12T00:00:00.0Z,Medium,Investigate the device and pivot into the pack's queries,,,,True
FileSha256,b857fdf59db6948ea01643d935742904e9275572e63cd23331a9405483c6379c,Audit,Ghost Dev Wallet Guard kit file wg_install.sh,Ghost Dev Wallet Guard kit file wg_install.sh (single source: OpenSourceMalware),2027-04-12T00:00:00.0Z,Medium,Investigate the device and pivot into the pack's queries,,,,True
FileSha256,33276fedf0632be39b4e8a646c520bfa081bb7e67dce52042da46bbbda16d440,Audit,Ghost Dev Wallet Guard kit file wgkit.tar.gz,Ghost Dev Wallet Guard kit file wgkit.tar.gz (single source: OpenSourceMalware),2027-04-12T00:00:00.0Z,Medium,Investigate the device and pivot into the pack's queries,,,,True
FileSha256,075b6ac656aa414e2d21b9d1bc8b0fd030902202222e9d06a326fd80d7827ae6,Audit,Ghost Dev Wallet Guard kit file wg-ca.crt,Ghost Dev Wallet Guard kit file wg-ca.crt (single source: OpenSourceMalware),2027-04-12T00:00:00.0Z,Medium,Investigate the device and pivot into the pack's queries,,,,True

Column names and allowed values are from Microsoft's documented parameter table. Prevent rows import as Block, without remediation; network indicators cannot use BlockAndRemediate. Check each expirationTime before importing: an indicator whose date has passed is already expired. Download the sample CSV from the Import page to confirm the column order before a bulk import. Each file takes at most 500 indicators. Learn describes the import per entity type, so if a mixed file is rejected, split it by indicatorType. An imported indicator does nothing unless the matching Advanced features are on (for example Custom network indicators and Allow or block file).

SentinelOne IOC importnot included

This pack does not ship a SentinelOne import block: the import format could not be verified against SentinelOne's own documentation. Export a sample from your console and the block can be added with confidence.

Malicious gems (42)gem mirror block by name
# Ghost Dev malicious gems (publisher account: reqthrottle_3474), 42 of the account's 48 gems
# Block by NAME in your gem mirror or artifact manager. Not a Falcon IOC type.
req-throttle-mini 1.0.1
throttle-requests 1.0.1
rate-limit-mini 1.0.1
request-guard 1.0.1
bitcoin-rpc-lite 1.0.0
web3-eth-utils 1.0.0
wallet-crypto-utils 1.0.0
eth-keystore-utils 1.0.0
btc-wallet-tools 1.0.0
eth-address-utils 1.0.0
crypto-mnemonic-tools 1.0.0
wallet-backup-tool 1.0.0
bip39-wordlist-utils 1.0.0
hdkey-derive-helper 1.0.0
blockchain-sync-utils 1.0.0
tx-broadcast-utils 1.0.0
electrum-protocol-lite 1.0.0
coinmarket-utils 1.0.0
web3-sign-helper 1.0.0
crypto-key-utils 1.0.0
bitcoin-address-utils 1.0.0
ethereum-tx-helper 1.0.0
merkle-proof-lite 1.0.0
utxo-set-utils 1.0.0
lightning-invoice-utils 1.0.0
base58-check-helper 1.0.0
bitcion-ruby 1.0.0
bitcoij-ruby 1.0.0
bitcion 1.0.0
bitciin 1.0.0
etheremu.rb 1.0.0
etherdum.rb 1.0.0
tron-rb 1.0.0
lightinng-invoice 1.0.0
ligbtning-invoice 1.0.0
lighthing-invoice 1.0.0
crypti-toolbox 1.0.0
crylto-toolbox 1.0.0
cryoto-toolbox 1.0.0
kecack 1.0.0
keccka 1.0.0
solaan-ruby 1.0.0
Host artifacts and variableshost sweep
# Host sweep list (SafeDep unless marked OSM = OpenSourceMalware only)
ext/req_throttle_mini/extconf.rb          (gemspec extensions: entry in all 42 gems)
/tmp/.w1.tgz                              (dropper download, deleted after run)
/tmp/.w1/                                 (extraction directory, deleted after run)
/tmp/.w1/wg_install.sh                    (installer; OSM describes its behavior)
$HOME/.cache/.wg                          (OSM: kit install directory)
$HOME/.config/autostart/wg.desktop        (OSM: launches the interception proxy)
$HOME/.config/autostart/wg-grab.desktop   (OSM: launches the wallet grabber)
127.0.0.1:8899                            (OSM: loopback interception proxy)
Trusted root: Wallet Guard Root CA, O=Wallet Guard LLC (OSM)
Env vars read by the Template B payload: WG_KIT_URL, WG_FAST
Env var read by the grabber: WG_C2 (OSM)
Operator wallet addresseschain analytics, not endpoint
# Operator wallet addresses embedded in the injected script (OpenSourceMalware)
# For blockchain analytics, exchange notification and customer-protection teams. Not importable as Falcon IOCs.
BTC bc1qzlfqqw7zchyklj408jktazm6yzrlptrxlz0v7u
ETH 0x97aF898dfB119215aFCBEb48bEeD7936A0E778Dd
TRX TMhrUqZpvNsNoSXU9qfhzoEHt6LEDRb2hc
SOL DC78HUMAE5QCY8M83yp3b83GL6zFQCScfbiVND7CVX8M
TON UQBgGzIPtF2VjdkUocoOLFZ3rjZuU2xw3w0Q-O-O8g7rGyI3
Behavioral signaturestool-agnostic
# Behavioral signatures (tool-agnostic)
1. gem or bundle install of a package whose gemspec extensions: entry is ext/<name>/extconf.rb with a decoy lib/ file.
2. extconf.rb that reads CI variables, hostname, username, working directory, /proc/uptime and HOME dotfiles before doing anything.
3. extconf.rb that decodes Base64 to source, forks twice, calls Process.setsid, reopens stdin/stdout/stderr to /dev/null, sleeps 1200+rand(1200) seconds, then evals.
4. A ruby process that connects out, wires /bin/sh to the socket, and retries every 30 seconds.
5. sh -c with curl -o /tmp/.w1.tgz; mkdir -p /tmp/.w1; tar xzf; bash .../wg_install.sh; rm -rf.
6. A Python process on 127.0.0.1:8899 with a newly trusted root CA and proxy variables in .bashrc or .zshrc.
7. xclip or xsel launched about every 800 ms by a Python parent.
8. A gem install whose parent shell has exited and whose child process is still alive 20 to 40 minutes later (orphaned, PPID 1).
Safe positive testsharmless triggers
# Safe positive tests (lab host only, never against the live C2)
Q1  gem list --local bitcion                       (reads the local list; fetches nothing)
Q3  sh -c 'echo .w1.tgz'                           (prints the string; downloads nothing)
Q7  ruby -e 'IO.popen(["/bin/sh"], "r+") { |s| s.close_write }'     (starts a shell and closes it at once)
Q9  python3 -m http.server 8899 --bind 127.0.0.1   (stop with Ctrl-C)
Q5, Q6, Q8: no safe test. Do not connect to 45.138.12.177. Validate with the Falcon IOC test workflow or a lab address you control.

One prevent row: the C2 address 45.138.12.177, which SafeDep and OpenSourceMalware both report. Every other row ships detect because it is single-sourced or lives on a legitimate path. No stock tool is blocked. Gem names are not a Falcon IOC type; enforce them in the gem mirror (PB1).

11

Detection Validation Gates

GateCheckPass criterion
Telemetry readyProcess creation with command line and parent (CrowdStrike: ProcessRollup2. SentinelOne: Process Creation. Defender: DeviceProcessEvents), outbound network connection (CrowdStrike: NetworkConnectIP4. SentinelOne: IP Connect. Defender: DeviceNetworkEvents) and listening-port telemetry (CrowdStrike: NetworkListenIP4) are reporting for Linux developer workstations.Each event type returns rows for a known Linux developer host in the last 24 hours. Also confirm the sensor reports the connecting image name as ruby for a test connection.
Fork visibilityConfirm what the sensor records for a forked child that never calls exec, using a harmless lab script that forks, sleeps 60 seconds and exits.Decide whether Q7 and Q8 can rely on a process event for the post-sleep process, or only on the network event. Record the result in the ticket.
Benign baselineRun Q6, Q7, Q8, Q9 and Q10 over 14 days before alerting.Record Ruby applications on 8089, 8090 or 8092, console helpers, local dev servers on 8899 and clipboard tools, then add them to the TUNING exclusions.
Positive testsRun the harmless triggers in the Section 10 test block on a lab host.Q1, Q3, Q7 and Q9 fire. Q5, Q6 and Q8 cannot be tested safely against the live C2.
PromotionA query becomes an alert after two clean baseline weeks.Only IOA-1 and IOA-3 may alert on first deployment; none moves to prevent. Document the owner and the rollback in the ticket.
Build gatesStructure, Falcon field, CQL syntax and IOC provenance linters run before this pack was staged.All exit 0.
12

Hardening: Tiered

Immediate (this week)
  • Block the 42 gem names and pin dependencies. Refuse the names in the gem mirror or artifact manager, commit Gemfile.lock, and run CI and build hosts with a frozen lockfile so a new name cannot enter silently. MITRE M1033 Limit Software Installation (T1195.001, T1204.005). Authority: Bundler documentation for frozen and lockfile checksums (checksums need Bundler 2.6 or newer; confirm with bundle --version). ⚠ best-practice, no formal benchmark. Verify: bundle config get frozen; N1 returns nothing. Playbook PB1.
  • Block 45.138.12.177 at every egress point. Falcon IOC at prevent (Section 10) plus a network or host firewall rule, because the reverse shell is raw TCP that a proxy will not see. MITRE M1037 Filter Network Traffic (T1095, T1105, T1041). Authority: MITRE M1037; no formal benchmark covers a single address. ⚠ best-practice, no formal benchmark. Verify: Q5 shows blocked attempts and no completed sessions; nft list table inet huntpack_block. Playbook PB2.
  • Remove standing credentials from developer home directories. The gate looks for .ssh, .gitconfig, .npmrc, .gem/credentials and .bundle, and the shell runs as that user. Use passphrase-protected, agent-held keys, short-lived tokens and RubyGems trusted publishing in place of a stored API key. MITRE M1026 Privileged Account Management, M1027 Password Policies (T1552.001 as a consequence control). Authority: RubyGems.org guidance on trusted publishing and API key scopes. ⚠ best-practice, no formal benchmark. Verify: test ! -e ~/.gem/credentials && echo absent. Playbook PB3.
  • Keep seed phrases and exchange sessions off developer workstations. Use a hardware wallet and a separate, non-developer profile or device for exchange work; the kit scans for BIP39 phrases, xprv, WIF keys and keystore files. MITRE M1017 User Training, M1048 Application Isolation and Sandboxing (T1005, T1115). ⚠ best-practice, no formal benchmark. Verify: a file-name scan of $HOME for wallet, seed, mnemonic and keystore terms finds nothing live.
Near term (1 to 4 weeks, pilot first)
  • Hold new gem versions behind a cooldown in the gem mirror. All 42 gems were published inside about an hour and none had history. A mirror policy that delays packages younger than a few days would have kept them out. MITRE M1033. ⚠ best-practice, no formal benchmark (depends on your mirror product). Verify: a test gem published today is refused by the mirror.
  • Install dependencies in disposable build environments that do not mount the developer home. A container or VM with no ~/.ssh or ~/.gem/credentials limits what any install-time code can reach. Note the limit: this campaign deliberately skips such environments, so this controls the blast radius of the next campaign, not this one. MITRE M1048 Application Isolation and Sandboxing, M1033. ⚠ best-practice, no formal benchmark. Verify: ls ~ /root inside the build container shows no credential files.
  • Restrict developer-workstation egress to non-HTTP(S) internet ports. Deny outbound TCP 8000 to 9000 to the internet by default and allow-list what is needed. MITRE M1037 (T1095, T1571). Authority: CIS Linux benchmark host firewall section (control numbers differ per distribution and version). Verify: a connection test to a lab address you control on 8090 is refused.
  • Alert on new trusted root certificates and autostart entries. The kit trusts its own root CA and writes two .desktop autostart files. MITRE M1047 Audit (T1553.004, T1547.013). Authority: CIS Linux and CIS Apple macOS benchmarks cover certificate and login-item management; control numbers differ per version. Verify: N4 returns nothing on a clean host.
Strategic (1 to 3 months)
  • Govern dependencies as an inventory. Keep an approved-gem list per team, record lockfile checksums, prefer gems with trusted publishing, and review any new native extension (extensions: in the gemspec) because it runs code at install. MITRE M1033, M1047 (T1195.001). ⚠ best-practice, no formal benchmark.
  • Separate crypto and exchange activity from build and development machines. A dedicated device removes the target from the machines that install third-party code. MITRE M1048. ⚠ best-practice, no formal benchmark.
  • Do not rely on noexec on /tmp for this chain. Reasoned from the quoted dropper command, not tested: bash /tmp/.w1/wg_install.sh runs a script by passing it to an interpreter, which a noexec mount does not stop. Keep noexec for other reasons, but do not count it here.
13

Deployable Playbooks

Each step lists Prerequisites, Reboot required and Rollback. Test on a pilot host first. Placeholders in angle brackets are replaced by you. Nothing here wraps an RMM tool.

PB1 Gem supply-chain lockdown (T1195.001, M1033)

PB1.1 Freeze dependencies and add lockfile checksums

Prerequisites: Bundler 2.6 or newer for checksums; write access to the project; a reviewed Gemfile.lock.
Reboot required: No

bundle --version
bundle lock --add-checksums
bundle config set --local frozen true
# CI and build hosts: install from the frozen lockfile only
bundle install
# Verify
bundle config get frozen

Rollback: bundle config unset --local frozen. A frozen bundle refuses any Gemfile change until you re-lock on a trusted machine and commit the new Gemfile.lock.

PB1.2 Sweep for the 42 names and block them at the mirror

Prerequisites: Read access to repositories and build caches; admin access to the gem mirror.
Reboot required: No

grep -rEwl --include=Gemfile --include=Gemfile.lock --include='*.gemspec' -e '(req-throttle-mini|throttle-requests|rate-limit-mini|request-guard|bitcoin-rpc-lite|web3-eth-utils|wallet-crypto-utils|eth-keystore-utils|btc-wallet-tools|eth-address-utils|crypto-mnemonic-tools|wallet-backup-tool|bip39-wordlist-utils|hdkey-derive-helper|blockchain-sync-utils|tx-broadcast-utils|electrum-protocol-lite|coinmarket-utils|web3-sign-helper|crypto-key-utils|bitcoin-address-utils|ethereum-tx-helper|merkle-proof-lite|utxo-set-utils|lightning-invoice-utils|base58-check-helper|bitcion-ruby|bitcoij-ruby|bitcion|bitciin|etheremu\.rb|etherdum\.rb|tron-rb|lightinng-invoice|ligbtning-invoice|lighthing-invoice|crypti-toolbox|crylto-toolbox|cryoto-toolbox|kecack|keccka|solaan-ruby)' /srv /opt /home 2>/dev/null
# Gem mirror: add the 42 names from the Section 10 gem block to the block or exclude list in your product's own configuration.

Rollback: Remove the block-list entries. The sweep is read-only, so Rollback: none required for the grep.

PB2 Egress block for the C2 (T1095, T1105, M1037)

PB2.1 Linux host firewall rule with nftables

Prerequisites: Root; nftables installed and used on the host (if the host uses iptables or a firewall manager, add the equivalent rule there). The rule is not persistent across reboots until you save it in your firewall configuration.
Reboot required: No

sudo nft add table inet huntpack_block
sudo nft add chain inet huntpack_block out '{ type filter hook output priority 0; policy accept; }'
sudo nft add rule inet huntpack_block out ip daddr 45.138.12.177 counter drop
# Verify
sudo nft list table inet huntpack_block

Rollback: sudo nft delete table inet huntpack_block. The rule affects only traffic to that one address.

PB2.2 Perimeter and Falcon

Prerequisites: Firewall or secure web gateway admin; Falcon IOC Management access.
Reboot required: No

# Perimeter: deny outbound IPv4 45.138.12.177 on all ports (raw TCP 8089, 8090, 8092 and HTTP 8080 are all used).
# Falcon: import the Section 10 CSV. The single ipv4 row is prevent/critical; the url and sha256 rows are detect.
# Verify: run Q5 and confirm blocked attempts appear with no established session.

Rollback: Remove the perimeter rule and set the Falcon IOC to detect or delete it. Expiry is set to 2027-04-12 so the entry does not outlive the infrastructure.

PB3 Credential rotation after exposure (T1552.001, M1026)

PB3.1 Revoke and reissue developer secrets

Prerequisites: A clean device for the new keys; access to the RubyGems account, source host and cloud consoles. Do this only after the affected host is isolated (Section 14).
Reboot required: No

# On the affected account, record what existed before touching it
ls -la ~/.ssh ~/.gem/credentials ~/.npmrc ~/.bundle ~/.gitconfig 2>/dev/null
# Remove the stored RubyGems API key from this machine
gem signout
# New SSH key pair on the clean device
ssh-keygen -t ed25519 -C '<owner>-<date>' -f ~/.ssh/id_ed25519_new
# Then in each console: revoke the old RubyGems API keys, SSH public keys, tokens and cloud keys; review recent use.
# From a clean device: move any wallet funds to a newly created wallet; change exchange passwords, API keys and 2FA.

Rollback: Not reversible by design. Do not restore a revoked credential. If a revoked key was needed by a service, issue a new key instead.

PB4 Kit removal after evidence capture (T1547.013, T1546.004, T1553.004)

PB4.1 Remove the Wallet Guard kit in place (only if you cannot rebuild)

Prerequisites: Host isolated; evidence copied (Section 14 phase 3); root for the trust-store step. Rebuilding the host from a known-good image is preferred over this.
Reboot required: No

pkill -f inject_proxy.py ; pkill -f _grab.py
rm -f ~/.config/autostart/wg.desktop ~/.config/autostart/wg-grab.desktop
rm -rf ~/.cache/.wg
# Edit ~/.bashrc and ~/.zshrc by hand and remove the proxy variable lines the installer added (compare with your evidence copy)
# Remove the trusted root CA: find where it was installed (N4), then
sudo update-ca-certificates --fresh          # Debian and Ubuntu, after deleting the file from /usr/local/share/ca-certificates
# macOS: security delete-certificate -c 'Wallet Guard Root CA' <keychain>
gem uninstall <malicious-gem-name>
# Verify
ls -la ~/.cache/.wg 2>/dev/null ; ss -ltnp 2>/dev/null | grep ':8899'

Rollback: Restore the removed files from the evidence archive if a cleanup step removed something legitimate, for example a proxy setting you added yourself. Re-add a CA only from a source you trust.

14

Containment Runbook

PhaseActionsOwnerEvidence
1 Triage (read-only)Run Q1, Q3, Q5 and Q8 for 30 days, then Q6, Q7 and Q9. Run N1 on repositories, mirrors and the host. Confirm the install time, the user and the Ruby version from shell history and gem metadata (N5).SOC / IRQuery exports, N1 hits
2 IsolateIsolate the host with Falcon network containment before cleanup. Isolation matters for Template A: the shell runs as the developer and retries every 30 seconds. Pause CI runners that installed a listed gem.IRContainment record
3 PreserveCopy ~/.cache/.wg, ~/.config/autostart, shell profiles, the installed gem directory for the malicious gem, shell history and a process listing before removal. Hash everything. A forensic image is preferred.IR / forensicsHashed evidence archive
4 EradicatePreferred: rebuild the host from a known-good image. If you must clean in place, follow PB4 after phase 3.IR / sysadminBefore and after file listing
5 Rotate credentialsTreat every secret readable by the installing user as stolen (PB3): SSH keys, RubyGems API keys, npm tokens, cloud keys, browser sessions. Move wallet funds from a clean device and change exchange credentials.IAM / dev leads / the individualRevocation log
6 DownstreamFor each host that user's keys could reach, run Q5 and the Section 7 checks. If the user maintains gems or packages, compare published versions with source control.IR / package ownersPer-host result table
7 Recover and verifyApply PB1 and PB2, rerun the query set for 30 days and require zero hits before returning the host to service.SOC / platformClean exports, change tickets

Rollback and reversibility. Eradication is destructive by design. Preserve evidence first (phase 3). Prerequisites for cleanup: root access, the host isolated, evidence copied. Reboot required: not for cleanup; a rebuild replaces the host.

15

Detection Coverage Map

TechniqueBehaviorCQLIOACrowdStrike coverageSentinelOneDefender XDR
T1195.001 / T1204.005Install of one of the 42 gems by nameQ1, Q2NonePARTIAL bundle install from a Gemfile carries no names on the command line; N1 covers itPartial: same limit as CrowdStrikePartial: same limit as CrowdStrike
T1095 / T1105 / T1041Connection to 45.138.12.177 on any portQ5IOA-3GOOD bare-IP C2; a moved C2 is not covered by Q5Good, with assumptionsGood, with assumptions
T1095 / T1571ruby callback on 8089, 8090, 8092Q6IOA-4PARTIAL address-independent but port-dependentPartial: same limit as CrowdStrikePartial: same limit as CrowdStrike
T1059.004ruby spawns a bare /bin/shQ7IOA-2PARTIAL depends on the sensor recording the forked child (Section 11 fork visibility gate)Partial: same limit as CrowdStrikePartial: same limit as CrowdStrike
T1105 / T1059.004 / T1070.004Template B dropper commandQ3IOA-1GOOD 31 of 42 gemsGood, with assumptionsGood, with assumptions
T1497.00320 to 40 minute sleep between install and payloadQ8NonePARTIAL host-level join only; lineage cannot be rebuiltPartial: same limit as CrowdStrikeGAP
T1497.001CI, hostname, user, path, uptime and developer-file gatingNoneNoneGAP by design: a lab, sandbox or CI run shows nothing. Hunt real workstationsGAPGAP
T1547.013 / T1553.004 / T1546.004Kit autostart, root CA, shell profile editsQ4NonePARTIAL command-line only; file writes and trust-store changes have no event used here (N4)Partial: same limit as CrowdStrikePartial: same limit as CrowdStrike
T1557Loopback proxy on 127.0.0.1:8899Q9NonePARTIAL single source; port reused by dev toolsPartial: Q9 approximatePartial: same limit as CrowdStrike
T1115Clipboard pollingQ10NoneGAP low-confidence behavior hunt only; in-browser address replacement is not visibleGAP: same limit as CrowdStrikeGAP: same limit as CrowdStrike
T1005Wallet extension, seed phrase and key file collectionNoneNoneGAP no file-read event used; rely on credential rotation and wallet hygieneGAPGAP
T1565.002Withdrawal address rewritten in the browserNoneNoneGAP outside endpoint telemetry; exchange-side and wallet verification onlyGAPGAP

Validation gates are in Section 11. The gaps above are real: no query in this pack covers them, and the first one is a property of the malware, not of the pack.

16

Hunt Summary Ticket

TITLE:      Ghost Dev: 42 malicious RubyGems that run a delayed reverse shell or wallet-theft kit at gem install
SEVERITY:   HIGH, developer-workstation supply chain; shell with the installing user's rights, wallet and credential theft
SCOPE:      Developer workstations (Linux and WSL; macOS by exposure) that installed any of the 42 gems
            published by reqthrottle_3474 on 2026-10-05, and every host and account that user could reach
HYPOTHESIS: A host that installed a Ghost Dev gem shows a gem or bundle command line naming it (Q1),
            a connection to 45.138.12.177 (Q5), a ruby process on 8089/8090/8092 (Q6) or a bare shell child (Q7),
            or the dropper strings .w1.tgz, wgkit.tar.gz, wg_install.sh (Q3).
QUERIES:    Q1-Q10 (Section 8); IOA-1 to IOA-4 (Section 9)
DO FIRST:   1. Sweep Gemfiles, lockfiles, mirrors and installed gems for the 42 names (N1, Q1).
            2. Run Q5 fleet-wide for 90 days and block 45.138.12.177 at the edge (PB2).
            3. On any hit: isolate, preserve evidence, rotate every secret the user could read (PB3), check wallets.
            4. Freeze dependencies and block the 42 names in the gem mirror (PB1).
FINDINGS:   REPLACE_WITH_FINDINGS_SUMMARY_AFTER_HUNT_EXECUTION
GAPS:       The 20-40 minute sleep breaks the install-to-payload process link (Q8 joins by host only); the campaign's
            own CI/sandbox gating means a detonation lab shows nothing; file reads, trust-store changes, clipboard and in-browser
            address rewriting are outside the endpoint telemetry used here (Section 15).
ACTIONS:    Deploy IOA-1 to IOA-4 in detect mode; import the Section 10 CSV (one prevent row: the C2 IPv4);
            enforce PB1 to PB3; validate fork visibility on a lab host before relying on Q7 and Q8.
OWNER:      REPLACE_WITH_HUNT_OWNER
VERSION:    v0.1 (Draft) 2026-10-12
17

Changelog

v0.12026-10-12Initial pack build from the SafeDep analysis and the OpenSourceMalware analysis, with The Hacker News as secondary coverage. A fourth supplied source (Cyber Security News) describes a different RubyGems campaign and was excluded. 10 CQL queries, 4 IOA candidates, 1 prevent row (the C2 IPv4) and 9 detect rows in the Falcon CSV. Stock tools held at detect. SentinelOne PowerQuery and Microsoft Defender KQL tabs added to Q1-Q10: SIEM Query Rosetta release 0.1.0 (public-beta; static-reviewed translations).
18

References

TierSourceUsed forAccess date
T1SafeDep, 42 Malicious RubyGems Open a Reverse Shell at Install TimeAccount, gem list, loader and payload behavior, C2 address and ports, dropper command2026-10-12
T2OpenSourceMalware, Malicious Crypto Shell Packages Target RubyGemsGhost Dev profile name, Wallet Guard kit, persistence, collector, wallets, hashes2026-10-12
T2The Hacker News, ThreatsDay bulletinSecondary confirmation of the account, count and C22026-10-12
T3Cyber Security News, Malicious RubyGems Turn Developer Machines Into Monero MinersReviewed and excluded: a different campaign2026-10-12
RefMITRE ATT&CK T1195.001Technique mapping2026-10-12
RefMITRE ATT&CK mitigationsM-number mapping2026-10-12
RefBundler, bundle configPB1 frozen and lockfile settings2026-10-12
RefRubyGems Guides, Trusted PublishingCredential hardening2026-10-12
RefCIS Benchmarks (Linux distributions, Apple macOS)Host firewall, certificate and login-item controls2026-10-12

Four fetched sources were saved to RubyGems-GhostDev-CryptoStealer-Hunt-sources/ for the provenance gate (01 to 04).

Generated 2026-10-12 by HuntPack · defensive content only