PortscanAttack.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. import logging
  2. import csv
  3. import socket
  4. from random import shuffle, randint, choice, uniform
  5. from lea import Lea
  6. from Attack import BaseAttack
  7. from Attack.AttackParameters import Parameter as Param
  8. from Attack.AttackParameters import ParameterTypes
  9. logging.getLogger("scapy.runtime").setLevel(logging.ERROR)
  10. # noinspection PyPep8
  11. from scapy.layers.inet import IP, Ether, TCP
  12. class PortscanAttack(BaseAttack.BaseAttack):
  13. # Aidmar
  14. def get_ports_from_nmap_service_dst(self, ports_num):
  15. """
  16. Read the most ports_num frequently open ports from nmap-service-tcp file to be used in Portscan attack.
  17. :return: Ports numbers to be used as default dest ports or default open ports in Portscan attack.
  18. """
  19. ports_dst = []
  20. spamreader = csv.reader(open('nmap-services-tcp.csv', 'rt'), delimiter=',')
  21. for count in range(ports_num):
  22. # escape first row (header)
  23. next(spamreader)
  24. # save ports numbers
  25. ports_dst.append(next(spamreader)[0])
  26. # shuffle ports numbers
  27. if(ports_num==1000): # used for port.dst
  28. temp_array = [[0 for i in range(10)] for i in range(100)]
  29. port_dst_shuffled = []
  30. for count in range(0, 9):
  31. temp_array[count] = ports_dst[count * 100:count * 100 + 99]
  32. shuffle(temp_array[count])
  33. port_dst_shuffled += temp_array[count]
  34. else: # used for port.open
  35. port_dst_shuffled = shuffle(ports_dst)
  36. return port_dst_shuffled
  37. def is_valid_ip_address(self,addr):
  38. """
  39. Check if the IP address family is suported.
  40. :param addr: IP address to be checked
  41. :return: Boolean
  42. """
  43. try:
  44. socket.inet_aton(addr)
  45. return True
  46. except socket.error:
  47. return False
  48. def __init__(self, statistics, pcap_file_path):
  49. """
  50. Creates a new instance of the PortscanAttack.
  51. :param statistics: A reference to the statistics class.
  52. """
  53. # Initialize attack
  54. super(PortscanAttack, self).__init__(statistics, "Portscan Attack", "Injects a nmap 'regular scan'",
  55. "Scanning/Probing")
  56. # Define allowed parameters and their type
  57. self.supported_params = {
  58. Param.IP_SOURCE: ParameterTypes.TYPE_IP_ADDRESS,
  59. Param.IP_DESTINATION: ParameterTypes.TYPE_IP_ADDRESS,
  60. Param.PORT_SOURCE: ParameterTypes.TYPE_PORT,
  61. Param.PORT_DESTINATION: ParameterTypes.TYPE_PORT,
  62. Param.PORT_OPEN: ParameterTypes.TYPE_PORT,
  63. Param.MAC_SOURCE: ParameterTypes.TYPE_MAC_ADDRESS,
  64. Param.MAC_DESTINATION: ParameterTypes.TYPE_MAC_ADDRESS,
  65. Param.INJECT_AT_TIMESTAMP: ParameterTypes.TYPE_FLOAT,
  66. Param.INJECT_AFTER_PACKET: ParameterTypes.TYPE_PACKET_POSITION,
  67. Param.PORT_DEST_SHUFFLE: ParameterTypes.TYPE_BOOLEAN,
  68. Param.PORT_DEST_ORDER_DESC: ParameterTypes.TYPE_BOOLEAN,
  69. Param.IP_SOURCE_RANDOMIZE: ParameterTypes.TYPE_BOOLEAN,
  70. Param.PACKETS_PER_SECOND: ParameterTypes.TYPE_FLOAT,
  71. Param.PORT_SOURCE_RANDOMIZE: ParameterTypes.TYPE_BOOLEAN
  72. }
  73. # PARAMETERS: initialize with default values
  74. # (values are overwritten if user specifies them)
  75. most_used_ip_address = self.statistics.get_most_used_ip_address()
  76. if isinstance(most_used_ip_address, list):
  77. most_used_ip_address = most_used_ip_address[0]
  78. self.add_param_value(Param.IP_SOURCE, most_used_ip_address)
  79. self.add_param_value(Param.IP_SOURCE_RANDOMIZE, 'False')
  80. self.add_param_value(Param.MAC_SOURCE, self.statistics.get_mac_address(most_used_ip_address))
  81. random_ip_address = self.statistics.get_random_ip_address()
  82. # Aidmar
  83. while not self.is_valid_ip_address(random_ip_address):
  84. random_ip_address = self.statistics.get_random_ip_address()
  85. self.add_param_value(Param.IP_DESTINATION, random_ip_address)
  86. destination_mac = self.statistics.get_mac_address(random_ip_address)
  87. if isinstance(destination_mac, list) and len(destination_mac) == 0:
  88. destination_mac = self.generate_random_mac_address()
  89. self.add_param_value(Param.MAC_DESTINATION, destination_mac)
  90. self.add_param_value(Param.PORT_DESTINATION, self.get_ports_from_nmap_service_dst(1000))
  91. #self.add_param_value(Param.PORT_DESTINATION, '1-1023,1720,1900,8080,56652')
  92. # Not used initial value
  93. self.add_param_value(Param.PORT_OPEN, '1,11,111,1111')
  94. self.add_param_value(Param.PORT_DEST_SHUFFLE, 'False')
  95. self.add_param_value(Param.PORT_DEST_ORDER_DESC, 'False')
  96. self.add_param_value(Param.PORT_SOURCE, randint(1024, 65535))
  97. self.add_param_value(Param.PORT_SOURCE_RANDOMIZE, 'False')
  98. self.add_param_value(Param.PACKETS_PER_SECOND,
  99. (self.statistics.get_pps_sent(most_used_ip_address) +
  100. self.statistics.get_pps_received(most_used_ip_address)) / 2)
  101. self.add_param_value(Param.INJECT_AFTER_PACKET, randint(0, self.statistics.get_packet_count()))
  102. def generate_attack_pcap(self):
  103. def update_timestamp(timestamp, pps, maxdelay):
  104. """
  105. Calculates the next timestamp to be used based on the packet per second rate (pps) and the maximum delay.
  106. :return: Timestamp to be used for the next packet.
  107. """
  108. return timestamp + uniform(0.1 / pps, maxdelay)
  109. # Determine ports
  110. dest_ports = self.get_param_value(Param.PORT_DESTINATION)
  111. if self.get_param_value(Param.PORT_DEST_ORDER_DESC):
  112. dest_ports.reverse()
  113. elif self.get_param_value(Param.PORT_DEST_SHUFFLE):
  114. shuffle(dest_ports)
  115. if self.get_param_value(Param.PORT_SOURCE_RANDOMIZE):
  116. # Aidmar
  117. sport = randint(1, 65535)
  118. #sport = randint(0, 65535)
  119. else:
  120. sport = self.get_param_value(Param.PORT_SOURCE)
  121. # Timestamp
  122. timestamp_next_pkt = self.get_param_value(Param.INJECT_AT_TIMESTAMP)
  123. # store start time of attack
  124. self.attack_start_utime = timestamp_next_pkt
  125. # Initialize parameters
  126. packets = []
  127. ip_source = self.get_param_value(Param.IP_SOURCE)
  128. ip_destination = self.get_param_value(Param.IP_DESTINATION)
  129. mac_source = self.get_param_value(Param.MAC_SOURCE)
  130. mac_destination = self.get_param_value(Param.MAC_DESTINATION)
  131. pps = self.get_param_value(Param.PACKETS_PER_SECOND)
  132. randomdelay = Lea.fromValFreqsDict({1 / pps: 70, 2 / pps: 30, 5 / pps: 15, 10 / pps: 3})
  133. maxdelay = randomdelay.random()
  134. # open ports
  135. # Aidmar
  136. ports_open = self.get_param_value(Param.PORT_OPEN)
  137. if ports_open == [1,11,111,1111]: # user did not define open ports
  138. # the ports that were already used by ip.dst (direction in) in the background traffic are open ports
  139. ports_used_by_ip_dst = self.statistics.process_db_query(
  140. "SELECT portNumber FROM ip_ports WHERE portDirection='in' AND ipAddress='" + ip_destination + "';")
  141. if ports_used_by_ip_dst:
  142. ports_open = ports_used_by_ip_dst
  143. else: # if no ports were retrieved from database
  144. ports_open = self.get_ports_from_nmap_service_dst(randint(0,10))
  145. #print("\nPorts used by %s: %s" % (ip_destination, ports_open))
  146. # in case of one open port, convert ports_open to array
  147. if not isinstance(ports_open, list):
  148. ports_open = [ports_open]
  149. # =========================================================================================================
  150. # MSS (Maximum Segment Size) for Ethernet. Allowed values [536,1500]
  151. # Aidmar
  152. # mss = self.statistics.get_mss(ip_destination)
  153. mss_dst = self.statistics.get_mss(ip_destination)
  154. mss_src = self.statistics.get_mss(ip_source)
  155. # =========================================================================================================
  156. # Set TTL based on TTL distribution of IP address
  157. ttl_dist = self.statistics.get_ttl_distribution(ip_source)
  158. if len(ttl_dist) > 0:
  159. ttl_prob_dict = Lea.fromValFreqsDict(ttl_dist)
  160. ttl_value = ttl_prob_dict.random()
  161. else:
  162. ttl_value = self.statistics.process_db_query("most_used(ttlValue)")
  163. for dport in dest_ports:
  164. # Parameters changing each iteration
  165. if self.get_param_value(Param.IP_SOURCE_RANDOMIZE) and isinstance(ip_source, list):
  166. ip_source = choice(ip_source)
  167. # 1) Build request package
  168. request_ether = Ether(src=mac_source, dst=mac_destination)
  169. request_ip = IP(src=ip_source, dst=ip_destination, ttl=ttl_value)
  170. # Aidmar - random src port for each packet
  171. sport = randint(1, 65535)
  172. if mss_src is None:
  173. request_tcp = TCP(sport=sport, dport=dport, flags='S')
  174. else:
  175. request_tcp = TCP(sport=sport, dport=dport, flags='S', options=[('MSS', mss_src)])
  176. # =========================================================================================================
  177. request = (request_ether / request_ip / request_tcp)
  178. # first packet uses timestamp provided by attack parameter Param.INJECT_AT_TIMESTAMP
  179. if len(packets) > 0:
  180. timestamp_next_pkt = update_timestamp(timestamp_next_pkt, pps, maxdelay)
  181. request.time = timestamp_next_pkt
  182. packets.append(request)
  183. # 2) Build reply package
  184. if dport in ports_open: # destination port is OPEN
  185. reply_ether = Ether(src=mac_destination, dst=mac_source)
  186. reply_ip = IP(src=ip_destination, dst=ip_source, flags='DF')
  187. if mss_dst is None:
  188. reply_tcp = TCP(sport=dport, dport=sport, seq=0, ack=1, flags='SA', window=29200)
  189. else:
  190. reply_tcp = TCP(sport=dport, dport=sport, seq=0, ack=1, flags='SA', window=29200,
  191. options=[('MSS', mss_dst)])
  192. reply = (reply_ether / reply_ip / reply_tcp)
  193. timestamp_next_pkt = update_timestamp(timestamp_next_pkt, pps, maxdelay)
  194. reply.time = timestamp_next_pkt
  195. packets.append(reply)
  196. # requester confirms
  197. confirm_ether = request_ether
  198. confirm_ip = request_ip
  199. confirm_tcp = TCP(sport=sport, dport=dport, seq=1, window=0, flags='R')
  200. reply = (confirm_ether / confirm_ip / confirm_tcp)
  201. timestamp_next_pkt = update_timestamp(timestamp_next_pkt, pps, maxdelay)
  202. reply.time = timestamp_next_pkt
  203. packets.append(reply)
  204. # else: destination port is NOT OPEN -> no reply is sent by target
  205. # store end time of attack
  206. self.attack_end_utime = packets[-1].time
  207. # write attack packets to pcap
  208. pcap_path = self.write_attack_pcap(sorted(packets, key=lambda pkt: pkt.time))
  209. # return packets sorted by packet time_sec_start
  210. return len(packets), pcap_path