Browse Source

Merge branch 'ddos_rework' of stefan.schmidt/ID2T-toolkit into master

Carlos Garcia 6 years ago
parent
commit
dcf114cb83
2 changed files with 101 additions and 44 deletions
  1. 83 38
      code/Attack/DDoSAttack.py
  2. 18 6
      code/Test/test_DDoSAttack.py

+ 83 - 38
code/Attack/DDoSAttack.py

@@ -78,7 +78,8 @@ class DDoSAttack(BaseAttack.BaseAttack):
 
         # Determine source IP and MAC address
         num_attackers = self.get_param_value(atkParam.Parameter.NUMBER_ATTACKERS)
-        if num_attackers is not None:  # user supplied atkParam.Parameter.NUMBER_ATTACKERS
+        if (num_attackers is not None) and (num_attackers is not 0):
+            # user supplied atkParam.Parameter.NUMBER_ATTACKERS
             # The most used IP class in background traffic
             most_used_ip_class = Util.handle_most_used_outputs(self.statistics.process_db_query("most_used(ipClass)"))
             # Create random attackers based on user input atkParam.Parameter.NUMBER_ATTACKERS
@@ -89,7 +90,19 @@ class DDoSAttack(BaseAttack.BaseAttack):
             # if user supplied any values for those params
             ip_source_list = self.get_param_value(atkParam.Parameter.IP_SOURCE)
             mac_source_list = self.get_param_value(atkParam.Parameter.MAC_SOURCE)
-            num_attackers = len(ip_source_list)
+
+        # Make sure IPs and MACs are lists
+        if not isinstance(ip_source_list, list):
+            ip_source_list = [ip_source_list]
+
+        if not isinstance(mac_source_list, list):
+            mac_source_list = [mac_source_list]
+
+        # Generate MACs for each IP that has no corresponding MAC yet
+        if (num_attackers is None) or (num_attackers is 0):
+            if len(ip_source_list) > len(mac_source_list):
+                mac_source_list.extend(self.generate_random_mac_address(len(ip_source_list)-len(mac_source_list)))
+            num_attackers = min(len(ip_source_list), len(mac_source_list))
 
         # Initialize parameters
         self.packets = col.deque(maxlen=buffer_size)
@@ -133,18 +146,8 @@ class DDoSAttack(BaseAttack.BaseAttack):
 
         port_destination = Util.handle_most_used_outputs(port_destination)
 
-        # FIXME: why are attacker_port_mapping and attacker_ttl_mapping never used?
-        attacker_port_mapping = {}
-        attacker_ttl_mapping = {}
-
-        # Gamma distribution parameters derived from MAWI 13.8G dataset
-        alpha, loc, beta = (2.3261710235, -0.188306914406, 44.4853123884)
-        # FIXME: why is gd never used?
-        gd = stats.gamma.rvs(alpha, loc=loc, scale=beta, size=len(ip_source_list))
-
         self.path_attack_pcap = None
 
-        timestamp_prv_reply, timestamp_confirm = 0, 0
         min_delay, max_delay = self.get_reply_delay(ip_destination)
         victim_buffer = self.get_param_value(atkParam.Parameter.VICTIM_BUFFER)
 
@@ -169,24 +172,69 @@ class DDoSAttack(BaseAttack.BaseAttack):
 
         mss_dst = Util.handle_most_used_outputs(mss_dst)
 
+        # Stores triples of (timestamp, source_id, destination_id) for each timestamp.
+        # Victim has id=0. Attacker tuple does not need to specify the destination because it's always the victim.
+        timestamps_tuples = []
+        # For each attacker(id), stores the current source-ports of SYN-packets
+        # which still have to be acknowledged by the victim, as a "FIFO" for each attacker
+        previous_attacker_port = []
         replies_count = 0
         self.total_pkt_num = 0
         # For each attacker, generate his own packets, then merge all packets
         for attacker in range(num_attackers):
-            # Timestamp
+            # Initialize empty port "FIFO" for current attacker
+            previous_attacker_port.append([])
+            # Calculate timestamp of first SYN-packet of attacker
             timestamp_next_pkt = self.get_param_value(atkParam.Parameter.INJECT_AT_TIMESTAMP)
             attack_ends_time = timestamp_next_pkt + attack_duration
-            timestamp_next_pkt = Util.update_timestamp(timestamp_next_pkt, attacker_pps)
+            timestamp_next_pkt = rnd.uniform(timestamp_next_pkt, Util.update_timestamp(timestamp_next_pkt, attacker_pps))
             attacker_pkts_num = int(pkts_num / num_attackers) + rnd.randint(0, 100)
+            timestamp_prv_reply = 0
             for pkt_num in range(attacker_pkts_num):
                 # Stop the attack when it exceeds the duration
                 if timestamp_next_pkt > attack_ends_time:
                     break
+
+                # Add timestamp of attacker SYN-packet. Attacker tuples do not need to specify destination
+                timestamps_tuples.append((timestamp_next_pkt, attacker+1))
+
+                # Calculate timestamp of victim ACK-packet
+                timestamp_reply = Util.update_timestamp(timestamp_next_pkt, attacker_pps, min_delay)
+                while timestamp_reply <= timestamp_prv_reply:
+                    timestamp_reply = Util.update_timestamp(timestamp_prv_reply, attacker_pps, min_delay)
+                timestamp_prv_reply = timestamp_reply
+
+                # Add timestamp of victim ACK-packet(victim always has id=0)
+                timestamps_tuples.append((timestamp_reply, 0, attacker+1))
+
+                # Calculate timestamp for next attacker SYN-packet
+                attacker_pps = max(Util.get_interval_pps(complement_interval_attacker_pps, timestamp_next_pkt),
+                                   (pps / num_attackers) / 2)
+                timestamp_next_pkt = Util.update_timestamp(timestamp_next_pkt, attacker_pps)
+
+        # Sort timestamp-triples according to their timestamps in ascending order
+        timestamps_tuples.sort(key=lambda tmstmp: tmstmp[0])
+        self.attack_start_utime = timestamps_tuples[0][0]
+
+        # For each triple, generate packet
+        for timestamp in timestamps_tuples:
+
+            # If current current triple is an attacker
+            if timestamp[1] != 0:
+
+                attacker_id = timestamp[1]-1
                 # Build request package
                 # Select one IP address and its corresponding MAC address
-                (ip_source, mac_source) = Util.get_nth_random_element(ip_source_list, mac_source_list)
+                ip_source = ip_source_list[attacker_id]
+                mac_source = mac_source_list[attacker_id]
+
                 # Determine source port
                 (port_source, ttl_value) = Util.get_attacker_config(ip_source_list, ip_source)
+                # Push port of current attacker SYN-packet into port "FIFO" of the current attacker
+                # only if victim can still respond, otherwise, memory is wasted
+                if replies_count <= victim_buffer:
+                    previous_attacker_port[attacker_id].insert(0, port_source)
+
                 request_ether = inet.Ether(dst=mac_destination, src=mac_source)
                 request_ip = inet.IP(src=ip_source, dst=ip_destination, ttl=ttl_value)
                 # Random win size for each packet
@@ -195,46 +243,43 @@ class DDoSAttack(BaseAttack.BaseAttack):
                                        window=source_win_size)
 
                 request = (request_ether / request_ip / request_tcp)
-                request.time = timestamp_next_pkt
+                request.time = timestamp[0]
                 # Append request
                 self.packets.append(request)
                 self.total_pkt_num += 1
 
+            # If current triple is the victim
+            else:
+
                 # Build reply package
                 if replies_count <= victim_buffer:
-                    reply_ether = inet.Ether(src=mac_destination, dst=mac_source)
-                    reply_ip = inet.IP(src=ip_destination, dst=ip_source, flags='DF')
-                    reply_tcp = inet.TCP(sport=port_destination, dport=port_source, seq=0, ack=1, flags='SA',
-                                         window=destination_win_value, options=[('MSS', mss_dst)])
-                    reply = (reply_ether / reply_ip / reply_tcp)
+                    attacker_id = timestamp[2]-1
 
-                    timestamp_reply = Util.update_timestamp(timestamp_next_pkt, attacker_pps, min_delay)
-                    while timestamp_reply <= timestamp_prv_reply:
-                        timestamp_reply = Util.update_timestamp(timestamp_prv_reply, attacker_pps, min_delay)
-                    timestamp_prv_reply = timestamp_reply
+                    reply_ether = inet.Ether(src=mac_destination, dst=mac_source_list[attacker_id])
+                    reply_ip = inet.IP(src=ip_destination, dst=ip_source_list[attacker_id], flags='DF')
+                    # Pop port from attacker's port "FIFO" into destination port
+                    reply_tcp = inet.TCP(sport=port_destination, dport=previous_attacker_port[attacker_id].pop(), seq=0,
+                                         ack=1, flags='SA', window=destination_win_value, options=[('MSS', mss_dst)])
+                    reply = (reply_ether / reply_ip / reply_tcp)
 
-                    reply.time = timestamp_reply
+                    reply.time = timestamp[0]
                     self.packets.append(reply)
                     replies_count += 1
                     self.total_pkt_num += 1
 
-                attacker_pps = max(Util.get_interval_pps(complement_interval_attacker_pps, timestamp_next_pkt),
-                                   (pps / num_attackers) / 2)
-                timestamp_next_pkt = Util.update_timestamp(timestamp_next_pkt, attacker_pps)
-
-                # Store timestamp of first packet (for attack label)
-                if self.total_pkt_num <= 2:
-                    self.attack_start_utime = self.packets[0].time
-                elif pkt_num % buffer_size == 0:  # every 1000 packets write them to the pcap file (append)
-                    self.last_packet = self.packets[-1]
-                    self.packets = sorted(self.packets, key=lambda pkt: pkt.time)
-                    self.path_attack_pcap = self.write_attack_pcap(self.packets, True, self.path_attack_pcap)
-                    self.packets = []
+            # every 1000 packets write them to the pcap file (append)
+            if (self.total_pkt_num > 0) and (self.total_pkt_num % buffer_size == 0) and (len(self.packets) > 0):
+                self.last_packet = self.packets[-1]
+                self.attack_end_utime = self.last_packet.time
+                self.packets = sorted(self.packets, key=lambda pkt: pkt.time)
+                self.path_attack_pcap = self.write_attack_pcap(self.packets, True, self.path_attack_pcap)
+                self.packets = []
 
     def generate_attack_pcap(self):
         if len(self.packets) > 0:
             self.packets = sorted(self.packets, key=lambda pkt: pkt.time)
             self.path_attack_pcap = self.write_attack_pcap(self.packets, True, self.path_attack_pcap)
+            self.last_packet = self.packets[-1]
 
         # Store timestamp of last packet
         self.attack_end_utime = self.last_packet.time

+ 18 - 6
code/Test/test_DDoSAttack.py

@@ -3,10 +3,12 @@ import unittest.mock as mock
 import ID2TLib.TestLibrary as Lib
 import Test.ID2TAttackTest as Test
 
-sha_basic_ddos = '87c6c9cf4b496b84fecfd758c1d891ff06fe234dba2f421a5ab8bd7d6d9239a5'
-sha_num_attackers_ddos = 'cbbb9b55d03a0efde965bbb8c38f6ba8a9acbd605cb2f3ac22a6ed6e3958f8e9'
-sha_dest_mac_length_zero_ddos = 'acf1d108ab3d4e76636c6b58e08296126a74fcf3936377588376a79716fffd60'
-sha_mss_none_ddos = '87c6c9cf4b496b84fecfd758c1d891ff06fe234dba2f421a5ab8bd7d6d9239a5'
+sha_basic_ddos = 'd30a14ba0568cb9c3be0db6a6d8e5d68b703d995015fc2215bfa150a8aff8b2a'
+sha_num_attackers_ddos = '0de1ac89bb02e0163a31a0215d59ef2e2d819ffb904f8a99be1ecb52a568a392'
+sha_dest_mac_length_zero_ddos = '55720bc3aa43a6abad2db1bd1f9c7ff71cb50f11ca5f17995b24184678c18226'
+sha_mss_none_ddos = 'd30a14ba0568cb9c3be0db6a6d8e5d68b703d995015fc2215bfa150a8aff8b2a'
+sha_one_attacker_ddos = '8bb7798e85cff15b91c5ee2c0bb65f01ff3097a417bdd2e58a540f89d542bea9'
+sha_ip_range_ddos = 'bef5deb3cc7ee7537a90a85323cf885cf5a0431e15ae7001c0c762afc643e7a6'
 
 # TODO: improve coverage
 
@@ -32,5 +34,15 @@ class UnitTestDDoS(Test.ID2TAttackTest):
     def test_ddos_mss_none(self, mock_mss, mock_get_attacker_config):
         self.checksum_test([['DDoSAttack']], sha_mss_none_ddos)
 
-    #def test_ddos_order(self):
-        #self.order_test([['DDoSAttack', 'attackers.count=2']])
+    @mock.patch('ID2TLib.Utility.get_attacker_config', side_effect=Lib.get_attacker_config)
+    def test_ddos_one_attacker(self, mock_get_attacker_config):
+        self.checksum_test([['DDoSAttack', 'ip.src=1.1.1.1']],
+                           sha_one_attacker_ddos)
+
+    @mock.patch('ID2TLib.Utility.get_attacker_config', side_effect=Lib.get_attacker_config)
+    def test_ddos_ip_range(self, mock_get_attacker_config):
+        self.checksum_test([['DDoSAttack', 'ip.src=1.1.1.1-1.1.1.10']],
+                           sha_ip_range_ddos)
+
+    def test_ddos_order(self):
+        self.order_test([['DDoSAttack', 'attackers.count=5']])