Browse Source

BaseAttack
- generates random timestamp's microseconds for first attack packet if no microseconds were provided by the attack parameter
- makes _is_ip_address compatible for list of IP addresses (strings)
- fixes bug: inject.after-packet cannot be determined

PortscanAttack
- improves timestamp generation, avoids using global variables

AttackController
- removes useless variable 'written_pcaps'

Controller
- fixes bug: intermediate pcaps of multiple attack injection are not deleted

Statistics
- adds the method: get_pps_received

Patrick Jattke 7 years ago
parent
commit
e524a7e179

+ 17 - 5
code/Attack/BaseAttack.py

@@ -1,4 +1,5 @@
 import ipaddress
+import random
 import re
 from abc import abstractmethod, ABCMeta
 
@@ -63,14 +64,19 @@ class BaseAttack(metaclass=ABCMeta):
     @staticmethod
     def _is_ip_address(ip_address: str):
         """
-        Verifies if the given string is a valid IPv4/IPv6 address. Accepts comma-separated lists of IP addresses,
-        like "192.169.178.1, 192.168.178.2"
+        Verifies that the given string or list of IP addresses (strings) is a valid IPv4/IPv6 address.
+        Accepts comma-separated lists of IP addresses, like "192.169.178.1, 192.168.178.2"
 
-        :param ip_address: The IP address as string.
+        :param ip_address: The IP address(es) as list of strings or comma-separated string.
         :return: True if all IP addresses are valid, otherwise False. And a list of IP addresses as string.
         """
         ip_address_output = []
-        for ip in ip_address.split(','):
+
+        # a comma-separated list of IP addresses must be splitted first
+        if isinstance(ip_address, str):
+            ip_address = ip_address.split(',')
+
+        for ip in ip_address:
             try:
                 ipaddress.ip_address(ip)
                 ip_address_output.append(ip)
@@ -233,12 +239,18 @@ class BaseAttack(metaclass=ABCMeta):
             is_valid = value is None or (value.isdigit() and int(value) >= 0)
         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'
+            # but microseconds are only chosen randomly if the given parameter does not already specify it
+            # e.g. inject.at-timestamp=123456.987654 -> is not changed
+            # e.g. inject.at-timestamp=123456 -> is changed to: 123456.[random digits]
+            if param_name == Parameter.INJECT_AT_TIMESTAMP and is_valid and ((value - int(value)) == 0):
+                value = value + random.uniform(0, 0.999999)
         elif param_type == ParameterTypes.TYPE_TIMESTAMP:
             is_valid = self._is_timestamp(value)
         elif param_type == ParameterTypes.TYPE_BOOLEAN:
             is_valid, value = self._is_boolean(value)
         elif param_type == ParameterTypes.TYPE_PACKET_POSITION:
-            ts = pr.pcap_processor(self.pcap_filepath).get_timestamp_mu_sec(int(value))
+            ts = pr.pcap_processor(self.statistics.pcap_filepath).get_timestamp_mu_sec(int(value))
             if 0 <= int(value) <= self.statistics.get_packet_count() and ts >= 0:
                 is_valid = True
                 param_name = Parameter.INJECT_AT_TIMESTAMP

+ 21 - 14
code/Attack/PortscanAttack.py

@@ -43,6 +43,8 @@ class PortscanAttack(BaseAttack.BaseAttack):
         # PARAMETERS: initialize with default values
         # (values are overwritten if user specifies them)
         most_used_ipAddress = self.statistics.process_db_query("most_used(ipAddress)")
+        if isinstance(most_used_ipAddress, list):
+            most_used_ipAddress = most_used_ipAddress[0]
         self.add_param_value(Param.IP_SOURCE, most_used_ipAddress)
         self.add_param_value(Param.IP_SOURCE_RANDOMIZE, 'False')
         self.add_param_value(Param.IP_DESTINATION, '192.168.178.13')
@@ -52,23 +54,22 @@ class PortscanAttack(BaseAttack.BaseAttack):
         self.add_param_value(Param.PORT_SOURCE_RANDOM, 'False')
         self.add_param_value(Param.PORT_DEST_SHUFFLE, 'False')
         self.add_param_value(Param.PORT_ORDER_DESC, 'False')
-        self.add_param_value(Param.MAC_SOURCE, 'macAddress(ipAddress=' + most_used_ipAddress + ')')
+        macAddress = self.statistics.process_db_query('macAddress(ipAddress=' + most_used_ipAddress + ")")
+        self.add_param_value(Param.MAC_SOURCE, macAddress)
         self.add_param_value(Param.MAC_DESTINATION, 'A0:1A:28:0B:62:F4')
-        self.add_param_value(Param.PACKETS_PER_SECOND, self.statistics.get_pps_sent(most_used_ipAddress))
+        self.add_param_value(Param.PACKETS_PER_SECOND,
+                             (self.statistics.get_pps_sent(most_used_ipAddress) +
+                              self.statistics.get_pps_received(most_used_ipAddress)) / 2)
         self.add_param_value(Param.INJECT_AT_TIMESTAMP, '1410733342')  # Sun, 14 Sep 2014 22:22:22 GMT
 
     def get_packets(self):
-        def get_timestamp():
+        def update_timestamp(timestamp, pps, maxdelay):
             """
-            Calculates the next timestamp and returns the timestamp to be used for the current packet.
+            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 current packet.
+            :return: Timestamp to be used for the next packet.
             """
-            nonlocal timestamp_next_pkt, pps, maxdelay
-            timestamp_current_packet = timestamp_next_pkt  # current timestamp
-            #TODO Derive timestamps based on pps rate given
-            timestamp_next_pkt = timestamp_next_pkt + uniform(0.1 / pps, maxdelay)  # timestamp for next pkt
-            return timestamp_current_packet
+            return timestamp + uniform(0.1 / pps, maxdelay)
 
         # Determine ports
         dest_ports = self.get_param_value(Param.PORT_DESTINATION)
@@ -114,7 +115,10 @@ class PortscanAttack(BaseAttack.BaseAttack):
             request_ip = IP(src=ip_source, dst=ip_destination, ttl=ttl_value)
             request_tcp = TCP(sport=sport, dport=dport)
             request = (request_ether / request_ip / request_tcp)
-            request.time = get_timestamp()
+            # first packet uses timestamp provided by attack parameter Param.INJECT_AT_TIMESTAMP
+            if len(packets) > 0:
+                timestamp_next_pkt = update_timestamp(timestamp_next_pkt, pps, maxdelay)
+            request.time = timestamp_next_pkt
             packets.append(request)
 
             # 2) Build reply package
@@ -127,7 +131,8 @@ class PortscanAttack(BaseAttack.BaseAttack):
                                 options=[mss])
                 # reply_tcp.time = time_sec_start + random.uniform(0.00005, 0.00013)
                 reply = (reply_ether / reply_ip / reply_tcp)
-                reply.time = get_timestamp()
+                timestamp_next_pkt = update_timestamp(timestamp_next_pkt, pps, maxdelay)
+                reply.time = timestamp_next_pkt
                 packets.append(reply)
 
                 # requester confirms
@@ -135,14 +140,16 @@ class PortscanAttack(BaseAttack.BaseAttack):
                 confirm_ip = request_ip
                 confirm_tcp = TCP(sport=sport, dport=dport, seq=1, window=0, flags='R')
                 reply = (confirm_ether / confirm_ip / confirm_tcp)
-                reply.time = get_timestamp()
+                timestamp_next_pkt = update_timestamp(timestamp_next_pkt, pps, maxdelay)
+                reply.time = timestamp_next_pkt
                 packets.append(reply)
 
             else:  # destination port is NOT OPEN
                 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)
-                reply.time = get_timestamp()
+                timestamp_next_pkt = update_timestamp(timestamp_next_pkt, pps, maxdelay)
+                reply.time = timestamp_next_pkt
                 packets.append(reply)
 
         # store end time of attack

+ 0 - 3
code/ID2TLib/AttackController.py

@@ -3,7 +3,6 @@ import tempfile
 
 from scapy.utils import PcapWriter
 
-import Attack
 from Attack.AttackParameters import Parameter
 from ID2TLib import LabelManager
 from ID2TLib import Statistics
@@ -23,7 +22,6 @@ class AttackController:
 
         self.current_attack = None
         self.added_attacks = []
-        self.written_pcaps = []
 
         # The PCAP where the attack should be injected into
         self.base_pcap = self.statistics.pcap_filepath
@@ -106,7 +104,6 @@ class AttackController:
 
         # Merge attack with existing pcap
         pcap_dest_path = self.pcap_file.merge_attack(temp_attack_pcap_path)
-        self.written_pcaps.append(pcap_dest_path)
 
         # Store label into LabelManager
         l = Label(attack, self.get_attack_start_utime(),

+ 1 - 0
code/ID2TLib/Controller.py

@@ -46,6 +46,7 @@ class Controller:
         # load attacks sequentially
         for attack in attacks_config:
             self.pcap_dest_path = self.attack_controller.process_attack(attack[0], attack[1:])
+            self.written_pcaps.append(self.pcap_dest_path)
 
         # delete intermediate PCAP files
         for i in range(len(self.written_pcaps) - 1):

+ 17 - 4
code/ID2TLib/Statistics.py

@@ -189,6 +189,18 @@ class Statistics:
         capture_duration = float(self.get_capture_duration())
         return int(float(packets_sent) / capture_duration)
 
+    def get_pps_received(self, ip_address: str):
+        """
+        Calculate the packets per second received for a given IP address.
+        :param ip_address: The IP address used for the calculation
+        :return: The number of packets per second received
+        """
+        packets_received = self.stats_db.process_db_query("SELECT pktsReceived FROM ip_statistics WHERE ipAddress=?",
+                                                          False,
+                                                          (ip_address,))
+        capture_duration = float(self.get_capture_duration())
+        return int(float(packets_received) / capture_duration)
+
     def get_packet_count(self):
         """
         :return: The number of packets in the loaded PCAP file
@@ -219,7 +231,8 @@ class Statistics:
         :param value: The string to be checked
         :return: True if the string is recognized as a query, otherwise False.
         """
-        is_scalar_value = type(value) in (int, float)
-        return not is_scalar_value and (
-            any(x in value.lower().strip() for x in self.stats_db.get_all_named_query_keywords()) or
-            any(x in value.lower().strip() for x in self.stats_db.get_all_sql_query_keywords()))
+        if not isinstance(value, str):
+            return False
+        else:
+            return (any(x in value.lower().strip() for x in self.stats_db.get_all_named_query_keywords()) or
+                    any(x in value.lower().strip() for x in self.stats_db.get_all_sql_query_keywords()))