3 Commits 2c3f4c9a1c ... 55a6276c93

Autore SHA1 Messaggio Data
  Patrick Jattke 55a6276c93 - Implemented DoS attack 8 anni fa
  Patrick Jattke 53e2150d06 - Improves PortscanAttack: Derives TTL of source IP address based on collected statistics 8 anni fa
  Patrick Jattke 9e9fe28404 - Improves the human-readable output in the label file 8 anni fa

+ 13 - 11
code/Attack/AttackParameters.py

@@ -6,26 +6,29 @@ class Parameter(Enum):
     Defines the shortname for attack parameters. The shortname may be used for attack parameter specification
     when calling ID2T via the command line.
     """
-    # type: IP address ------------------------------
+    # recommended type: IP address -------------------------------
     IP_SOURCE = 'ip.src'  # source IP address
     IP_DESTINATION = 'ip.dst'  # destination IP address
     IP_DNS = 'ip.dns'  # IP address of DNS server
-    # type: MAC address -----------------------------
+    # recommended type: MAC address ------------------------------
     MAC_SOURCE = 'mac.src'  # MAC address of source
     MAC_DESTINATION = 'mac.dst'  # MAC address of destination
-    # type: Port ------------------------------------
+    # recommended type: Port -------------------------------------
     PORT_OPEN = 'port.open'  # open ports
     PORT_DESTINATION = 'port.dst'  # destination ports
     PORT_SOURCE = 'port.src'  # source ports
-    # type: Digits ----------------------------------
+    # recommended type: Integer positive -------------------------
+    PACKETS_LIMIT = 'packets.limit'  # the
+    # recommended type: Float ------------------------------------
     PACKETS_PER_SECOND = 'packets.per-second'  # packets per second
-    INJECT_AT_TIMESTAMP = 'inject.at-timestamp'  # unix epoch time where attack should be injected
+    INJECT_AT_TIMESTAMP = 'inject.at-timestamp'  # unix epoch time (seconds.millis) where attack should be injected
+    # recommended type: Packet Position ----------------------------------
     INJECT_AFTER_PACKET = 'inject.after-pkt'  # packet after which attack should be injected
-    # type: boolean  --------------------------------
+    # recommended type: boolean  --------------------------------
     PORT_DEST_SHUFFLE = 'port.dst.shuffle'  # shuffles the destination ports if a list of ports is given
-    PORT_ORDER_DESC = 'port.dst.order-desc'  # uses a descending port order instead of a ascending order
+    PORT_DEST_ORDER_DESC = 'port.dst.order-desc'  # uses a descending port order instead of a ascending order
     IP_SOURCE_RANDOMIZE = 'ip.src.shuffle'  # randomizes the sources IP address if a list of IP addresses is given
-    PORT_SOURCE_RANDOM = 'port.src.shuffle'  # randomizes the source port if a list of sources ports is given
+    PORT_SOURCE_RANDOMIZE = 'port.src.shuffle'  # randomizes the source port if a list of sources ports is given
 
 
 class ParameterTypes(Enum):
@@ -39,6 +42,5 @@ class ParameterTypes(Enum):
     TYPE_INTEGER_POSITIVE = 3
     TYPE_TIMESTAMP = 4
     TYPE_BOOLEAN = 5
-    TYPE_ASC_DSC = 6
-    TYPE_FLOAT = 7
-    TYPE_PACKET_POSITION = 8  # used to derive timestamp from parameter INJECT_AFTER_PACKET
+    TYPE_FLOAT = 6
+    TYPE_PACKET_POSITION = 7  # used to derive timestamp from parameter INJECT_AFTER_PACKET

+ 75 - 2
code/Attack/BaseAttack.py

@@ -108,7 +108,9 @@ class BaseAttack(metaclass=ABCMeta):
             """
             return num < 0 or num > 65535
 
-        ports_input = ports_input.replace(' ', '').split(',')
+        if isinstance(ports_input, str):
+            ports_input = ports_input.replace(' ', '').split(',')
+
         ports_output = []
 
         for port_entry in ports_input:
@@ -240,7 +242,11 @@ class BaseAttack(metaclass=ABCMeta):
         elif param_type == ParameterTypes.TYPE_MAC_ADDRESS:
             is_valid = self._is_mac_address(value)
         elif param_type == ParameterTypes.TYPE_INTEGER_POSITIVE:
-            is_valid = value is None or (value.isdigit() and int(value) >= 0)
+            if isinstance(value, int) and int(value) >= 0:
+                is_valid = True
+            elif isinstance(value, str) and value.isdigit() and int(value) >= 0:
+                is_valid = True
+                value = int(value)
         elif param_type == ParameterTypes.TYPE_FLOAT:
             is_valid, value = self._is_float(value)
             # this is required to avoid that the timestamp's microseconds of the first attack packet is '000000'
@@ -275,3 +281,70 @@ class BaseAttack(metaclass=ABCMeta):
         :return: The parameter's value.
         """
         return self.params[param]
+
+    def check_parameters(self):
+        """
+        Checks whether all parameter values are defined. If a value is not defined, the application is terminated.
+        However, this should not happen as all attack should define default parameter values.
+        """
+        for param, type in self.supported_params.items():
+            # checks whether all params have assigned values, INJECT_AFTER_PACKET must not be considered because the
+            # timestamp derived from it is set to Parameter.INJECT_AT_TIMESTAMP
+            if param not in self.params.keys() and param is not Parameter.INJECT_AFTER_PACKET:
+                print("\033[91mCRITICAL ERROR: Attack '" + self.attack_name + "' does not define the parameter '" +
+                      str(param) + "'.\n The attack must define default values for all parameters."
+                      + "\n Cannot continue attack generation.\033[0m")
+                import sys
+                sys.exit(0)
+
+    def generate_random_ipv4_address(self, n: int = 1):
+        """
+        Generates n random IPv4 addresses.
+        :param n: The number of IP addresses to be generated
+        :return: A single IP address, or if n>1, a list of IP addresses
+        """
+
+        def is_invalid(ipAddress: ipaddress.IPv4Address):
+            return ipAddress.is_multicast or ipAddress.is_unspecified or ipAddress.is_loopback or \
+                   ipAddress.is_link_local or ipAddress.is_reserved
+
+        def generate_address():
+            return ipaddress.IPv4Address(random.randint(0, 2 ** 32 - 1))
+
+        ip_addresses = []
+        for i in range(0, n):
+            address = generate_address()
+            while (is_invalid(address)):
+                address = generate_address()
+            ip_addresses.append(str(address))
+
+        if n == 1:
+            return ip_addresses[0]
+        else:
+            return ip_addresses
+
+    def generate_random_ipv6_address(self, n: int = 1):
+        """
+        Generates n random IPv6 addresses.
+        :param n: The number of IP addresses to be generated
+        :return: A single IP address, or if n>1, a list of IP addresses
+        """
+
+        def is_invalid(ipAddress: ipaddress.IPv6Address):
+            return ipAddress.is_multicast or ipAddress.is_unspecified or ipAddress.is_loopback or \
+                   ipAddress.is_link_local or ipAddress.is_reserved
+
+        def generate_address():
+            return str(ipaddress.IPv6Address(random.randint(0, 2 ** 128 - 1)))
+
+        ip_addresses = []
+        for i in range(0, n):
+            address = generate_address()
+            while (is_invalid(address)):
+                address = generate_address()
+            ip_addresses.append(str(address))
+
+        if n == 1:
+            return ip_addresses[0]
+        else:
+            return ip_addresses

+ 134 - 0
code/Attack/DosAttack.py

@@ -0,0 +1,134 @@
+import logging
+from random import randint, choice, uniform
+from lea import Lea
+from Attack import BaseAttack
+from Attack.AttackParameters import Parameter as Param
+from Attack.AttackParameters import ParameterTypes
+
+logging.getLogger("scapy.runtime").setLevel(logging.ERROR)
+# noinspection PyPep8
+from scapy.layers.inet import IP, Ether, TCP, RandShort
+from collections import deque
+
+
+class DosAttack(BaseAttack.BaseAttack):
+    def __init__(self, statistics, pcap_file_path):
+        """
+        Creates a new instance of the PortscanAttack.
+
+        :param statistics: A reference to the statistics class.
+        """
+        # Initialize attack
+        super(DosAttack, self).__init__(statistics, "DoS Attack", "Injects a DoS attack'",
+                                        "Resource Exhaustion")
+
+        # Define allowed parameters and their type
+        self.supported_params = {
+            Param.IP_SOURCE: ParameterTypes.TYPE_IP_ADDRESS,
+            Param.MAC_SOURCE: ParameterTypes.TYPE_MAC_ADDRESS,
+            Param.PORT_SOURCE: ParameterTypes.TYPE_PORT,
+            Param.PORT_SOURCE_RANDOMIZE: ParameterTypes.TYPE_BOOLEAN,
+            Param.IP_DESTINATION: ParameterTypes.TYPE_IP_ADDRESS,
+            Param.MAC_DESTINATION: ParameterTypes.TYPE_MAC_ADDRESS,
+            Param.PORT_DESTINATION: ParameterTypes.TYPE_PORT,
+            Param.INJECT_AT_TIMESTAMP: ParameterTypes.TYPE_FLOAT,
+            Param.INJECT_AFTER_PACKET: ParameterTypes.TYPE_PACKET_POSITION,
+            Param.PACKETS_PER_SECOND: ParameterTypes.TYPE_FLOAT,
+            Param.PACKETS_LIMIT: ParameterTypes.TYPE_INTEGER_POSITIVE
+        }
+
+        # PARAMETERS: initialize with default values
+        # (values are overwritten if user specifies them)
+        most_used_ip_address = self.statistics.get_most_used_ip_address()
+        if isinstance(most_used_ip_address, list): most_used_ip_address = most_used_ip_address[0]
+        self.add_param_value(Param.INJECT_AFTER_PACKET, randint(0, self.statistics.get_packet_count()))
+        # sender configuration
+        self.add_param_value(Param.IP_SOURCE, most_used_ip_address)
+        self.add_param_value(Param.MAC_SOURCE, self.statistics.get_mac_address(most_used_ip_address))
+        self.add_param_value(Param.PORT_SOURCE, str(RandShort()))
+        self.add_param_value(Param.PORT_SOURCE_RANDOMIZE, False)
+        self.add_param_value(Param.PACKETS_PER_SECOND,
+                             (self.statistics.get_pps_sent(most_used_ip_address) +
+                              self.statistics.get_pps_received(most_used_ip_address)) / 2)
+        # receiver configuration
+        random_ip_address = self.statistics.get_random_ip_address()
+        self.add_param_value(Param.IP_DESTINATION, random_ip_address)
+        self.add_param_value(Param.MAC_DESTINATION, self.statistics.get_mac_address(random_ip_address))
+        self.add_param_value(Param.PORT_DESTINATION, '80')
+        self.add_param_value(Param.PACKETS_LIMIT, randint(10, 1000))
+
+    def get_packets(self):
+        def update_timestamp(timestamp, pps, maxdelay):
+            """
+            Calculates the next timestamp to be used based on the packet per second rate (pps) and the maximum delay.
+
+            :return: Timestamp to be used for the next packet.
+            """
+            return timestamp + uniform(0.1 / pps, maxdelay)
+
+        def get_nth_random_element(*element_list):
+            """
+
+            :param element_list:
+            :return:
+            """
+            range_max = min([len(x) for x in element_list])
+            if range_max > 0: range_max -= 1
+            n = randint(0, range_max)
+            return tuple(x[n] for x in element_list)
+
+        BUFFER_SIZE_PACKETS = self.get_param_value(Param.PACKETS_LIMIT)
+
+        # Timestamp
+        timestamp_next_pkt = self.get_param_value(Param.INJECT_AT_TIMESTAMP)
+        # store start time of attack
+        self.attack_start_utime = timestamp_next_pkt
+        pps = self.get_param_value(Param.PACKETS_PER_SECOND)
+        randomdelay = Lea.fromValFreqsDict({1 / pps: 70, 2 / pps: 30, 5 / pps: 15, 10 / pps: 3})
+
+        # Initialize parameters
+        packets = deque(maxlen=BUFFER_SIZE_PACKETS)
+        # packets = []
+        mac_source = self.get_param_value(Param.MAC_SOURCE)
+        ip_source = self.get_param_value(Param.IP_SOURCE)
+        port_source = self.get_param_value(Param.PORT_SOURCE)
+        mac_destination = self.get_param_value(Param.MAC_DESTINATION)
+        ip_destination = self.get_param_value(Param.IP_DESTINATION)
+        port_destination = self.get_param_value(Param.PORT_DESTINATION)
+
+        # Set TTL based on TTL distribution of IP address
+        ttl_dist = self.statistics.get_ttl_distribution(ip_source)
+        if len(ttl_dist) > 0:
+            ttl_prob_dict = Lea.fromValFreqsDict(ttl_dist)
+            ttl_value = ttl_prob_dict.random()
+        else:
+            ttl_value = self.statistics.process_db_query("most_used(ttlValue)")
+
+        # MSS (Maximum Segment Size) for Ethernet. Allowed values [536,1500]
+        mss = self.statistics.get_mss(ip_destination)
+
+        for pkt_num in range(self.get_param_value(Param.PACKETS_LIMIT)):
+            # Determine source port
+            if self.get_param_value(Param.PORT_SOURCE_RANDOMIZE):
+                cur_port_source = RandShort()
+            elif isinstance(port_source, list):
+                cur_port_source = choice(port_source)
+            else:
+                cur_port_source = port_source
+
+            maxdelay = randomdelay.random()
+
+            request_ether = Ether(dst=mac_destination, src=mac_source)
+            request_ip = IP(src=ip_source, dst=ip_destination, ttl=ttl_value)
+            request_tcp = TCP(sport=cur_port_source, dport=port_destination, flags='S', ack=0)
+
+            request = (request_ether / request_ip / request_tcp)
+            request.time = timestamp_next_pkt
+            packets.append(request)
+
+            timestamp_next_pkt = update_timestamp(timestamp_next_pkt, pps, maxdelay)
+
+        self.attack_end_utime = request.time
+
+        # return packets sorted by packet time_sec_start
+        return sorted(packets, key=lambda pkt: pkt.time)

+ 24 - 28
code/Attack/PortscanAttack.py

@@ -35,10 +35,11 @@ class PortscanAttack(BaseAttack.BaseAttack):
             Param.INJECT_AT_TIMESTAMP: ParameterTypes.TYPE_FLOAT,
             Param.INJECT_AFTER_PACKET: ParameterTypes.TYPE_PACKET_POSITION,
             Param.PORT_DEST_SHUFFLE: ParameterTypes.TYPE_BOOLEAN,
-            Param.PORT_ORDER_DESC: ParameterTypes.TYPE_BOOLEAN,
+            Param.PORT_DEST_ORDER_DESC: ParameterTypes.TYPE_BOOLEAN,
             Param.IP_SOURCE_RANDOMIZE: ParameterTypes.TYPE_BOOLEAN,
             Param.PACKETS_PER_SECOND: ParameterTypes.TYPE_FLOAT,
-            Param.PORT_SOURCE_RANDOM: ParameterTypes.TYPE_BOOLEAN}
+            Param.PORT_SOURCE_RANDOMIZE: ParameterTypes.TYPE_BOOLEAN
+        }
 
         # PARAMETERS: initialize with default values
         # (values are overwritten if user specifies them)
@@ -57,10 +58,10 @@ class PortscanAttack(BaseAttack.BaseAttack):
         self.add_param_value(Param.PORT_DESTINATION, '0-1023,1720,1900,8080')
         self.add_param_value(Param.PORT_OPEN, '8080,9232,9233')
         self.add_param_value(Param.PORT_DEST_SHUFFLE, 'False')
-        self.add_param_value(Param.PORT_ORDER_DESC, 'False')
+        self.add_param_value(Param.PORT_DEST_ORDER_DESC, 'False')
 
         self.add_param_value(Param.PORT_SOURCE, '8542')
-        self.add_param_value(Param.PORT_SOURCE_RANDOM, 'False')
+        self.add_param_value(Param.PORT_SOURCE_RANDOMIZE, 'False')
 
         self.add_param_value(Param.PACKETS_PER_SECOND,
                              (self.statistics.get_pps_sent(most_used_ip_address) +
@@ -78,27 +79,19 @@ class PortscanAttack(BaseAttack.BaseAttack):
 
         # Determine ports
         dest_ports = self.get_param_value(Param.PORT_DESTINATION)
-        if self.get_param_value(Param.PORT_ORDER_DESC):
+        if self.get_param_value(Param.PORT_DEST_ORDER_DESC):
             dest_ports.reverse()
         elif self.get_param_value(Param.PORT_DEST_SHUFFLE):
             shuffle(dest_ports)
-        if self.get_param_value(Param.PORT_SOURCE_RANDOM):
+        if self.get_param_value(Param.PORT_SOURCE_RANDOMIZE):
             sport = randint(0, 65535)
         else:
             sport = self.get_param_value(Param.PORT_SOURCE)
 
-        # Get TTL distribution
-        # keys = list(self.statistics.get_ttl_distribution().vals()
-        # values = list(self.statistics.get_ttl_distribution().pmf())
-        # TTL_samples = numpy.random.choice(keys, size=len(dest_ports), replace=True, dport=values)
-        ttl_value = self.statistics.process_db_query("most_used(ttlValue)")
-
         # Timestamp
         timestamp_next_pkt = self.get_param_value(Param.INJECT_AT_TIMESTAMP)
-        self.attack_start_utime = timestamp_next_pkt  # store start time of attack
-        pps = self.get_param_value(Param.PACKETS_PER_SECOND)
-        randomdelay = Lea.fromValFreqsDict({1 / pps: 70, 2 / pps: 30, 5 / pps: 15, 10 / pps: 3})
-        maxdelay = randomdelay.random()
+        # store start time of attack
+        self.attack_start_utime = timestamp_next_pkt
 
         # Initialize parameters
         packets = []
@@ -106,10 +99,21 @@ class PortscanAttack(BaseAttack.BaseAttack):
         ip_destination = self.get_param_value(Param.IP_DESTINATION)
         mac_source = self.get_param_value(Param.MAC_SOURCE)
         mac_destination = self.get_param_value(Param.MAC_DESTINATION)
+        pps = self.get_param_value(Param.PACKETS_PER_SECOND)
+        randomdelay = Lea.fromValFreqsDict({1 / pps: 70, 2 / pps: 30, 5 / pps: 15, 10 / pps: 3})
+        maxdelay = randomdelay.random()
 
         # MSS (Maximum Segment Size) for Ethernet. Allowed values [536,1500]
         mss = self.statistics.get_mss(ip_destination)
 
+        # Set TTL based on TTL distribution of IP address
+        ttl_dist = self.statistics.get_ttl_distribution(ip_source)
+        if len(ttl_dist) > 0:
+            ttl_prob_dict = Lea.fromValFreqsDict(ttl_dist)
+            ttl_value = ttl_prob_dict.random()
+        else:
+            ttl_value = self.statistics.process_db_query("most_used(ttlValue)")
+
         for dport in dest_ports:
             # Parameters changing each iteration
             if self.get_param_value(Param.IP_SOURCE_RANDOMIZE) and isinstance(ip_source, list):
@@ -118,7 +122,7 @@ class PortscanAttack(BaseAttack.BaseAttack):
             # 1) Build request package
             request_ether = Ether(src=mac_source, dst=mac_destination)
             request_ip = IP(src=ip_source, dst=ip_destination, ttl=ttl_value)
-            request_tcp = TCP(sport=sport, dport=dport)
+            request_tcp = TCP(sport=sport, dport=dport, flags='S')
             request = (request_ether / request_ip / request_tcp)
             # first packet uses timestamp provided by attack parameter Param.INJECT_AT_TIMESTAMP
             if len(packets) > 0:
@@ -127,17 +131,15 @@ class PortscanAttack(BaseAttack.BaseAttack):
             packets.append(request)
 
             # 2) Build reply package
-            reply_ether = Ether(src=mac_destination, dst=mac_source)
-            reply_ip = IP(src=ip_destination, dst=ip_source, flags='DF')
-
             if dport in self.get_param_value(Param.PORT_OPEN):  # destination port is OPEN
+                reply_ether = Ether(src=mac_destination, dst=mac_source)
+                reply_ip = IP(src=ip_destination, dst=ip_source, flags='DF')
                 # target answers
                 if mss is None:
                     reply_tcp = TCP(sport=dport, dport=sport, seq=0, ack=1, flags='SA', window=29200)
                 else:
                     reply_tcp = TCP(sport=dport, dport=sport, seq=0, ack=1, flags='SA', window=29200,
                                     options=[('MSS', mss)])
-                # reply_tcp.time = time_sec_start + random.uniform(0.00005, 0.00013)
                 reply = (reply_ether / reply_ip / reply_tcp)
                 timestamp_next_pkt = update_timestamp(timestamp_next_pkt, pps, maxdelay)
                 reply.time = timestamp_next_pkt
@@ -152,13 +154,7 @@ class PortscanAttack(BaseAttack.BaseAttack):
                 reply.time = timestamp_next_pkt
                 packets.append(reply)
 
-                # else:  # destination port is NOT OPEN -> no reply is sent by target
-                #     reply_tcp = TCP(sport=dport, dport=sport, flags='RA', seq=1, ack=1, window=0)
-                #     # reply_tcp.time = time_sec_start + random.uniform(0.00005, 0.00013)
-                #     reply = (reply_ether / reply_ip / reply_tcp)
-                #     timestamp_next_pkt = update_timestamp(timestamp_next_pkt, pps, maxdelay)
-                #     reply.time = timestamp_next_pkt
-                #     packets.append(reply)
+                # else: destination port is NOT OPEN -> no reply is sent by target
 
         # store end time of attack
         self.attack_end_utime = reply.time

+ 0 - 0
code/GUI/__init__.py


+ 8 - 2
code/ID2TLib/AttackController.py

@@ -1,9 +1,8 @@
 import importlib
 import os
 import tempfile
-
+import sys
 from scapy.utils import PcapWriter
-
 from Attack.AttackParameters import Parameter
 from ID2TLib import LabelManager
 from ID2TLib import Statistics
@@ -32,7 +31,14 @@ class AttackController:
         Writes the attack's packets into a PCAP file with a temporary filename.
         :return: The path of the written PCAP file.
         """
+        # Check if all req. parameters are set
+        self.current_attack.check_parameters()
+
+        # Create attack packets
+        print("Generating attack packets...", end=" ")
+        sys.stdout.flush()  # force python to print text immediately
         packets = self.current_attack.get_packets()
+        print("done.")
 
         # Write packets into pcap file
         temp_pcap = tempfile.NamedTemporaryFile(delete=False)

+ 5 - 0
code/ID2TLib/Controller.py

@@ -1,5 +1,7 @@
 import os
 
+import sys
+
 from ID2TLib.AttackController import AttackController
 from ID2TLib.LabelManager import LabelManager
 from ID2TLib.PcapFile import PcapFile
@@ -49,8 +51,11 @@ class Controller:
             self.written_pcaps.append(self.pcap_dest_path)
 
         # delete intermediate PCAP files
+        print('Deleting intermediate attack pcaps...', end="")
+        sys.stdout.flush()  # force python to print text immediately
         for i in range(len(self.written_pcaps) - 1):
             os.remove(self.written_pcaps[i])
+        print("done.")
 
         # print status message
         print('\nOutput file created: ', self.pcap_dest_path)

+ 1 - 1
code/ID2TLib/LabelManager.py

@@ -76,7 +76,7 @@ class LabelManager:
 
             # add timestamp in human-readable format
             timestamp_hr = doc.createElement(self.TAG_TIMESTAMP_HR)
-            timestamp_hr_text = datetime.fromtimestamp(int(timestamp_entry)).strftime('%Y-%m-%d %H:%M:%S')
+            timestamp_hr_text = datetime.fromtimestamp(timestamp_entry).strftime('%Y-%m-%d %H:%M:%S.%f')
             timestamp_hr.appendChild(doc.createTextNode(timestamp_hr_text))
             timestamp_root.appendChild(timestamp_hr)
 

+ 1 - 1
code/ID2TLib/PcapFile.py

@@ -21,7 +21,7 @@ class PcapFile(object):
         :param attack_pcap_path: The path to the PCAP file to merge with the PCAP at pcap_file_path
         :return: The file path of the resulting PCAP file
         """
-        print("Merging base PCAP with attack PCAP...", end=" ")
+        print("Merging base pcap with attack pcap...", end=" ")
         sys.stdout.flush()  # force python to print text immediately
 
         pcap = pr.pcap_processor(self.pcap_file_path)

+ 5 - 0
code/ID2TLib/Statistics.py

@@ -213,6 +213,11 @@ class Statistics:
         """
         return self.process_db_query("most_used(ipAddress)")
 
+    def get_ttl_distribution(self, ipAddress: str):
+        result = self.process_db_query('SELECT ttlValue, ttlCount from ip_ttl WHERE ipAddress="' + ipAddress + '"')
+        result_dict = {key: value for (key, value) in result}
+        return result_dict
+
     def get_random_ip_address(self, count: int = 1):
         """
         :param count: The number of IP addreses to return