├── handlers ├── __init__.py ├── typescript ├── netcat.py └── openssl.py ├── LICENSE ├── supertty.py ├── README.md ├── .gitignore ├── upgrader.py └── lib.py /handlers/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /handlers/typescript: -------------------------------------------------------------------------------- 1 | Script started on 2019-03-15 14:51:06+00:00 [] 2 | root@kali:~/tools/supertty/handlers# tty 3 | /dev/pts/13 4 | root@kali:~/tools/supertty/handlers# exit 5 | exit 6 | 7 | Script done on 2019-03-15 14:51:17+00:00 [COMMAND_EXIT_CODE="0"] 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 bad-hombres 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /handlers/netcat.py: -------------------------------------------------------------------------------- 1 | import lib 2 | 3 | class Handler(lib.BaseHandler): 4 | def __init__(self, args): 5 | super(Handler, self).__init__(args) 6 | 7 | def get_process_commands(self): 8 | proclist = [] 9 | args = ["nc"] 10 | 11 | if self.args.host: 12 | args.append(self.args.host) 13 | if self.args.udp: 14 | args.insert(1, "-u") 15 | else: 16 | if self.args.udp: 17 | args.append("-nvlup") 18 | else: 19 | args.append("-nvlp") 20 | 21 | args.append(str(self.args.port)) 22 | proclist.append(args) 23 | if self.args.stdout_port: 24 | stdout_args = list(args) 25 | stdout_args[-1] = str(self.args.stdout_port) 26 | proclist.append(stdout_args) 27 | 28 | if self.args.verbose: 29 | for args in proclist: 30 | print "[+] Netcat command: {}".format(" ".join(args)) 31 | 32 | return proclist 33 | 34 | def get_argparser(self): 35 | p = super(Handler, self).get_argparser() 36 | p.add_argument("--udp", action="store_true", help="listen over udp") 37 | return p 38 | 39 | def wait_for_connection(self): 40 | if self.args.host: return 41 | while True: 42 | output = self.get_output() 43 | if output and "connect to" in output: 44 | break 45 | -------------------------------------------------------------------------------- /supertty.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | banner = """ ( ) 3 | )\ ) * ) * ) ( /( ) ) 4 | (()/( ( ( ( ` ) /(` ) /( )\()) ) ( /( ( /( 5 | /(_))))\ ` ) ))\ )( ( )(_))( )(_)|(_)\ /(( )\()))\()) 6 | (_)) /((_)/(/( /((_|()\(_(_())(_(_())_ ((_) (_))((_)\((_)\\ 7 | / __(_))(((_)_\(_)) ((_)_ _||_ _\ \ / / _)((_) (_) (_) 8 | \__ \ || | '_ \) -_)| '_| | | | | \ V / \ V /| || () | 9 | |___/\_,_| .__/\___||_| |_| |_| |_| \_/ |_(_)__/ 10 | |_| 11 | (c) Bad Hombres 2017 12 | """ 13 | import sys, os, signal, lib, argparse, upgrader 14 | print banner 15 | handler = None 16 | 17 | args = argparse.ArgumentParser(description="Reverse shell catcher") 18 | args.add_argument("--handler", help="Name of the handler module to use") 19 | opts, handler_args = args.parse_known_args() 20 | if not opts.handler: 21 | args.print_usage() 22 | sys.exit(1) 23 | 24 | def sigint_handler(signal, frame): 25 | print "!!!!!SIGINT!!!!!" 26 | if handler: handler.stop() 27 | sys.exit() 28 | 29 | signal.signal(signal.SIGINT, sigint_handler) 30 | upgrader = upgrader.ShellUpgrader() 31 | 32 | try: 33 | handler = lib.get_handler(opts.handler, handler_args) 34 | handler.start(upgrader) 35 | except Exception as ex: 36 | print "[!] An unexpected error occurred: {}".format(ex) 37 | finally: 38 | if handler: handler.stop() 39 | upgrader.reset_local(banner + "\n" + "[+] Hack el planeta!..... ") 40 | os._exit(0) 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ``` 2 | ( ) 3 | )\ ) * ) * ) ( /( ) ) 4 | (()/( ( ( ( ` ) /(` ) /( )\()) ) ( /( ( /( 5 | /(_))))\ ` ) ))\ )( ( )(_))( )(_)|(_)\ /(( )\()))\()) 6 | (_)) /((_)/(/( /((_|()\(_(_())(_(_())_ ((_) (_))((_)\((_)\ 7 | / __(_))(((_)_\(_)) ((_)_ _||_ _\ \ / / _)((_) (_) (_) 8 | \__ \ || | '_ \) -_)| '_| | | | | \ V / \ V /| || () | 9 | |___/\_,_| .__/\___||_| |_| |_| |_| \_/ |_(_)__/ 10 | |_| 11 | (c) Bad Hombres 2017 12 | ``` 13 | Reverse or bind shell catcher which uprgrades the caught shell to be more like a regular shell. 14 | 15 | This method is not my own invention and was pinched from [this post](https://blog.ropnop.com/upgrading-simple-shells-to-fully-interactive-ttys/) by ropnop 16 | 17 | ## Install 18 | Installation couldnt be simpler. 19 | 20 | * Clone this repo 21 | 22 | ## Usage 23 | Can be used as a catcher or to connect to a bind shell, at the moment it uses 24 | python to spawn a PTY. I will work on other options in the future 25 | 26 | ``` 27 | usage: supertty.py [-h] [--handler HANDLER] 28 | 29 | Reverse shell catcher 30 | 31 | optional arguments: 32 | -h, --help show this help message and exit 33 | --handler HANDLER Name of the handler module to use 34 | ``` 35 | 36 | Each handler has its own options see them with `--handler-help` 37 | 38 | Currently has two handlers: 39 | 40 | * netcat 41 | * openssl 42 | 43 | Pull requests for other types welcome! 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | 106 | -------------------------------------------------------------------------------- /handlers/openssl.py: -------------------------------------------------------------------------------- 1 | import lib, os, argparse 2 | 3 | class Handler(lib.BaseHandler): 4 | def __init__(self, args): 5 | super(Handler, self).__init__(args) 6 | 7 | if (not self.args.cert_path or not self.args.key_path): 8 | if not self.args.host: 9 | self.key = "{}/key.pem".format(self.args.cert_dir) 10 | self.cert = "{}/cert.pem".format(self.args.cert_dir) 11 | print "[+] Generating new cert in {} with subject: {}".format(self.args.cert_dir, self.args.subject) 12 | os.system("openssl req -x509 -newkey rsa:4096 -keyout {} -out {} -days 365 -nodes -subj {}".format(self.key, self.cert, self.args.subject)) 13 | else: 14 | self.key = self.args.key_path.name 15 | self.cert = self.args.cert_path.name 16 | 17 | if self.args.key_path: self.args.key_path.close() 18 | if self.args.cert_path: self.args.cert_path.close() 19 | 20 | def get_process_commands(self): 21 | proclist = [] 22 | args = ["openssl", "s_server", "-quiet", "-key", self.key, "-cert", self.cert, "-port", str(self.args.port), "-naccept", "1"] 23 | if self.args.host: 24 | args = ["openssl", "s_client", "-quiet", "-connect", "{}:{}".format(self.args.host, str(self.args.port))] 25 | 26 | proclist.append(args) 27 | if self.args.stdout_port: 28 | stdout_args = list(args) 29 | stdout_args[-3] = str(self.args.stdout_port) 30 | proclist.append(stdout_args) 31 | 32 | if self.args.verbose: 33 | for p in proclist: 34 | print "[+] Command: {}".format(" ".join(p)) 35 | 36 | return proclist 37 | 38 | def get_argparser(self): 39 | p = super(Handler, self).get_argparser() 40 | p.add_argument("--subject", help="subject of the certificate to generate", default="/C=US/ST=Denial/L=Springfield/O=Dis/CN=www.example.com") 41 | p.add_argument("--key-path", type=argparse.FileType("r"), help="path to the private key to use (if no generating new)") 42 | p.add_argument("--cert-path", type=argparse.FileType("r"), help="path to the cert to use (if not generating new") 43 | p.add_argument("--cert-dir", help="path to store the generated certificates (if key path and cert path not provided", default="/tmp") 44 | return p 45 | 46 | def wait_for_connection(self): 47 | while True: 48 | output = self.get_output() 49 | if output: break 50 | -------------------------------------------------------------------------------- /upgrader.py: -------------------------------------------------------------------------------- 1 | import os, time 2 | 3 | class ShellUpgrader(object): 4 | upgrade_bins = ["python", 'python2', 'python3', 'script', "ruby"] 5 | upgrade_commands = { 6 | "python": "python -c 'import pty; pty.spawn(\"/bin/bash\")'", 7 | "python2": "python -c 'import pty; pty.spawn(\"/bin/bash\")'", 8 | "python3": "python -c 'import pty; pty.spawn(\"/bin/bash\")'", 9 | "script": "script /dev/null", 10 | "ruby": "ruby -r pty -e '$stdout.sync = true; PTY.spawn(\"/bin/bash -i\"){|r, w, p| Thread.new { while true do IO.copy_stream($stdin, w, 1) end };Thread.new { while true do IO.copy_stream(r, $stdout, 1) end }; Process.waitpid(p) }'" 11 | } 12 | 13 | def __init__(self): 14 | self.needs_reset = False 15 | 16 | def setup(self): 17 | self.term = os.environ["TERM"] 18 | self.rows, self.columns = os.popen('stty size', 'r').read().split() 19 | print "[+] Got terminal: {}".format(self.term) 20 | print "[+] Got terminal size {} rows, {} columns".format(self.rows, self.columns) 21 | 22 | def find_bin(self): 23 | for b in ShellUpgrader.upgrade_bins: 24 | print "[+] Trying.....{}".format(b) 25 | out = self.handler.send_command_get_output("which {}".format(b)) 26 | if out: 27 | print "[+] Binary found in {}".format(out.rstrip()) 28 | return b 29 | 30 | return False 31 | 32 | def upgrade_shell(self,handler): 33 | self.handler = handler 34 | found = self.find_bin() 35 | 36 | print "[+] Setting up local terminal....." 37 | os.system("stty raw -echo") 38 | self.needs_reset = True 39 | if found: 40 | command = ShellUpgrader.upgrade_commands[found] 41 | handler.send_command(command) 42 | time.sleep(2) 43 | handler.send_command("export TERM={}".format(self.term)) 44 | handler.send_command("export SHELL={}".format("/bin/bash")) 45 | handler.send_command("stty rows {} columns {}".format(self.rows, self.columns)) 46 | handler.send_command("reset") 47 | else: 48 | print "[_] No ninary found to upgrade to pty :-(" 49 | 50 | 51 | def reset_local(self, message): 52 | if self.needs_reset: 53 | print "[+] Resetting local terminal....." 54 | os.system("stty raw echo") 55 | os.system("reset") 56 | self.needs_reset = False 57 | print message 58 | -------------------------------------------------------------------------------- /lib.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import threading 4 | import fcntl 5 | import subprocess 6 | import time 7 | import argparse 8 | 9 | def get_handler(handler, args): 10 | m = __import__("handlers.{}".format(handler), fromlist=[handler]) 11 | return m.Handler(args) 12 | 13 | 14 | class GetOutputThread(threading.Thread): 15 | def __init__(self, handler): 16 | threading.Thread.__init__(self) 17 | self.handler = handler 18 | 19 | def run(self): 20 | print "[+] Output thread started...." 21 | while True: 22 | output = self.handler.get_output() 23 | if output: 24 | sys.stdout.write(output) 25 | sys.stdout.flush() 26 | 27 | class BaseHandler(object): 28 | def __init__(self, args): 29 | parser = self.get_argparser() 30 | self.args, rest = parser.parse_known_args(args) 31 | if self.args.handler_help: 32 | parser.print_help() 33 | sys,exit(1) 34 | 35 | if not self.args.port: 36 | parser.print_help() 37 | sys.exit(1) 38 | 39 | def get_argparser(self): 40 | p = argparse.ArgumentParser("supertty.py --listener {}".format(type(self).__module__.replace("handlers.", "")), description="Handler") 41 | required = p.add_argument_group("Required arguments") 42 | required.add_argument("--port", type=int, help="Port to listen on or connect to") 43 | group = p.add_mutually_exclusive_group() 44 | group.add_argument("--host", help="Host to connect to for bind shells") 45 | group.add_argument("--stdout-port", type=int, help="Port to listen on for stdout (useful if no pipes are available etc)") 46 | p.add_argument("--handler-help", action="store_true") 47 | p.add_argument("--verbose", action="store_true") 48 | return p 49 | 50 | def get_process_commands(self): 51 | pass 52 | 53 | def get_output(self): 54 | try: 55 | out = self.stdout.read() 56 | return out 57 | except IOError: 58 | return None 59 | 60 | def send_command(self, command): 61 | self.stdin.write("{}\n".format(command)) 62 | self.stdin.flush() 63 | 64 | def send_command_get_output(self, command, timeout = 1): 65 | self.send_command(command) 66 | out = self.get_output() 67 | i = 0 68 | while out is None: 69 | if i >= timeout: break 70 | time.sleep(1) 71 | i += 1 72 | out = self.get_output() 73 | 74 | return out 75 | 76 | def wait_for_connection(self): 77 | raise "Please Implement wait_for_connection in derived classes..." 78 | 79 | def setup_processes(self): 80 | process_info = self.get_process_commands() 81 | print process_info 82 | self.processes = [] 83 | 84 | for i, p in enumerate(process_info): 85 | if i > 1: break 86 | tmp = subprocess.Popen(p, stdout = subprocess.PIPE, stderr = subprocess.STDOUT, stdin = subprocess.PIPE) 87 | fcntl.fcntl(tmp.stdout.fileno(), fcntl.F_SETFL, os.O_NONBLOCK) 88 | self.processes.append(tmp) 89 | 90 | self.stdin = self.processes[0].stdin 91 | if len(self.processes) == 2: 92 | self.stdout = self.processes[1].stdout 93 | else: 94 | self.stdout = self.processes[0].stdout 95 | 96 | def start(self, upgrader): 97 | self.setup_processes() 98 | upgrader.setup() 99 | self.wait_for_connection() 100 | print "[+] Connection establishd!" 101 | upgrader.upgrade_shell(self) 102 | 103 | t = GetOutputThread(self) 104 | t.daemoun = True 105 | t.start() 106 | 107 | while True: 108 | line = sys.stdin.read(1) 109 | if ord(line[0]) in [4]: break 110 | if line == "": break 111 | self.stdin.write(line) 112 | 113 | def stop(self): 114 | for p in self.processes: 115 | p.kill() 116 | --------------------------------------------------------------------------------