DDoSAttack.py 13 KB

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