diff --git a/core/utilities.py b/core/utilities.py index ef16278..7781676 100644 --- a/core/utilities.py +++ b/core/utilities.py @@ -1,10 +1,12 @@ +import os import json +from json import JSONDecodeError import socket import logging from colorama import Fore from datetime import datetime -logging.basicConfig(filename="dystopia.log", encoding="utf-8", level=logging.INFO, format="%(asctime)s:%(message)s") +logging.basicConfig(filename="dystopia.log", encoding="utf-8", level=logging.INFO, format="%(asctime)s:%(threadName)s:%(message)s") class DisplayStatistics: def __init__(self): @@ -91,7 +93,7 @@ def writeJsonToFile(jsonData, fileName): if fileName.endswith(".json") == False: fileName = fileName + ".json" with open(fileName, "a+") as outFile: - data = json.load(fileName) + data = json.loads(fileName) outFile.seek(0) data.update(jsonData) @@ -113,10 +115,13 @@ def writeToBlacklist(clientIPAddress): def readJsonFile(fileName): if fileName is None: printError("file was not found!") + quit() try: with open(fileName, "r") as outFile: data = json.load(outFile) return data + except JSONDecodeError: + printError("JSONDecodeError in thread ") except FileNotFoundError: printError("file: '{}' was not found.".format(fileName)) quit() @@ -138,7 +143,6 @@ def yn(message): except KeyboardInterrupt: quit() - def formatString(s): s = s.decode().strip() if s.endswith("\x00"): diff --git a/dystopia.py b/dystopia.py index 3e5cde0..54d1565 100644 --- a/dystopia.py +++ b/dystopia.py @@ -11,6 +11,9 @@ import json import os +globalLock = threading.Lock() + + class Statistics: def __init__(self, clientAddress): self.address = clientAddress[0] @@ -20,18 +23,19 @@ def __init__(self, clientAddress): self.seen = 0 def save(self): - with open("stats.json", "r") as jsonFile: - data = json.load(jsonFile) - try: - data[self.address]["Failed Logins"] = self.failedLogins - data[self.address]["Correct Logins"] = self.correctLogins - data[self.seen]["Times Connected"] = self.seen - except KeyError: - newData = '{"'+self.address+'":{"Failed Logins":'+str(self.failedLogins)+',"Correct Logins":'+str(self.correctLogins)+', "Times Connected":'+str(self.seen)+'}}' - jsonData = json.loads(newData) - data.update(jsonData) - with open("stats.json", "w") as jsonFile: - json.dump(data, jsonFile,indent=4,ensure_ascii=False) + with globalLock: + with open("stats.json", "r") as jsonFile: + data = json.load(jsonFile) + try: + data[self.address]["Failed Logins"] = self.failedLogins + data[self.address]["Correct Logins"] = self.correctLogins + data[self.seen]["Times Connected"] = self.seen + except KeyError: + newData = '{"'+self.address+'":{"Failed Logins":'+str(self.failedLogins)+',"Correct Logins":'+str(self.correctLogins)+', "Times Connected":'+str(self.seen)+'}}' + jsonData = json.loads(newData) + data.update(jsonData) + with open("stats.json", "w") as jsonFile: + json.dump(data, jsonFile,indent=4,ensure_ascii=False) def increaseFailedLogin(self): self.failedLogins += 1 @@ -56,11 +60,15 @@ def __init__(self): self.port = args.port self.motd = args.motd self.max = args.max + self.fake = args.login self.hostname = args.hostname self.localhost = args.localhost self.username = args.username self.password = args.password + self.capture = args.capture + self.interface = args.interface else: # Load config + print(args.load) Honeypot.loadConfig(self, args.load) if self.localhost: @@ -70,7 +78,6 @@ def __init__(self): if self.max != 0: printMessage("Max clients allowed: " + str(self.max)) - if args.save: Honeypot.exportConfig(self) @@ -94,6 +101,8 @@ def bind(self): + textwrap.shorten(self.motd, 35)) self.sock.bind((self.ipaddress, self.port)) printMessage(message) + if args.capture: + Honeypot.capturePot(self) except PermissionError: printError("Please run 'dystopia.py' as root") quit() @@ -104,12 +113,10 @@ def handleClient(self, connection, clientAddress): stats = Statistics(clientAddress) Statistics.load(stats) Statistics.increaseSeenCount(stats) - if self.password is not None: - if not Honeypot.login(self, connection, clientAddress): + if self.fake: + while not Honeypot.login(self, connection, clientAddress): Statistics.increaseFailedLogin(stats) Statistics.save(stats) - connection.close() - return else: Statistics.increaseCorrectLogins(stats) connection.sendto(self.motd.encode() + b"\n", clientAddress) # Send MOTD @@ -131,7 +138,6 @@ def handleClient(self, connection, clientAddress): self.clientList.remove(clientAddress[0]) # clientList is for only ACTIVE clients def commandResponse(self, connection, clientAddress, command): - dir = os.path.dirname(os.path.abspath(__file__)) # Only put some basic commands in here for now. You can always add more via the 'commands.json' file. response = None if "sudo" in command and command != "sudo": # No sudo for you! response = self.username.encode() + b" is not in the sudoers file. This incident will be reported.\n" @@ -146,10 +152,7 @@ def commandResponse(self, connection, clientAddress, command): elif "cd" in command: dir = command.split() if len(dir) > 1: - response = ( - b"cd: " + dir[1].encode() + b": No such file or directory\n", - clientAddress - ) + response = b"cd: " + dir[1].encode() + b": No such file or directory\n" else: # If command is unknown try: # Try to see if it is in the commands.json file response = self.commands[command].encode() @@ -174,10 +177,10 @@ def login(self, connection, clientAddress): printError("client " + clientAddress[0] + " tried {0}:{1}".format(username, password)) return False # If connection is dropped / lost - except (BrokenPipeError, ConnectionAbortedError): + except (BrokenPipeError, ConnectionAbortedError, ConnectionResetError): return False except UnicodeDecodeError: - # When trying to connect to the honeypot with 'telnet' this error always displayed. + # resend Honeypot.handleClient(self, connection, clientAddress) def checkClientLimit(self): @@ -199,65 +202,91 @@ def listen(self): t = threading.Thread( target=Honeypot.handleClient, args=(self, connection, clientAddress) ) # Start thread to handle the connection + t.daemon = True t.start() # Start thread + def capturePot(self): + now = datetime.now() + filename = now.strftime("%d-%m-%y-%H-%M-%S.pcap") + os.system("tcpdump -i " + self.interface + " -w " + filename + " &") + def exportConfig(self): j = { "port": self.port, "motd": self.motd, "max": self.max, + "login": self.fake, "username": self.username, "password": self.password, "hostname": self.hostname, "localhost": self.localhost, + "capture": self.capture, + "interface": self.interface, } jsonSettings = json.dumps(j) # Parse python dict to JSON jsonSettingsObj = json.loads(jsonSettings) # Load as JSON object - writeJsonToFile(jsonSettingsObj, args.save) + with open(args.save, "a") as outFile: + json.dump(jsonSettingsObj, outFile) def loadConfig(self, fileName): config = readJsonFile(fileName) self.port = config["port"] self.motd = config["motd"] self.max = config["max"] + self.fake = config["login"] self.username = config["username"] self.password = config["password"] self.hostname = config["hostname"] self.localhost = config["localhost"] + self.capture = config["capture"] + self.interface = config["interface"] if __name__ == "__main__": - parser = argparse.ArgumentParser(description="Dystopia | A python honeypot.") + parser = argparse.ArgumentParser(description="Dystopia | A python Honeypot.") parser.add_argument( "--port","-P", help="specify a port to bind to", default=23, type=int ) parser.add_argument( "--motd", "-m", help="specify the message of the day", - default="Welcome to Ubuntu Core 18 (GNU/Linux 4.15.0-143-generic x86_64)\n* Ubuntu Core: https://www.ubuntu.com/core\n* Community: https://forum.snapcraft.io\n* Snaps: https://snapcraft.io\nThis Ubuntu Core 18 machine is a tiny, transactional edition of Ubuntu,\ndesigned for appliances, firmware and fixed-function VMs.\n\nIf all the software you care about is available as snaps, you are in\nthe right place. If not, you will be more comfortable with classic\ndeb-based Ubuntu Server or Desktop, where you can mix snaps with\ntraditional debs. It's a brave new world here in Ubuntu Core!\n\nPlease see 'snap --help' for app installation and updates.\n", + default="Welcome to Ubuntu Core r18 (GNU/Linux 4.15.0-143-generic x86_64)\n* Ubuntu Core: " + "https://www.ubuntu.com/core\n* Community: https://forum.snapcraft.io\n* Snaps: " + "https://snapcraft.io\nThis Ubuntu Core 18 machine is a tiny, transactional edition of Ubuntu," + "\ndesigned for appliances, firmware and fixed-function VMs.\n\nIf all the software you care about is " + "available as snaps, you are in\nthe right place. If not, you will be more comfortable with " + "classic\ndeb-based Ubuntu Server or Desktop, where you can mix snaps with\ntraditional debs. It's a " + "brave new world here in Ubuntu Core!\n\nPlease see 'snap --help' for app installation and updates.\n", ) parser.add_argument( "--max", "-M", - help="max number of clients allowed to be connected at once.", + help="max number of clients allowed to be connected at once default is unlimited", default=0, type=int, ) + parser.add_argument( + "--login","-f", help="create a fake login prompt (no encryption)", action="store_true", default=False + ) parser.add_argument( "--username", "-u", - help="username for fake login prompt and the user for the honeypot session", + help="username for fake login prompt and the user for the Honeypot session default: 'ubuntu'", default="ubuntu", ) parser.add_argument( - "--password","-p", help="password for fake login prompt", default=None + "--password","-p", help="password for fake login prompt. Default: 'P@$$W0RD'", default="P@$$W0RD" ) - parser.add_argument("--hostname", "-H", help="hostname of the honeypot", default="localhost") + parser.add_argument("--hostname", "-H", help="Hostname of the Honeypot default: 'localhost'", default="localhost") parser.add_argument( "--localhost", "-L", - help="host honeypot on localhost", + help="start Honeypot on localhost", default=False, action="store_true", ) - parser.add_argument("--save", "-s", help="save config to a json file") - parser.add_argument("--load", "-l", help="load a config file") + parser.add_argument("--capture", "-c", help="enable packet capturing using the tool Tcpdump", action="store_true", default=False) + parser.add_argument( + "--interface", "-i", help="interface to capture traffic on if --capture / -c is used and no interface is configured, the default is: 'eth0'", default="eth0" + ) + parser.add_argument("--save", "-s", help="save config to a json file E.g: '--save settings.json'") + parser.add_argument("--load", "-l", help="load config from a json file E.g '--load settings.json'") args = parser.parse_args() printBanner() s = Honeypot()