SmbScanAttack.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  1. import logging
  2. from random import shuffle, randint, choice, uniform
  3. from lea import Lea
  4. from Attack import BaseAttack
  5. from Attack.AttackParameters import Parameter as Param
  6. from Attack.AttackParameters import ParameterTypes
  7. from ID2TLib.smb2 import *
  8. logging.getLogger("scapy.runtime").setLevel(logging.ERROR)
  9. # noinspection PyPep8
  10. from scapy.layers.inet import IP, Ether, TCP
  11. from scapy.layers.smb import *
  12. from scapy.layers.netbios import *
  13. class SmbScanAttack(BaseAttack.BaseAttack):
  14. # SMB port
  15. smb_port = 445
  16. # SMB versions
  17. smb_versions = {"1", "2.0", "2.1", "3.0", "3.0.2", "3.1.1", "mac", "samba"}
  18. smb_versions_per_win = {'Win7': "2.1", 'Win10': "3.1.1", 'WinXP': "1", 'Win8.1': "3.0.2", 'Win8': "3.0",
  19. 'WinVista': "2.0", 'WinNT': "1"}
  20. smb_versions_per_samba = {'3.6': "2.0", '4.1': "3.0", '4.3': "3.1.1"}
  21. def __init__(self):
  22. """
  23. Creates a new instance of the SmbScanAttack.
  24. """
  25. # Initialize attack
  26. super(SmbScanAttack, self).__init__("SmbScan Attack", "Injects an SMB scan",
  27. "Scanning/Probing")
  28. # Define allowed parameters and their type
  29. self.supported_params = {
  30. Param.IP_SOURCE: ParameterTypes.TYPE_IP_ADDRESS,
  31. Param.IP_DESTINATION: ParameterTypes.TYPE_IP_ADDRESS,
  32. Param.PORT_SOURCE: ParameterTypes.TYPE_PORT,
  33. Param.MAC_SOURCE: ParameterTypes.TYPE_MAC_ADDRESS,
  34. Param.MAC_DESTINATION: ParameterTypes.TYPE_MAC_ADDRESS,
  35. Param.INJECT_AT_TIMESTAMP: ParameterTypes.TYPE_FLOAT,
  36. Param.INJECT_AFTER_PACKET: ParameterTypes.TYPE_PACKET_POSITION,
  37. Param.IP_SOURCE_RANDOMIZE: ParameterTypes.TYPE_BOOLEAN,
  38. Param.PACKETS_PER_SECOND: ParameterTypes.TYPE_FLOAT,
  39. Param.PORT_SOURCE_RANDOMIZE: ParameterTypes.TYPE_BOOLEAN,
  40. Param.IP_HOSTING: ParameterTypes.TYPE_IP_ADDRESS,
  41. Param.PROTOCOL_VERSION: ParameterTypes.TYPE_STRING,
  42. Param.SOURCE_PLATFORM: ParameterTypes.TYPE_STRING
  43. }
  44. def init_params(self):
  45. """
  46. Initialize the parameters of this attack using the user supplied command line parameters.
  47. Use the provided statistics to calculate default parameters and to process user
  48. supplied queries.
  49. :param statistics: Reference to a statistics object.
  50. """
  51. # PARAMETERS: initialize with default values
  52. # (values are overwritten if user specifies them)
  53. most_used_ip_address = self.statistics.get_most_used_ip_address()
  54. if isinstance(most_used_ip_address, list):
  55. most_used_ip_address = most_used_ip_address[0]
  56. self.add_param_value(Param.IP_SOURCE, most_used_ip_address)
  57. self.add_param_value(Param.IP_SOURCE_RANDOMIZE, 'False')
  58. self.add_param_value(Param.MAC_SOURCE, self.statistics.get_mac_address(most_used_ip_address))
  59. all_ips = self.statistics.get_ip_addresses()
  60. if not isinstance(all_ips, list):
  61. ip_destinations = []
  62. ip_destinations.append(all_ips)
  63. else:
  64. ip_destinations = all_ips
  65. self.add_param_value(Param.IP_DESTINATION, ip_destinations)
  66. # MAYBE REMOVE/CHANGE THIS MAC STUFF
  67. #
  68. destination_mac = []
  69. for ip in ip_destinations:
  70. destination_mac.append(self.statistics.get_mac_address(str(ip)))
  71. if isinstance(destination_mac, list) and len(destination_mac) == 0:
  72. destination_mac = self.generate_random_mac_address()
  73. self.add_param_value(Param.MAC_DESTINATION, destination_mac)
  74. #
  75. #
  76. self.add_param_value(Param.PORT_SOURCE, randint(1024, 65535))
  77. self.add_param_value(Param.PORT_SOURCE_RANDOMIZE, 'True')
  78. self.add_param_value(Param.PACKETS_PER_SECOND,
  79. (self.statistics.get_pps_sent(most_used_ip_address) +
  80. self.statistics.get_pps_received(most_used_ip_address)) / 2)
  81. self.add_param_value(Param.INJECT_AFTER_PACKET, randint(0, self.statistics.get_packet_count()))
  82. rnd_ip_count = self.statistics.get_ip_address_count()/2
  83. self.add_param_value(Param.IP_HOSTING, self.statistics.get_random_ip_address(rnd_ip_count))
  84. # maybe change to version 1 as default
  85. self.add_param_value(Param.PROTOCOL_VERSION, self.get_rnd_smb_version())
  86. self.add_param_value(Param.SOURCE_PLATFORM, "Windows")
  87. def get_rnd_os(self):
  88. os_dist = Lea.fromValFreqsDict({"Win7": 48.43, "Win10": 27.99, "WinXP": 6.07, "Win8.1": 6.07, "macOS": 5.94,
  89. "Linux": 3.38, "Win8": 1.35, "WinVista": 0.46, "WinNT": 0.31})
  90. return os_dist.random()
  91. def get_rnd_smb_version(self):
  92. os = self.get_rnd_os()
  93. if os is "Linux":
  94. # FIXME: doublecheck samba releases
  95. return random.choice(self.smb_versions_per_samba.values())
  96. elif os is "macOS":
  97. # TODO: figure out macOS smb version(s)
  98. return random.choice(self.smb_versions)
  99. else:
  100. return self.smb_versions_per_win[os]
  101. @property
  102. def generate_attack_pcap(self):
  103. def update_timestamp(timestamp, pps, delay=0):
  104. """
  105. Calculates the next timestamp to be used based on the packet per second rate (pps) and the maximum delay.
  106. :return: Timestamp to be used for the next packet.
  107. """
  108. if delay == 0:
  109. # Calculate request timestamp
  110. # To imitate the bursty behavior of traffic
  111. randomdelay = Lea.fromValFreqsDict({1 / pps: 70, 2 / pps: 20, 5 / pps: 7, 10 / pps: 3})
  112. return timestamp + uniform(1/pps , randomdelay.random())
  113. else:
  114. # Calculate reply timestamp
  115. randomdelay = Lea.fromValFreqsDict({2*delay: 70, 3*delay: 20, 5*delay: 7, 10*delay: 3})
  116. return timestamp + uniform(1 / pps + delay, 1 / pps + randomdelay.random())
  117. def getIntervalPPS(complement_interval_pps, timestamp):
  118. """
  119. Gets the packet rate (pps) for a specific time interval.
  120. :param complement_interval_pps: an array of tuples (the last timestamp in the interval, the packet rate in the crresponding interval).
  121. :param timestamp: the timestamp at which the packet rate is required.
  122. :return: the corresponding packet rate (pps) .
  123. """
  124. for row in complement_interval_pps:
  125. if timestamp<=row[0]:
  126. return row[1]
  127. return complement_interval_pps[-1][1] # in case the timstamp > capture max timestamp
  128. def getIpData(ip_address: str):
  129. """
  130. :param ip_address: the ip of which (packet-)data shall be returned
  131. :return: MSS, TTL and Window Size values of the given IP
  132. """
  133. # Set MSS (Maximum Segment Size) based on MSS distribution of IP address
  134. mss_dist = self.statistics.get_mss_distribution(ip_address)
  135. if len(mss_dist) > 0:
  136. mss_prob_dict = Lea.fromValFreqsDict(mss_dist)
  137. mss_value = mss_prob_dict.random()
  138. else:
  139. mss_value = self.statistics.process_db_query("most_used(mssValue)")
  140. # Set TTL based on TTL distribution of IP address
  141. ttl_dist = self.statistics.get_ttl_distribution(ip_address)
  142. if len(ttl_dist) > 0:
  143. ttl_prob_dict = Lea.fromValFreqsDict(ttl_dist)
  144. ttl_value = ttl_prob_dict.random()
  145. else:
  146. ttl_value = self.statistics.process_db_query("most_used(ttlValue)")
  147. # Set Window Size based on Window Size distribution of IP address
  148. win_dist = self.statistics.get_win_distribution(ip_address)
  149. if len(win_dist) > 0:
  150. win_prob_dict = Lea.fromValFreqsDict(win_dist)
  151. win_value = win_prob_dict.random()
  152. else:
  153. win_value = self.statistics.process_db_query("most_used(winSize)")
  154. return mss_value, ttl_value, win_value
  155. pps = self.get_param_value(Param.PACKETS_PER_SECOND)
  156. # Calculate complement packet rates of the background traffic for each interval
  157. complement_interval_pps = self.statistics.calculate_complement_packet_rates(pps)
  158. # Timestamp
  159. timestamp_next_pkt = self.get_param_value(Param.INJECT_AT_TIMESTAMP)
  160. # store start time of attack
  161. self.attack_start_utime = timestamp_next_pkt
  162. timestamp_prv_reply, timestamp_confirm = 0,0
  163. # Initialize parameters
  164. ip_source = self.get_param_value(Param.IP_SOURCE)
  165. ip_destinations = self.get_param_value(Param.IP_DESTINATION)
  166. ip_hosting = self.get_param_value(Param.IP_HOSTING)
  167. mac_source = self.get_param_value(Param.MAC_SOURCE)
  168. mac_dest = self.get_param_value(Param.MAC_DESTINATION)
  169. # Check source platform
  170. src_platform = self.get_param_value(Param.SOURCE_PLATFORM)
  171. if (src_platform != "Windows") and (src_platform != "Linux"):
  172. print("Invalid source platform: " + src_platform + ". Selecting Windows as default source platform.")
  173. src_platform = "Windows"
  174. packets = []
  175. if self.get_param_value(Param.PORT_SOURCE_RANDOMIZE):
  176. if src_platform == "Windows":
  177. sport = randint(1024, 5000)
  178. else:
  179. pass
  180. # LINUX HERE
  181. else:
  182. sport = self.get_param_value(Param.PORT_SOURCE)
  183. # No destination IP was specified, but a destination MAC was specified, generate IP that fits MAC
  184. if isinstance(ip_destinations, list) and isinstance(mac_dest, str):
  185. ip_destinations = self.statistics.get_ip_address_from_mac(mac_dest)
  186. if len(ip_destinations) == 0:
  187. ip_destinations = self.generate_random_ipv4_address("Unknown", 1)
  188. # Check ip.src == ip.dst
  189. self.ip_src_dst_equal_check(ip_source, ip_destinations)
  190. # Get MSS, TTL and Window size value for source IP
  191. source_mss_value, source_ttl_value, source_win_value = getIpData(ip_source)
  192. #print(source_mss_value, source_ttl_value, source_win_value)
  193. # ACTUAL ATTACK GOES HERE
  194. #print(len(mac_dest))
  195. #print(mac_destination)
  196. #print(len(ip_destinations))
  197. #print(ip_destinations)
  198. #print(ip_source)
  199. ip_dests = []
  200. if isinstance(ip_destinations, list):
  201. ip_dests = ip_destinations
  202. else:
  203. ip_dests.append(ip_destinations)
  204. #print(ip_dests)
  205. for ip in ip_dests:
  206. # Randomize source IP for each connection, if specified
  207. if self.get_param_value(Param.IP_SOURCE_RANDOMIZE):
  208. ip_source = self.generate_random_ipv4_address("Unknown", 1)
  209. while ip_source == ip:
  210. ip_source = self.generate_random_ipv4_address("Unknown", 1)
  211. mac_source = self.statistics.get_mac_address(str(ip_source))
  212. if len(mac_source) == 0:
  213. mac_source = self.generate_random_mac_address()
  214. if ip != ip_source:
  215. # Get destination Mac Address
  216. #print(ip)
  217. mac_destination = self.statistics.get_mac_address(str(ip))
  218. if len(mac_destination) == 0:
  219. if isinstance(mac_dest, str):
  220. if len(self.statistics.get_ip_address_from_mac(mac_dest)) != 0:
  221. ip = self.statistics.get_ip_address_from_mac(mac_dest)
  222. self.ip_src_dst_equal_check(ip_source, ip)
  223. mac_destination = mac_dest
  224. else:
  225. mac_destination = self.generate_random_mac_address()
  226. #print(len(mac_destination))
  227. #print(mac_destination)
  228. #print(ip)
  229. # Get MSS, TTL and Window size value for destination IP
  230. destination_mss_value, destination_ttl_value, destination_win_value = getIpData(ip)
  231. #print(destination_mss_value, destination_ttl_value, destination_win_value)
  232. minDelay, maxDelay = self.get_reply_delay(ip)
  233. # New connection, new random TCP sequence numbers
  234. attacker_seq = randint(1000, 50000)
  235. victim_seq = randint(1000, 50000)
  236. # Randomize source port for each connection if specified
  237. if self.get_param_value(Param.PORT_SOURCE_RANDOMIZE):
  238. if src_platform == "Windows":
  239. if self.get_param_value(Param.IP_SOURCE_RANDOMIZE):
  240. sport = randint(1024, 5000)
  241. else:
  242. sport = sport+1
  243. else:
  244. pass
  245. #INSERT LINUX HERE
  246. # 1) Build request package
  247. request_ether = Ether(src=mac_source, dst=mac_destination)
  248. request_ip = IP(src=ip_source, dst=ip, ttl=source_ttl_value, flags='DF')
  249. request_tcp = TCP(sport=sport, dport=self.smb_port, window=source_win_value, flags='S',
  250. seq=attacker_seq, options=[('MSS', source_mss_value)])
  251. attacker_seq += 1
  252. request = (request_ether / request_ip / request_tcp)
  253. request.time = timestamp_next_pkt
  254. # Append request
  255. packets.append(request)
  256. # Update timestamp for next package
  257. timestamp_reply = update_timestamp(timestamp_next_pkt, pps, minDelay)
  258. while (timestamp_reply <= timestamp_prv_reply):
  259. timestamp_reply = update_timestamp(timestamp_prv_reply, pps, minDelay)
  260. timestamp_prv_reply = timestamp_reply
  261. if ip in ip_hosting:
  262. # 2) Build TCP packages for ip that hosts SMB
  263. # destination sends SYN, ACK
  264. reply_ether = Ether(src=mac_destination, dst=mac_source)
  265. reply_ip = IP(src=ip, dst=ip_source, ttl=destination_ttl_value, flags='DF')
  266. reply_tcp = TCP(sport=self.smb_port, dport=sport, seq=victim_seq, ack=attacker_seq, flags='SA', window=destination_win_value,
  267. options=[('MSS', destination_mss_value)])
  268. victim_seq += 1
  269. reply = (reply_ether / reply_ip / reply_tcp)
  270. reply.time = timestamp_reply
  271. packets.append(reply)
  272. # requester confirms, ACK
  273. confirm_ether = request_ether
  274. confirm_ip = request_ip
  275. confirm_tcp = TCP(sport=sport, dport=self.smb_port, seq=attacker_seq, ack=victim_seq, window=source_win_value, flags='A')
  276. confirm = (confirm_ether / confirm_ip / confirm_tcp)
  277. timestamp_confirm = update_timestamp(timestamp_reply, pps, minDelay)
  278. confirm.time = timestamp_confirm
  279. packets.append(confirm)
  280. # INSERT SMB-REQUEST PACKAGE HERE
  281. # CHECK FOR PROTOCOL VERSION?
  282. smb_MID = randint(1, 65535)
  283. smb_PID = randint(1, 65535)
  284. smb_req_tail_arr = []
  285. smb_req_tail_size = 0
  286. #Dialects are saved in this array
  287. smb_req_dialects = ["SMB 2.000"]
  288. if (len(smb_req_dialects) == 0):
  289. smb_req_tail_arr.append(SMBNegociate_Protocol_Request_Tail())
  290. smb_req_tail_size = len(SMBNegociate_Protocol_Request_Tail())
  291. else:
  292. for i in range(0,len(smb_req_dialects)):
  293. smb_req_tail_arr.append(SMBNegociate_Protocol_Request_Tail(BufferData = smb_req_dialects[i]))
  294. smb_req_tail_size += len(SMBNegociate_Protocol_Request_Tail(BufferData = smb_req_dialects[i]))
  295. smb_req_head = SMBNegociate_Protocol_Request_Header(Flags2=0x2801, PID=smb_PID, MID=smb_MID , ByteCount = smb_req_tail_size)
  296. smb_req_length = len(smb_req_head) + smb_req_tail_size
  297. smb_req_net_bio = NBTSession(TYPE=0x00, LENGTH=smb_req_length)
  298. smb_req_tcp = TCP(sport=sport, dport=self.smb_port, flags='PA', seq=attacker_seq, ack=victim_seq)
  299. smb_req_ip = IP(src=ip_source, dst=ip, ttl=source_ttl_value)
  300. smb_req_ether = Ether(src=mac_source, dst=mac_destination)
  301. attacker_seq += len(smb_req_net_bio) + len(smb_req_head) + smb_req_tail_size
  302. smb_req_combined = (smb_req_ether / smb_req_ip / smb_req_tcp / smb_req_net_bio / smb_req_head )
  303. for i in range(0 , len(smb_req_tail_arr)):
  304. smb_req_combined = smb_req_combined / smb_req_tail_arr[i]
  305. timestamp_smb_req = update_timestamp(timestamp_confirm, pps, minDelay)
  306. smb_req_combined.time = timestamp_smb_req
  307. packets.append(smb_req_combined)
  308. # destination confirms SMB request package
  309. reply_tcp = TCP(sport=self.smb_port, dport=sport, seq=victim_seq, ack=attacker_seq, window=destination_win_value, flags='A')
  310. confirm_smb_req = (reply_ether / reply_ip / reply_tcp)
  311. timestamp_reply = update_timestamp(timestamp_smb_req, pps, minDelay)
  312. confirm_smb_req.time = timestamp_reply
  313. packets.append(confirm_smb_req)
  314. # INSERT SMB-RESPONSE PACKAGE HERE
  315. # CHECK FOR PROTOCOL VERSION?
  316. #Add here relevant dialects for smb2
  317. if("SMB 2.000" , "SMB 2.???" in smb_req_dialects):
  318. smb2 = 1
  319. else:
  320. smb2 = 0
  321. if(smb2 == 1):
  322. smb_rsp_paket = SMB2_SYNC_Header()
  323. smb_rsp_negotiate_body = SMB2_Negotiate_Protocol_Response()
  324. smb_rsp_length = len(smb_rsp_paket) + len(smb_rsp_negotiate_body)
  325. else:
  326. smb_rsp_paket = SMBNegociate_Protocol_Response_No_Security_No_Key(Start = "\xffSMB" , PID=smb_PID, MID=smb_MID)
  327. smb_rsp_length = len(smb_rsp_paket)
  328. smb_rsp_net_bio = NBTSession(TYPE=0x00, LENGTH=smb_rsp_length)
  329. smb_rsp_tcp = TCP(sport=self.smb_port, dport=sport, flags='PA', seq=victim_seq, ack=attacker_seq)
  330. smb_rsp_ip = IP(src=ip, dst=ip_source, ttl=destination_ttl_value)
  331. smb_rsp_ether = Ether(src=mac_destination, dst=mac_source)
  332. victim_seq += len(smb_rsp_net_bio) + len(smb_rsp_paket)
  333. if(smb2 == 1):
  334. victim_seq += len(smb_rsp_negotiate_body)
  335. smb_rsp_combined = (smb_rsp_ether / smb_rsp_ip / smb_rsp_tcp / smb_rsp_net_bio / smb_rsp_paket)
  336. if (smb2 == 1):
  337. smb_rsp_combined = (smb_rsp_combined / smb_rsp_negotiate_body)
  338. timestamp_smb_rsp = update_timestamp(timestamp_reply, pps, minDelay)
  339. smb_rsp_combined.time = timestamp_smb_rsp
  340. packets.append(smb_rsp_combined)
  341. # source confirms SMB response package
  342. confirm_tcp = TCP(sport=sport, dport=self.smb_port, seq=attacker_seq, ack=victim_seq, window=source_win_value, flags='A')
  343. confirm_smb_res = (confirm_ether / confirm_ip / confirm_tcp)
  344. timestamp_confirm = update_timestamp(timestamp_smb_rsp, pps, minDelay)
  345. confirm_smb_res.time = timestamp_confirm
  346. packets.append(confirm_smb_res)
  347. # attacker sends FIN ACK
  348. confirm_tcp = TCP(sport=sport, dport=self.smb_port, seq=attacker_seq, ack=victim_seq, window=source_win_value, flags='FA')
  349. source_fin_ack = (confirm_ether / confirm_ip / confirm_tcp)
  350. timestamp_src_fin_ack = update_timestamp(timestamp_confirm, pps, minDelay)
  351. source_fin_ack.time = timestamp_src_fin_ack
  352. attacker_seq += 1
  353. packets.append(source_fin_ack)
  354. # victim sends FIN ACK
  355. reply_tcp = TCP(sport=self.smb_port, dport=sport, seq=victim_seq, ack=attacker_seq, window=destination_win_value, flags='FA')
  356. destination_fin_ack = (reply_ether / reply_ip / reply_tcp)
  357. timestamp_dest_fin_ack = update_timestamp(timestamp_src_fin_ack, pps, minDelay)
  358. victim_seq += 1
  359. destination_fin_ack.time = timestamp_dest_fin_ack
  360. packets.append(destination_fin_ack)
  361. # source sends final ACK
  362. confirm_tcp = TCP(sport=sport, dport=self.smb_port, seq=attacker_seq, ack=victim_seq, window=source_win_value, flags='A')
  363. final_ack = (confirm_ether / confirm_ip / confirm_tcp)
  364. timestamp_final_ack = update_timestamp(timestamp_dest_fin_ack, pps, minDelay)
  365. final_ack.time = timestamp_final_ack
  366. packets.append(final_ack)
  367. else:
  368. # Build RST package
  369. reply_ether = Ether(src=mac_destination, dst=mac_source)
  370. reply_ip = IP(src=ip, dst=ip_source, ttl=destination_ttl_value, flags='DF')
  371. reply_tcp = TCP(sport=self.smb_port, dport=sport, seq=0, ack=attacker_seq, flags='RA', window=destination_win_value,
  372. options=[('MSS', destination_mss_value)])
  373. reply = (reply_ether / reply_ip / reply_tcp)
  374. reply.time = timestamp_reply
  375. packets.append(reply)
  376. pps = max(getIntervalPPS(complement_interval_pps, timestamp_next_pkt), 10)
  377. timestamp_next_pkt = update_timestamp(timestamp_next_pkt, pps)
  378. # store end time of attack
  379. self.attack_end_utime = packets[-1].time
  380. # write attack packets to pcap
  381. pcap_path = self.write_attack_pcap(sorted(packets, key=lambda pkt: pkt.time))
  382. # return packets sorted by packet time_sec_start
  383. return len(packets), pcap_path