BaseAttack.py 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851
  1. import abc
  2. import csv
  3. import ipaddress
  4. import os
  5. import random
  6. import random as rnd
  7. import re
  8. import socket
  9. import sys
  10. import tempfile
  11. import time
  12. # TODO: double check this import
  13. # does it complain because libpcapreader is not a .py?
  14. import ID2TLib.libpcapreader as pr
  15. import lea
  16. import numpy as np
  17. import scapy.layers.inet as inet
  18. import scapy.utils
  19. import Attack.AttackParameters as atkParam
  20. import ID2TLib.Utility as Util
  21. class BaseAttack(metaclass=abc.ABCMeta):
  22. """
  23. Abstract base class for all attack classes. Provides basic functionalities, like parameter validation.
  24. """
  25. def __init__(self, name, description, attack_type):
  26. """
  27. To be called within the individual attack class to initialize the required parameters.
  28. :param name: The name of the attack class.
  29. :param description: A short description of the attack.
  30. :param attack_type: The type the attack belongs to, like probing/scanning, malware.
  31. """
  32. # Reference to statistics class
  33. self.statistics = None
  34. # Class fields
  35. self.attack_name = name
  36. self.attack_description = description
  37. self.attack_type = attack_type
  38. self.params = {}
  39. self.supported_params = {}
  40. self.attack_start_utime = 0
  41. self.attack_end_utime = 0
  42. self.start_time = 0
  43. self.finish_time = 0
  44. self.packets = []
  45. self.path_attack_pcap = ""
  46. def set_statistics(self, statistics):
  47. """
  48. Specify the statistics object that will be used to calculate the parameters of this attack.
  49. The statistics are used to calculate default parameters and to process user supplied
  50. queries.
  51. :param statistics: Reference to a statistics object.
  52. """
  53. self.statistics = statistics
  54. @abc.abstractmethod
  55. def init_params(self):
  56. """
  57. Initialize all required parameters taking into account user supplied values. If no value is supplied,
  58. or if a user defined query is supplied, use a statistics object to do the calculations.
  59. A call to this function requires a call to 'set_statistics' first.
  60. """
  61. pass
  62. @abc.abstractmethod
  63. def generate_attack_packets(self):
  64. """
  65. Creates the attack packets.
  66. """
  67. pass
  68. @abc.abstractmethod
  69. def generate_attack_pcap(self):
  70. """
  71. Creates a pcap containing the attack packets.
  72. :return: The location of the generated pcap file.
  73. """
  74. pass
  75. ################################################
  76. # HELPER VALIDATION METHODS
  77. # Used to validate the given parameter values
  78. ################################################
  79. @staticmethod
  80. def _is_mac_address(mac_address: str):
  81. """
  82. Verifies if the given string is a valid MAC address.
  83. Accepts the formats 00:80:41:ae:fd:7e and 00-80-41-ae-fd-7e.
  84. :param mac_address: The MAC address as string.
  85. :return: True if the MAC address is valid, otherwise False.
  86. """
  87. pattern = re.compile('^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$', re.MULTILINE)
  88. if isinstance(mac_address, list):
  89. for mac in mac_address:
  90. if re.match(pattern, mac) is None:
  91. return False
  92. else:
  93. if re.match(pattern, mac_address) is None:
  94. return False
  95. return True
  96. @staticmethod
  97. def _is_ip_address(ip_address: str):
  98. """
  99. Verifies that the given string or list of IP addresses (strings) is a valid IPv4/IPv6 address.
  100. Accepts comma-separated lists of IP addresses, like "192.169.178.1, 192.168.178.2"
  101. :param ip_address: The IP address(es) as list of strings, comma-separated or dash-separated string.
  102. :return: True if all IP addresses are valid, otherwise False. And a list of IP addresses as string.
  103. """
  104. def append_ips(ip_address_input):
  105. """
  106. Recursive appending function to handle lists and ranges of IP addresses.
  107. :param ip_address_input: The IP address(es) as list of strings, comma-separated or dash-separated string.
  108. :return: List of all given IP addresses.
  109. """
  110. ip_list = []
  111. is_valid = True
  112. for ip in ip_address_input:
  113. if '-' in ip:
  114. ip_range = ip.split('-')
  115. ip_range = Util.get_ip_range(ip_range[0], ip_range[1])
  116. is_valid, ips = append_ips(ip_range)
  117. ip_list.extend(ips)
  118. else:
  119. try:
  120. ipaddress.ip_address(ip)
  121. ip_list.append(ip)
  122. except ValueError:
  123. return False, ip_list
  124. return is_valid, ip_list
  125. # a comma-separated list of IP addresses must be split first
  126. if isinstance(ip_address, str):
  127. ip_address = ip_address.split(',')
  128. result, ip_address_output = append_ips(ip_address)
  129. if len(ip_address_output) == 1:
  130. return result, ip_address_output[0]
  131. else:
  132. return result, ip_address_output
  133. @staticmethod
  134. def _is_port(ports_input: str):
  135. """
  136. Verifies if the given value is a valid port. Accepts port ranges, like 80-90, 80..99, 80...99.
  137. :param ports_input: The port number as int or string.
  138. :return: True if the port number is valid, otherwise False. If a single port or a comma-separated list of ports
  139. was given, a list of int is returned. If a port range was given, the range is resolved
  140. and a list of int is returned.
  141. """
  142. def _is_invalid_port(num):
  143. """
  144. Checks whether the port number is invalid.
  145. :param num: The port number as int.
  146. :return: True if the port number is invalid, otherwise False.
  147. """
  148. return num < 1 or num > 65535
  149. if ports_input is None or ports_input is "":
  150. return False
  151. if isinstance(ports_input, str):
  152. ports_input = ports_input.replace(' ', '').split(',')
  153. elif isinstance(ports_input, int):
  154. ports_input = [ports_input]
  155. elif len(ports_input) is 0:
  156. return False
  157. ports_output = []
  158. for port_entry in ports_input:
  159. if isinstance(port_entry, int):
  160. if _is_invalid_port(port_entry):
  161. return False
  162. ports_output.append(port_entry)
  163. # TODO: validate last condition
  164. elif isinstance(port_entry, str) and port_entry.isdigit():
  165. # port_entry describes a single port
  166. port_entry = int(port_entry)
  167. if _is_invalid_port(port_entry):
  168. return False
  169. ports_output.append(port_entry)
  170. elif '-' in port_entry or '..' in port_entry:
  171. # port_entry describes a port range
  172. # allowed format: '1-49151', '1..49151', '1...49151'
  173. match = re.match(r'^([0-9]{1,5})(?:-|\.{2,3})([0-9]{1,5})$', str(port_entry))
  174. # check validity of port range
  175. # and create list of ports derived from given start and end port
  176. (port_start, port_end) = int(match.group(1)), int(match.group(2))
  177. if _is_invalid_port(port_start) or _is_invalid_port(port_end):
  178. return False
  179. else:
  180. ports_list = [i for i in range(port_start, port_end + 1)]
  181. # append ports at ports_output list
  182. ports_output += ports_list
  183. if len(ports_output) == 1:
  184. return True, ports_output[0]
  185. else:
  186. return True, ports_output
  187. @staticmethod
  188. def _is_timestamp(timestamp: str):
  189. """
  190. Checks whether the given value is in a valid timestamp format. The accepted format is:
  191. YYYY-MM-DD h:m:s, whereas h, m, s may be one or two digits.
  192. :param timestamp: The timestamp to be checked.
  193. :return: True if the timestamp is valid, otherwise False.
  194. """
  195. is_valid = re.match(r'[0-9]{4}(?:-[0-9]{1,2}){2} (?:[0-9]{1,2}:){2}[0-9]{1,2}', timestamp)
  196. return is_valid is not None
  197. @staticmethod
  198. def _is_boolean(value):
  199. """
  200. Checks whether the given value (string or bool) is a boolean. Strings are valid booleans if they are in:
  201. {y, yes, t, true, on, 1, n, no, f, false, off, 0}.
  202. :param value: The value to be checked.
  203. :return: True if the value is a boolean, otherwise false. And the casted boolean.
  204. """
  205. # If value is already a boolean
  206. if isinstance(value, bool):
  207. return True, value
  208. # If value is a string
  209. # True values are y, yes, t, true, on and 1;
  210. # False values are n, no, f, false, off and 0.
  211. # Raises ValueError if value is anything else.
  212. try:
  213. import distutils.core
  214. import distutils.util
  215. value = distutils.util.strtobool(value.lower())
  216. is_bool = True
  217. except ValueError:
  218. is_bool = False
  219. return is_bool, value
  220. @staticmethod
  221. def _is_float(value):
  222. """
  223. Checks whether the given value is a float.
  224. :param value: The value to be checked.
  225. :return: True if the value is a float, otherwise False. And the casted float.
  226. """
  227. try:
  228. value = float(value)
  229. return True, value
  230. except ValueError:
  231. return False, value
  232. @staticmethod
  233. def _is_domain(val: str):
  234. """
  235. Verifies that the given string is a valid URI.
  236. :param val: The URI as string.
  237. :return: True if URI is valid, otherwise False.
  238. """
  239. domain = re.match(r'^(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+$', val)
  240. return domain is not None
  241. @staticmethod
  242. def _is_filepath(val: str):
  243. """
  244. Verifies that the given string points to an existing file
  245. :param filepath: The filepath as string
  246. :return: True if the file at the given location exists, otherwise False
  247. """
  248. return os.path.isfile(val)
  249. @staticmethod
  250. def _is_comm_type(val: str):
  251. """
  252. Verifies that the given string is a valid communications type
  253. :param comm_type: the type of communication as a string
  254. :return: True if the given type is a valid communications type, otherwise False
  255. """
  256. comm_types = {"local", "external", "mixed"}
  257. return val in comm_types
  258. @staticmethod
  259. def _is_percentage(val: float):
  260. """
  261. Verifies that the given float value is a valid percentage, i.e. between 0 and 1
  262. :param percentage: the float to test for validity
  263. :return: True if the given type is a valid percentage, otherwise False
  264. """
  265. if val >= 0 and val <= 1:
  266. return True
  267. return False
  268. @staticmethod
  269. def _is_padding(val: int):
  270. """
  271. Verifies that the given int is a valid padding size, i.e. between 0 and 100
  272. :param padding: the padding to test for its size
  273. :return: True if the given type is valid padding, False otherwise
  274. """
  275. if val >= 0 and val <= 100:
  276. return True
  277. return False
  278. #########################################
  279. # HELPER METHODS
  280. #########################################
  281. @staticmethod
  282. def set_seed(seed: int):
  283. """
  284. :param seed: The random seed to be set.
  285. """
  286. if isinstance(seed, int):
  287. random.seed(seed)
  288. np.random.seed(seed)
  289. def set_start_time(self):
  290. """
  291. Set the current time as global starting time.
  292. """
  293. self.start_time = time.time()
  294. def set_finish_time(self):
  295. """
  296. Set the current time as global finishing time.
  297. """
  298. self.finish_time = time.time()
  299. def get_packet_generation_time(self):
  300. """
  301. :return difference between starting and finishing time.
  302. """
  303. return self.finish_time - self.start_time
  304. def add_param_value(self, param, value):
  305. """
  306. Adds the pair param : value to the dictionary of attack parameters. Prints and error message and skips the
  307. parameter if the validation fails.
  308. :param param: Name of the parameter that we wish to modify.
  309. :param value: The value we wish to assign to the specified parameter.
  310. :return: None.
  311. """
  312. # This function call is valid only if there is a statistics object available.
  313. if self.statistics is None:
  314. print('Error: Attack parameter added without setting a statistics object first.')
  315. exit(1)
  316. # by default no param is valid
  317. is_valid = False
  318. # get AttackParameters instance associated with param
  319. # for default values assigned in attack classes, like Parameter.PORT_OPEN
  320. if isinstance(param, atkParam.Parameter):
  321. param_name = param
  322. # for values given by user input, like port.open
  323. else:
  324. # Get Enum key of given string identifier
  325. param_name = atkParam.Parameter(param)
  326. # Get parameter type of attack's required_params
  327. param_type = self.supported_params.get(param_name)
  328. # Verify validity of given value with respect to parameter type
  329. if param_type is None:
  330. print('Parameter ' + str(param_name) + ' not available for chosen attack. Skipping parameter.')
  331. # If value is query -> get value from database
  332. elif self.statistics.is_query(value):
  333. value = self.statistics.process_db_query(value, False)
  334. if value is not None and value is not "":
  335. is_valid = True
  336. else:
  337. print('Error in given parameter value: ' + str(value) + '. Data could not be retrieved.')
  338. # Validate parameter depending on parameter's type
  339. elif param_type == atkParam.ParameterTypes.TYPE_IP_ADDRESS:
  340. is_valid, value = self._is_ip_address(value)
  341. elif param_type == atkParam.ParameterTypes.TYPE_PORT:
  342. is_valid, value = self._is_port(value)
  343. elif param_type == atkParam.ParameterTypes.TYPE_MAC_ADDRESS:
  344. is_valid = self._is_mac_address(value)
  345. elif param_type == atkParam.ParameterTypes.TYPE_INTEGER_POSITIVE:
  346. if isinstance(value, int) and int(value) >= 0:
  347. is_valid = True
  348. elif isinstance(value, str) and value.isdigit() and int(value) >= 0:
  349. is_valid = True
  350. value = int(value)
  351. elif param_type == atkParam.ParameterTypes.TYPE_STRING:
  352. if isinstance(value, str):
  353. is_valid = True
  354. elif param_type == atkParam.ParameterTypes.TYPE_FLOAT:
  355. is_valid, value = self._is_float(value)
  356. # this is required to avoid that the timestamp's microseconds of the first attack packet is '000000'
  357. # but microseconds are only chosen randomly if the given parameter does not already specify it
  358. # e.g. inject.at-timestamp=123456.987654 -> is not changed
  359. # e.g. inject.at-timestamp=123456 -> is changed to: 123456.[random digits]
  360. if param_name == atkParam.Parameter.INJECT_AT_TIMESTAMP and is_valid and ((value - int(value)) == 0):
  361. value = value + random.uniform(0, 0.999999)
  362. elif param_type == atkParam.ParameterTypes.TYPE_TIMESTAMP:
  363. is_valid = self._is_timestamp(value)
  364. elif param_type == atkParam.ParameterTypes.TYPE_BOOLEAN:
  365. is_valid, value = self._is_boolean(value)
  366. elif param_type == atkParam.ParameterTypes.TYPE_PACKET_POSITION:
  367. ts = pr.pcap_processor(self.statistics.pcap_filepath, "False").get_timestamp_mu_sec(int(value))
  368. if 0 <= int(value) <= self.statistics.get_packet_count() and ts >= 0:
  369. is_valid = True
  370. param_name = atkParam.Parameter.INJECT_AT_TIMESTAMP
  371. value = (ts / 1000000) # convert microseconds from getTimestampMuSec into seconds
  372. elif param_type == atkParam.ParameterTypes.TYPE_DOMAIN:
  373. is_valid = self._is_domain(value)
  374. elif param_type == atkParam.ParameterTypes.TYPE_FILEPATH:
  375. is_valid = self._is_filepath(value)
  376. elif param_type == atkParam.ParameterTypes.TYPE_COMM_TYPE:
  377. is_valid = self._is_comm_type(value)
  378. elif param_type == atkParam.ParameterTypes.TYPE_PERCENTAGE:
  379. is_valid, value = self._is_float(value)
  380. if is_valid and (
  381. param_name in {atkParam.Parameter.IP_REUSE_TOTAL, atkParam.Parameter.IP_REUSE_LOCAL, atkParam.Parameter.IP_REUSE_EXTERNAL}):
  382. is_valid = self._is_percentage(value)
  383. else:
  384. is_valid = False
  385. elif param_type == atkParam.ParameterTypes.TYPE_PADDING:
  386. if isinstance(value, int):
  387. is_valid = True
  388. elif isinstance(value, str) and value.isdigit():
  389. is_valid = True
  390. value = int(value)
  391. if is_valid:
  392. is_valid = self._is_padding(value)
  393. # add value iff validation was successful
  394. if is_valid:
  395. self.params[param_name] = value
  396. else:
  397. print("ERROR: Parameter " + str(param) + " or parameter value " + str(value) +
  398. " not valid. Skipping parameter.")
  399. def get_param_value(self, param: atkParam.Parameter):
  400. """
  401. Returns the parameter value for a given parameter.
  402. :param param: The parameter whose value is wanted.
  403. :return: The parameter's value.
  404. """
  405. return self.params.get(param)
  406. def check_parameters(self):
  407. """
  408. Checks whether all parameter values are defined. If a value is not defined, the application is terminated.
  409. However, this should not happen as all attack should define default parameter values.
  410. """
  411. # parameters which do not require default values
  412. non_obligatory_params = [atkParam.Parameter.INJECT_AFTER_PACKET, atkParam.Parameter.NUMBER_ATTACKERS]
  413. for param, param_type in self.supported_params.items():
  414. # checks whether all params have assigned values, INJECT_AFTER_PACKET must not be considered because the
  415. # timestamp derived from it is set to Parameter.INJECT_AT_TIMESTAMP
  416. if param not in self.params.keys() and param not in non_obligatory_params:
  417. print("\033[91mCRITICAL ERROR: Attack '" + self.attack_name + "' does not define the parameter '" +
  418. str(param) + "'.\n The attack must define default values for all parameters."
  419. + "\n Cannot continue attack generation.\033[0m")
  420. import sys
  421. sys.exit(0)
  422. def write_attack_pcap(self, packets: list, append_flag: bool = False, destination_path: str = None):
  423. """
  424. Writes the attack's packets into a PCAP file with a temporary filename.
  425. :return: The path of the written PCAP file.
  426. """
  427. # Only check params initially when attack generation starts
  428. if append_flag is False and destination_path is None:
  429. # Check if all req. parameters are set
  430. self.check_parameters()
  431. # Determine destination path
  432. if destination_path is not None and os.path.exists(destination_path):
  433. destination = destination_path
  434. else:
  435. temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.pcap')
  436. destination = temp_file.name
  437. # Write packets into pcap file
  438. pktdump = scapy.utils.PcapWriter(destination, append=append_flag)
  439. pktdump.write(packets)
  440. # Store pcap path and close file objects
  441. pktdump.close()
  442. return destination
  443. def get_reply_delay(self, ip_dst):
  444. """
  445. Gets the minimum and the maximum reply delay for all the connections of a specific IP.
  446. :param ip_dst: The IP to reterive its reply delay.
  447. :return minDelay: minimum delay
  448. :return maxDelay: maximum delay
  449. """
  450. result = self.statistics.process_db_query(
  451. "SELECT AVG(minDelay), AVG(maxDelay) FROM conv_statistics WHERE ipAddressB='" + ip_dst + "';")
  452. if result[0][0] and result[0][1]:
  453. min_delay = result[0][0]
  454. max_delay = result[0][1]
  455. else:
  456. all_min_delays = self.statistics.process_db_query("SELECT minDelay FROM conv_statistics LIMIT 500;")
  457. min_delay = np.median(all_min_delays)
  458. all_max_delays = self.statistics.process_db_query("SELECT maxDelay FROM conv_statistics LIMIT 500;")
  459. max_delay = np.median(all_max_delays)
  460. min_delay = int(min_delay) * 10 ** -6 # convert from micro to seconds
  461. max_delay = int(max_delay) * 10 ** -6
  462. return min_delay, max_delay
  463. @staticmethod
  464. def packets_to_convs(exploit_raw_packets):
  465. """
  466. Classifies a bunch of packets to conversations groups. A conversation is a set of packets go between host A
  467. (IP,port) to host B (IP,port)
  468. :param exploit_raw_packets: A set of packets contains several conversations.
  469. :return conversations: A set of arrays, each array contains the packet of specifc conversation
  470. :return orderList_conversations: An array contains the conversations ids (IP_A,port_A, IP_b,port_B) in the
  471. order they appeared in the original packets.
  472. """
  473. conversations = {}
  474. order_list_conversations = []
  475. for pkt_num, pkt in enumerate(exploit_raw_packets):
  476. eth_frame = inet.Ether(pkt[0])
  477. ip_pkt = eth_frame.payload
  478. ip_dst = ip_pkt.getfieldval("dst")
  479. ip_src = ip_pkt.getfieldval("src")
  480. tcp_pkt = ip_pkt.payload
  481. port_dst = tcp_pkt.getfieldval("dport")
  482. port_src = tcp_pkt.getfieldval("sport")
  483. conv_req = (ip_src, port_src, ip_dst, port_dst)
  484. conv_rep = (ip_dst, port_dst, ip_src, port_src)
  485. if conv_req not in conversations and conv_rep not in conversations:
  486. pkt_list = [pkt]
  487. conversations[conv_req] = pkt_list
  488. # Order list of conv
  489. order_list_conversations.append(conv_req)
  490. else:
  491. if conv_req in conversations:
  492. pkt_list = conversations[conv_req]
  493. pkt_list.append(pkt)
  494. conversations[conv_req] = pkt_list
  495. else:
  496. pkt_list = conversations[conv_rep]
  497. pkt_list.append(pkt)
  498. conversations[conv_rep] = pkt_list
  499. return conversations, order_list_conversations
  500. @staticmethod
  501. def is_valid_ip_address(addr):
  502. """
  503. Checks if the IP address family is supported.
  504. :param addr: IP address to be checked.
  505. :return: Boolean
  506. """
  507. try:
  508. socket.inet_aton(addr)
  509. return True
  510. except socket.error:
  511. return False
  512. @staticmethod
  513. def ip_src_dst_equal_check(ip_source, ip_destination):
  514. """
  515. Checks if the source IP and destination IP are equal.
  516. :param ip_source: source IP address.
  517. :param ip_destination: destination IP address.
  518. """
  519. equal = False
  520. if isinstance(ip_source, list):
  521. if ip_destination in ip_source:
  522. equal = True
  523. else:
  524. if ip_source == ip_destination:
  525. equal = True
  526. if equal:
  527. print("\nERROR: Invalid IP addresses; source IP is the same as destination IP: " + ip_destination + ".")
  528. sys.exit(0)
  529. @staticmethod
  530. def get_inter_arrival_time(packets, distribution: bool = False):
  531. """
  532. Gets the inter-arrival times array and its distribution of a set of packets.
  533. :param packets: the packets to extract their inter-arrival time.
  534. :param distribution: build distribution dictionary or not
  535. :return inter_arrival_times: array of the inter-arrival times
  536. :return dict: the inter-arrival time distribution as a histogram {inter-arrival time:frequency}
  537. """
  538. inter_arrival_times = []
  539. prvs_pkt_time = 0
  540. for index, pkt in enumerate(packets):
  541. timestamp = pkt[2][0] + pkt[2][1] / 10 ** 6
  542. if index == 0:
  543. prvs_pkt_time = timestamp
  544. inter_arrival_times.append(0)
  545. else:
  546. inter_arrival_times.append(timestamp - prvs_pkt_time)
  547. prvs_pkt_time = timestamp
  548. if distribution:
  549. # Build a distribution dictionary
  550. freq, values = np.histogram(inter_arrival_times, bins=20)
  551. dist_dict = {}
  552. for i, val in enumerate(values):
  553. if i < len(freq):
  554. dist_dict[str(val)] = freq[i]
  555. return inter_arrival_times, dist_dict
  556. else:
  557. return inter_arrival_times
  558. @staticmethod
  559. def clean_white_spaces(str_param):
  560. """
  561. Delete extra backslash from white spaces. This function is used to process the payload of packets.
  562. :param str_param: the payload to be processed.
  563. """
  564. str_param = str_param.replace("\\n", "\n")
  565. str_param = str_param.replace("\\r", "\r")
  566. str_param = str_param.replace("\\t", "\t")
  567. str_param = str_param.replace("\\\'", "\'")
  568. return str_param
  569. def modify_http_header(self, str_tcp_seg, orig_target_uri, target_uri, orig_ip_dst, target_host):
  570. """
  571. Substitute the URI and HOST in a HTTP header with new values.
  572. :param str_tcp_seg: the payload to be processed.
  573. :param orig_target_uri: old URI
  574. :param target_uri: new URI
  575. :param orig_ip_dst: old host
  576. :param target_host: new host
  577. """
  578. if len(str_tcp_seg) > 0:
  579. # convert payload bytes to str => str = "b'..\\r\\n..'"
  580. str_tcp_seg = str_tcp_seg[2:-1]
  581. str_tcp_seg = str_tcp_seg.replace(orig_target_uri, target_uri)
  582. str_tcp_seg = str_tcp_seg.replace(orig_ip_dst, target_host)
  583. str_tcp_seg = self.clean_white_spaces(str_tcp_seg)
  584. return str_tcp_seg
  585. def get_ip_data(self, ip_address: str):
  586. """
  587. :param ip_address: the ip of which (packet-)data shall be returned
  588. :return: MSS, TTL and Window Size values of the given IP
  589. """
  590. # Set MSS (Maximum Segment Size) based on MSS distribution of IP address
  591. mss_dist = self.statistics.get_mss_distribution(ip_address)
  592. if len(mss_dist) > 0:
  593. mss_prob_dict = lea.Lea.fromValFreqsDict(mss_dist)
  594. mss_value = mss_prob_dict.random()
  595. else:
  596. mss_value = Util.handle_most_used_outputs(self.statistics.process_db_query("most_used(mssValue)"))
  597. # Set TTL based on TTL distribution of IP address
  598. ttl_dist = self.statistics.get_ttl_distribution(ip_address)
  599. if len(ttl_dist) > 0:
  600. ttl_prob_dict = lea.Lea.fromValFreqsDict(ttl_dist)
  601. ttl_value = ttl_prob_dict.random()
  602. else:
  603. ttl_value = Util.handle_most_used_outputs(self.statistics.process_db_query("most_used(ttlValue)"))
  604. # Set Window Size based on Window Size distribution of IP address
  605. win_dist = self.statistics.get_win_distribution(ip_address)
  606. if len(win_dist) > 0:
  607. win_prob_dict = lea.Lea.fromValFreqsDict(win_dist)
  608. win_value = win_prob_dict.random()
  609. else:
  610. win_value = Util.handle_most_used_outputs(self.statistics.process_db_query("most_used(winSize)"))
  611. return mss_value, ttl_value, win_value
  612. #########################################
  613. # RANDOM IP/MAC ADDRESS GENERATORS
  614. #########################################
  615. @staticmethod
  616. def generate_random_ipv4_address(ip_class, n: int = 1):
  617. # TODO: document ip_class
  618. """
  619. Generates n random IPv4 addresses.
  620. :param ip_class:
  621. :param n: The number of IP addresses to be generated
  622. :return: A single IP address, or if n>1, a list of IP addresses
  623. """
  624. def is_invalid(ip_address_param: ipaddress.IPv4Address):
  625. return ip_address_param.is_multicast or ip_address_param.is_unspecified or ip_address_param.is_loopback or \
  626. ip_address_param.is_link_local or ip_address_param.is_reserved or ip_address_param.is_private
  627. # Generate a random IP from specific class
  628. def generate_address(ip_class_param):
  629. if ip_class_param == "Unknown":
  630. return ipaddress.IPv4Address(random.randint(0, 2 ** 32 - 1))
  631. else:
  632. # For DDoS attack, we do not generate private IPs
  633. if "private" in ip_class_param:
  634. ip_class_param = ip_class_param[0] # convert A-private to A
  635. ip_classes_byte1 = {"A": {1, 126}, "B": {128, 191}, "C": {192, 223}, "D": {224, 239}, "E": {240, 254}}
  636. temp = list(ip_classes_byte1[ip_class_param])
  637. min_b1 = temp[0]
  638. max_b1 = temp[1]
  639. b1 = random.randint(min_b1, max_b1)
  640. b2 = random.randint(1, 255)
  641. b3 = random.randint(1, 255)
  642. b4 = random.randint(1, 255)
  643. ip_address = ipaddress.IPv4Address(str(b1) + "." + str(b2) + "." + str(b3) + "." + str(b4))
  644. return ip_address
  645. ip_addresses = []
  646. for i in range(0, n):
  647. address = generate_address(ip_class)
  648. while is_invalid(address):
  649. address = generate_address(ip_class)
  650. ip_addresses.append(str(address))
  651. if n == 1:
  652. return ip_addresses[0]
  653. else:
  654. return ip_addresses
  655. @staticmethod
  656. def generate_random_ipv6_address(n: int = 1):
  657. """
  658. Generates n random IPv6 addresses.
  659. :param n: The number of IP addresses to be generated
  660. :return: A single IP address, or if n>1, a list of IP addresses
  661. """
  662. def is_invalid(ip_address: ipaddress.IPv6Address):
  663. return ip_address.is_multicast or ip_address.is_unspecified or ip_address.is_loopback or \
  664. ip_address.is_link_local or ip_address.is_private or ip_address.is_reserved
  665. def generate_address():
  666. return ipaddress.IPv6Address(random.randint(0, 2 ** 128 - 1))
  667. ip_addresses = []
  668. for i in range(0, n):
  669. address = generate_address()
  670. while is_invalid(address):
  671. address = generate_address()
  672. ip_addresses.append(str(address))
  673. if n == 1:
  674. return ip_addresses[0]
  675. else:
  676. return ip_addresses
  677. @staticmethod
  678. def generate_random_mac_address(n: int = 1):
  679. """
  680. Generates n random MAC addresses.
  681. :param n: The number of MAC addresses to be generated.
  682. :return: A single MAC addres, or if n>1, a list of MAC addresses
  683. """
  684. def is_invalid(address_param: str):
  685. first_octet = int(address_param[0:2], 16)
  686. is_multicast_address = bool(first_octet & 0b01)
  687. is_locally_administered = bool(first_octet & 0b10)
  688. return is_multicast_address or is_locally_administered
  689. def generate_address():
  690. # FIXME: cleanup
  691. mac = [random.randint(0x00, 0xff) for i in range(0, 6)]
  692. return ':'.join(map(lambda x: "%02x" % x, mac))
  693. mac_addresses = []
  694. for i in range(0, n):
  695. address = generate_address()
  696. while is_invalid(address):
  697. address = generate_address()
  698. mac_addresses.append(address)
  699. if n == 1:
  700. return mac_addresses[0]
  701. else:
  702. return mac_addresses
  703. @staticmethod
  704. def get_ports_from_nmap_service_dst(ports_num):
  705. """
  706. Read the most ports_num frequently open ports from nmap-service-tcp file to be used in the port scan.
  707. :return: Ports numbers to be used as default destination ports or default open ports in the port scan.
  708. """
  709. ports_dst = []
  710. file = open(Util.RESOURCE_DIR + 'nmap-services-tcp.csv', 'rt')
  711. spamreader = csv.reader(file, delimiter=',')
  712. for count in range(ports_num):
  713. # escape first row (header)
  714. next(spamreader)
  715. # save ports numbers
  716. ports_dst.append(next(spamreader)[0])
  717. file.close()
  718. # rnd.shuffle ports numbers partially
  719. if ports_num == 1000: # used for port.dst
  720. # FIXME: cleanup
  721. temp_array = [[0 for i in range(10)] for i in range(100)]
  722. port_dst_shuffled = []
  723. for count in range(0, 10):
  724. temp_array[count] = ports_dst[count * 100:(count + 1) * 100]
  725. rnd.shuffle(temp_array[count])
  726. port_dst_shuffled += temp_array[count]
  727. else: # used for port.open
  728. rnd.shuffle(ports_dst)
  729. port_dst_shuffled = ports_dst
  730. return port_dst_shuffled