EternalBlueExploit.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. # Created by Aidmar
  2. import logging
  3. import math
  4. from operator import itemgetter
  5. import operator
  6. from random import randint, uniform
  7. from lea import Lea
  8. from Attack import BaseAttack
  9. from Attack.AttackParameters import Parameter as Param
  10. from Attack.AttackParameters import ParameterTypes
  11. logging.getLogger("scapy.runtime").setLevel(logging.ERROR)
  12. # noinspection PyPep8
  13. from scapy.utils import RawPcapReader
  14. from scapy.layers.inet import IP, Ether, TCP, RandShort
  15. class EternalBlueExploit(BaseAttack.BaseAttack):
  16. # Metasploit default packet rate
  17. maxDefaultPPS = 100
  18. minDefaultPPS = 5
  19. # SMB port
  20. smb_port = 445
  21. # Metasploit experiments show this range of ports
  22. minDefaultPort = 30000
  23. maxDefaultPort = 50000
  24. last_conn_dst_port = 4444
  25. def __init__(self, statistics, pcap_file_path):
  26. """
  27. Creates a new instance of the EternalBlue Exploit.
  28. :param statistics: A reference to the statistics class.
  29. """
  30. # Initialize attack
  31. super(EternalBlueExploit, self).__init__(statistics, "EternalBlue Exploit", "Injects an EternalBlue exploit'",
  32. "Resource Exhaustion")
  33. # Define allowed parameters and their type
  34. self.supported_params = {
  35. Param.MAC_SOURCE: ParameterTypes.TYPE_MAC_ADDRESS,
  36. Param.IP_SOURCE: ParameterTypes.TYPE_IP_ADDRESS,
  37. #Param.PORT_SOURCE: ParameterTypes.TYPE_PORT,
  38. Param.MAC_DESTINATION: ParameterTypes.TYPE_MAC_ADDRESS,
  39. Param.IP_DESTINATION: ParameterTypes.TYPE_IP_ADDRESS,
  40. Param.INJECT_AT_TIMESTAMP: ParameterTypes.TYPE_FLOAT,
  41. Param.INJECT_AFTER_PACKET: ParameterTypes.TYPE_PACKET_POSITION,
  42. Param.PACKETS_PER_SECOND: ParameterTypes.TYPE_FLOAT
  43. }
  44. # PARAMETERS: initialize with default utilsvalues
  45. # (values are overwritten if user specifies them)
  46. most_used_ip_address = self.statistics.get_most_used_ip_address()
  47. if isinstance(most_used_ip_address, list):
  48. most_used_ip_address = most_used_ip_address[0]
  49. self.add_param_value(Param.IP_SOURCE, most_used_ip_address)
  50. self.add_param_value(Param.MAC_SOURCE, self.statistics.get_mac_address(most_used_ip_address))
  51. self.add_param_value(Param.INJECT_AFTER_PACKET, randint(0, self.statistics.get_packet_count()))
  52. #self.add_param_value(Param.PORT_SOURCE, str(RandShort()))
  53. self.add_param_value(Param.PACKETS_PER_SECOND,self.maxDefaultPPS)
  54. # victim configuration
  55. # TO-DO: confirm that ip.dst uses Win OS
  56. random_ip_address = self.statistics.get_random_ip_address()
  57. self.add_param_value(Param.IP_DESTINATION, random_ip_address)
  58. destination_mac = self.statistics.get_mac_address(random_ip_address)
  59. if isinstance(destination_mac, list) and len(destination_mac) == 0:
  60. destination_mac = self.generate_random_mac_address()
  61. self.add_param_value(Param.MAC_DESTINATION, destination_mac)
  62. def generate_attack_pcap(self):
  63. def update_timestamp(timestamp, pps, maxdelay):
  64. """
  65. Calculates the next timestamp to be used based on the packet per second rate (pps) and the maximum delay.
  66. :return: Timestamp to be used for the next packet.
  67. """
  68. return timestamp + uniform(1 / pps, maxdelay)
  69. # Aidmar
  70. def getIntervalPPS(complement_interval_pps, timestamp):
  71. """
  72. Gets the packet rate (pps) in specific time interval.
  73. :return: the corresponding packet rate for packet rate (pps) .
  74. """
  75. for row in complement_interval_pps:
  76. if timestamp<=row[0]:
  77. return row[1]
  78. return complement_interval_pps[-1][1] # in case the timstamp > capture max timestamp
  79. # Timestamp
  80. timestamp_next_pkt = self.get_param_value(Param.INJECT_AT_TIMESTAMP)
  81. # TO-DO: find better pkt rate
  82. pps = self.get_param_value(Param.PACKETS_PER_SECOND)
  83. randomdelay = Lea.fromValFreqsDict({1 / pps: 70, 2 / pps: 30, 5 / pps: 15, 10 / pps: 3})
  84. # Aidmar - calculate complement packet rates of BG traffic per interval
  85. complement_interval_pps = self.statistics.calculate_complement_packet_rates(pps)
  86. # Initialize parameters
  87. packets = []
  88. mac_source = self.get_param_value(Param.MAC_SOURCE)
  89. ip_source = self.get_param_value(Param.IP_SOURCE)
  90. #port_source = self.get_param_value(Param.PORT_SOURCE)
  91. mac_destination = self.get_param_value(Param.MAC_DESTINATION)
  92. ip_destination = self.get_param_value(Param.IP_DESTINATION)
  93. # Aidmar - check ip.src == ip.dst
  94. if ip_source == ip_destination:
  95. print("\nERROR: Invalid IP addresses; source IP is the same as destination IP: " + ip_source + ".")
  96. import sys
  97. sys.exit(0)
  98. path_attack_pcap = None
  99. replayDelay = self.get_reply_delay(ip_destination)
  100. # Scan (MS17) for EternalBlue
  101. # Read Win7_eternalblue_scan_vulnerable pcap file
  102. orig_ip_dst = None
  103. exploit_raw_packets = RawPcapReader("Win7_eternalblue_scan.pcap")
  104. port_source = randint(self.minDefaultPort,self.maxDefaultPort) # experiments show this range of ports
  105. for pkt_num, pkt in enumerate(exploit_raw_packets):
  106. eth_frame = Ether(pkt[0])
  107. ip_pkt = eth_frame.payload
  108. tcp_pkt = ip_pkt.payload
  109. if pkt_num == 0:
  110. if tcp_pkt.getfieldval("dport") == self.smb_port:
  111. orig_ip_dst = ip_pkt.getfieldval("dst") # victim IP
  112. # Request
  113. if ip_pkt.getfieldval("dst") == orig_ip_dst: # victim IP
  114. # Ether
  115. eth_frame.setfieldval("src", mac_source)
  116. eth_frame.setfieldval("dst", mac_destination)
  117. # IP
  118. ip_pkt.setfieldval("src", ip_source)
  119. ip_pkt.setfieldval("dst", ip_destination)
  120. # TCP
  121. tcp_pkt.setfieldval("sport",port_source)
  122. new_pkt = (eth_frame / ip_pkt / tcp_pkt)
  123. new_pkt.time = timestamp_next_pkt
  124. maxdelay = randomdelay.random()
  125. pps = self.minDefaultPPS if getIntervalPPS(complement_interval_pps,timestamp_next_pkt) is None else max(
  126. getIntervalPPS(complement_interval_pps,timestamp_next_pkt), self.minDefaultPPS)
  127. timestamp_next_pkt = update_timestamp(timestamp_next_pkt, pps, maxdelay)
  128. # Reply
  129. else:
  130. # Ether
  131. eth_frame.setfieldval("src", mac_destination)
  132. eth_frame.setfieldval("dst", mac_source)
  133. # IP
  134. ip_pkt.setfieldval("src", ip_destination)
  135. ip_pkt.setfieldval("dst", ip_source)
  136. # TCP
  137. tcp_pkt.setfieldval("dport", port_source)
  138. new_pkt = (eth_frame / ip_pkt / tcp_pkt)
  139. timestamp_next_pkt = timestamp_next_pkt + uniform(replayDelay, 2 * replayDelay)
  140. new_pkt.time = timestamp_next_pkt
  141. packets.append(new_pkt)
  142. # Inject EternalBlue exploit packets
  143. # Read Win7_eternalblue_exploit pcap file
  144. exploit_raw_packets = RawPcapReader("Win7_eternalblue_exploit.pcap")
  145. # Group the packets in conversations
  146. def packetsToConvs(exploit_raw_packets):
  147. conversations = {}
  148. orderList_conversations = []
  149. for pkt_num, pkt in enumerate(exploit_raw_packets):
  150. eth_frame = Ether(pkt[0])
  151. ip_pkt = eth_frame.payload
  152. ip_dst = ip_pkt.getfieldval("dst")
  153. ip_src = ip_pkt.getfieldval("src")
  154. tcp_pkt = ip_pkt.payload
  155. port_dst = tcp_pkt.getfieldval("dport")
  156. port_src = tcp_pkt.getfieldval("sport")
  157. conv_req = (ip_src, port_src, ip_dst, port_dst)
  158. conv_rep = (ip_dst, port_dst, ip_src, port_src)
  159. if conv_req not in conversations and conv_rep not in conversations:
  160. pktList = [pkt]
  161. conversations[conv_req] = pktList
  162. # Order list of conv
  163. orderList_conversations.append(conv_req)
  164. else:
  165. if conv_req in conversations:
  166. pktList = conversations[conv_req]
  167. pktList.append(pkt)
  168. conversations[conv_req] = pktList
  169. else:
  170. pktList = conversations[conv_rep]
  171. pktList.append(pkt)
  172. conversations[conv_rep] = pktList
  173. return (conversations,orderList_conversations)
  174. port_source = randint(self.minDefaultPort,self.maxDefaultPort) # experiments show this range of ports
  175. # conversations = {(ip.src, ip.dst, port.src, port.dst): packets}
  176. temp_tuple = packetsToConvs(exploit_raw_packets)
  177. conversations = temp_tuple[0]
  178. orderList_conversations = temp_tuple[1]
  179. for conv_index, conv in enumerate(orderList_conversations):
  180. conv_pkts = conversations[conv]
  181. if conv_index != len(orderList_conversations)-1: # Not the last conversation
  182. port_source += 2
  183. for pkt_num, pkt in enumerate(conv_pkts):
  184. eth_frame = Ether(pkt[0])
  185. ip_pkt = eth_frame.payload
  186. tcp_pkt = ip_pkt.payload
  187. if pkt_num == 0:
  188. if tcp_pkt.getfieldval("dport") == self.smb_port:
  189. orig_ip_dst = ip_pkt.getfieldval("dst")
  190. # defining req/rep should be adapted to fit the last converstaion where
  191. # victim start a connection with the attacker
  192. # Request
  193. if ip_pkt.getfieldval("dst") == orig_ip_dst: # victim IP
  194. # Ether
  195. eth_frame.setfieldval("src", mac_source)
  196. eth_frame.setfieldval("dst", mac_destination)
  197. # IP
  198. ip_pkt.setfieldval("src", ip_source)
  199. ip_pkt.setfieldval("dst", ip_destination)
  200. # TCP
  201. tcp_pkt.setfieldval("sport", port_source)
  202. new_pkt = (eth_frame / ip_pkt / tcp_pkt)
  203. # TO-DO: reply should have different timestamp delay
  204. new_pkt.time = timestamp_next_pkt
  205. maxdelay = randomdelay.random()
  206. pps = self.minDefaultPPS if getIntervalPPS(complement_interval_pps, timestamp_next_pkt) is None else max(
  207. getIntervalPPS(complement_interval_pps, timestamp_next_pkt), self.minDefaultPPS)
  208. timestamp_next_pkt = update_timestamp(timestamp_next_pkt, pps, maxdelay)
  209. # Not perfect timestamp
  210. #req_time = req_time + randomDelay || rep_time + randomDelay
  211. # Reply
  212. else:
  213. # Ether
  214. eth_frame.setfieldval("src", mac_destination)
  215. eth_frame.setfieldval("dst", mac_source)
  216. # IP
  217. ip_pkt.setfieldval("src", ip_destination)
  218. ip_pkt.setfieldval("dst", ip_source)
  219. # TCP
  220. tcp_pkt.setfieldval("dport", port_source)
  221. new_pkt = (eth_frame / ip_pkt / tcp_pkt)
  222. timestamp_next_pkt = timestamp_next_pkt + uniform(replayDelay, 2 * replayDelay)
  223. new_pkt.time = timestamp_next_pkt
  224. # Not perfect timestamp
  225. # rep_time = req_time + replayDelay
  226. packets.append(new_pkt)
  227. else: # Last conversation where the victim start a connection with the attacker
  228. port_source = randint(self.minDefaultPort,self.maxDefaultPort)
  229. for pkt_num, pkt in enumerate(conv_pkts):
  230. eth_frame = Ether(pkt[0])
  231. ip_pkt = eth_frame.payload
  232. tcp_pkt = ip_pkt.payload
  233. # defining req/rep should be adapted to fit the last converstaion where
  234. # victim start a connection with the attacker
  235. # Request
  236. if tcp_pkt.getfieldval("dport") == self.last_conn_dst_port:
  237. # Ether
  238. eth_frame.setfieldval("src", mac_destination)
  239. eth_frame.setfieldval("dst", mac_source)
  240. # IP
  241. ip_pkt.setfieldval("src", ip_destination)
  242. ip_pkt.setfieldval("dst", ip_source)
  243. # TCP
  244. tcp_pkt.setfieldval("sport", port_source)
  245. new_pkt = (eth_frame / ip_pkt / tcp_pkt)
  246. # TO-DO: reply should have different timestamp delay
  247. new_pkt.time = timestamp_next_pkt
  248. maxdelay = randomdelay.random()
  249. pps = self.minDefaultPPS if getIntervalPPS(complement_interval_pps, timestamp_next_pkt) is None else max(
  250. getIntervalPPS(complement_interval_pps, timestamp_next_pkt), self.minDefaultPPS)
  251. timestamp_next_pkt = update_timestamp(timestamp_next_pkt, pps, maxdelay)
  252. # Not perfect timestamp
  253. # req_time = req_time + randomDelay || rep_time + randomDelay
  254. # Reply
  255. else:
  256. # Ether
  257. eth_frame.setfieldval("src", mac_source)
  258. eth_frame.setfieldval("dst", mac_destination)
  259. # IP
  260. ip_pkt.setfieldval("src", ip_source)
  261. ip_pkt.setfieldval("dst", ip_destination)
  262. # TCP
  263. tcp_pkt.setfieldval("dport", port_source)
  264. new_pkt = (eth_frame / ip_pkt / tcp_pkt)
  265. timestamp_next_pkt = timestamp_next_pkt + uniform(replayDelay, 2 * replayDelay)
  266. new_pkt.time = timestamp_next_pkt
  267. # Not perfect timestamp
  268. # rep_time = req_time + replayDelay
  269. packets.append(new_pkt)
  270. # Store timestamp of first packet (for attack label)
  271. self.attack_start_utime = packets[0].time
  272. self.attack_end_utime = packets[-1].time
  273. if len(packets) > 0:
  274. packets = sorted(packets, key=lambda pkt: pkt.time)
  275. path_attack_pcap = self.write_attack_pcap(packets, True, path_attack_pcap)
  276. # return packets sorted by packet time_sec_start
  277. # pkt_num+1: because pkt_num starts at 0
  278. return pkt_num + 1, path_attack_pcap