PortscanAttack.py 17 KB

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