Controller.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. import os
  2. import sys
  3. import shutil
  4. import readline
  5. from ID2TLib.AttackController import AttackController
  6. from ID2TLib.LabelManager import LabelManager
  7. from ID2TLib.PcapFile import PcapFile
  8. from ID2TLib.Statistics import Statistics
  9. from ID2TLib.AttackContext import AttackContext
  10. class Controller:
  11. def __init__(self, in_pcap_file_path: str, do_extra_tests: bool, out_pcap_file_path):
  12. """
  13. Creates a new Controller, acting as a central coordinator for the whole application.
  14. :param pcap_file_path:
  15. """
  16. # Fields
  17. self.pcap_src_path = in_pcap_file_path.strip()
  18. if out_pcap_file_path:
  19. self.pcap_out_path = out_pcap_file_path.strip()
  20. else:
  21. self.pcap_out_path = None
  22. self.pcap_dest_path = ''
  23. self.written_pcaps = []
  24. self.do_extra_tests = do_extra_tests
  25. # Initialize class instances
  26. print("Input file: %s" % self.pcap_src_path)
  27. self.pcap_file = PcapFile(self.pcap_src_path)
  28. self.label_manager = LabelManager(self.pcap_src_path)
  29. self.statistics = Statistics(self.pcap_file)
  30. self.statistics.do_extra_tests = self.do_extra_tests
  31. self.statisticsDB = self.statistics.get_statistics_database()
  32. self.attack_controller = AttackController(self.pcap_file, self.statistics, self.label_manager)
  33. def load_pcap_statistics(self, flag_write_file: bool, flag_recalculate_stats: bool, flag_print_statistics: bool):
  34. """
  35. Loads the PCAP statistics either from the database, if the statistics were calculated earlier, or calculates
  36. the statistics and creates a new database.
  37. :param flag_write_file: Writes the statistics to a file.
  38. :param flag_recalculate_stats: Forces the recalculation of statistics.
  39. :param flag_print_statistics: Prints the statistics on the terminal.
  40. :return: None
  41. """
  42. self.statistics.load_pcap_statistics(flag_write_file, flag_recalculate_stats, flag_print_statistics)
  43. def process_attacks(self, attacks_config: list):
  44. """
  45. Creates the attack based on the attack name and the attack parameters given in the attacks_config. The
  46. attacks_config is a list of attacks, e.g.
  47. [['PortscanAttack', 'ip.src="192.168.178.2",'dst.port=80'],['PortscanAttack', 'ip.src="10.10.10.2"]].
  48. Merges the individual temporary attack pcaps into one single pcap and merges this single pcap with the
  49. input dataset.
  50. :param attacks_config: A list of attacks with their attack parameters.
  51. """
  52. # get output directory
  53. if self.pcap_out_path:
  54. out_dir = os.path.dirname(self.pcap_out_path)
  55. else:
  56. out_dir = os.path.dirname(self.pcap_src_path)
  57. # if out_dir is cwd
  58. if out_dir == "":
  59. out_dir = "."
  60. # context for the attack(s)
  61. context = AttackContext(out_dir)
  62. # note if new xml file has been created by MembersMgmtCommAttack
  63. # load attacks sequentially
  64. for attack in attacks_config:
  65. temp_attack_pcap = self.attack_controller.process_attack(attack[0], attack[1:], context)
  66. self.written_pcaps.append(temp_attack_pcap)
  67. # merge attack pcaps to get single attack pcap
  68. if len(self.written_pcaps) > 1:
  69. print("\nMerging temporary attack pcaps into single pcap file...", end=" ")
  70. sys.stdout.flush() # force python to print text immediately
  71. for i in range(0, len(self.written_pcaps) - 1):
  72. attacks_pcap = PcapFile(self.written_pcaps[i])
  73. attacks_pcap_path = attacks_pcap.merge_attack(self.written_pcaps[i + 1])
  74. os.remove(self.written_pcaps[i + 1]) # remove merged pcap
  75. self.written_pcaps[i + 1] = attacks_pcap_path
  76. print("done.")
  77. else:
  78. attacks_pcap_path = self.written_pcaps[0]
  79. # merge single attack pcap with all attacks into base pcap
  80. print("Merging base pcap with single attack pcap...", end=" ")
  81. sys.stdout.flush() # force python to print text immediately
  82. # cp merged PCAP to output path
  83. self.pcap_dest_path = self.pcap_file.merge_attack(attacks_pcap_path)
  84. if self.pcap_out_path:
  85. if not self.pcap_out_path.endswith(".pcap"):
  86. self.pcap_out_path += ".pcap"
  87. os.rename(self.pcap_dest_path, self.pcap_out_path)
  88. self.pcap_dest_path = self.pcap_out_path
  89. print("done.")
  90. # delete intermediate PCAP files
  91. print('Deleting intermediate attack pcap...', end=" ")
  92. sys.stdout.flush() # force python to print text immediately
  93. os.remove(attacks_pcap_path)
  94. print("done.")
  95. # write label file with attacks
  96. self.label_manager.write_label_file(self.pcap_dest_path)
  97. # pcap_base contains the name of the pcap-file without the ".pcap" extension
  98. pcap_base = os.path.splitext(self.pcap_dest_path)[0]
  99. created_files = [self.pcap_dest_path, self.label_manager.label_file_path]
  100. for suffix, filename in context.get_allocated_files():
  101. shutil.move(filename, pcap_base + suffix)
  102. created_files.append(pcap_base + suffix)
  103. context.reset()
  104. # print status message
  105. created_files += context.get_other_created_files()
  106. created_files.sort()
  107. print("\nOutput files created:")
  108. for file in created_files:
  109. # remove ./ at beginning of file to have only one representation for cwd
  110. if file.startswith("./"):
  111. file = file[2:]
  112. print(file)
  113. def process_db_queries(self, query, print_results=False):
  114. """
  115. Processes a statistics database query. This can be a standard SQL query or a named query.
  116. :param query: The query as a string or multiple queries as a list of strings.
  117. :param print_results: Must be True if the results should be printed to terminal.
  118. :return: The query's result
  119. """
  120. print("Processing database query/queries...")
  121. if isinstance(query, list) or isinstance(query, tuple):
  122. for q in query:
  123. self.statisticsDB.process_db_query(q, print_results)
  124. else:
  125. self.statisticsDB.process_db_query(query, print_results)
  126. @staticmethod
  127. def process_help(params):
  128. if not params:
  129. print("Query mode allows you to enter SQL-queries as well as named queries.")
  130. print()
  131. print("Named queries:")
  132. print("\tSelectors:")
  133. print("\t\tmost_used(...) -> Returns the most occurring element in all elements")
  134. print("\t\tleast_used(...) -> Returns the least occurring element in all elements")
  135. print("\t\tavg(...) -> Returns the average of all elements")
  136. print("\t\tall(...) -> Returns all elements")
  137. print("\tExtractors:")
  138. print("\t\trandom(...) -> Returns a random element from a list")
  139. print("\t\tfirst(...) -> Returns the first element from a list")
  140. print("\t\tlast(...) -> Returns the last element from a list")
  141. print("\tParameterized selectors:")
  142. print("\t\tipAddress(...) -> Returns all IP addresses fulfilling the specified conditions")
  143. print("\t\tmacAddress(...) -> Returns all MAC addresses fulfilling the specified conditions")
  144. print()
  145. print("Miscellaneous:")
  146. print("\tlabels -> List all attacks listed in the label file, if any")
  147. print()
  148. print("Additional information is available with 'help [KEYWORD];'")
  149. print("To get a list of examples, type 'help examples;'")
  150. print()
  151. return
  152. param = params[0].lower()
  153. if param == "most_used":
  154. print("most_used can be used as a selector for the following attributes:")
  155. print("ipAddress | macAddress | portNumber | protocolName | ttlValue | mssValue | winSize | ipClass")
  156. print()
  157. elif param == "least_used":
  158. print("least_used can be used as a selector for the following attributes:")
  159. print("ipAddress | macAddress | portNumber | protocolName | ttlValue")
  160. print()
  161. elif param == "avg":
  162. print("avg can be used as a selector for the following attributes:")
  163. print("pktsReceived | pktsSent | kbytesSent | kbytesReceived | ttlValue | mss")
  164. print()
  165. elif param == "all":
  166. print("all can be used as a selector for the following attributes:")
  167. print("ipAddress | ttlValue | mss | macAddress | portNumber | protocolName")
  168. print()
  169. elif param in ["random", "first", "last"]:
  170. print("No additional info available for this keyword.")
  171. print()
  172. elif param == "ipaddress":
  173. print("ipAddress is a parameterized selector which fetches IP addresses based on (a list of) conditions.")
  174. print("Conditions are of the following form: PARAMETER OPERATOR VALUE")
  175. print("The following parameters can be specified:")
  176. print("pktsReceived | pktsSent | kbytesReceived | kbytesSent | maxPktRate | minPktRate | ipClass\n"
  177. "macAddress | ttlValue | ttlCount | portDirection | portNumber | portCount | protocolCount\n"
  178. "protocolName")
  179. print()
  180. print("See 'help examples;' for usage examples.")
  181. print()
  182. elif param == "macaddress":
  183. print("macAddress is a parameterized selector which fetches MAC addresses based on (a list of) conditions.")
  184. print("Conditions are of the following form: PARAMETER OPERATOR VALUE")
  185. print("The following parameters can be specified:")
  186. print("ipAddress")
  187. print()
  188. print("See 'help examples;' for usage examples.")
  189. print()
  190. elif param == "examples":
  191. print("Get the average amount of sent packets per IP:")
  192. print("\tavg(pktsSent);")
  193. print("Get a random IP from all addresses occuring in the pcap:")
  194. print("\trandom(all(ipAddress));")
  195. print("Return the MAC address of a specified IP:")
  196. print("\tmacAddress(ipAddress=192.168.178.2);")
  197. print("Get the average TTL-value with SQL:")
  198. print("\tSELECT avg(ttlValue) from ip_ttl;")
  199. print("Get a random IP address from all addresses that sent and received at least 10 packets:")
  200. print("\trandom(ipAddress(pktsSent > 10, pktsReceived > 10));")
  201. print()
  202. else:
  203. print("Unknown keyword '" + param + "', try 'help;' to get a list of allowed keywords'")
  204. print()
  205. def enter_query_mode(self):
  206. """
  207. Enters into the query mode. This is a read-eval-print-loop, where the user can input named queries or SQL
  208. queries and the results are printed.
  209. """
  210. def make_completer(vocabulary):
  211. def custom_template(text, state):
  212. results = [x for x in vocabulary if x.startswith(text)] + [None]
  213. return results[state]
  214. return custom_template
  215. readline.parse_and_bind('tab: complete')
  216. readline.set_completer(make_completer(self.statisticsDB.get_all_named_query_keywords()+self.statisticsDB.get_all_sql_query_keywords()))
  217. history_file = os.path.join(os.path.expanduser('~'), 'ID2T_data', 'query_history')
  218. try:
  219. readline.read_history_file(history_file)
  220. except IOError:
  221. pass
  222. print("Entering into query mode...")
  223. print("Enter statement ending by ';' and press ENTER to send query. Exit by sending an empty query.")
  224. print("Type 'help;' for information on possible queries.")
  225. buffer = ""
  226. while True:
  227. line = input("> ")
  228. if line == "":
  229. break
  230. buffer += line
  231. import sqlite3
  232. if sqlite3.complete_statement(buffer):
  233. try:
  234. buffer = buffer.strip()
  235. if buffer.lower().startswith('help'):
  236. buffer = buffer.strip(';')
  237. self.process_help(buffer.split(' ')[1:])
  238. elif buffer.lower().strip() == 'labels;':
  239. if not self.label_manager.labels:
  240. print("No labels found.")
  241. else:
  242. print("Attacks listed in the label file:")
  243. print()
  244. for label in self.label_manager.labels:
  245. print("Attack name: " + str(label.attack_name))
  246. print("Attack note: " + str(label.attack_note))
  247. print("Start timestamp: " + str(label.timestamp_start))
  248. print("End timestamp: " + str(label.timestamp_end))
  249. print()
  250. print()
  251. else:
  252. self.statisticsDB.process_db_query(buffer, True)
  253. except sqlite3.Error as e:
  254. print("An error occurred:", e.args[0])
  255. buffer = ""
  256. readline.set_history_length(1000)
  257. readline.write_history_file(history_file)
  258. def create_statistics_plot(self, params: str):
  259. """
  260. Plots the statistics to a file by using the given customization parameters.
  261. """
  262. if params is not None and params[0] is not None:
  263. params_dict = dict([z.split("=") for z in params])
  264. self.statistics.plot_statistics(format=params_dict['format'])
  265. else:
  266. self.statistics.plot_statistics()