DDoSAttack.py 13 KB

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