DDoSAttack.py 14 KB

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