DDoSAttack.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. import collections as col
  2. import logging
  3. import random as rnd
  4. import lea
  5. import scapy.layers.inet as inet
  6. import Attack.AttackParameters as atkParam
  7. import Attack.BaseAttack as BaseAttack
  8. import ID2TLib.Utility as Util
  9. logging.getLogger("scapy.runtime").setLevel(logging.ERROR)
  10. # noinspection PyPep8
  11. class DDoSAttack(BaseAttack.BaseAttack):
  12. def __init__(self):
  13. """
  14. Creates a new instance of the DDoS attack.
  15. """
  16. # Initialize attack
  17. super(DDoSAttack, self).__init__("DDoS Attack", "Injects a DDoS attack'",
  18. "Resource Exhaustion")
  19. self.last_packet = None
  20. self.total_pkt_num = 0
  21. self.default_port = 0
  22. # Define allowed parameters and their type
  23. self.supported_params.update({
  24. atkParam.Parameter.IP_SOURCE: atkParam.ParameterTypes.TYPE_IP_ADDRESS,
  25. atkParam.Parameter.MAC_SOURCE: atkParam.ParameterTypes.TYPE_MAC_ADDRESS,
  26. atkParam.Parameter.PORT_SOURCE: atkParam.ParameterTypes.TYPE_PORT,
  27. atkParam.Parameter.IP_DESTINATION: atkParam.ParameterTypes.TYPE_IP_ADDRESS,
  28. atkParam.Parameter.MAC_DESTINATION: atkParam.ParameterTypes.TYPE_MAC_ADDRESS,
  29. atkParam.Parameter.PORT_DESTINATION: atkParam.ParameterTypes.TYPE_PORT,
  30. atkParam.Parameter.INJECT_AT_TIMESTAMP: atkParam.ParameterTypes.TYPE_FLOAT,
  31. atkParam.Parameter.INJECT_AFTER_PACKET: atkParam.ParameterTypes.TYPE_PACKET_POSITION,
  32. atkParam.Parameter.PACKET_LIMIT_PER_SECOND: atkParam.ParameterTypes.TYPE_FLOAT,
  33. atkParam.Parameter.NUMBER_ATTACKERS: atkParam.ParameterTypes.TYPE_INTEGER_POSITIVE,
  34. atkParam.Parameter.ATTACK_DURATION: atkParam.ParameterTypes.TYPE_INTEGER_POSITIVE,
  35. atkParam.Parameter.VICTIM_BUFFER: atkParam.ParameterTypes.TYPE_INTEGER_POSITIVE
  36. })
  37. def init_params(self):
  38. """
  39. Initialize the parameters of this attack using the user supplied command line parameters.
  40. Use the provided statistics to calculate default parameters and to process user
  41. supplied queries.
  42. """
  43. # PARAMETERS: initialize with default values
  44. # (values are overwritten if user specifies them)
  45. self.add_param_value(atkParam.Parameter.INJECT_AFTER_PACKET, rnd.randint(0, self.statistics.get_packet_count()))
  46. # attacker configuration
  47. num_attackers = rnd.randint(1, 16)
  48. # The most used IP class in background traffic
  49. most_used_ip_class = Util.handle_most_used_outputs(self.statistics.get_most_used_ip_class())
  50. self.add_param_value(atkParam.Parameter.IP_SOURCE,
  51. self.generate_random_ipv4_address(most_used_ip_class, num_attackers))
  52. self.add_param_value(atkParam.Parameter.MAC_SOURCE, self.generate_random_mac_address(num_attackers))
  53. self.default_port = int(inet.RandShort())
  54. self.add_param_value(atkParam.Parameter.PORT_SOURCE, self.default_port)
  55. self.add_param_value(atkParam.Parameter.PACKET_LIMIT_PER_SECOND, 0)
  56. self.add_param_value(atkParam.Parameter.ATTACK_DURATION, rnd.randint(5, 30))
  57. # victim configuration
  58. random_ip_address = self.statistics.get_random_ip_address()
  59. self.add_param_value(atkParam.Parameter.IP_DESTINATION, random_ip_address)
  60. destination_mac = self.statistics.get_mac_address(random_ip_address)
  61. if isinstance(destination_mac, list) and len(destination_mac) == 0:
  62. destination_mac = self.generate_random_mac_address()
  63. self.add_param_value(atkParam.Parameter.MAC_DESTINATION, destination_mac)
  64. self.add_param_value(atkParam.Parameter.VICTIM_BUFFER, rnd.randint(1000, 10000))
  65. def generate_attack_packets(self):
  66. """
  67. Creates the attack packets.
  68. """
  69. buffer_size = 1000
  70. # Determine source IP and MAC address
  71. num_attackers = self.get_param_value(atkParam.Parameter.NUMBER_ATTACKERS)
  72. if (num_attackers is not None) and (num_attackers is not 0):
  73. # user supplied atkParam.Parameter.NUMBER_ATTACKERS
  74. # The most used IP class in background traffic
  75. most_used_ip_class = Util.handle_most_used_outputs(self.statistics.get_most_used_ip_class())
  76. # Create random attackers based on user input atkParam.Parameter.NUMBER_ATTACKERS
  77. ip_source_list = self.generate_random_ipv4_address(most_used_ip_class, num_attackers)
  78. mac_source_list = self.generate_random_mac_address(num_attackers)
  79. else: # user did not supply atkParam.Parameter.NUMBER_ATTACKS
  80. # use default values for IP_SOURCE/MAC_SOURCE or overwritten values
  81. # if user supplied any values for those params
  82. ip_source_list = self.get_param_value(atkParam.Parameter.IP_SOURCE)
  83. mac_source_list = self.get_param_value(atkParam.Parameter.MAC_SOURCE)
  84. # Make sure IPs and MACs are lists
  85. if not isinstance(ip_source_list, list):
  86. ip_source_list = [ip_source_list]
  87. if not isinstance(mac_source_list, list):
  88. mac_source_list = [mac_source_list]
  89. # Generate MACs for each IP that has no corresponding MAC yet
  90. if (num_attackers is None) or (num_attackers is 0):
  91. if len(ip_source_list) > len(mac_source_list):
  92. mac_source_list.extend(self.generate_random_mac_address(len(ip_source_list)-len(mac_source_list)))
  93. num_attackers = min(len(ip_source_list), len(mac_source_list))
  94. # Initialize parameters
  95. self.packets = col.deque(maxlen=buffer_size)
  96. port_source_list = self.get_param_value(atkParam.Parameter.PORT_SOURCE)
  97. if not isinstance(port_source_list, list):
  98. port_source_list = [port_source_list]
  99. mac_destination = self.get_param_value(atkParam.Parameter.MAC_DESTINATION)
  100. ip_destination = self.get_param_value(atkParam.Parameter.IP_DESTINATION)
  101. most_used_ip_address = self.statistics.get_most_used_ip_address()
  102. pps = self.get_param_value(atkParam.Parameter.PACKET_LIMIT_PER_SECOND)
  103. if pps == 0:
  104. result = self.statistics.process_db_query(
  105. "SELECT MAX(maxPktRate) FROM ip_statistics WHERE ipAddress='" + ip_destination + "';")
  106. if result is not None and not 0:
  107. pps = num_attackers * result
  108. else:
  109. result = self.statistics.process_db_query(
  110. "SELECT MAX(maxPktRate) FROM ip_statistics WHERE ipAddress='" + most_used_ip_address + "';")
  111. pps = num_attackers * result
  112. # Calculate complement packet rates of the background traffic for each interval
  113. attacker_pps = pps / num_attackers
  114. complement_interval_attacker_pps = self.statistics.calculate_complement_packet_rates(attacker_pps)
  115. # Check ip.src == ip.dst
  116. self.ip_src_dst_equal_check(ip_source_list, ip_destination)
  117. port_destination = self.get_param_value(atkParam.Parameter.PORT_DESTINATION)
  118. if not port_destination: # user did not define port_dest
  119. port_destination = self.statistics.process_db_query(
  120. "SELECT portNumber FROM ip_ports WHERE portDirection='in' AND ipAddress='" + ip_destination +
  121. "' AND portCount==(SELECT MAX(portCount) FROM ip_ports WHERE portDirection='in' AND ipAddress='" +
  122. ip_destination + "');")
  123. if not port_destination: # no port was retrieved
  124. port_destination = self.statistics.process_db_query(
  125. "SELECT portNumber FROM (SELECT portNumber, SUM(portCount) as occ FROM ip_ports WHERE "
  126. "portDirection='in' GROUP BY portNumber ORDER BY occ DESC) WHERE occ=(SELECT SUM(portCount) "
  127. "FROM ip_ports WHERE portDirection='in' GROUP BY portNumber ORDER BY SUM(portCount) DESC LIMIT 1);")
  128. if not port_destination:
  129. port_destination = max(1, int(inet.RandShort()))
  130. port_destination = Util.handle_most_used_outputs(port_destination)
  131. self.path_attack_pcap = None
  132. min_delay, max_delay = self.get_reply_delay(ip_destination)
  133. victim_buffer = self.get_param_value(atkParam.Parameter.VICTIM_BUFFER)
  134. attack_duration = self.get_param_value(atkParam.Parameter.ATTACK_DURATION)
  135. pkts_num = int(pps * attack_duration)
  136. source_win_sizes = self.statistics.get_rnd_win_size(pkts_num)
  137. destination_win_dist = self.statistics.get_win_distribution(ip_destination)
  138. if len(destination_win_dist) > 0:
  139. destination_win_prob_dict = lea.Lea.fromValFreqsDict(destination_win_dist)
  140. destination_win_value = destination_win_prob_dict.random()
  141. else:
  142. destination_win_value = self.statistics.get_most_used_win_size()
  143. destination_win_value = Util.handle_most_used_outputs(destination_win_value)
  144. # MSS that was used by IP destination in background traffic
  145. mss_dst = self.statistics.get_most_used_mss(ip_destination)
  146. if mss_dst is None:
  147. mss_dst = self.statistics.get_most_used_mss_value()
  148. mss_dst = Util.handle_most_used_outputs(mss_dst)
  149. # Stores triples of (timestamp, source_id, destination_id) for each timestamp.
  150. # Victim has id=0. Attacker tuple does not need to specify the destination because it's always the victim.
  151. timestamps_tuples = []
  152. # For each attacker(id), stores the current source-ports of SYN-packets
  153. # which still have to be acknowledged by the victim, as a "FIFO" for each attacker
  154. previous_attacker_port = []
  155. replies_count = 0
  156. self.total_pkt_num = 0
  157. # For each attacker, generate his own packets, then merge all packets
  158. for attacker in range(num_attackers):
  159. # Initialize empty port "FIFO" for current attacker
  160. previous_attacker_port.append([])
  161. # Calculate timestamp of first SYN-packet of attacker
  162. timestamp_next_pkt = self.get_param_value(atkParam.Parameter.INJECT_AT_TIMESTAMP)
  163. attack_ends_time = timestamp_next_pkt + attack_duration
  164. timestamp_next_pkt = rnd.uniform(timestamp_next_pkt, Util.update_timestamp(timestamp_next_pkt, attacker_pps))
  165. attacker_pkts_num = int(pkts_num / num_attackers) + rnd.randint(0, 100)
  166. timestamp_prv_reply = 0
  167. for pkt_num in range(attacker_pkts_num):
  168. # Stop the attack when it exceeds the duration
  169. if timestamp_next_pkt > attack_ends_time:
  170. break
  171. # Add timestamp of attacker SYN-packet. Attacker tuples do not need to specify destination
  172. timestamps_tuples.append((timestamp_next_pkt, attacker+1))
  173. # Calculate timestamp of victim ACK-packet
  174. timestamp_reply = Util.update_timestamp(timestamp_next_pkt, attacker_pps, min_delay)
  175. while timestamp_reply <= timestamp_prv_reply:
  176. timestamp_reply = Util.update_timestamp(timestamp_prv_reply, attacker_pps, min_delay)
  177. timestamp_prv_reply = timestamp_reply
  178. # Add timestamp of victim ACK-packet(victim always has id=0)
  179. timestamps_tuples.append((timestamp_reply, 0, attacker+1))
  180. # Calculate timestamp for next attacker SYN-packet
  181. attacker_pps = max(Util.get_interval_pps(complement_interval_attacker_pps, timestamp_next_pkt),
  182. (pps / num_attackers) / 2)
  183. timestamp_next_pkt = Util.update_timestamp(timestamp_next_pkt, attacker_pps)
  184. # Sort timestamp-triples according to their timestamps in ascending order
  185. timestamps_tuples.sort(key=lambda tmstmp: tmstmp[0])
  186. self.attack_start_utime = timestamps_tuples[0][0]
  187. # For each triple, generate packet
  188. for timestamp in timestamps_tuples:
  189. # If current current triple is an attacker
  190. if timestamp[1] != 0:
  191. attacker_id = timestamp[1]-1
  192. # Build request package
  193. # Select one IP address and its corresponding MAC address
  194. ip_source = ip_source_list[attacker_id]
  195. mac_source = mac_source_list[attacker_id]
  196. # Determine source port
  197. (port_source, ttl_value) = Util.get_attacker_config(ip_source_list, ip_source)
  198. # If source ports were specified by the user, get random port from specified ports
  199. if port_source_list[0] != self.default_port:
  200. port_source = rnd.choice(port_source_list)
  201. # Push port of current attacker SYN-packet into port "FIFO" of the current attacker
  202. # only if victim can still respond, otherwise, memory is wasted
  203. if replies_count <= victim_buffer:
  204. previous_attacker_port[attacker_id].insert(0, port_source)
  205. request_ether = inet.Ether(dst=mac_destination, src=mac_source)
  206. request_ip = inet.IP(src=ip_source, dst=ip_destination, ttl=ttl_value)
  207. # Random win size for each packet
  208. source_win_size = rnd.choice(source_win_sizes)
  209. request_tcp = inet.TCP(sport=port_source, dport=port_destination, flags='S', ack=0,
  210. window=source_win_size)
  211. request = (request_ether / request_ip / request_tcp)
  212. request.time = timestamp[0]
  213. # Append request
  214. self.packets.append(request)
  215. self.total_pkt_num += 1
  216. # If current triple is the victim
  217. else:
  218. # Build reply package
  219. if replies_count <= victim_buffer:
  220. attacker_id = timestamp[2]-1
  221. reply_ether = inet.Ether(src=mac_destination, dst=mac_source_list[attacker_id])
  222. reply_ip = inet.IP(src=ip_destination, dst=ip_source_list[attacker_id], flags='DF')
  223. # Pop port from attacker's port "FIFO" into destination port
  224. reply_tcp = inet.TCP(sport=port_destination, dport=previous_attacker_port[attacker_id].pop(), seq=0,
  225. ack=1, flags='SA', window=destination_win_value, options=[('MSS', mss_dst)])
  226. reply = (reply_ether / reply_ip / reply_tcp)
  227. reply.time = timestamp[0]
  228. self.packets.append(reply)
  229. replies_count += 1
  230. self.total_pkt_num += 1
  231. # every 1000 packets write them to the pcap file (append)
  232. if (self.total_pkt_num > 0) and (self.total_pkt_num % buffer_size == 0) and (len(self.packets) > 0):
  233. self.last_packet = self.packets[-1]
  234. self.attack_end_utime = self.last_packet.time
  235. self.packets = sorted(self.packets, key=lambda pkt: pkt.time)
  236. self.path_attack_pcap = self.write_attack_pcap(self.packets, True, self.path_attack_pcap)
  237. self.packets = []
  238. def generate_attack_pcap(self):
  239. """
  240. Creates a pcap containing the attack packets.
  241. :return: The location of the generated pcap file.
  242. """
  243. if len(self.packets) > 0:
  244. self.packets = sorted(self.packets, key=lambda pkt: pkt.time)
  245. self.path_attack_pcap = self.write_attack_pcap(self.packets, True, self.path_attack_pcap)
  246. self.last_packet = self.packets[-1]
  247. # Store timestamp of last packet
  248. self.attack_end_utime = self.last_packet.time
  249. # Return packets sorted by packet time_sec_start
  250. # pkt_num+1: because pkt_num starts at 0
  251. return self.total_pkt_num, self.path_attack_pcap