EternalBlueExploit.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  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. # Timestamp
  70. timestamp_next_pkt = self.get_param_value(Param.INJECT_AT_TIMESTAMP)
  71. # TO-DO: find better pkt rate
  72. pps = self.get_param_value(Param.PACKETS_PER_SECOND)
  73. randomdelay = Lea.fromValFreqsDict({1 / pps: 70, 2 / pps: 30, 5 / pps: 15, 10 / pps: 3})
  74. # Aidmar
  75. # Calculate the complement packet rates of the background traffic packet rates per interval
  76. result = self.statistics.process_db_query(
  77. "SELECT timestamp,pktsCount FROM interval_statistics ORDER BY timestamp")
  78. print(result)
  79. bg_interval_pps = []
  80. intervalsSum = 0
  81. if result:
  82. # Get the interval in seconds
  83. for i, row in enumerate(result):
  84. if i < len(result) - 1:
  85. intervalsSum += math.ceil((int(result[i + 1][0]) * 10 ** -6) - (int(row[0]) * 10 ** -6))
  86. interval = intervalsSum / (len(result) - 1)
  87. # Convert timestamp from micro to seconds, convert packet rate "per interval" to "per second"
  88. for row in result:
  89. bg_interval_pps.append((int(row[0]) * 10 ** -6, int(row[1] / interval)))
  90. # Find max PPS
  91. maxPPS = max(bg_interval_pps, key=itemgetter(1))[1]
  92. complement_interval_pps = []
  93. for row in bg_interval_pps:
  94. complement_interval_pps.append((row[0], int(pps * (maxPPS - row[1]) / maxPPS)))
  95. print(complement_interval_pps)
  96. def getIntervalPPS(timestamp):
  97. for row in complement_interval_pps:
  98. if timestamp <= row[0]:
  99. return row[1]
  100. return complement_interval_pps[-1][1] # in case the timstamp > capture max timestamp
  101. # Initialize parameters
  102. packets = []
  103. mac_source = self.get_param_value(Param.MAC_SOURCE)
  104. ip_source = self.get_param_value(Param.IP_SOURCE)
  105. #port_source = self.get_param_value(Param.PORT_SOURCE)
  106. mac_destination = self.get_param_value(Param.MAC_DESTINATION)
  107. ip_destination = self.get_param_value(Param.IP_DESTINATION)
  108. # Aidmar - check ip.src == ip.dst
  109. if ip_source == ip_destination:
  110. print("\nERROR: Invalid IP addresses; source IP is the same as destination IP: " + ip_source + ".")
  111. import sys
  112. sys.exit(0)
  113. path_attack_pcap = None
  114. replayDelay = self.get_reply_delay(ip_destination)
  115. # Scan (MS17) for EternalBlue
  116. # Read Win7_eternalblue_scan_vulnerable pcap file
  117. orig_ip_dst = None
  118. exploit_raw_packets = RawPcapReader("Win7_eternalblue_scan.pcap")
  119. port_source = randint(self.minDefaultPort,self.maxDefaultPort) # experiments show this range of ports
  120. for pkt_num, pkt in enumerate(exploit_raw_packets):
  121. eth_frame = Ether(pkt[0])
  122. ip_pkt = eth_frame.payload
  123. tcp_pkt = ip_pkt.payload
  124. if pkt_num == 0:
  125. if tcp_pkt.getfieldval("dport") == self.smb_port:
  126. orig_ip_dst = ip_pkt.getfieldval("dst") # victim IP
  127. # Request
  128. if ip_pkt.getfieldval("dst") == orig_ip_dst: # victim IP
  129. # Ether
  130. eth_frame.setfieldval("src", mac_source)
  131. eth_frame.setfieldval("dst", mac_destination)
  132. # IP
  133. ip_pkt.setfieldval("src", ip_source)
  134. ip_pkt.setfieldval("dst", ip_destination)
  135. # TCP
  136. tcp_pkt.setfieldval("sport",port_source)
  137. new_pkt = (eth_frame / ip_pkt / tcp_pkt)
  138. new_pkt.time = timestamp_next_pkt
  139. maxdelay = randomdelay.random()
  140. pps = self.minDefaultPPS if getIntervalPPS(timestamp_next_pkt) is None else max(
  141. getIntervalPPS(timestamp_next_pkt), self.minDefaultPPS)
  142. timestamp_next_pkt = update_timestamp(timestamp_next_pkt, pps, maxdelay)
  143. # Reply
  144. else:
  145. # Ether
  146. eth_frame.setfieldval("src", mac_destination)
  147. eth_frame.setfieldval("dst", mac_source)
  148. # IP
  149. ip_pkt.setfieldval("src", ip_destination)
  150. ip_pkt.setfieldval("dst", ip_source)
  151. # TCP
  152. tcp_pkt.setfieldval("dport", port_source)
  153. new_pkt = (eth_frame / ip_pkt / tcp_pkt)
  154. timestamp_next_pkt = timestamp_next_pkt + uniform(replayDelay, 2 * replayDelay)
  155. new_pkt.time = timestamp_next_pkt
  156. packets.append(new_pkt)
  157. # Inject EternalBlue exploit packets
  158. # Read Win7_eternalblue_exploit pcap file
  159. exploit_raw_packets = RawPcapReader("Win7_eternalblue_exploit.pcap")
  160. # Group the packets in conversations
  161. def packetsToConvs(exploit_raw_packets):
  162. conversations = {}
  163. orderList_conversations = []
  164. for pkt_num, pkt in enumerate(exploit_raw_packets):
  165. eth_frame = Ether(pkt[0])
  166. ip_pkt = eth_frame.payload
  167. ip_dst = ip_pkt.getfieldval("dst")
  168. ip_src = ip_pkt.getfieldval("src")
  169. tcp_pkt = ip_pkt.payload
  170. port_dst = tcp_pkt.getfieldval("dport")
  171. port_src = tcp_pkt.getfieldval("sport")
  172. conv_req = (ip_src, port_src, ip_dst, port_dst)
  173. conv_rep = (ip_dst, port_dst, ip_src, port_src)
  174. if conv_req not in conversations and conv_rep not in conversations:
  175. pktList = [pkt]
  176. conversations[conv_req] = pktList
  177. # Order list of conv
  178. orderList_conversations.append(conv_req)
  179. else:
  180. if conv_req in conversations:
  181. pktList = conversations[conv_req]
  182. pktList.append(pkt)
  183. conversations[conv_req] = pktList
  184. else:
  185. pktList = conversations[conv_rep]
  186. pktList.append(pkt)
  187. conversations[conv_rep] = pktList
  188. return (conversations,orderList_conversations)
  189. port_source = randint(self.minDefaultPort,self.maxDefaultPort) # experiments show this range of ports
  190. # conversations = {(ip.src, ip.dst, port.src, port.dst): packets}
  191. temp_tuple = packetsToConvs(exploit_raw_packets)
  192. conversations = temp_tuple[0]
  193. orderList_conversations = temp_tuple[1]
  194. for conv_index, conv in enumerate(orderList_conversations):
  195. conv_pkts = conversations[conv]
  196. if conv_index != len(orderList_conversations)-1: # Not the last conversation
  197. port_source += 2
  198. for pkt_num, pkt in enumerate(conv_pkts):
  199. eth_frame = Ether(pkt[0])
  200. ip_pkt = eth_frame.payload
  201. tcp_pkt = ip_pkt.payload
  202. if pkt_num == 0:
  203. if tcp_pkt.getfieldval("dport") == self.smb_port:
  204. orig_ip_dst = ip_pkt.getfieldval("dst")
  205. # defining req/rep should be adapted to fit the last converstaion where
  206. # victim start a connection with the attacker
  207. # Request
  208. if ip_pkt.getfieldval("dst") == orig_ip_dst: # victim IP
  209. # Ether
  210. eth_frame.setfieldval("src", mac_source)
  211. eth_frame.setfieldval("dst", mac_destination)
  212. # IP
  213. ip_pkt.setfieldval("src", ip_source)
  214. ip_pkt.setfieldval("dst", ip_destination)
  215. # TCP
  216. tcp_pkt.setfieldval("sport", port_source)
  217. new_pkt = (eth_frame / ip_pkt / tcp_pkt)
  218. # TO-DO: reply should have different timestamp delay
  219. new_pkt.time = timestamp_next_pkt
  220. maxdelay = randomdelay.random()
  221. pps = self.minDefaultPPS if getIntervalPPS(timestamp_next_pkt) is None else max(
  222. getIntervalPPS(timestamp_next_pkt), self.minDefaultPPS)
  223. timestamp_next_pkt = update_timestamp(timestamp_next_pkt, pps, maxdelay)
  224. # Not perfect timestamp
  225. #req_time = req_time + randomDelay || rep_time + randomDelay
  226. # Reply
  227. else:
  228. # Ether
  229. eth_frame.setfieldval("src", mac_destination)
  230. eth_frame.setfieldval("dst", mac_source)
  231. # IP
  232. ip_pkt.setfieldval("src", ip_destination)
  233. ip_pkt.setfieldval("dst", ip_source)
  234. # TCP
  235. tcp_pkt.setfieldval("dport", port_source)
  236. new_pkt = (eth_frame / ip_pkt / tcp_pkt)
  237. timestamp_next_pkt = timestamp_next_pkt + uniform(replayDelay, 2 * replayDelay)
  238. new_pkt.time = timestamp_next_pkt
  239. # Not perfect timestamp
  240. # rep_time = req_time + replayDelay
  241. packets.append(new_pkt)
  242. else: # Last conversation where the victim start a connection with the attacker
  243. port_source = randint(self.minDefaultPort,self.maxDefaultPort)
  244. for pkt_num, pkt in enumerate(conv_pkts):
  245. eth_frame = Ether(pkt[0])
  246. ip_pkt = eth_frame.payload
  247. tcp_pkt = ip_pkt.payload
  248. # defining req/rep should be adapted to fit the last converstaion where
  249. # victim start a connection with the attacker
  250. # Request
  251. if tcp_pkt.getfieldval("dport") == self.last_conn_dst_port:
  252. # Ether
  253. eth_frame.setfieldval("src", mac_destination)
  254. eth_frame.setfieldval("dst", mac_source)
  255. # IP
  256. ip_pkt.setfieldval("src", ip_destination)
  257. ip_pkt.setfieldval("dst", ip_source)
  258. # TCP
  259. tcp_pkt.setfieldval("sport", port_source)
  260. new_pkt = (eth_frame / ip_pkt / tcp_pkt)
  261. # TO-DO: reply should have different timestamp delay
  262. new_pkt.time = timestamp_next_pkt
  263. maxdelay = randomdelay.random()
  264. pps = self.minDefaultPPS if getIntervalPPS(timestamp_next_pkt) is None else max(
  265. getIntervalPPS(timestamp_next_pkt), self.minDefaultPPS)
  266. timestamp_next_pkt = update_timestamp(timestamp_next_pkt, pps, maxdelay)
  267. # Not perfect timestamp
  268. # req_time = req_time + randomDelay || rep_time + randomDelay
  269. # Reply
  270. else:
  271. # Ether
  272. eth_frame.setfieldval("src", mac_source)
  273. eth_frame.setfieldval("dst", mac_destination)
  274. # IP
  275. ip_pkt.setfieldval("src", ip_source)
  276. ip_pkt.setfieldval("dst", ip_destination)
  277. # TCP
  278. tcp_pkt.setfieldval("dport", port_source)
  279. new_pkt = (eth_frame / ip_pkt / tcp_pkt)
  280. timestamp_next_pkt = timestamp_next_pkt + uniform(replayDelay, 2 * replayDelay)
  281. new_pkt.time = timestamp_next_pkt
  282. # Not perfect timestamp
  283. # rep_time = req_time + replayDelay
  284. packets.append(new_pkt)
  285. # Store timestamp of first packet (for attack label)
  286. self.attack_start_utime = packets[0].time
  287. self.attack_end_utime = packets[-1].time
  288. if len(packets) > 0:
  289. packets = sorted(packets, key=lambda pkt: pkt.time)
  290. path_attack_pcap = self.write_attack_pcap(packets, True, path_attack_pcap)
  291. # return packets sorted by packet time_sec_start
  292. # pkt_num+1: because pkt_num starts at 0
  293. return pkt_num + 1, path_attack_pcap