Developed by SPYDIRBYTE
A calibrated cracking toolkit, four modes: HTTP login form auditing,
SSH, FTP, and offline hash cracking. Hydra needs you to hand it an
exact failure string and hope it never changes. It'll burn your entire
wordlist against an account that got locked on attempt six. Its
http-post-form module breaks on any site with a dynamic anti-CSRF
token. This does none of that, and every "success" it reports ships
with the actual evidence, re-verified before it's called confirmed.
Run it against targets you own or have explicit written authorization to test.
Hashcat cracks a hash. Hydra sprays one protocol. Nothing connects
them. spray.py does: crack a password offline (from a leaked backup
file, a cracked ZIP, a JWT secret) and it feeds straight in as the top
priority candidate for a coordinated spray across every discovered
live service at once, HTTP login forms, SSH, FTP, HTTP Basic Auth, in
actual spray order, every account gets tried against candidate #1
across every service before anyone sees candidate #2, the real
technique that avoids tripping per-account lockout thresholds, not
three separate uncoordinated brute-force runs with no shared memory
between them.
The output that matters most: a cross-service credential reuse map, proof that the same password works on more than one service, not just that it's weak in one place. Tested against three real, independent local services (an HTTP login form, a real SSH server, an HTTP Basic Auth endpoint) where the same password was deliberately reused on two of the three and a different one used on the third, it correctly flagged the real reuse and correctly stayed silent on the one that wasn't actually shared.
python cli.py spray -y \\
--service "http:main login:https://example.com/login" \\
--service "ssh:internal jumpbox:203.0.113.5:22" \\
--service "basic:legacy admin panel:https://old.example.com/" \\
-u alice -u bob \\
--seed-password "CrackedFromLeakedBackup2026!" \\
--seed-origin "cracked from an exposed backup.zip"HTTP login forms. A couple of deliberately bad login attempts get sampled first (fuzzy matched, not exact string comparison), and that becomes the baseline every real attempt gets compared against. Lockout, rate-limiting, and CAPTCHA aren't sampled deliberately (triggering a real lockout just to learn its shape would be the exact problem this avoids), they're caught by keyword and status heuristics during the run itself. The login page gets refreshed before every single attempt, fresh session, fresh CSRF token, whatever the page is actually serving right now.
SSH and FTP work differently, and better. Both protocols have
explicit, unambiguous success/failure signals built in, SSH raises a
real exception on rejected auth and gives you a live connection on
success, FTP returns numeric codes (230 for success, 530 for
rejected). No fuzzy inference needed at all here, which means false
positives on the classification itself are close to structurally
impossible, the only real ambiguity left is lockout/tarpit detection,
handled via explicit server disconnects and response-timing
comparisons against a baseline.
Offline hash cracking is exact-match, always. hash(candidate) == target, either true or false, no similarity scoring anywhere in this
path. Every format (MD5, SHA1, SHA256, SHA512, NTLM, bcrypt) was
verified against independently-sourced test vectors before being
trusted, NTLM's MD4 dependency in particular ships with a pure Python
fallback and was cross-validated against passlib's implementation
across multiple test words, since a lot of modern OpenSSL builds
disable MD4 outright and silently produce nothing without one.
A possible HTTP-form success gets re-verified before it's reported as confirmed. A genuine hit reproduces reliably on a second, independent attempt. Something that looked like a hit because of a race condition or a session artifact usually doesn't. Anything that doesn't reproduce still gets reported, just honestly labeled "possible, unconfirmed" instead of quietly upgraded to a false certainty.
Hashcat's rule syntax (c $1 $2 $! r) is powerful and completely
opaque unless you've memorized it. rules.py does the same job, turn
a base word into plausible variants, but every rule has a plain
English name (append-year, leet-full, toggle-case) and every
candidate it produces remembers exactly which rule made it. Rules can
chain two deep (capitalize then append-year turns summer into
Summer2026, the single most common real password shape), bounded at
two on purpose, past that the candidate space explodes for very little
real payoff. The same engine drives both the HTTP --mutate flag and
the hash --rules/--chain-rules flags.
MySQL, PostgreSQL, IMAP, SMTP, and LDAP all work the same calibrated, lockout-aware way as everything else, each verified against a real running server during development, not mocked: a real MariaDB and PostgreSQL instance, a real Dovecot IMAP login, a real Postfix SMTP AUTH exchange (wired up with Dovecot's SASL backend), a real OpenLDAP bind. SMB was verified against an actually installed and configured Samba server, reading real NT status codes.
Telnet's module is written (protocol_telnet.py) using the same
approach, but couldn't be verified against a real telnetd instance in
the environment this was built in, that specific package expects to
run under inetd/xinetd socket activation rather than standalone,
and getting that working cleanly didn't happen in the time available.
Treat Telnet as less battle-tested than the others until you've run it
against something real yourself.
RDP isn't included at all, the only Python library for it needs a Rust compiler toolchain that wasn't available to properly build and test against, rather than ship something unverified, it's left out entirely.
A login response that says "enter the verification code we just sent
you" isn't a failure and isn't really a success either, the password
was almost certainly correct and a second factor is what's actually in
the way. That's now its own distinct classification (mfa_required)
instead of getting lost as an ambiguous "possible success" or
miscounted as a wrong password, verified against a target where the
correct password triggers exactly that response.
Once an HTTP login succeeds, the same session that just authenticated gets a bounded, careful check against a handful of commonly over-exposed paths (an admin panel, a user list, an export endpoint). A weak password is one finding, "this account can also reach the admin panel" is very often the more important one. Verified against a target where a regular user's session could reach an admin panel that should have been access-controlled and wasn't.
pip install -r requirements.txt
# HTTP login form
python cli.py http https://example.com/login -u admin -P wordlists/common-passwords.txt
python cli.py http https://example.com/login -U usernames.txt -P passwords.txt --mutate
python cli.py http https://example.com/login -u admin --org-term "Acme Corp"
# SSH
python cli.py ssh 203.0.113.5 -u root -P passwords.txt --concurrency 3
# FTP
python cli.py ftp 203.0.113.5 -u admin -P passwords.txt
# offline hash cracking, type auto-detected from the hash's shape when unambiguous
python cli.py hash 5f4dcc3b5aa765d61d8327deb882cf99 -P wordlist.txt
python cli.py hash <ntlm-hash> --type ntlm -P wordlist.txt --rules --chain-rules
python cli.py hash '$2b$12$...' --type bcrypt -P wordlist.txt
python cli.py hash '$6$...' --type sha512crypt -P wordlist.txt # real /etc/shadow style hashes
python cli.py hash <sha256-hash> --parallel -P bigwordlist.txt # split across CPU cores
# password-protected files
python cli.py file secret.zip --type zip -P wordlist.txt --rules
python cli.py file secret.pdf --type pdf -P wordlist.txt
# JWT weak-secret cracking
python cli.py jwt eyJhbGciOiJIUzI1NiIs... -P wordlist.txt --rules
# SMB
python cli.py smb 203.0.113.5 -u administrator -P wordlist.txt
# HTTP Basic Auth
python cli.py basic https://example.com/admin -u admin -P wordlist.txt
# auto target-aware wordlist, scrapes the target's own site for names/terms
# instead of typing them in with --org-term
python cli.py http https://example.com/login -u admin --scrape-terms
# after a confirmed hit, check whether that session can reach commonly
# over-exposed paths, an admin panel, a user list, an export endpoint
python cli.py http https://example.com/login -u admin -P wordlist.txt --probe-session
# databases
python cli.py mysql 203.0.113.5 -u root -P wordlist.txt
python cli.py postgres 203.0.113.5 -u postgres -P wordlist.txt --database postgres
# mail
python cli.py imap 203.0.113.5 -u user@example.com -P wordlist.txt
python cli.py smtp 203.0.113.5 -u user@example.com -P wordlist.txt
# LDAP, needs a full bind DN, not just a username
python cli.py ldap 203.0.113.5 --bind-dn "cn=admin,dc=example,dc=com" -P wordlist.txt
# coordinated spray across multiple services, with cross-service reuse detection
python cli.py spray -y \
--service "http:main login:https://example.com/login" \
--service "ssh:internal jumpbox:203.0.113.5:22" \
-u alice --seed-password "CrackedPassword!" --seed-origin "cracked from a leaked backup"--rps on the HTTP module defaults to a conservative 3 requests/sec
on purpose, this is a live authentication endpoint, not a static page.
SSH and FTP default to a low concurrency for the same reason.
.
├── fetcher.py # async HTTP layer, fresh session per attempt
├── response_classify.py # HTTP baseline + defense signature classification
├── login_form.py # CSRF-aware login page parsing, refreshed per attempt
├── protocol_ssh.py # SSH auth, protocol-level success/failure signals
├── protocol_ftp.py # FTP auth, raw asyncio, numeric response codes
├── protocol_smb.py # SMB auth via impacket, real NT status codes
├── protocol_basic_auth.py # HTTP Basic Auth, status-code based
├── protocol_mysql.py # MySQL/MariaDB auth, real error codes
├── protocol_postgres.py # PostgreSQL auth, real SQLSTATE codes
├── protocol_mail.py # IMAP + SMTP auth (stdlib, thread-wrapped)
├── protocol_ldap.py # LDAP bind auth, real result codes
├── protocol_telnet.py # Telnet auth, written but not server-verified
├── session_probe.py # post-login check for over-exposed paths
├── hash_crack.py # offline exact-match hash cracking, MD5 through bcrypt/crypt
├── file_crack.py # offline ZIP/PDF password cracking
├── jwt_crack.py # offline JWT HMAC secret cracking
├── mp_crack.py # multiprocessing wrapper for offline cracking
├── scrape_terms.py # CeWL-style target-aware term scraping
├── spray.py # cross-protocol spray orchestrator + reuse detection
├── rules.py # the explainable mutation/rule engine
├── candidates.py # wordlist loading, target-aware generation
├── engine.py # HTTP calibration + lockout-aware loop + re-verification
├── cli.py # terminal front end, fourteen subcommands
└── requirements.txt
This tests provided credential lists against systems you're authorized to test, the same category as Hydra, Medusa, Ncrack, or Hashcat. It doesn't attempt to defeat a CAPTCHA, and it stops itself the moment it detects a lockout rather than pushing through it. Offline hash cracking never generates a false positive by design (exact match only), but a "possible" or "confirmed" success on the live HTTP/SSH/FTP side is still a strong signal worth your own verification, not an unconditional guarantee.
mfa_requiredclassification is keyword-based, like the lockout and CAPTCHA detection it's built on, a target phrasing its 2FA prompt in unusual language or a language other than English might not match the built-in hint list--probe-sessionchecks a fixed, small list of commonly over-exposed paths (admin,users,export, and similar), it's a lead worth following, not a substitute for the kind of thorough authenticated crawl SPY-VECTOR is actually built for- The spray orchestrator's per-service handlers reuse the same
detection logic as the standalone
http/ssh/ftp/basicsubcommands, HTTP spray hits go through the same re-verification step, SSH/FTP/Basic Auth rely on their protocol-level signals, same strengths and same caveats as those modules individually - RDP support isn't included, the only Python library for it needs a Rust compiler toolchain that wasn't available to build and properly test against here, rather than fake support that was never actually verified, it's left out
mp_crack.py's multiprocessing speedup is real and functionally verified, but the actual speedup you see depends entirely on how many CPU cores the machine running it has, none on a single-core box- The wordlist scraper (
scrape_terms.py) reads what a site's own pages actually say, a site with very generic, non-distinctive content won't produce much worth generating candidates from, this augments--org-term, it doesn't replace knowing something real about the target - Hash cracking speed is Python-native, it will never out-speed Hashcat's GPU acceleration on a large hash set, that's a hardware gap no amount of engineering here closes, the value is candidate quality, explainability, and the offline-to-online chain, not raw throughput
- Calibration on the HTTP module sends real login attempts, a target
with an unusually aggressive lockout policy (locks after 1-2
failures) could trip on calibration itself,
--calibration-attempts 1reduces that risk at some cost to baseline quality - SSH/FTP lockout detection relies on the server actually saying something recognizable or dropping the connection, a server that silently rate-limits without any signal at all won't be caught
- The rule engine's chain depth is capped at two by design, a password needing three or more stacked transforms to reach won't be found without a wordlist that already contains a closer starting point
- NTLM's pure Python MD4 fallback is slower than the OpenSSL path when available, noticeable on very large wordlists, negligible for typical dictionary sizes
MIT