Browse Source

AttackParameters
- Adds the parameter NUMBER.ATTACKERS used by DDoSAttack

BaseAttack
- Improves parameter validation
- Adds method to generate random MAC address

DDoSAttack
- Improves derivation of attack parameters

Patrick Jattke 7 years ago
parent
commit
c26b45f9d5

+ 2 - 1
code/Attack/AttackParameters.py

@@ -18,7 +18,8 @@ class Parameter(Enum):
     PORT_DESTINATION = 'port.dst'  # destination ports
     PORT_SOURCE = 'port.src'  # source ports
     # recommended type: Integer positive -------------------------
-    PACKETS_LIMIT = 'packets.limit'  # the
+    PACKETS_LIMIT = 'packets.limit'
+    NUMBER_ATTACKERS = 'attackers.count'
     # recommended type: Float ------------------------------------
     PACKETS_PER_SECOND = 'packets.per-second'  # packets per second
     INJECT_AT_TIMESTAMP = 'inject.at-timestamp'  # unix epoch time (seconds.millis) where attack should be injected

+ 56 - 12
code/Attack/BaseAttack.py

@@ -58,8 +58,16 @@ class BaseAttack(metaclass=ABCMeta):
         :param mac_address: The MAC address as string.
         :return: True if the MAC address is valid, otherwise False.
         """
-        result = re.match('^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$', mac_address, re.MULTILINE)
-        return result is not None
+        pattern = re.compile('^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$', re.MULTILINE)
+        if isinstance(mac_address, list):
+            for mac in mac_address:
+                if re.match(pattern, mac) is None:
+                    return False
+        else:
+            if re.match(pattern, mac_address) is None:
+                return False
+
+        return True
 
     @staticmethod
     def _is_ip_address(ip_address: str):
@@ -110,6 +118,8 @@ class BaseAttack(metaclass=ABCMeta):
 
         if isinstance(ports_input, str):
             ports_input = ports_input.replace(' ', '').split(',')
+        elif isinstance(ports_input, int):
+            ports_input = [ports_input]
 
         ports_output = []
 
@@ -198,7 +208,7 @@ class BaseAttack(metaclass=ABCMeta):
     # HELPER METHODS
     #########################################
 
-    def add_param_value(self, param, value: str):
+    def add_param_value(self, param, value):
         """
         Adds the pair param : value to the dictionary of attack parameters. Prints and error message and skips the
         parameter if the validation fails.
@@ -280,24 +290,27 @@ class BaseAttack(metaclass=ABCMeta):
         :param param: The parameter whose value is wanted.
         :return: The parameter's value.
         """
-        return self.params[param]
+        return self.params.get(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.
         """
+        # parameters which do not require default values
+        non_obligatory_params = [Parameter.INJECT_AFTER_PACKET, Parameter.NUMBER_ATTACKERS]
         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:
+            if param not in self.params.keys() and param not in non_obligatory_params:
                 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):
+    @staticmethod
+    def generate_random_ipv4_address(n: int = 1):
         """
         Generates n random IPv4 addresses.
         :param n: The number of IP addresses to be generated
@@ -306,7 +319,7 @@ class BaseAttack(metaclass=ABCMeta):
 
         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
+                   ipAddress.is_link_local or ipAddress.is_private or ipAddress.is_reserved
 
         def generate_address():
             return ipaddress.IPv4Address(random.randint(0, 2 ** 32 - 1))
@@ -314,7 +327,7 @@ class BaseAttack(metaclass=ABCMeta):
         ip_addresses = []
         for i in range(0, n):
             address = generate_address()
-            while (is_invalid(address)):
+            while is_invalid(address):
                 address = generate_address()
             ip_addresses.append(str(address))
 
@@ -323,7 +336,8 @@ class BaseAttack(metaclass=ABCMeta):
         else:
             return ip_addresses
 
-    def generate_random_ipv6_address(self, n: int = 1):
+    @staticmethod
+    def generate_random_ipv6_address(n: int = 1):
         """
         Generates n random IPv6 addresses.
         :param n: The number of IP addresses to be generated
@@ -332,15 +346,15 @@ class BaseAttack(metaclass=ABCMeta):
 
         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
+                   ipAddress.is_link_local or ipAddress.is_private or ipAddress.is_reserved
 
         def generate_address():
-            return str(ipaddress.IPv6Address(random.randint(0, 2 ** 128 - 1)))
+            return ipaddress.IPv6Address(random.randint(0, 2 ** 128 - 1))
 
         ip_addresses = []
         for i in range(0, n):
             address = generate_address()
-            while (is_invalid(address)):
+            while is_invalid(address):
                 address = generate_address()
             ip_addresses.append(str(address))
 
@@ -348,3 +362,33 @@ class BaseAttack(metaclass=ABCMeta):
             return ip_addresses[0]
         else:
             return ip_addresses
+
+    @staticmethod
+    def generate_random_mac_address(n: int = 1):
+        """
+        Generates n random MAC addresses.
+        :param n: The number of MAC addresses to be generated.
+        :return: A single MAC addres, or if n>1, a list of MAC addresses
+        """
+
+        def is_invalid(address: str):
+            first_octet = int(address[0:2], 16)
+            is_multicast_address = bool(first_octet & 0b01)
+            is_locally_administered = bool(first_octet & 0b10)
+            return is_multicast_address or is_locally_administered
+
+        def generate_address():
+            mac = [random.randint(0x00, 0xff) for i in range(0, 6)]
+            return ':'.join(map(lambda x: "%02x" % x, mac))
+
+        mac_addresses = []
+        for i in range(0, n):
+            address = generate_address()
+            while is_invalid(address):
+                address = generate_address()
+            mac_addresses.append(address)
+
+        if n == 1:
+            return mac_addresses[0]
+        else:
+            return mac_addresses

+ 176 - 0
code/Attack/DDoSAttack.py

@@ -0,0 +1,176 @@
+import logging
+from random import randint, choice, uniform
+from lea import Lea
+from scipy.stats import stats, gamma
+
+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 DDoSAttack(BaseAttack.BaseAttack):
+    def __init__(self, statistics, pcap_file_path):
+        """
+        Creates a new instance of the DDoS attack.
+
+        :param statistics: A reference to the statistics class.
+        """
+        # Initialize attack
+        super(DDoSAttack, self).__init__(statistics, "DDoS Attack", "Injects a DDoS 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.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,
+            Param.NUMBER_ATTACKERS: ParameterTypes.TYPE_INTEGER_POSITIVE
+        }
+
+        # PARAMETERS: initialize with default values
+        # (values are overwritten if user specifies them)
+
+        self.add_param_value(Param.INJECT_AFTER_PACKET, randint(0, self.statistics.get_packet_count()))
+        # attacker configuration
+        num_attackers = randint(1, 16)
+        self.add_param_value(Param.IP_SOURCE, self.generate_random_ipv4_address(num_attackers))
+        self.add_param_value(Param.MAC_SOURCE, self.generate_random_mac_address(num_attackers))
+        self.add_param_value(Param.PORT_SOURCE, str(RandShort()))
+        self.add_param_value(Param.PACKETS_PER_SECOND, randint(1, 64))
+        # victim 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))
+        port_destination = self.statistics.process_db_query(
+            "SELECT portNumber FROM ip_ports WHERE portDirection='in' ORDER BY RANDOM() LIMIT 1;")
+        if port_destination is None:
+            port_destination = str(RandShort())
+        self.add_param_value(Param.PORT_DESTINATION, port_destination)
+        self.add_param_value(Param.PACKETS_LIMIT, randint(1000, 5000))
+
+    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):
+            """
+            Returns the n-th element of every list from an arbitrary number of given lists.
+            For example, list1 contains IP addresses, list 2 contains MAC addresses. Use of this function ensures that
+            the n-th IP address uses always the n-th MAC address.
+            :param element_list: An arbitrary number of lists.
+            :return: A tuple of the n-th element of every list.
+            """
+            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)
+
+        def index_increment(number: int, max: int):
+            if number + 1 < max:
+                return number + 1
+            else:
+                return 0
+
+        def get_attacker_config(ipAddress: str):
+            """
+
+            :param ipAddress:
+            :return:
+            """
+            # Determine port
+            port = attacker_port_mapping.get(ipAddress)
+            if port is not None:  # use next port
+                next_port = attacker_port_mapping.get(ipAddress) + 1
+                if next_port > (2 ** 16 - 1):
+                    next_port = 1
+            else:  # generate starting port
+                next_port = RandShort()
+            attacker_port_mapping[ipAddress] = next_port
+            # Determine TTL value
+            ttl = attacker_ttl_mapping.get(ipAddress)
+            if ttl is None:  # determine TTL value
+                is_invalid = True
+                pos = ip_source_list.index(ipAddress)
+                pos_max = len(gd)
+                while is_invalid:
+                    ttl = int(round(gd[pos]))
+                    if 0 < ttl < 256:  # validity check
+                        is_invalid = False
+                        pos = index_increment(pos, pos_max)
+                attacker_ttl_mapping[ipAddress] = ttl
+            # return port and TTL
+            return next_port, ttl
+
+        # Determine source IP and MAC address
+        num_attackers = self.get_param_value(Param.NUMBER_ATTACKERS)
+        if num_attackers is not None:  # user supplied Param.NUMBER_ATTACKERS
+            # Create random attackers based on user input Param.NUMBER_ATTACKERS
+            ip_source_list = self.generate_random_ipv4_address(num_attackers)
+            mac_source_list = self.generate_random_mac_address(num_attackers)
+        else:  # user did not supply Param.NUMBER_ATTACKS
+            # use default values for IP_SOURCE/MAC_SOURCE or overwritten values
+            # if user supplied any values for those params
+            ip_source_list = self.get_param_value(Param.IP_SOURCE)
+            mac_source_list = self.get_param_value(Param.MAC_SOURCE)
+
+        BUFFER_SIZE_PACKETS = self.get_param_value(Param.PACKETS_LIMIT)
+
+        # Timestamp
+        timestamp_next_pkt = self.get_param_value(Param.INJECT_AT_TIMESTAMP)
+        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)
+        port_source_list = 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)
+        attacker_port_mapping = {}
+        attacker_ttl_mapping = {}
+
+        # Gamma distribution parameters derived from MAWI 13.8G dataset
+        alpha, loc, beta = (2.3261710235, -0.188306914406, 44.4853123884)
+        gd = gamma.rvs(alpha, loc=loc, scale=beta, size=len(ip_source_list))
+
+        for pkt_num in range(self.get_param_value(Param.PACKETS_LIMIT)):
+            # Select one IP address and its corresponding MAC address
+            (ip_source, mac_source) = get_nth_random_element(ip_source_list, mac_source_list)
+
+            # Determine source port
+            (port_source, ttl_value) = get_attacker_config(ip_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=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)
+
+        # Store timestamp of first and last packet
+        self.attack_start_utime = packets[0].time
+        self.attack_end_utime = packets[-1].time
+
+        # return packets sorted by packet time_sec_start
+        return sorted(packets, key=lambda pkt: pkt.time)

+ 0 - 134
code/Attack/DosAttack.py

@@ -1,134 +0,0 @@
-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)

+ 3 - 3
code/ID2TLib/Controller.py

@@ -57,12 +57,12 @@ class Controller:
             os.remove(self.written_pcaps[i])
         print("done.")
 
-        # print status message
-        print('\nOutput file created: ', self.pcap_dest_path)
-
         # write label file with attacks
         self.label_manager.write_label_file(self.pcap_dest_path)
 
+        # print status message
+        print('\nOutput files created: \n', self.pcap_dest_path, '\n', self.label_manager.label_file_path)
+
     def process_db_queries(self, query, print_results=False):
         """
         Processes a statistics database query. This can be a standard SQL query or a named query.