CLI.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. #! /usr/bin/env python3
  2. import argparse
  3. import sys
  4. from Core.Controller import Controller
  5. class LoadFromFile(argparse.Action):
  6. """
  7. Parses the parameter file given by application param -c/--config.
  8. """
  9. def __call__(self, parser, namespace, values, option_string=None):
  10. with values as f:
  11. parser.parse_args(f.read().split(), namespace)
  12. class CLI(object):
  13. def __init__(self):
  14. """
  15. Creates a new CLI object used to handle
  16. """
  17. # Reference to PcapFile object
  18. self.args = None
  19. self.attack_config = None
  20. def parse_arguments(self, args):
  21. """
  22. Defines the allowed application arguments and invokes the evaluation of the arguments.
  23. :param args: The application arguments
  24. """
  25. # Create parser for arguments
  26. parser = argparse.ArgumentParser(description="Intrusion Detection Dataset Toolkit (Core) - A toolkit for "
  27. "injecting synthetically created attacks into PCAP files.",
  28. prog="id2t")
  29. # Required arguments
  30. required_group = parser.add_argument_group('required arguments')
  31. required_args_group = required_group.add_mutually_exclusive_group(required=True)
  32. required_args_group.add_argument('-i', '--input', metavar="PCAP_FILE",
  33. help='path to the input pcap file')
  34. required_args_group.add_argument('-l', '--list-attacks', action='store_true')
  35. # Optional arguments
  36. parser.add_argument('-c', '--config', metavar='CONFIG_FILE', help='file containing configuration parameters.',
  37. action=LoadFromFile, type=open)
  38. parser.add_argument('-e', '--export',
  39. help='store statistics as a ".stat" file',
  40. action='store_true', default=False)
  41. parser.add_argument('-r', '--recalculate',
  42. help='recalculate statistics even if a cached version exists.',
  43. action='store_true', default=False)
  44. parser.add_argument('-s', '--statistics', help='print file statistics to stdout.', action='store_true',
  45. default=False)
  46. parser.add_argument('-p', '--plot',
  47. help='creates the following plots: the values distributions of TTL, MSS, Window Size, '
  48. 'protocol, and the novelty distributions of IP, port, TTL, MSS, Window Size,'
  49. ' and ToS. In addition to packets count in interval-wise.', action='append',
  50. nargs='?')
  51. parser.add_argument('-q', '--query', metavar="QUERY",
  52. action='append', nargs='?',
  53. help='query the statistics database. If no query is provided, '
  54. 'the application enters query mode.')
  55. parser.add_argument('-t', '--extraTests',
  56. help='perform extra tests on the input pcap file, including calculating IP entropy'
  57. 'in interval-wise, TCP checksum, and checking payload availability.',
  58. action='store_true', default=False)
  59. parser.add_argument('-S', '--rngSeed', action='append', help='sets rng seed for testing or benchmarking',
  60. nargs='+', default=[])
  61. parser.add_argument('-T', '--time', help='measures packet generation time', action='store_true', default=False)
  62. parser.add_argument('-V', '--non-verbose', help='reduces terminal clutter', action='store_true', default=False)
  63. parser.add_argument('-o', '--output', metavar="PCAP_FILE", help='path to the output pcap file')
  64. parser.add_argument('-ie', '--inject_empty', action='store_true',
  65. help='injects ATTACK into an EMPTY PCAP file, using the statistics of the input PCAP.')
  66. parser.add_argument('-d', '--debug', help='Runs ID2T in debug mode.', action='store_true', default=False)
  67. parser.add_argument('-si', '--statistics_interval', help='interval duration in seconds', action='store',
  68. type=float, nargs='+', default=[])
  69. parser.add_argument('-rd', '--recalculate-delete',
  70. help='recalculate statistics even if a cached version exists.'
  71. 'also delete old interval statistics.'
  72. 'surpresses (yes, no, delete) prompt.', action='store_true',
  73. default=False)
  74. parser.add_argument('-ry', '--recalculate-yes',
  75. help='recalculate statistics even if a cached version exists.'
  76. 'also recalculates old interval statistics.'
  77. 'surpresses (yes, no, delete) prompt.', action='store_true',
  78. default=False)
  79. parser.add_argument('-rn', '--recalculate-no',
  80. help='recalculate statistics even if a cached version exists.'
  81. 'does not recalculate old interval statistics, but keeps them.'
  82. 'surpresses (yes, no, delete) prompt.', action='store_true',
  83. default=False)
  84. parser.add_argument('-li', '--list-intervals', action='store_true',
  85. help='prints all interval statistics tables available in the database')
  86. parser.add_argument('--skip', action='store_true', help='skips every initialization right to query mode\n'
  87. 'CAUTION: this will only work if there already is a '
  88. 'database')
  89. # Attack arguments
  90. parser.add_argument('-a', '--attack', metavar="ATTACK", action='append',
  91. help='injects ATTACK into a PCAP file.', nargs='+')
  92. # Parse arguments
  93. self.args = parser.parse_args(args)
  94. self.process_arguments()
  95. def process_arguments(self):
  96. """
  97. Decide what to do with each of the command line parameters.
  98. """
  99. if self.args.list_attacks:
  100. # User wants to see the available attacks
  101. self.process_attack_listing()
  102. else:
  103. # User wants to process a PCAP
  104. self.process_pcap()
  105. @staticmethod
  106. def process_attack_listing():
  107. import pkgutil
  108. import importlib
  109. import Attack
  110. # Find all attacks, exclude some classes
  111. package = Attack
  112. attack_names = []
  113. for _, name, __ in pkgutil.iter_modules(package.__path__):
  114. if name != 'BaseAttack' and name != 'AttackParameters':
  115. attack_names.append(name)
  116. # List the attacks and their parameters
  117. emph_start = '\033[1m'
  118. emph_end = '\033[0m'
  119. for attack_name in attack_names:
  120. attack_module = importlib.import_module('Attack.{}'.format(attack_name))
  121. attack_class = getattr(attack_module, attack_name)
  122. # Instantiate the attack to get to its definitions.
  123. attack_obj = attack_class()
  124. print('* {}{}{}'.format(emph_start, attack_obj.attack_name, emph_end))
  125. print('\t- {}Description:{} {}'.format(emph_start, emph_end,
  126. attack_obj.attack_description))
  127. print('\t- {}Type:{} {}'.format(emph_start, emph_end,
  128. attack_obj.attack_type))
  129. print('\t- {}Supported Parameters:{}'.format(emph_start, emph_end), end=' ')
  130. # Get all the parameter names in a list and sort them
  131. param_list = []
  132. for key in attack_obj.supported_params:
  133. param_list.append(key.value)
  134. param_list.sort()
  135. # Print each parameter type per line
  136. last_prefix = None
  137. current_prefix = None
  138. for param in param_list:
  139. current_prefix = param.split('.')[0]
  140. if not last_prefix or current_prefix != last_prefix:
  141. print('\n\t + |', end=' ')
  142. print(param, end=' | ')
  143. last_prefix = current_prefix
  144. # Print an empty line
  145. print()
  146. def process_pcap(self):
  147. """
  148. Loads the application controller, the PCAP file statistics and if present, processes the given attacks.
  149. Evaluates given queries.
  150. """
  151. # Create Core Controller
  152. controller = Controller(self.args.input, self.args.extraTests, self.args.non_verbose, self.args.output,
  153. self.args.debug)
  154. if not self.args.skip:
  155. # Load PCAP statistics
  156. recalculate_intervals = None
  157. if self.args.recalculate_delete:
  158. recalculate_intervals = True
  159. elif self.args.recalculate_yes:
  160. recalculate_intervals = True
  161. self.args.recalculate = True
  162. elif self.args.recalculate_no:
  163. recalculate_intervals = False
  164. self.args.recalculate = True
  165. controller.load_pcap_statistics(self.args.export, self.args.recalculate, self.args.statistics,
  166. self.args.statistics_interval, self.args.recalculate_delete,
  167. recalculate_intervals)
  168. if self.args.list_intervals:
  169. controller.list_interval_statistics()
  170. # Create statistics plots
  171. if self.args.plot is not None:
  172. do_entropy = False
  173. if self.args.extraTests:
  174. do_entropy = True
  175. controller.create_statistics_plot(self.args.plot, do_entropy)
  176. # Check rng seed
  177. if not isinstance(self.args.rngSeed, list):
  178. self.args.rngSeed = [self.args.rngSeed]
  179. # Process attack(s) with given attack params
  180. if self.args.attack is not None:
  181. # If attack is present, load attack with params
  182. controller.process_attacks(self.args.attack, self.args.rngSeed, self.args.time, self.args.inject_empty)
  183. # Parameter -q without arguments was given -> go into query loop
  184. if self.args.query == [None]:
  185. controller.enter_query_mode()
  186. # Parameter -q with arguments was given -> process query
  187. elif self.args.query is not None:
  188. controller.process_db_queries(self.args.query, True)
  189. def main(args):
  190. """
  191. Creates a new CLI object and invokes the arguments parsing.
  192. :param args: The provided arguments
  193. """
  194. cli = CLI()
  195. # Check arguments
  196. cli.parse_arguments(args)
  197. # Uncomment to enable calling by terminal
  198. if __name__ == '__main__':
  199. main(sys.argv[1:])