PortscanAttack.py 14 KB

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