BaseAttack.py 29 KB

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