PortscanAttack.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  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. shuffle(ports_dst)
  36. port_dst_shuffled = ports_dst
  37. return port_dst_shuffled
  38. def is_valid_ip_address(self,addr):
  39. """
  40. Check if the IP address family is suported.
  41. :param addr: IP address to be checked
  42. :return: Boolean
  43. """
  44. try:
  45. socket.inet_aton(addr)
  46. return True
  47. except socket.error:
  48. return False
  49. def __init__(self, statistics, pcap_file_path):
  50. """
  51. Creates a new instance of the PortscanAttack.
  52. :param statistics: A reference to the statistics class.
  53. """
  54. # Initialize attack
  55. super(PortscanAttack, self).__init__(statistics, "Portscan Attack", "Injects a nmap 'regular scan'",
  56. "Scanning/Probing")
  57. # Define allowed parameters and their type
  58. self.supported_params = {
  59. Param.IP_SOURCE: ParameterTypes.TYPE_IP_ADDRESS,
  60. Param.IP_DESTINATION: ParameterTypes.TYPE_IP_ADDRESS,
  61. Param.PORT_SOURCE: ParameterTypes.TYPE_PORT,
  62. Param.PORT_DESTINATION: ParameterTypes.TYPE_PORT,
  63. Param.PORT_OPEN: ParameterTypes.TYPE_PORT,
  64. Param.MAC_SOURCE: ParameterTypes.TYPE_MAC_ADDRESS,
  65. Param.MAC_DESTINATION: ParameterTypes.TYPE_MAC_ADDRESS,
  66. Param.INJECT_AT_TIMESTAMP: ParameterTypes.TYPE_FLOAT,
  67. Param.INJECT_AFTER_PACKET: ParameterTypes.TYPE_PACKET_POSITION,
  68. Param.PORT_DEST_SHUFFLE: ParameterTypes.TYPE_BOOLEAN,
  69. Param.PORT_DEST_ORDER_DESC: ParameterTypes.TYPE_BOOLEAN,
  70. Param.IP_SOURCE_RANDOMIZE: ParameterTypes.TYPE_BOOLEAN,
  71. Param.PACKETS_PER_SECOND: ParameterTypes.TYPE_FLOAT,
  72. Param.PORT_SOURCE_RANDOMIZE: ParameterTypes.TYPE_BOOLEAN
  73. }
  74. # PARAMETERS: initialize with default values
  75. # (values are overwritten if user specifies them)
  76. most_used_ip_address = self.statistics.get_most_used_ip_address()
  77. if isinstance(most_used_ip_address, list):
  78. most_used_ip_address = most_used_ip_address[0]
  79. self.add_param_value(Param.IP_SOURCE, most_used_ip_address)
  80. self.add_param_value(Param.IP_SOURCE_RANDOMIZE, 'False')
  81. self.add_param_value(Param.MAC_SOURCE, self.statistics.get_mac_address(most_used_ip_address))
  82. random_ip_address = self.statistics.get_random_ip_address()
  83. # Aidmar - ip-dst should be valid and not equal to ip.src
  84. while not self.is_valid_ip_address(random_ip_address) or random_ip_address==most_used_ip_address:
  85. random_ip_address = self.statistics.get_random_ip_address()
  86. self.add_param_value(Param.IP_DESTINATION, random_ip_address)
  87. destination_mac = self.statistics.get_mac_address(random_ip_address)
  88. if isinstance(destination_mac, list) and len(destination_mac) == 0:
  89. destination_mac = self.generate_random_mac_address()
  90. self.add_param_value(Param.MAC_DESTINATION, destination_mac)
  91. self.add_param_value(Param.PORT_DESTINATION, self.get_ports_from_nmap_service_dst(1000))
  92. #self.add_param_value(Param.PORT_DESTINATION, '1-1023,1720,1900,8080,56652')
  93. # Not used initial value
  94. self.add_param_value(Param.PORT_OPEN, '1,11,111,1111')
  95. self.add_param_value(Param.PORT_DEST_SHUFFLE, 'False')
  96. self.add_param_value(Param.PORT_DEST_ORDER_DESC, 'False')
  97. self.add_param_value(Param.PORT_SOURCE, randint(1024, 65535))
  98. self.add_param_value(Param.PORT_SOURCE_RANDOMIZE, 'False')
  99. # Aidamr - we used pps for sent packets, so no need to include received packets rate
  100. # most used ip not necessary provide a realsitic packet rate for portscan attack
  101. # calculating the pps is not accurate (taking the whole capture duration into account ingnores the intermittent
  102. # of packets flow)
  103. #self.add_param_value(Param.PACKETS_PER_SECOND,
  104. # (self.statistics.get_pps_sent(most_used_ip_address) +
  105. # self.statistics.get_pps_received(most_used_ip_address)) / 2)
  106. # Aidmar
  107. # using nmap empirically observed packet rate [0,300] packet per second
  108. self.add_param_value(Param.PACKETS_PER_SECOND,300)
  109. self.add_param_value(Param.INJECT_AFTER_PACKET, randint(0, self.statistics.get_packet_count()))
  110. def generate_attack_pcap(self):
  111. def update_timestamp(timestamp, pps, maxdelay):
  112. """
  113. Calculates the next timestamp to be used based on the packet per second rate (pps) and the maximum delay.
  114. :return: Timestamp to be used for the next packet.
  115. """
  116. # Aidmar - why to use 0.1/pps?
  117. # return timestamp + uniform(0.1 / pps, maxdelay)
  118. # Aidmar
  119. return timestamp + uniform(1 / pps, maxdelay)
  120. # Determine ports
  121. dest_ports = self.get_param_value(Param.PORT_DESTINATION)
  122. if self.get_param_value(Param.PORT_DEST_ORDER_DESC):
  123. dest_ports.reverse()
  124. elif self.get_param_value(Param.PORT_DEST_SHUFFLE):
  125. shuffle(dest_ports)
  126. if self.get_param_value(Param.PORT_SOURCE_RANDOMIZE):
  127. # Aidmar
  128. sport = randint(1, 65535)
  129. #sport = randint(0, 65535)
  130. else:
  131. sport = self.get_param_value(Param.PORT_SOURCE)
  132. # Timestamp
  133. timestamp_next_pkt = self.get_param_value(Param.INJECT_AT_TIMESTAMP)
  134. # store start time of attack
  135. self.attack_start_utime = timestamp_next_pkt
  136. # Initialize parameters
  137. packets = []
  138. ip_source = self.get_param_value(Param.IP_SOURCE)
  139. ip_destination = self.get_param_value(Param.IP_DESTINATION)
  140. # Aidmar - check ip.src == ip.dst
  141. if ip_source == ip_destination:
  142. print("\nERROR: Invalid IP addresses; source IP is the same as destination IP: " + ip_source + ".")
  143. import sys
  144. sys.exit(0)
  145. mac_source = self.get_param_value(Param.MAC_SOURCE)
  146. mac_destination = self.get_param_value(Param.MAC_DESTINATION)
  147. pps = self.get_param_value(Param.PACKETS_PER_SECOND)
  148. # Aidmar - unjustified distribution
  149. #randomdelay = Lea.fromValFreqsDict({1 / pps: 70, 2 / pps: 30, 5 / pps: 15, 10 / pps: 3})
  150. # nmap empirically observed distribution
  151. randomdelay = Lea.fromValFreqsDict({1 / pps: 50, 10 / pps: 50}) #2 / pps: 10, 5 / pps: 10,
  152. #maxdelay = randomdelay.random()
  153. # open ports
  154. # Aidmar
  155. ports_open = self.get_param_value(Param.PORT_OPEN)
  156. if ports_open == [1,11,111,1111]: # user did not define open ports
  157. # the ports that were already used by ip.dst (direction in) in the background traffic are open ports
  158. ports_used_by_ip_dst = None #self.statistics.process_db_query(
  159. #"SELECT portNumber FROM ip_ports WHERE portDirection='in' AND ipAddress='" + ip_destination + "'")
  160. if ports_used_by_ip_dst:
  161. ports_open = ports_used_by_ip_dst
  162. print("\nPorts used by %s: %s" % (ip_destination, ports_open))
  163. else: # if no ports were retrieved from database
  164. # Take open ports from nmap-service file
  165. #ports_temp = self.get_ports_from_nmap_service_dst(100)
  166. #ports_open = ports_temp[0:randint(1,10)]
  167. # OR take open ports from the most used ports in traffic statistics
  168. ports_open = self.statistics.process_db_query(
  169. "SELECT portNumber FROM ip_ports GROUP BY portNumber ORDER BY SUM(portCount) DESC LIMIT "+str(randint(1,10)))
  170. print("\nPorts retrieved from statistics: %s" % (ports_open))
  171. # in case of one open port, convert ports_open to array
  172. if not isinstance(ports_open, list):
  173. ports_open = [ports_open]
  174. # =========================================================================================================
  175. # MSS (Maximum Segment Size) for Ethernet. Allowed values [536,1500]
  176. # Aidmar
  177. mss_dst = self.statistics.get_most_used_mss(ip_destination)
  178. if mss_dst is None:
  179. mss_dst = self.statistics.process_db_query("most_used(mssValue)")
  180. mss_src = self.statistics.get_most_used_mss(ip_source)
  181. if mss_src is None:
  182. mss_src = self.statistics.process_db_query("most_used(mssValue)")
  183. # mss = self.statistics.get_mss(ip_destination)
  184. # =========================================================================================================
  185. # Set TTL based on TTL distribution of IP address
  186. ttl_dist = self.statistics.get_ttl_distribution(ip_source)
  187. if len(ttl_dist) > 0:
  188. ttl_prob_dict = Lea.fromValFreqsDict(ttl_dist)
  189. ttl_value = ttl_prob_dict.random()
  190. else:
  191. ttl_value = self.statistics.process_db_query("most_used(ttlValue)")
  192. # Aidmar
  193. replies = []
  194. for dport in dest_ports:
  195. # Aidmar - differnt maxdelay for each packet
  196. maxdelay = randomdelay.random()
  197. # Parameters changing each iteration
  198. if self.get_param_value(Param.IP_SOURCE_RANDOMIZE) and isinstance(ip_source, list):
  199. ip_source = choice(ip_source)
  200. # 1) Build request package
  201. request_ether = Ether(src=mac_source, dst=mac_destination)
  202. request_ip = IP(src=ip_source, dst=ip_destination, ttl=ttl_value)
  203. # Aidmar - random src port for each packet
  204. sport = randint(1, 65535)
  205. # Aidmar - use most used window size
  206. win_size = self.statistics.process_db_query("most_used(winSize)")
  207. request_tcp = TCP(sport=sport, dport=dport, window=win_size, flags='S', options=[('MSS', mss_src)])
  208. # =========================================================================================================
  209. request = (request_ether / request_ip / request_tcp)
  210. # first packet uses timestamp provided by attack parameter Param.INJECT_AT_TIMESTAMP
  211. """if len(packets) > 0:
  212. timestamp_next_pkt = update_timestamp(timestamp_next_pkt, pps, maxdelay)
  213. request.time = timestamp_next_pkt
  214. """
  215. # Aidmar - mimic DDoS attack style: put update_timestamp at the end of the loop
  216. request.time = timestamp_next_pkt
  217. # 2) Build reply package
  218. if dport in ports_open: # destination port is OPEN
  219. reply_ether = Ether(src=mac_destination, dst=mac_source)
  220. reply_ip = IP(src=ip_destination, dst=ip_source, flags='DF')
  221. #if mss_dst is None:
  222. # reply_tcp = TCP(sport=dport, dport=sport, seq=0, ack=1, flags='SA', window=29200)
  223. #else:
  224. reply_tcp = TCP(sport=dport, dport=sport, seq=0, ack=1, flags='SA', window=29200,
  225. options=[('MSS', mss_dst)])
  226. reply = (reply_ether / reply_ip / reply_tcp)
  227. # Aidmar - edit name timestamp_reply
  228. timestamp_reply = update_timestamp(timestamp_next_pkt, pps, maxdelay) # TO-DO
  229. if len(replies) > 0:
  230. last_reply_timestamp = replies[-1].time
  231. timestamp_reply = timestamp_next_pkt
  232. while (timestamp_reply <= last_reply_timestamp):
  233. timestamp_reply = update_timestamp(timestamp_reply, pps, maxdelay)
  234. else:
  235. timestamp_reply = update_timestamp(timestamp_next_pkt, pps, maxdelay)
  236. reply.time = timestamp_reply
  237. replies.append(reply)
  238. # requester confirms
  239. # TO-DO: confirms should be in Attacker queue not in victim (reply) queue
  240. confirm_ether = request_ether
  241. confirm_ip = request_ip
  242. confirm_tcp = TCP(sport=sport, dport=dport, seq=1, window=0, flags='R')
  243. reply = (confirm_ether / confirm_ip / confirm_tcp)
  244. # Aidmar - edit name timestamp_confirm
  245. timestamp_confirm = update_timestamp(timestamp_reply, pps, maxdelay) # TO-DO
  246. reply.time = timestamp_confirm
  247. replies.append(reply)
  248. # else: destination port is NOT OPEN -> no reply is sent by target
  249. # Aidmar
  250. # Append reply
  251. if replies:
  252. while timestamp_next_pkt >= replies[0].time:
  253. packets.append(replies[0])
  254. replies.remove(replies[0])
  255. if len(replies) == 0:
  256. break
  257. # Append request
  258. packets.append(request)
  259. timestamp_next_pkt = update_timestamp(timestamp_next_pkt, pps, maxdelay)
  260. # Requests are sent all, send all replies
  261. if len(replies)>0:
  262. for reply in replies:
  263. packets.append(reply)
  264. # store end time of attack
  265. self.attack_end_utime = packets[-1].time
  266. # write attack packets to pcap
  267. pcap_path = self.write_attack_pcap(sorted(packets, key=lambda pkt: pkt.time))
  268. # return packets sorted by packet time_sec_start
  269. return len(packets), pcap_path