PortscanAttack.py 14 KB

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