DDoSAttack.py 15 KB

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