Skip to content

Commit

Permalink
Fixed Various Bugs & Added Packet Capture
Browse files Browse the repository at this point in the history
  • Loading branch information
Drew-Alleman authored May 29, 2021
1 parent 84253c4 commit 1053d1b
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 37 deletions.
10 changes: 7 additions & 3 deletions core/utilities.py
Original file line number Diff line number Diff line change
@@ -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):
Expand Down Expand Up @@ -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)

Expand All @@ -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()
Expand All @@ -138,7 +143,6 @@ def yn(message):
except KeyboardInterrupt:
quit()


def formatString(s):
s = s.decode().strip()
if s.endswith("\x00"):
Expand Down
97 changes: 63 additions & 34 deletions dystopia.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
import json
import os

globalLock = threading.Lock()


class Statistics:
def __init__(self, clientAddress):
self.address = clientAddress[0]
Expand All @@ -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
Expand All @@ -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:
Expand All @@ -70,7 +78,6 @@ def __init__(self):

if self.max != 0:
printMessage("Max clients allowed: " + str(self.max))

if args.save:
Honeypot.exportConfig(self)

Expand All @@ -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()
Expand All @@ -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
Expand All @@ -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"
Expand All @@ -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()
Expand All @@ -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):
Expand All @@ -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()
Expand Down

0 comments on commit 1053d1b

Please sign in to comment.