DDoSAttack.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. import logging
  2. from random import randint, choice
  3. from lea import Lea
  4. from collections import deque
  5. from scipy.stats import gamma
  6. from scapy.layers.inet import IP, Ether, TCP, RandShort
  7. from Attack import BaseAttack
  8. from Attack.AttackParameters import Parameter as Param
  9. from Attack.AttackParameters import ParameterTypes
  10. from ID2TLib.Utility import update_timestamp, get_interval_pps, get_nth_random_element, index_increment
  11. logging.getLogger("scapy.runtime").setLevel(logging.ERROR)
  12. # noinspection PyPep8
  13. class DDoSAttack(BaseAttack.BaseAttack):
  14. def __init__(self):
  15. """
  16. Creates a new instance of the DDoS attack.
  17. """
  18. # Initialize attack
  19. super(DDoSAttack, self).__init__("DDoS Attack", "Injects a DDoS attack'",
  20. "Resource Exhaustion")
  21. # Define allowed parameters and their type
  22. self.supported_params = {
  23. Param.IP_SOURCE: ParameterTypes.TYPE_IP_ADDRESS,
  24. Param.MAC_SOURCE: ParameterTypes.TYPE_MAC_ADDRESS,
  25. Param.PORT_SOURCE: ParameterTypes.TYPE_PORT,
  26. Param.IP_DESTINATION: ParameterTypes.TYPE_IP_ADDRESS,
  27. Param.MAC_DESTINATION: ParameterTypes.TYPE_MAC_ADDRESS,
  28. Param.PORT_DESTINATION: ParameterTypes.TYPE_PORT,
  29. Param.INJECT_AT_TIMESTAMP: ParameterTypes.TYPE_FLOAT,
  30. Param.INJECT_AFTER_PACKET: ParameterTypes.TYPE_PACKET_POSITION,
  31. Param.PACKETS_PER_SECOND: ParameterTypes.TYPE_FLOAT,
  32. Param.NUMBER_ATTACKERS: ParameterTypes.TYPE_INTEGER_POSITIVE,
  33. Param.ATTACK_DURATION: ParameterTypes.TYPE_INTEGER_POSITIVE,
  34. Param.VICTIM_BUFFER: ParameterTypes.TYPE_INTEGER_POSITIVE
  35. }
  36. def init_params(self):
  37. """
  38. Initialize the parameters of this attack using the user supplied command line parameters.
  39. Use the provided statistics to calculate default parameters and to process user
  40. supplied queries.
  41. :param statistics: Reference to a statistics object.
  42. """
  43. # PARAMETERS: initialize with default values
  44. # (values are overwritten if user specifies them)
  45. self.add_param_value(Param.INJECT_AFTER_PACKET, randint(0, self.statistics.get_packet_count()))
  46. # attacker configuration
  47. num_attackers = randint(1, 16)
  48. # The most used IP class in background traffic
  49. most_used_ip_class = self.statistics.process_db_query("most_used(ipClass)")
  50. if isinstance(most_used_ip_class, list):
  51. most_used_ip_class.sort()
  52. most_used_ip_class = most_used_ip_class[0]
  53. self.add_param_value(Param.IP_SOURCE, self.generate_random_ipv4_address(most_used_ip_class, num_attackers))
  54. self.add_param_value(Param.MAC_SOURCE, self.generate_random_mac_address(num_attackers))
  55. self.add_param_value(Param.PORT_SOURCE, str(RandShort()))
  56. self.add_param_value(Param.PACKETS_PER_SECOND, 0)
  57. self.add_param_value(Param.ATTACK_DURATION, randint(5,30))
  58. # victim configuration
  59. random_ip_address = self.statistics.get_random_ip_address()
  60. self.add_param_value(Param.IP_DESTINATION, random_ip_address)
  61. destination_mac = self.statistics.get_mac_address(random_ip_address)
  62. if isinstance(destination_mac, list) and len(destination_mac) == 0:
  63. destination_mac = self.generate_random_mac_address()
  64. self.add_param_value(Param.MAC_DESTINATION, destination_mac)
  65. self.add_param_value(Param.VICTIM_BUFFER, randint(1000,10000))
  66. def generate_attack_pcap(self):
  67. def get_attacker_config(ipAddress: str):
  68. """
  69. Returns the attacker configuration depending on the IP address, this includes the port for the next
  70. attacking packet and the previously used (fixed) TTL value.
  71. :param ipAddress: The IP address of the attacker
  72. :return: A tuple consisting of (port, ttlValue)
  73. """
  74. # Determine port
  75. port = attacker_port_mapping.get(ipAddress)
  76. if port is not None: # use next port
  77. next_port = attacker_port_mapping.get(ipAddress) + 1
  78. if next_port > (2 ** 16 - 1):
  79. next_port = 1
  80. else: # generate starting port
  81. next_port = RandShort()
  82. attacker_port_mapping[ipAddress] = next_port
  83. # Determine TTL value
  84. ttl = attacker_ttl_mapping.get(ipAddress)
  85. if ttl is None: # determine TTL value
  86. is_invalid = True
  87. pos = ip_source_list.index(ipAddress)
  88. pos_max = len(gd)
  89. while is_invalid:
  90. ttl = int(round(gd[pos]))
  91. if 0 < ttl < 256: # validity check
  92. is_invalid = False
  93. else:
  94. pos = index_increment(pos, pos_max)
  95. attacker_ttl_mapping[ipAddress] = ttl
  96. # return port and TTL
  97. return next_port, ttl
  98. BUFFER_SIZE = 1000
  99. # Determine source IP and MAC address
  100. num_attackers = self.get_param_value(Param.NUMBER_ATTACKERS)
  101. if num_attackers is not None: # user supplied Param.NUMBER_ATTACKERS
  102. # The most used IP class in background traffic
  103. most_used_ip_class = self.statistics.process_db_query("most_used(ipClass)")
  104. if isinstance(most_used_ip_class, list):
  105. most_used_ip_class.sort()
  106. most_used_ip_class = most_used_ip_class[0]
  107. # Create random attackers based on user input Param.NUMBER_ATTACKERS
  108. ip_source_list = self.generate_random_ipv4_address(most_used_ip_class, num_attackers)
  109. mac_source_list = self.generate_random_mac_address(num_attackers)
  110. else: # user did not supply Param.NUMBER_ATTACKS
  111. # use default values for IP_SOURCE/MAC_SOURCE or overwritten values
  112. # if user supplied any values for those params
  113. ip_source_list = self.get_param_value(Param.IP_SOURCE)
  114. mac_source_list = self.get_param_value(Param.MAC_SOURCE)
  115. num_attackers = len(ip_source_list)
  116. # Initialize parameters
  117. packets = deque(maxlen=BUFFER_SIZE)
  118. port_source_list = self.get_param_value(Param.PORT_SOURCE)
  119. mac_destination = self.get_param_value(Param.MAC_DESTINATION)
  120. ip_destination = self.get_param_value(Param.IP_DESTINATION)
  121. most_used_ip_address = self.statistics.get_most_used_ip_address()
  122. pps = self.get_param_value(Param.PACKETS_PER_SECOND)
  123. if pps == 0:
  124. result = self.statistics.process_db_query("SELECT MAX(maxPktRate) FROM ip_statistics WHERE ipAddress='"+ip_destination+"';")
  125. if result is not None and not 0:
  126. pps = num_attackers * result
  127. else:
  128. result = self.statistics.process_db_query("SELECT MAX(maxPktRate) FROM ip_statistics WHERE ipAddress='"+most_used_ip_address+"';")
  129. pps = num_attackers * result
  130. # Calculate complement packet rates of the background traffic for each interval
  131. attacker_pps = pps / num_attackers
  132. complement_interval_attacker_pps = self.statistics.calculate_complement_packet_rates(attacker_pps)
  133. # Check ip.src == ip.dst
  134. self.ip_src_dst_equal_check(ip_source_list, ip_destination)
  135. port_destination = self.get_param_value(Param.PORT_DESTINATION)
  136. if not port_destination: # user did not define port_dest
  137. port_destination = self.statistics.process_db_query(
  138. "SELECT portNumber FROM ip_ports WHERE portDirection='in' AND ipAddress='" + ip_destination + "' AND portCount==(SELECT MAX(portCount) FROM ip_ports WHERE portDirection='in' AND ipAddress='" + ip_destination + "');")
  139. if not port_destination: # no port was retrieved
  140. port_destination = self.statistics.process_db_query(
  141. "SELECT portNumber FROM (SELECT portNumber, SUM(portCount) as occ FROM ip_ports WHERE portDirection='in' GROUP BY portNumber ORDER BY occ DESC) WHERE occ=(SELECT SUM(portCount) FROM ip_ports WHERE portDirection='in' GROUP BY portNumber ORDER BY SUM(portCount) DESC LIMIT 1);")
  142. if not port_destination:
  143. port_destination = max(1, str(RandShort()))
  144. if isinstance(port_destination, list):
  145. port_destination.sort()
  146. port_destination = port_destination[0]
  147. attacker_port_mapping = {}
  148. attacker_ttl_mapping = {}
  149. # Gamma distribution parameters derived from MAWI 13.8G dataset
  150. alpha, loc, beta = (2.3261710235, -0.188306914406, 44.4853123884)
  151. gd = gamma.rvs(alpha, loc=loc, scale=beta, size=len(ip_source_list))
  152. path_attack_pcap = None
  153. timestamp_prv_reply, timestamp_confirm = 0, 0
  154. minDelay, maxDelay = self.get_reply_delay(ip_destination)
  155. victim_buffer = self.get_param_value(Param.VICTIM_BUFFER)
  156. attack_duration = self.get_param_value(Param.ATTACK_DURATION)
  157. pkts_num = int(pps * attack_duration)
  158. source_win_sizes = self.statistics.process_db_query(
  159. "SELECT DISTINCT winSize FROM tcp_win ORDER BY RANDOM() LIMIT "+str(pkts_num)+";")
  160. destination_win_dist = self.statistics.get_win_distribution(ip_destination)
  161. if len(destination_win_dist) > 0:
  162. destination_win_prob_dict = Lea.fromValFreqsDict(destination_win_dist)
  163. destination_win_value = destination_win_prob_dict.random()
  164. else:
  165. destination_win_value = self.statistics.process_db_query("most_used(winSize)")
  166. # MSS that was used by IP destination in background traffic
  167. mss_dst = self.statistics.get_most_used_mss(ip_destination)
  168. if mss_dst is None:
  169. mss_dst = self.statistics.process_db_query("most_used(mssValue)")
  170. replies_count = 0
  171. total_pkt_num = 0
  172. # For each attacker, generate his own packets, then merge all packets
  173. for attacker in range(num_attackers):
  174. # Timestamp
  175. timestamp_next_pkt = self.get_param_value(Param.INJECT_AT_TIMESTAMP)
  176. attack_ends_time = timestamp_next_pkt + attack_duration
  177. timestamp_next_pkt = update_timestamp(timestamp_next_pkt, attacker_pps)
  178. attacker_pkts_num = int(pkts_num / num_attackers) + randint(0,100)
  179. for pkt_num in range(attacker_pkts_num):
  180. # Stop the attack when it exceeds the duration
  181. if timestamp_next_pkt > attack_ends_time:
  182. break
  183. # Build request package
  184. # Select one IP address and its corresponding MAC address
  185. (ip_source, mac_source) = get_nth_random_element(ip_source_list, mac_source_list)
  186. # Determine source port
  187. (port_source, ttl_value) = get_attacker_config(ip_source)
  188. request_ether = Ether(dst=mac_destination, src=mac_source)
  189. request_ip = IP(src=ip_source, dst=ip_destination, ttl=ttl_value)
  190. # Random win size for each packet
  191. source_win_size = choice(source_win_sizes)
  192. request_tcp = TCP(sport=port_source, dport=port_destination, flags='S', ack=0, window=source_win_size)
  193. request = (request_ether / request_ip / request_tcp)
  194. request.time = timestamp_next_pkt
  195. # Append request
  196. packets.append(request)
  197. total_pkt_num +=1
  198. # Build reply package
  199. if replies_count <= victim_buffer:
  200. reply_ether = Ether(src=mac_destination, dst=mac_source)
  201. reply_ip = IP(src=ip_destination, dst=ip_source, flags='DF')
  202. reply_tcp = TCP(sport=port_destination, dport=port_source, seq=0, ack=1, flags='SA', window=destination_win_value,options=[('MSS', mss_dst)])
  203. reply = (reply_ether / reply_ip / reply_tcp)
  204. timestamp_reply = update_timestamp(timestamp_next_pkt, attacker_pps, minDelay)
  205. while (timestamp_reply <= timestamp_prv_reply):
  206. timestamp_reply = update_timestamp(timestamp_prv_reply, attacker_pps, minDelay)
  207. timestamp_prv_reply = timestamp_reply
  208. reply.time = timestamp_reply
  209. packets.append(reply)
  210. replies_count+=1
  211. total_pkt_num += 1
  212. attacker_pps = max(get_interval_pps(complement_interval_attacker_pps, timestamp_next_pkt), (pps / num_attackers) / 2)
  213. timestamp_next_pkt = update_timestamp(timestamp_next_pkt, attacker_pps)
  214. # Store timestamp of first packet (for attack label)
  215. if total_pkt_num <= 2 :
  216. self.attack_start_utime = packets[0].time
  217. elif pkt_num % BUFFER_SIZE == 0: # every 1000 packets write them to the pcap file (append)
  218. last_packet = packets[-1]
  219. packets = sorted(packets, key=lambda pkt: pkt.time)
  220. path_attack_pcap = self.write_attack_pcap(packets, True, path_attack_pcap)
  221. packets = []
  222. if len(packets) > 0:
  223. packets = sorted(packets, key=lambda pkt: pkt.time)
  224. path_attack_pcap = self.write_attack_pcap(packets, True, path_attack_pcap)
  225. # Store timestamp of last packet
  226. self.attack_end_utime = last_packet.time
  227. # Return packets sorted by packet time_sec_start
  228. # pkt_num+1: because pkt_num starts at 0
  229. return total_pkt_num , path_attack_pcap