BaseAttack.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. import ipaddress
  2. import re
  3. from abc import abstractmethod, ABCMeta
  4. import ID2TLib.libpcapreader as pr
  5. from Attack import AttackParameters
  6. from Attack.AttackParameters import Parameter
  7. from Attack.AttackParameters import ParameterTypes
  8. class BaseAttack(metaclass=ABCMeta):
  9. """
  10. Abstract base class for all attack classes. Provides basic functionalities, like parameter validation.
  11. """
  12. def __init__(self, statistics, name, description, attack_type):
  13. """
  14. To be called within the individual attack class to initialize the required parameters.
  15. :param statistics: A reference to the Statistics class.
  16. :param name: The name of the attack class.
  17. :param description: A short description of the attack.
  18. :param attack_type: The type the attack belongs to, like probing/scanning, malware.
  19. """
  20. # Reference to statistics class
  21. self.statistics = statistics
  22. # Class fields
  23. self.attack_name = name
  24. self.attack_description = description
  25. self.attack_type = attack_type
  26. self.params = {}
  27. self.supported_params = {}
  28. self.attack_start_utime = 0
  29. self.attack_end_utime = 0
  30. @abstractmethod
  31. def get_packets(self):
  32. """
  33. Creates the packets containing the attack.
  34. :return: A list of packets ordered ascending by the packet's timestamp.
  35. """
  36. pass
  37. ################################################
  38. # HELPER VALIDATION METHODS
  39. # Used to validate the given parameter values
  40. ################################################
  41. @staticmethod
  42. def _is_mac_address(mac_address: str):
  43. """
  44. 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.
  45. :param mac_address: The MAC address as string.
  46. :return: True if the MAC address is valid, otherwise False.
  47. """
  48. result = re.match('^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$', mac_address, re.MULTILINE)
  49. return result is not None
  50. @staticmethod
  51. def _is_ip_address(ip_address: str):
  52. """
  53. Verifies if the given string is a valid IPv4/IPv6 address. Accepts comma-separated lists of IP addresses,
  54. like "192.169.178.1, 192.168.178.2"
  55. :param ip_address: The IP address as string.
  56. :return: True if all IP addresses are valid, otherwise False. And a list of IP addresses as string.
  57. """
  58. ip_address_output = []
  59. for ip in ip_address.split(','):
  60. try:
  61. ipaddress.ip_address(ip)
  62. ip_address_output.append(ip)
  63. except ValueError:
  64. return False, ip_address_output
  65. if len(ip_address_output) == 1:
  66. return True, ip_address_output[0]
  67. else:
  68. return True, ip_address_output
  69. @staticmethod
  70. def _is_port(ports_input: str):
  71. """
  72. Verifies if the given value is a valid port. Accepts port ranges, like 80-90, 80..99, 80...99.
  73. :param ports_input: The port number as int or string.
  74. :return: True if the port number is valid, otherwise False. If a port range was given, the range is resolved
  75. and a list of ports is additionally returned.
  76. """
  77. def _is_invalid_port(num):
  78. """
  79. Checks whether the port number is invalid.
  80. :param num: The port number as int.
  81. :return: True if the port number is invalid, otherwise False.
  82. """
  83. return num < 0 or num > 65535
  84. ports_input = ports_input.replace(' ', '').split(',')
  85. ports_output = []
  86. for port_entry in ports_input:
  87. if isinstance(port_entry, int):
  88. if _is_invalid_port(port_entry):
  89. return False
  90. ports_output.append(port_entry)
  91. elif isinstance(port_entry, str) and port_entry.isdigit():
  92. # port_entry describes a single port
  93. port_entry = int(port_entry)
  94. if _is_invalid_port(port_entry):
  95. return False
  96. ports_output.append(port_entry)
  97. elif '-' in port_entry or '..' in port_entry:
  98. # port_entry describes a port range
  99. # allowed format: '12-123', '12..123', '12...123'
  100. match = re.match('^([0-9]{1,4})(?:-|\.{2,3})([0-9]{1,4})$', port_entry)
  101. # check validity of port range
  102. # and create list of ports derived from given start and end port
  103. (port_start, port_end) = int(match.group(1)), int(match.group(2))
  104. if _is_invalid_port(port_start) or _is_invalid_port(port_end):
  105. return False
  106. else:
  107. ports_list = [i for i in range(port_start, port_end + 1)]
  108. # append ports at ports_output list
  109. ports_output += ports_list
  110. return True, ports_output
  111. @staticmethod
  112. def _is_timestamp(timestamp: str):
  113. """
  114. Checks whether the given value is in a valid timestamp format. The accepted format is:
  115. YYYY-MM-DD h:m:s, whereas h, m, s may be one or two digits.
  116. :param timestamp: The timestamp to be checked.
  117. :return: True if the timestamp is valid, otherwise False.
  118. """
  119. is_valid = re.match('[0-9]{4}(?:-[0-9]{1,2}){2} (?:[0-9]{1,2}:){2}[0-9]{1,2}', timestamp)
  120. return is_valid is not None
  121. @staticmethod
  122. def _is_boolean(value):
  123. """
  124. Checks whether the given value (string or bool) is a boolean. Strings are valid booleans if they are in:
  125. {y, yes, t, true, on, 1, n, no, f, false, off, 0}.
  126. :param value: The value to be checked.
  127. :return: True if the value is a boolean, otherwise false. And the casted boolean.
  128. """
  129. # If value is already a boolean
  130. if isinstance(value, bool):
  131. return True, value
  132. # If value is a string
  133. # True values are y, yes, t, true, on and 1;
  134. # False values are n, no, f, false, off and 0.
  135. # Raises ValueError if value is anything else.
  136. try:
  137. import distutils.core
  138. value = distutils.util.strtobool(value.lower())
  139. is_bool = True
  140. except ValueError:
  141. is_bool = False
  142. return is_bool, value
  143. @staticmethod
  144. def _is_float(value):
  145. """
  146. Checks whether the given value is a float.
  147. :param value: The value to be checked.
  148. :return: True if the value is a float, otherwise False. And the casted float.
  149. """
  150. try:
  151. value = float(value)
  152. return True, value
  153. except ValueError:
  154. return False, value
  155. #########################################
  156. # HELPER METHODS
  157. #########################################
  158. def add_param_value(self, param, value: str):
  159. """
  160. Adds the pair param : value to the dictionary of attack parameters. Prints and error message and skips the
  161. parameter if the validation fails.
  162. :param param: The parameter name.
  163. :param value: The parameter's value.
  164. :return: None.
  165. """
  166. # by default no param is valid
  167. is_valid = False
  168. # get AttackParameters instance associated with param
  169. # for default values assigned in attack classes, like Parameter.PORT_OPEN
  170. if isinstance(param, AttackParameters.Parameter):
  171. param_name = param
  172. # for values given by user input, like port.open
  173. else:
  174. # Get Enum key of given string identifier
  175. param_name = AttackParameters.Parameter(param)
  176. # Get parameter type of attack's required_params
  177. param_type = self.supported_params.get(param_name)
  178. # Verify validity of given value with respect to parameter type
  179. if param_type is None:
  180. print('Parameter ' + str(param_name) + ' not available for chosen attack. Skipping parameter.')
  181. # If value is query -> get value from database
  182. elif self.statistics.is_query(value):
  183. value = self.statistics.process_db_query(value, False)
  184. if value is not None and value is not "":
  185. is_valid = True
  186. else:
  187. print('Error in given parameter value: ' + value + '. Data could not be retrieved.')
  188. # Validate parameter depending on parameter's type
  189. elif param_type == ParameterTypes.TYPE_IP_ADDRESS:
  190. is_valid, value = self._is_ip_address(value)
  191. elif param_type == ParameterTypes.TYPE_PORT:
  192. is_valid, value = self._is_port(value)
  193. elif param_type == ParameterTypes.TYPE_MAC_ADDRESS:
  194. is_valid = self._is_mac_address(value)
  195. elif param_type == ParameterTypes.TYPE_INTEGER_POSITIVE:
  196. is_valid = value is None or (value.isdigit() and int(value) >= 0)
  197. elif param_type == ParameterTypes.TYPE_FLOAT:
  198. is_valid, value = self._is_float(value)
  199. elif param_type == ParameterTypes.TYPE_TIMESTAMP:
  200. is_valid = self._is_timestamp(value)
  201. elif param_type == ParameterTypes.TYPE_BOOLEAN:
  202. is_valid, value = self._is_boolean(value)
  203. elif param_type == ParameterTypes.TYPE_PACKET_POSITION:
  204. ts = pr.pcap_processor(self.pcap_filepath).get_timestamp_mu_sec(int(value))
  205. if 0 <= int(value) <= self.statistics.get_packet_count() and ts >= 0:
  206. is_valid = True
  207. param_name = Parameter.INJECT_AT_TIMESTAMP
  208. value = (ts / 1000000) # convert microseconds from getTimestampMuSec into seconds
  209. # add value iff validation was successful
  210. if is_valid:
  211. self.params[param_name] = value
  212. else:
  213. print("ERROR: Parameter " + str(param) + " or parameter value " + str(value) +
  214. " not valid. Skipping parameter.")
  215. def get_param_value(self, param: Parameter):
  216. """
  217. Returns the parameter value for a given parameter.
  218. :param param: The parameter whose value is wanted.
  219. :return: The parameter's value.
  220. """
  221. return self.params[param]