SMBLorisAttack.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  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. }
  38. def init_params(self):
  39. """
  40. Initialize the parameters of this attack using the user supplied command line parameters.
  41. Use the provided statistics to calculate default parameters and to process user
  42. supplied queries.
  43. :param statistics: Reference to a statistics object.
  44. """
  45. # PARAMETERS: initialize with default values
  46. # (values are overwritten if user specifies them)
  47. most_used_ip_address = self.statistics.get_most_used_ip_address()
  48. if isinstance(most_used_ip_address, list):
  49. most_used_ip_address = most_used_ip_address[0]
  50. self.add_param_value(Param.IP_SOURCE, most_used_ip_address)
  51. self.add_param_value(Param.MAC_SOURCE, self.statistics.get_mac_address(most_used_ip_address))
  52. random_ip_address = self.statistics.get_random_ip_address()
  53. # ip-dst should be valid and not equal to ip.src
  54. while not self.is_valid_ip_address(random_ip_address) or random_ip_address==most_used_ip_address:
  55. random_ip_address = self.statistics.get_random_ip_address()
  56. self.add_param_value(Param.IP_DESTINATION, random_ip_address)
  57. destination_mac = self.statistics.get_mac_address(random_ip_address)
  58. if isinstance(destination_mac, list) and len(destination_mac) == 0:
  59. destination_mac = self.generate_random_mac_address()
  60. self.add_param_value(Param.MAC_DESTINATION, destination_mac)
  61. self.add_param_value(Param.PACKETS_PER_SECOND,
  62. (self.statistics.get_pps_sent(most_used_ip_address) +
  63. self.statistics.get_pps_received(most_used_ip_address)) / 2)
  64. self.add_param_value(Param.INJECT_AFTER_PACKET, randint(0, self.statistics.get_packet_count()))
  65. self.add_param_value(Param.ATTACK_DURATION, 30)
  66. def generate_attack_pcap(self):
  67. def update_timestamp(timestamp, pps, delay=0):
  68. """
  69. Calculates the next timestamp to be used based on the packet per second rate (pps) and the maximum delay.
  70. :return: Timestamp to be used for the next packet.
  71. """
  72. if delay == 0:
  73. # Calculate request timestamp
  74. # To imitate the bursty behavior of traffic
  75. randomdelay = Lea.fromValFreqsDict({1 / pps: 70, 2 / pps: 20, 5 / pps: 7, 10 / pps: 3})
  76. return timestamp + uniform(1/pps , randomdelay.random())
  77. else:
  78. # Calculate reply timestamp
  79. randomdelay = Lea.fromValFreqsDict({2*delay: 70, 3*delay: 20, 5*delay: 7, 10*delay: 3})
  80. return timestamp + uniform(1 / pps + delay, 1 / pps + randomdelay.random())
  81. def getIntervalPPS(complement_interval_pps, timestamp):
  82. """
  83. Gets the packet rate (pps) for a specific time interval.
  84. :param complement_interval_pps: an array of tuples (the last timestamp in the interval, the packet rate in the crresponding interval).
  85. :param timestamp: the timestamp at which the packet rate is required.
  86. :return: the corresponding packet rate (pps) .
  87. """
  88. for row in complement_interval_pps:
  89. if timestamp<=row[0]:
  90. return row[1]
  91. return complement_interval_pps[-1][1] # in case the timstamp > capture max timestamp
  92. def getIpData(ip_address: str):
  93. """
  94. :param ip_address: the ip of which (packet-)data shall be returned
  95. :return: MSS, TTL and Window Size values of the given IP
  96. """
  97. # Set MSS (Maximum Segment Size) based on MSS distribution of IP address
  98. mss_dist = self.statistics.get_mss_distribution(ip_address)
  99. if len(mss_dist) > 0:
  100. mss_prob_dict = Lea.fromValFreqsDict(mss_dist)
  101. mss_value = mss_prob_dict.random()
  102. else:
  103. mss_value = self.statistics.process_db_query("most_used(mssValue)")
  104. # Set TTL based on TTL distribution of IP address
  105. ttl_dist = self.statistics.get_ttl_distribution(ip_address)
  106. if len(ttl_dist) > 0:
  107. ttl_prob_dict = Lea.fromValFreqsDict(ttl_dist)
  108. ttl_value = ttl_prob_dict.random()
  109. else:
  110. ttl_value = self.statistics.process_db_query("most_used(ttlValue)")
  111. # Set Window Size based on Window Size distribution of IP address
  112. win_dist = self.statistics.get_win_distribution(ip_address)
  113. if len(win_dist) > 0:
  114. win_prob_dict = Lea.fromValFreqsDict(win_dist)
  115. win_value = win_prob_dict.random()
  116. else:
  117. win_value = self.statistics.process_db_query("most_used(winSize)")
  118. return mss_value, ttl_value, win_value
  119. mac_source = self.get_param_value(Param.MAC_SOURCE)
  120. mac_destination = self.get_param_value(Param.MAC_DESTINATION)
  121. pps = self.get_param_value(Param.PACKETS_PER_SECOND)
  122. # Calculate complement packet rates of the background traffic for each interval
  123. complement_interval_pps = self.statistics.calculate_complement_packet_rates(pps)
  124. # Timestamp
  125. timestamp_next_pkt = self.get_param_value(Param.INJECT_AT_TIMESTAMP)
  126. # store start time of attack
  127. self.attack_start_utime = timestamp_next_pkt
  128. timestamp_prv_reply, timestamp_confirm = 0,0
  129. # Initialize parameters
  130. packets = []
  131. ip_source = self.get_param_value(Param.IP_SOURCE)
  132. ip_destination = self.get_param_value(Param.IP_DESTINATION)
  133. # Check ip.src == ip.dst
  134. self.ip_src_dst_equal_check(ip_source, ip_destination)
  135. # Get MSS, TTL and Window size value for source and destination IP
  136. source_mss_value, source_ttl_value, source_win_value = getIpData(ip_source)
  137. destination_mss_value, destination_ttl_value, destination_win_value = getIpData(ip_destination)
  138. minDelay,maxDelay = self.get_reply_delay(ip_destination)
  139. attack_duration = self.get_param_value(Param.ATTACK_DURATION)
  140. attack_ends_time = timestamp_next_pkt + attack_duration
  141. sport = 1025
  142. attacker_seq = randint(1000, 50000)
  143. victim_seq = randint(1000, 50000)
  144. # FIXME: Improve timestamp generation
  145. while timestamp_next_pkt <= attack_ends_time:
  146. # Establish TCP connection
  147. if sport > 65535:
  148. sport = 1025
  149. # prepare reusable Ethernet- and IP-headers
  150. attacker_ether = Ether(src=mac_source, dst=mac_destination)
  151. attacker_ip = IP(src=ip_source, dst=ip_destination, ttl=source_ttl_value, flags='DF')
  152. victim_ether = Ether(src=mac_destination, dst=mac_source)
  153. victim_ip = IP(src=ip_destination, dst=ip_source, ttl=destination_ttl_value, flags='DF')
  154. # connection request from attacker (client)
  155. syn_tcp = TCP(sport=sport, dport=self.smb_port, window=source_win_value, flags='S',
  156. seq=attacker_seq, options=[('MSS', source_mss_value)])
  157. attacker_seq += 1
  158. syn = (attacker_ether / attacker_ip / syn_tcp)
  159. syn.time = timestamp_next_pkt
  160. timestamp_next_pkt = update_timestamp(timestamp_next_pkt, pps, minDelay)
  161. packets.append(syn)
  162. # response from victim (server)
  163. synack_tcp = TCP(sport=self.smb_port, dport=sport, seq=victim_seq, ack=attacker_seq, flags='SA',
  164. window=destination_win_value, options=[('MSS', destination_mss_value)])
  165. victim_seq += 1
  166. synack = (victim_ether / victim_ip / synack_tcp)
  167. synack.time = timestamp_next_pkt
  168. timestamp_next_pkt = update_timestamp(timestamp_next_pkt, pps, minDelay)
  169. packets.append(synack)
  170. # acknowledgement from attacker (client)
  171. ack_tcp = TCP(sport=sport, dport=self.smb_port, seq=attacker_seq, ack=victim_seq, flags='A',
  172. window=source_win_value, options=[('MSS', source_mss_value)])
  173. ack = (attacker_ether / attacker_ip / ack_tcp)
  174. ack.time = timestamp_next_pkt
  175. timestamp_next_pkt = update_timestamp(timestamp_next_pkt, pps, minDelay)
  176. packets.append(ack)
  177. # send NBT session header paket with maximum LENGTH-field
  178. req_tcp = TCP(sport=sport, dport=self.smb_port, seq=attacker_seq, ack=victim_seq, flags='AP',
  179. window=source_win_value, options=[('MSS', source_mss_value)])
  180. req_payload = NBTSession(TYPE=0x00, LENGTH=0x1FFFF)
  181. attacker_seq += len(req_payload)
  182. req = (attacker_ether / attacker_ip / req_tcp / req_payload)
  183. req.time = timestamp_next_pkt
  184. timestamp_next_pkt = update_timestamp(timestamp_next_pkt, pps, minDelay)
  185. packets.append(req)
  186. # final ack from victim (server)
  187. last_ack_tcp = TCP(sport=self.smb_port, dport=sport, seq=victim_seq, ack=attacker_seq, flags='A',
  188. window=destination_win_value, options=[('MSS', destination_mss_value)])
  189. last_ack = (victim_ether / victim_ip / last_ack_tcp)
  190. last_ack.time = timestamp_next_pkt
  191. timestamp_next_pkt = update_timestamp(timestamp_next_pkt, pps, minDelay)
  192. packets.append(last_ack)
  193. sport += 1
  194. # FIXME: RST?
  195. # store end time of attack
  196. self.attack_end_utime = packets[-1].time
  197. # write attack packets to pcap
  198. pcap_path = self.write_attack_pcap(sorted(packets, key=lambda pkt: pkt.time))
  199. # return packets sorted by packet time_sec_start
  200. return len(packets), pcap_path