PortscanAttack.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. import logging
  2. import csv
  3. from random import shuffle, randint, choice, uniform
  4. from lea import Lea
  5. from scapy.layers.inet import IP, Ether, TCP
  6. from Attack import BaseAttack
  7. from Attack.AttackParameters import Parameter as Param
  8. from Attack.AttackParameters import ParameterTypes
  9. from ID2TLib.Utility import update_timestamp, get_interval_pps
  10. logging.getLogger("scapy.runtime").setLevel(logging.ERROR)
  11. # noinspection PyPep8
  12. from scapy.layers.inet import IP, Ether, TCP
  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 = {
  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. if isinstance(most_used_ip_address, list):
  49. most_used_ip_address = most_used_ip_address[0]
  50. self.add_param_value(Param.IP_SOURCE, most_used_ip_address)
  51. self.add_param_value(Param.IP_SOURCE_RANDOMIZE, 'False')
  52. self.add_param_value(Param.MAC_SOURCE, self.statistics.get_mac_address(most_used_ip_address))
  53. random_ip_address = self.statistics.get_random_ip_address()
  54. # ip-dst should be valid and not equal to ip.src
  55. while not self.is_valid_ip_address(random_ip_address) or random_ip_address==most_used_ip_address:
  56. random_ip_address = self.statistics.get_random_ip_address()
  57. self.add_param_value(Param.IP_DESTINATION, random_ip_address)
  58. destination_mac = self.statistics.get_mac_address(random_ip_address)
  59. if isinstance(destination_mac, list) and len(destination_mac) == 0:
  60. destination_mac = self.generate_random_mac_address()
  61. self.add_param_value(Param.MAC_DESTINATION, destination_mac)
  62. self.add_param_value(Param.PORT_DESTINATION, self.get_ports_from_nmap_service_dst(1000))
  63. self.add_param_value(Param.PORT_OPEN, '1')
  64. self.add_param_value(Param.PORT_DEST_SHUFFLE, 'False')
  65. self.add_param_value(Param.PORT_DEST_ORDER_DESC, 'False')
  66. self.add_param_value(Param.PORT_SOURCE, randint(1024, 65535))
  67. self.add_param_value(Param.PORT_SOURCE_RANDOMIZE, 'False')
  68. self.add_param_value(Param.PACKETS_PER_SECOND,
  69. (self.statistics.get_pps_sent(most_used_ip_address) +
  70. self.statistics.get_pps_received(most_used_ip_address)) / 2)
  71. self.add_param_value(Param.INJECT_AFTER_PACKET, randint(0, self.statistics.get_packet_count()))
  72. def get_ports_from_nmap_service_dst(self, ports_num):
  73. """
  74. Read the most ports_num frequently open ports from nmap-service-tcp file to be used in the port scan.
  75. :return: Ports numbers to be used as default destination ports or default open ports in the port scan.
  76. """
  77. ports_dst = []
  78. spamreader = csv.reader(open('resources/nmap-services-tcp.csv', 'rt'), delimiter=',')
  79. for count in range(ports_num):
  80. # escape first row (header)
  81. next(spamreader)
  82. # save ports numbers
  83. ports_dst.append(next(spamreader)[0])
  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, 9):
  89. temp_array[count] = ports_dst[count * 100:count * 100 + 99]
  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, context):
  97. def update_timestamp(timestamp, pps, delay=0):
  98. """
  99. Calculates the next timestamp to be used based on the packet per second rate (pps) and the maximum delay.
  100. :return: Timestamp to be used for the next packet.
  101. """
  102. if delay == 0:
  103. # Calculate request timestamp
  104. # To imitate the bursty behavior of traffic
  105. randomdelay = Lea.fromValFreqsDict({1 / pps: 70, 2 / pps: 20, 5 / pps: 7, 10 / pps: 3})
  106. return timestamp + uniform(1/pps , randomdelay.random())
  107. else:
  108. # Calculate reply timestamp
  109. randomdelay = Lea.fromValFreqsDict({2*delay: 70, 3*delay: 20, 5*delay: 7, 10*delay: 3})
  110. return timestamp + uniform(1 / pps + delay, 1 / pps + randomdelay.random())
  111. def getIntervalPPS(complement_interval_pps, timestamp):
  112. """
  113. Gets the packet rate (pps) for a specific time interval.
  114. :param complement_interval_pps: an array of tuples (the last timestamp in the interval, the packet rate in the crresponding interval).
  115. :param timestamp: the timestamp at which the packet rate is required.
  116. :return: the corresponding packet rate (pps) .
  117. """
  118. for row in complement_interval_pps:
  119. if timestamp<=row[0]:
  120. return row[1]
  121. return complement_interval_pps[-1][1] # in case the timstamp > capture max timestamp
  122. mac_source = self.get_param_value(Param.MAC_SOURCE)
  123. mac_destination = self.get_param_value(Param.MAC_DESTINATION)
  124. pps = self.get_param_value(Param.PACKETS_PER_SECOND)
  125. # Calculate complement packet rates of the background traffic for each interval
  126. complement_interval_pps = self.statistics.calculate_complement_packet_rates(pps)
  127. # Determine ports
  128. dest_ports = self.get_param_value(Param.PORT_DESTINATION)
  129. if self.get_param_value(Param.PORT_DEST_ORDER_DESC):
  130. dest_ports.reverse()
  131. elif self.get_param_value(Param.PORT_DEST_SHUFFLE):
  132. shuffle(dest_ports)
  133. if self.get_param_value(Param.PORT_SOURCE_RANDOMIZE):
  134. sport = randint(1, 65535)
  135. else:
  136. sport = self.get_param_value(Param.PORT_SOURCE)
  137. # Timestamp
  138. timestamp_next_pkt = self.get_param_value(Param.INJECT_AT_TIMESTAMP)
  139. # store start time of attack
  140. self.attack_start_utime = timestamp_next_pkt
  141. timestamp_prv_reply, timestamp_confirm = 0,0
  142. # Initialize parameters
  143. packets = []
  144. ip_source = self.get_param_value(Param.IP_SOURCE)
  145. ip_destination = self.get_param_value(Param.IP_DESTINATION)
  146. # Check ip.src == ip.dst
  147. self.ip_src_dst_equal_check(ip_source, ip_destination)
  148. # Select open ports
  149. ports_open = self.get_param_value(Param.PORT_OPEN)
  150. if ports_open == 1: # user did not specify open ports
  151. # the ports that were already used by ip.dst (direction in) in the background traffic are open ports
  152. ports_used_by_ip_dst = self.statistics.process_db_query(
  153. "SELECT portNumber FROM ip_ports WHERE portDirection='in' AND ipAddress='" + ip_destination + "'")
  154. if ports_used_by_ip_dst:
  155. ports_open = ports_used_by_ip_dst
  156. else: # if no ports were retrieved from database
  157. # Take open ports from nmap-service file
  158. #ports_temp = self.get_ports_from_nmap_service_dst(100)
  159. #ports_open = ports_temp[0:randint(1,10)]
  160. # OR take open ports from the most used ports in traffic statistics
  161. ports_open = self.statistics.process_db_query(
  162. "SELECT portNumber FROM ip_ports GROUP BY portNumber ORDER BY SUM(portCount) DESC LIMIT "+str(randint(1,10)))
  163. # in case of one open port, convert ports_open to array
  164. if not isinstance(ports_open, list):
  165. ports_open = [ports_open]
  166. # Set MSS (Maximum Segment Size) based on MSS distribution of IP address
  167. source_mss_dist = self.statistics.get_mss_distribution(ip_source)
  168. if len(source_mss_dist) > 0:
  169. source_mss_prob_dict = Lea.fromValFreqsDict(source_mss_dist)
  170. source_mss_value = source_mss_prob_dict.random()
  171. else:
  172. source_mss_value = self.statistics.process_db_query("most_used(mssValue)")
  173. destination_mss_dist = self.statistics.get_mss_distribution(ip_destination)
  174. if len(destination_mss_dist) > 0:
  175. destination_mss_prob_dict = Lea.fromValFreqsDict(destination_mss_dist)
  176. destination_mss_value = destination_mss_prob_dict.random()
  177. else:
  178. destination_mss_value = self.statistics.process_db_query("most_used(mssValue)")
  179. # Set TTL based on TTL distribution of IP address
  180. source_ttl_dist = self.statistics.get_ttl_distribution(ip_source)
  181. if len(source_ttl_dist) > 0:
  182. source_ttl_prob_dict = Lea.fromValFreqsDict(source_ttl_dist)
  183. source_ttl_value = source_ttl_prob_dict.random()
  184. else:
  185. source_ttl_value = self.statistics.process_db_query("most_used(ttlValue)")
  186. destination_ttl_dist = self.statistics.get_ttl_distribution(ip_destination)
  187. if len(destination_ttl_dist) > 0:
  188. destination_ttl_prob_dict = Lea.fromValFreqsDict(destination_ttl_dist)
  189. destination_ttl_value = destination_ttl_prob_dict.random()
  190. else:
  191. destination_ttl_value = self.statistics.process_db_query("most_used(ttlValue)")
  192. # Set Window Size based on Window Size distribution of IP address
  193. source_win_dist = self.statistics.get_win_distribution(ip_source)
  194. if len(source_win_dist) > 0:
  195. source_win_prob_dict = Lea.fromValFreqsDict(source_win_dist)
  196. source_win_value = source_win_prob_dict.random()
  197. else:
  198. source_win_value = self.statistics.process_db_query("most_used(winSize)")
  199. destination_win_dist = self.statistics.get_win_distribution(ip_destination)
  200. if len(destination_win_dist) > 0:
  201. destination_win_prob_dict = Lea.fromValFreqsDict(destination_win_dist)
  202. destination_win_value = destination_win_prob_dict.random()
  203. else:
  204. destination_win_value = self.statistics.process_db_query("most_used(winSize)")
  205. minDelay,maxDelay = self.get_reply_delay(ip_destination)
  206. for dport in dest_ports:
  207. # Parameters changing each iteration
  208. if self.get_param_value(Param.IP_SOURCE_RANDOMIZE) and isinstance(ip_source, list):
  209. ip_source = choice(ip_source)
  210. # 1) Build request package
  211. request_ether = Ether(src=mac_source, dst=mac_destination)
  212. request_ip = IP(src=ip_source, dst=ip_destination, ttl=source_ttl_value)
  213. # Random src port for each packet
  214. sport = randint(1, 65535)
  215. request_tcp = TCP(sport=sport, dport=dport, window= source_win_value, flags='S', options=[('MSS', source_mss_value)])
  216. request = (request_ether / request_ip / request_tcp)
  217. request.time = timestamp_next_pkt
  218. # Append request
  219. packets.append(request)
  220. # 2) Build reply (for open ports) package
  221. if dport in ports_open: # destination port is OPEN
  222. reply_ether = Ether(src=mac_destination, dst=mac_source)
  223. reply_ip = IP(src=ip_destination, dst=ip_source, ttl=destination_ttl_value, flags='DF')
  224. reply_tcp = TCP(sport=dport, dport=sport, seq=0, ack=1, flags='SA', window=destination_win_value,
  225. options=[('MSS', destination_mss_value)])
  226. reply = (reply_ether / reply_ip / reply_tcp)
  227. timestamp_reply = update_timestamp(timestamp_next_pkt,pps,minDelay)
  228. while (timestamp_reply <= timestamp_prv_reply):
  229. timestamp_reply = update_timestamp(timestamp_prv_reply,pps,minDelay)
  230. timestamp_prv_reply = timestamp_reply
  231. reply.time = timestamp_reply
  232. packets.append(reply)
  233. # requester confirms
  234. confirm_ether = request_ether
  235. confirm_ip = request_ip
  236. confirm_tcp = TCP(sport=sport, dport=dport, seq=1, window=0, flags='R')
  237. confirm = (confirm_ether / confirm_ip / confirm_tcp)
  238. timestamp_confirm = update_timestamp(timestamp_reply,pps,minDelay)
  239. confirm.time = timestamp_confirm
  240. packets.append(confirm)
  241. # else: destination port is NOT OPEN -> no reply is sent by target
  242. pps = max(getIntervalPPS(complement_interval_pps, timestamp_next_pkt),10)
  243. timestamp_next_pkt = update_timestamp(timestamp_next_pkt, pps)
  244. # store end time of attack
  245. self.attack_end_utime = packets[-1].time
  246. # write attack packets to pcap
  247. pcap_path = self.write_attack_pcap(sorted(packets, key=lambda pkt: pkt.time))
  248. # return packets sorted by packet time_sec_start
  249. return len(packets), pcap_path