SmbScanAttack.py 21 KB

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