PortscanAttack.py 13 KB

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