Files
backdoor/src/ssh-backdoor

306 lines
7.9 KiB
Python
Executable File

#!/usr/bin/env python3
from datetime import timedelta, datetime
from tabulate import tabulate
import psutil
import random
import sqlite3
import time, os, sys
def setup_options():
''' Setup the command line options '''
from argparse import ArgumentParser
parser=ArgumentParser(description="Alive notifier.")
parser.add_argument(
"--db",
action = 'store',
dest = 'DB',
default = '/tmp/ssh-backdoor.sqlite',
type = str,
help = "Sqlite file for database: %(default)s"
)
# TODO --connect (replace ssh-local-connect)
parser.add_argument(
"--clear",
action = 'store_true',
dest = 'clear',
default = False,
help = "Clear the database of everything, first"
)
parser.add_argument(
"--kill",
action = 'store',
dest = 'kill',
default = None,
type = str,
help="Kill processes of given ID"
)
parser.add_argument(
"--list","-l",
action = 'store_true',
dest = 'list',
default = False,
help = "List ports"
)
parser.add_argument(
"--list-names",
action = 'store_true',
dest = 'list_names',
default = False,
help = "List alive host names only"
)
parser.add_argument(
"--query",
action = 'store',
dest = 'query',
default = None,
type = str,
help="Query port for an id"
)
parser.add_argument(
"--quiet", "-q",
action = 'store_true',
dest = 'quiet',
default = False,
help = "Quiet operation: %(default)s"
)
parser.add_argument(
"--wait", "-w",
action = 'store_true',
dest = 'wait',
default = False,
help = "Enter infinite loop"
)
parser.add_argument(
action = 'store',
dest = 'id',
default = None ,
type = str,
nargs = '?',
help="Id name for port"
)
options=parser.parse_args()
return options
def eprint(s):
ewrite(str(s) + "\n")
def ewrite(s):
sys.stderr.write(str(s))
sys.stderr.flush()
class DataBase:
def __init__(self, DB):
self.alive = 7 * 24 * 3600 # 7 days
self.DBfile = DB
self.conn = None
self.db = None
self.id = None
self.to_die = False
if not os.path.exists(self.DBfile):
self.createDB()
self.conn_init()
self.clean_db()
def clear(self):
self.db = self.conn.cursor()
self.db.execute("DELETE FROM ports")
self.conn_end()
def conn_init(self):
self.conn = sqlite3.connect(self.DBfile)
self.db = self.conn.cursor()
self.conn.text_factory = str
def conn_end(self):
self.conn.commit()
#self.conn.close()
def createDB(self):
self.conn_init()
self.db.execute('CREATE TABLE ports (\
id TEXT PRIMARY KEY,\
host TEXT, \
port INTEGER,\
pid INTEGER,\
die INTEGER,\
date INTEGER)')
self.conn_end()
def clean_db(self):
self.db = self.conn.cursor()
self.db.execute("SELECT id,port,date,pid,host FROM ports")
for row in self.db.fetchall():
age = int(time.time() - row[2])
running = self.is_running(row[3])
if running:
continue
if age < self.alive:
continue
self.db.execute("DELETE FROM ports WHERE id = ?", (row[0],))
self.conn_end()
def die(self):
eprint("\n\nDying by request\n\n")
parent = self.get_pid()
if parent > 1:
try:
os.kill(parent, 9)
except OSError:
pass
sys.exit(1)
def check_die(self):
""" kills process, and parents, if set to die """
if self.id == None:
return
self.db = self.conn.cursor()
self.db.execute("SELECT id,pid,die FROM ports WHERE id = ?", (self.id,))
result = self.db.fetchall()
if len(result) == 0:
return None
to_die = result[0][2]
# if parent process is 1, then ssh connection has died
self.to_die = self.to_die or to_die == 1 or result[0][1] == 1
if self.to_die:
self.die()
def set_to_die(self, id):
""" set to die """
self.db = self.conn.cursor()
self.db.execute("SELECT id FROM ports WHERE id = ?", (id,))
result = self.db.fetchall()
if len(result) == 0:
eprint("No such ID found")
return None
self.db.execute("UPDATE ports SET die = 1 WHERE id = ?",
(id,)
)
self.conn_end()
def update(self, id):
self.id = id
port = self.get_port(id)
if port == None:
port = self.new_port()
parent = self.get_pid()
self.db = self.conn.cursor()
self.db.execute("INSERT OR REPLACE INTO ports(id,port,date,pid,host,die) \
VALUES(?,?,?,?,?,?)",(
id,
port,
int(time.time()),
parent,
os.getenv("SSH_CLIENT","-").split(" ")[0],
self.to_die
)
)
self.conn_end()
return port
def list(self):
self.db = self.conn.cursor()
self.db.execute("SELECT id,port,host,date,pid FROM ports ORDER BY id")
rows = []
for row in self.db:
row = list(row)
row[3] = self.human_age(row[3])
row.append(self.is_running(row[4]))
# ~ row.append("ssh-nosave -p %d localhost"%( row[1], ) )
rows.append(row)
return rows
def human_age(self, sec):
sec = timedelta(seconds = time.time() - sec)
d = datetime(1,1,1) + sec
return "%d %02d:%02d:%02d" % (d.day - 1, d.hour, d.minute, d.second)
def is_running(self, pid):
try:
os.kill(pid, 0)
except OSError:
return False
else:
return True
def get_pid(self):
return os.getppid()
def get_port(self, id):
self.db = self.conn.cursor()
self.db.execute("SELECT port FROM ports WHERE id = ?", (id,))
result = self.db.fetchall()
if len(result) == 0:
return None
return result[0][0]
def new_port(self):
self.db = self.conn.cursor()
while True:
port = random.randint(22200, 22400)
self.db.execute("SELECT port FROM ports WHERE port = ?", (port,))
result = self.db.fetchall()
if len(result) == 0:
return port
if __name__ == "__main__":
opts=setup_options()
db = DataBase(opts.DB)
start_time = time.time()
if opts.clear:
db.clear()
if opts.list_names:
for row in db.list():
if row[5]:
print(row[0])
sys.exit(0)
if opts.list:
print(tabulate(
db.list(),
headers = ['Id','Port','Host','Age','PID','Alive']
))
if opts.query != None:
port = db.get_port(opts.query)
if port == None:
sys.exit(1)
print(port)
sys.exit(0)
if opts.kill:
db.set_to_die(opts.kill)
sys.exit(0)
if opts.id != None:
print(db.update(opts.id))
if opts.wait:
ewrite(
" Connected\r"
)
while True:
if time.time() - start_time > 3600:
sys.exit(0)
db.check_die()
db.update(opts.id)
for i in range(10):
time.sleep(1)#0 * random.random())
ewrite(
" " +
time.strftime("%c") +
"\r"
)