DDoSAttack.py 15 KB

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