PortscanAttack.py 15 KB

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