PortscanAttack.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. import csv
  2. import logging
  3. import random as rnd
  4. import lea
  5. import scapy.layers.inet as inet
  6. import Attack.AttackParameters as atkParam
  7. import Attack.BaseAttack as BaseAttack
  8. import ID2TLib.Utility as Util
  9. logging.getLogger("scapy.runtime").setLevel(logging.ERROR)
  10. # noinspection PyPep8
  11. class PortscanAttack(BaseAttack.BaseAttack):
  12. def __init__(self):
  13. """
  14. Creates a new instance of the PortscanAttack.
  15. """
  16. # Initialize attack
  17. super(PortscanAttack, self).__init__("Portscan Attack", "Injects a nmap 'regular scan'",
  18. "Scanning/Probing")
  19. # Define allowed parameters and their type
  20. self.supported_params.update({
  21. atkParam.Parameter.IP_SOURCE: atkParam.ParameterTypes.TYPE_IP_ADDRESS,
  22. atkParam.Parameter.IP_DESTINATION: atkParam.ParameterTypes.TYPE_IP_ADDRESS,
  23. atkParam.Parameter.PORT_SOURCE: atkParam.ParameterTypes.TYPE_PORT,
  24. atkParam.Parameter.PORT_DESTINATION: atkParam.ParameterTypes.TYPE_PORT,
  25. atkParam.Parameter.PORT_OPEN: atkParam.ParameterTypes.TYPE_PORT,
  26. atkParam.Parameter.MAC_SOURCE: atkParam.ParameterTypes.TYPE_MAC_ADDRESS,
  27. atkParam.Parameter.MAC_DESTINATION: atkParam.ParameterTypes.TYPE_MAC_ADDRESS,
  28. atkParam.Parameter.INJECT_AT_TIMESTAMP: atkParam.ParameterTypes.TYPE_FLOAT,
  29. atkParam.Parameter.INJECT_AFTER_PACKET: atkParam.ParameterTypes.TYPE_PACKET_POSITION,
  30. atkParam.Parameter.PORT_DEST_SHUFFLE: atkParam.ParameterTypes.TYPE_BOOLEAN,
  31. atkParam.Parameter.PORT_DEST_ORDER_DESC: atkParam.ParameterTypes.TYPE_BOOLEAN,
  32. atkParam.Parameter.IP_SOURCE_RANDOMIZE: atkParam.ParameterTypes.TYPE_BOOLEAN,
  33. atkParam.Parameter.PACKETS_PER_SECOND: atkParam.ParameterTypes.TYPE_FLOAT,
  34. atkParam.Parameter.PORT_SOURCE_RANDOMIZE: atkParam.ParameterTypes.TYPE_BOOLEAN
  35. })
  36. def init_params(self):
  37. """
  38. Initialize the parameters of this attack using the user supplied command line parameters.
  39. Use the provided statistics to calculate default parameters and to process user
  40. supplied queries.
  41. """
  42. # PARAMETERS: initialize with default values
  43. # (values are overwritten if user specifies them)
  44. most_used_ip_address = self.statistics.get_most_used_ip_address()
  45. self.add_param_value(atkParam.Parameter.IP_SOURCE, most_used_ip_address)
  46. self.add_param_value(atkParam.Parameter.IP_SOURCE_RANDOMIZE, 'False')
  47. self.add_param_value(atkParam.Parameter.MAC_SOURCE, self.statistics.get_mac_address(most_used_ip_address))
  48. random_ip_address = self.statistics.get_random_ip_address()
  49. # ip-dst should be valid and not equal to ip.src
  50. while not self.is_valid_ip_address(random_ip_address) or random_ip_address == most_used_ip_address:
  51. random_ip_address = self.statistics.get_random_ip_address()
  52. self.add_param_value(atkParam.Parameter.IP_DESTINATION, random_ip_address)
  53. destination_mac = self.statistics.get_mac_address(random_ip_address)
  54. if isinstance(destination_mac, list) and len(destination_mac) == 0:
  55. destination_mac = self.generate_random_mac_address()
  56. self.add_param_value(atkParam.Parameter.MAC_DESTINATION, destination_mac)
  57. self.add_param_value(atkParam.Parameter.PORT_DESTINATION, self.get_ports_from_nmap_service_dst(1000))
  58. self.add_param_value(atkParam.Parameter.PORT_OPEN, '1')
  59. self.add_param_value(atkParam.Parameter.PORT_DEST_SHUFFLE, 'False')
  60. self.add_param_value(atkParam.Parameter.PORT_DEST_ORDER_DESC, 'False')
  61. self.add_param_value(atkParam.Parameter.PORT_SOURCE, rnd.randint(1024, 65535))
  62. self.add_param_value(atkParam.Parameter.PORT_SOURCE_RANDOMIZE, 'False')
  63. self.add_param_value(atkParam.Parameter.PACKETS_PER_SECOND,
  64. (self.statistics.get_pps_sent(most_used_ip_address) +
  65. self.statistics.get_pps_received(most_used_ip_address)) / 2)
  66. self.add_param_value(atkParam.Parameter.INJECT_AFTER_PACKET, rnd.randint(0, self.statistics.get_packet_count()))
  67. @staticmethod
  68. def get_ports_from_nmap_service_dst(ports_num):
  69. """
  70. Read the most ports_num frequently open ports from nmap-service-tcp file to be used in the port scan.
  71. :return: Ports numbers to be used as default destination ports or default open ports in the port scan.
  72. """
  73. ports_dst = []
  74. file = open(Util.RESOURCE_DIR + 'nmap-services-tcp.csv', 'rt')
  75. spamreader = csv.reader(file, delimiter=',')
  76. for count in range(ports_num):
  77. # escape first row (header)
  78. next(spamreader)
  79. # save ports numbers
  80. ports_dst.append(next(spamreader)[0])
  81. file.close()
  82. # rnd.shuffle ports numbers partially
  83. if ports_num == 1000: # used for port.dst
  84. # FIXME: cleanup
  85. temp_array = [[0 for i in range(10)] for i in range(100)]
  86. port_dst_shuffled = []
  87. for count in range(0, 10):
  88. temp_array[count] = ports_dst[count * 100:(count + 1) * 100]
  89. rnd.shuffle(temp_array[count])
  90. port_dst_shuffled += temp_array[count]
  91. else: # used for port.open
  92. rnd.shuffle(ports_dst)
  93. port_dst_shuffled = ports_dst
  94. return port_dst_shuffled
  95. def generate_attack_packets(self):
  96. mac_source = self.get_param_value(atkParam.Parameter.MAC_SOURCE)
  97. mac_destination = self.get_param_value(atkParam.Parameter.MAC_DESTINATION)
  98. pps = self.get_param_value(atkParam.Parameter.PACKETS_PER_SECOND)
  99. # Calculate complement packet rates of the background traffic for each interval
  100. complement_interval_pps = self.statistics.calculate_complement_packet_rates(pps)
  101. # Determine ports
  102. dest_ports = self.get_param_value(atkParam.Parameter.PORT_DESTINATION)
  103. if self.get_param_value(atkParam.Parameter.PORT_DEST_ORDER_DESC):
  104. dest_ports.reverse()
  105. elif self.get_param_value(atkParam.Parameter.PORT_DEST_SHUFFLE):
  106. rnd.shuffle(dest_ports)
  107. if self.get_param_value(atkParam.Parameter.PORT_SOURCE_RANDOMIZE):
  108. # FIXME: why is sport never used?
  109. sport = rnd.randint(1, 65535)
  110. else:
  111. sport = self.get_param_value(atkParam.Parameter.PORT_SOURCE)
  112. # Timestamp
  113. timestamp_next_pkt = self.get_param_value(atkParam.Parameter.INJECT_AT_TIMESTAMP)
  114. # store start time of attack
  115. self.attack_start_utime = timestamp_next_pkt
  116. timestamp_prv_reply, timestamp_confirm = 0, 0
  117. # Initialize parameters
  118. self.packets = []
  119. ip_source = self.get_param_value(atkParam.Parameter.IP_SOURCE)
  120. if isinstance(ip_source, list):
  121. ip_source = ip_source[0]
  122. ip_destination = self.get_param_value(atkParam.Parameter.IP_DESTINATION)
  123. if isinstance(ip_destination, list):
  124. ip_destination = ip_destination[0]
  125. # Check ip.src == ip.dst
  126. self.ip_src_dst_equal_check(ip_source, ip_destination)
  127. # Select open ports
  128. ports_open = self.get_param_value(atkParam.Parameter.PORT_OPEN)
  129. if ports_open == 1: # user did not specify open ports
  130. # the ports that were already used by ip.dst (direction in) in the background traffic are open ports
  131. ports_used_by_ip_dst = self.statistics.process_db_query(
  132. "SELECT portNumber FROM ip_ports WHERE portDirection='in' AND ipAddress='" + ip_destination + "'")
  133. if ports_used_by_ip_dst:
  134. ports_open = ports_used_by_ip_dst
  135. else: # if no ports were retrieved from database
  136. # Take open ports from nmap-service file
  137. # ports_temp = self.get_ports_from_nmap_service_dst(100)
  138. # ports_open = ports_temp[0:rnd.randint(1,10)]
  139. # OR take open ports from the most used ports in traffic statistics
  140. ports_open = self.statistics.process_db_query(
  141. "SELECT portNumber FROM ip_ports GROUP BY portNumber ORDER BY SUM(portCount) DESC LIMIT " + str(
  142. rnd.randint(1, 10)))
  143. # in case of one open port, convert ports_open to array
  144. if not isinstance(ports_open, list):
  145. ports_open = [ports_open]
  146. # Set MSS (Maximum Segment Size) based on MSS distribution of IP address
  147. source_mss_dist = self.statistics.get_mss_distribution(ip_source)
  148. if len(source_mss_dist) > 0:
  149. source_mss_prob_dict = lea.Lea.fromValFreqsDict(source_mss_dist)
  150. source_mss_value = source_mss_prob_dict.random()
  151. else:
  152. source_mss_value = Util.handle_most_used_outputs(self.statistics.process_db_query("most_used(mssValue)"))
  153. destination_mss_dist = self.statistics.get_mss_distribution(ip_destination)
  154. if len(destination_mss_dist) > 0:
  155. destination_mss_prob_dict = lea.Lea.fromValFreqsDict(destination_mss_dist)
  156. destination_mss_value = destination_mss_prob_dict.random()
  157. else:
  158. destination_mss_value = Util.handle_most_used_outputs(
  159. self.statistics.process_db_query("most_used(mssValue)"))
  160. # Set TTL based on TTL distribution of IP address
  161. source_ttl_dist = self.statistics.get_ttl_distribution(ip_source)
  162. if len(source_ttl_dist) > 0:
  163. source_ttl_prob_dict = lea.Lea.fromValFreqsDict(source_ttl_dist)
  164. source_ttl_value = source_ttl_prob_dict.random()
  165. else:
  166. source_ttl_value = Util.handle_most_used_outputs(self.statistics.process_db_query("most_used(ttlValue)"))
  167. destination_ttl_dist = self.statistics.get_ttl_distribution(ip_destination)
  168. if len(destination_ttl_dist) > 0:
  169. destination_ttl_prob_dict = lea.Lea.fromValFreqsDict(destination_ttl_dist)
  170. destination_ttl_value = destination_ttl_prob_dict.random()
  171. else:
  172. destination_ttl_value = Util.handle_most_used_outputs(
  173. self.statistics.process_db_query("most_used(ttlValue)"))
  174. # Set Window Size based on Window Size distribution of IP address
  175. source_win_dist = self.statistics.get_win_distribution(ip_source)
  176. if len(source_win_dist) > 0:
  177. source_win_prob_dict = lea.Lea.fromValFreqsDict(source_win_dist)
  178. source_win_value = source_win_prob_dict.random()
  179. else:
  180. source_win_value = Util.handle_most_used_outputs(self.statistics.process_db_query("most_used(winSize)"))
  181. destination_win_dist = self.statistics.get_win_distribution(ip_destination)
  182. if len(destination_win_dist) > 0:
  183. destination_win_prob_dict = lea.Lea.fromValFreqsDict(destination_win_dist)
  184. destination_win_value = destination_win_prob_dict.random()
  185. else:
  186. destination_win_value = Util.handle_most_used_outputs(
  187. self.statistics.process_db_query("most_used(winSize)"))
  188. min_delay, max_delay = self.get_reply_delay(ip_destination)
  189. for dport in dest_ports:
  190. # Parameters changing each iteration
  191. if self.get_param_value(atkParam.Parameter.IP_SOURCE_RANDOMIZE) and isinstance(ip_source, list):
  192. ip_source = rnd.choice(ip_source)
  193. # 1) Build request package
  194. request_ether = inet.Ether(src=mac_source, dst=mac_destination)
  195. request_ip = inet.IP(src=ip_source, dst=ip_destination, ttl=source_ttl_value)
  196. # Random src port for each packet
  197. sport = rnd.randint(1, 65535)
  198. request_tcp = inet.TCP(sport=sport, dport=dport, window=source_win_value, flags='S',
  199. options=[('MSS', source_mss_value)])
  200. request = (request_ether / request_ip / request_tcp)
  201. request.time = timestamp_next_pkt
  202. # Append request
  203. self.packets.append(request)
  204. # 2) Build reply (for open ports) package
  205. if dport in ports_open: # destination port is OPEN
  206. reply_ether = inet.Ether(src=mac_destination, dst=mac_source)
  207. reply_ip = inet.IP(src=ip_destination, dst=ip_source, ttl=destination_ttl_value, flags='DF')
  208. reply_tcp = inet.TCP(sport=dport, dport=sport, seq=0, ack=1, flags='SA', window=destination_win_value,
  209. options=[('MSS', destination_mss_value)])
  210. reply = (reply_ether / reply_ip / reply_tcp)
  211. timestamp_reply = Util.update_timestamp(timestamp_next_pkt, pps, min_delay)
  212. while timestamp_reply <= timestamp_prv_reply:
  213. timestamp_reply = Util.update_timestamp(timestamp_prv_reply, pps, min_delay)
  214. timestamp_prv_reply = timestamp_reply
  215. reply.time = timestamp_reply
  216. self.packets.append(reply)
  217. # requester confirms
  218. confirm_ether = request_ether
  219. confirm_ip = request_ip
  220. confirm_tcp = inet.TCP(sport=sport, dport=dport, seq=1, window=0, flags='R')
  221. confirm = (confirm_ether / confirm_ip / confirm_tcp)
  222. timestamp_confirm = Util.update_timestamp(timestamp_reply, pps, min_delay)
  223. confirm.time = timestamp_confirm
  224. self.packets.append(confirm)
  225. # else: destination port is NOT OPEN -> no reply is sent by target
  226. pps = max(Util.get_interval_pps(complement_interval_pps, timestamp_next_pkt), 10)
  227. timestamp_next_pkt = Util.update_timestamp(timestamp_next_pkt, pps)
  228. def generate_attack_pcap(self):
  229. # store end time of attack
  230. self.attack_end_utime = self.packets[-1].time
  231. # write attack packets to pcap
  232. pcap_path = self.write_attack_pcap(sorted(self.packets, key=lambda pkt: pkt.time))
  233. # return packets sorted by packet time_sec_start
  234. return len(self.packets), pcap_path