Browse Source

CLI parameter to list available attacks and their parameters.

This is a major change: it changes how parameters are created and instatiated in
each attack.
Carlos Garcia 6 years ago
parent
commit
b46267d8f4
5 changed files with 147 additions and 46 deletions
  1. 29 4
      code/Attack/BaseAttack.py
  2. 10 4
      code/Attack/DDoSAttack.py
  3. 10 2
      code/Attack/PortscanAttack.py
  4. 92 34
      code/CLI.py
  5. 6 2
      code/ID2TLib/AttackController.py

+ 29 - 4
code/Attack/BaseAttack.py

@@ -18,7 +18,7 @@ class BaseAttack(metaclass=ABCMeta):
     Abstract base class for all attack classes. Provides basic functionalities, like parameter validation.
     """
 
-    def __init__(self, statistics, name, description, attack_type):
+    def __init__(self, name, description, attack_type):
         """
         To be called within the individual attack class to initialize the required parameters.
 
@@ -28,7 +28,7 @@ class BaseAttack(metaclass=ABCMeta):
         :param attack_type: The type the attack belongs to, like probing/scanning, malware.
         """
         # Reference to statistics class
-        self.statistics = statistics
+        self.statistics = None
 
         # Class fields
         self.attack_name = name
@@ -39,6 +39,25 @@ class BaseAttack(metaclass=ABCMeta):
         self.attack_start_utime = 0
         self.attack_end_utime = 0
 
+    def set_statistics(self, statistics):
+        """
+        Specify the statistics object that will be used to calculate the parameters of this attack.
+        The statistics are used to calculate default parameters and to process user supplied 
+        queries.
+
+        :param statistics: Reference to a statistics object.
+        """
+        self.statistics = statistics
+
+    @abstractmethod
+    def init_params(self):
+        """
+        Initialize all required parameters taking into account user supplied values. If no value is supplied,
+        or if a user defined query is supplied, use a statistics object to do the calculations.
+        A call to this function requires a call to 'set_statistics' first.
+        """
+        pass
+
     @abstractmethod
     def generate_attack_pcap(self):
         """
@@ -216,10 +235,16 @@ class BaseAttack(metaclass=ABCMeta):
         Adds the pair param : value to the dictionary of attack parameters. Prints and error message and skips the
         parameter if the validation fails.
 
-        :param param: The parameter name.
-        :param value: The parameter's value.
+        :param stats: Statistics used to calculate user queries or default values.
+        :param param: Name of the parameter that we wish to modify.
+        :param value: The value we wish to assign to the specifried parameter.
         :return: None.
         """
+        # This function call is valid only if there is a statistics object available.
+        if self.statistics is None:
+            print('Error: Attack parameter added without setting a statistics object first.')
+            exit(1)
+
         # by default no param is valid
         is_valid = False
 

+ 10 - 4
code/Attack/DDoSAttack.py

@@ -15,14 +15,12 @@ from collections import deque
 
 
 class DDoSAttack(BaseAttack.BaseAttack):
-    def __init__(self, statistics, pcap_file_path):
+    def __init__(self):
         """
         Creates a new instance of the DDoS attack.
-
-        :param statistics: A reference to the statistics class.
         """
         # Initialize attack
-        super(DDoSAttack, self).__init__(statistics, "DDoS Attack", "Injects a DDoS attack'",
+        super(DDoSAttack, self).__init__("DDoS Attack", "Injects a DDoS attack'",
                                         "Resource Exhaustion")
 
         # Define allowed parameters and their type
@@ -40,6 +38,14 @@ class DDoSAttack(BaseAttack.BaseAttack):
             Param.NUMBER_ATTACKERS: ParameterTypes.TYPE_INTEGER_POSITIVE
         }
 
+    def init_params(self):
+        """
+        Initialize the parameters of this attack using the user supplied command line parameters.
+        Use the provided statistics to calculate default parameters and to process user 
+        supplied queries.
+
+        :param statistics: Reference to a statistics object.
+        """
         # PARAMETERS: initialize with default values
         # (values are overwritten if user specifies them)
 

+ 10 - 2
code/Attack/PortscanAttack.py

@@ -13,14 +13,14 @@ from scapy.layers.inet import IP, Ether, TCP
 
 
 class PortscanAttack(BaseAttack.BaseAttack):
-    def __init__(self, statistics, pcap_file_path):
+    def __init__(self):
         """
         Creates a new instance of the PortscanAttack.
 
         :param statistics: A reference to the statistics class.
         """
         # Initialize attack
-        super(PortscanAttack, self).__init__(statistics, "Portscan Attack", "Injects a nmap 'regular scan'",
+        super(PortscanAttack, self).__init__("Portscan Attack", "Injects a nmap 'regular scan'",
                                              "Scanning/Probing")
 
         # Define allowed parameters and their type
@@ -41,6 +41,14 @@ class PortscanAttack(BaseAttack.BaseAttack):
             Param.PORT_SOURCE_RANDOMIZE: ParameterTypes.TYPE_BOOLEAN
         }
 
+    def init_params(self):
+        """
+        Initialize the parameters of this attack using the user supplied command line parameters.
+        Use the provided statistics to calculate default parameters and to process user 
+        supplied queries.
+
+        :param statistics: Reference to a statistics object.
+        """
         # PARAMETERS: initialize with default values
         # (values are overwritten if user specifies them)
         most_used_ip_address = self.statistics.get_most_used_ip_address()

+ 92 - 34
code/CLI.py

@@ -23,33 +23,6 @@ class CLI(object):
         self.args = None
         self.attack_config = None
 
-    def process_arguments(self):
-        """
-        Loads the application controller, the PCAP file statistics and if present, processes the given attacks. Evaluates
-        given queries.
-        """
-        # Create ID2T Controller
-        controller = Controller(self.args.input)
-
-        # Load PCAP statistics
-        controller.load_pcap_statistics(self.args.export, self.args.recalculate, self.args.statistics)
-
-        # Create statistics plots
-        if self.args.plot is not None:
-            controller.create_statistics_plot(self.args.plot)
-
-        # Process attack(s) with given attack params
-        if self.args.attack is not None:
-            # If attack is present, load attack with params
-            controller.process_attacks(self.args.attack)
-
-        # Parameter -q without arguments was given -> go into query loop
-        if self.args.query == [None]:
-            controller.enter_query_mode()
-        # Parameter -q with arguments was given -> process query
-        elif self.args.query is not None:
-            controller.process_db_queries(self.args.query, True)
-
     def parse_arguments(self, args):
         """
         Defines the allowed application arguments and invokes the evaluation of the arguments.
@@ -60,18 +33,19 @@ class CLI(object):
         parser = argparse.ArgumentParser(description="Intrusion Detection Dataset Toolkit (ID2T) - A toolkit for "
                                          "injecting synthetically created attacks into PCAP files.",
                                          prog="id2t")
-        # Define required arguments
-        requiredNamed = parser.add_argument_group('required named arguments')
-        requiredNamed.add_argument('-i', '--input', metavar="PCAP_FILE", help='path to the input pcap file', required=True)
-
-        # Define optional arguments
+        # Required arguments
+        required_group = parser.add_argument_group('required arguments')
+        required_args_group = required_group.add_mutually_exclusive_group(required=True)
+        required_args_group.add_argument('-i', '--input', metavar="PCAP_FILE", 
+                                         help='path to the input pcap file')
+        required_args_group.add_argument('-l', '--list-attacks', action='store_true')
+
+        # Optional arguments
         parser.add_argument('-c', '--config', metavar='CONFIG_FILE', help='file containing configuration parameters.',
                             action=LoadFromFile, type=open)
         parser.add_argument('-e', '--export',
                             help='store statistics as a ".stat" file',
                             action='store_true', default=False)
-        parser.add_argument('-a', '--attack', metavar="ATTACK", action='append',
-                            help='injects an ATTACK into a PCAP file.', nargs='+')
         parser.add_argument('-r', '--recalculate',
                             help='recalculate statistics even if a cached version exists.',
                             action='store_true', default=False)
@@ -82,12 +56,96 @@ class CLI(object):
         parser.add_argument('-q', '--query', metavar="QUERY",
                             action='append', nargs='?',
                             help='query the statistics database. If no query is provided, the application enters query mode.')
+        # Attack arguments
+        parser.add_argument('-a', '--attack', metavar="ATTACK", action='append',
+                                       help='injects ATTACK into a PCAP file.', nargs='+')
 
         # Parse arguments
         self.args = parser.parse_args(args)
 
         self.process_arguments()
 
+    def process_arguments(self):
+        """
+        Decide what to do with each  of the command line parameters.
+        """
+        if self.args.list_attacks:
+            # User wants to see the available attacks
+            self.process_attack_listing()
+        else:
+            # User wants to process a PCAP
+            self.process_pcap()
+
+    def process_attack_listing(self):
+        import pkgutil
+        import importlib
+        import Attack
+
+        # Find all attacks, exclude some classes
+        package = Attack
+        attack_names = []
+        for _, name, __ in pkgutil.iter_modules(package.__path__):
+            if name != 'BaseAttack' and name != 'AttackParameters':
+                attack_names.append(name)
+
+        # List the attacks and their parameters
+        emph_start = '\033[1m'
+        emph_end = '\033[0m'
+        for attack_name in attack_names:
+            attack_module = importlib.import_module('Attack.{}'.format(attack_name))
+            attack_class = getattr(attack_module, attack_name)
+            # Instantiate the attack to get to its definitions.
+            attack_obj = attack_class()
+            print('* {}{}{}'.format(emph_start, attack_obj.attack_name, emph_end))
+            print('\t- {}Description:{} {}'.format(emph_start, emph_end,
+                                                   attack_obj.attack_description))
+            print('\t- {}Type:{} {}'.format(emph_start, emph_end,
+                                            attack_obj.attack_type))
+            print('\t- {}Supported Parameters:{}'.format(emph_start, emph_end), end=' ')
+            # Get all the parameter names in a list and sort them
+            param_list = []
+            for key in attack_obj.supported_params:
+                param_list.append(key.value)
+            param_list.sort()
+            # Print each parameter type per line
+            last_prefix = None
+            current_prefix = None
+            for param in param_list:
+                current_prefix = param.split('.')[0]
+                if not last_prefix or current_prefix != last_prefix:
+                    print('\n\t + |', end=' ')
+                print(param, end=' | ')
+                last_prefix = current_prefix
+            # Print an empty line
+            print()
+
+    def process_pcap(self):
+        """
+        Loads the application controller, the PCAP file statistics and if present, processes the given attacks. Evaluates
+        given queries.
+        """
+        # Create ID2T Controller
+        controller = Controller(self.args.input)
+
+        # Load PCAP statistics
+        controller.load_pcap_statistics(self.args.export, self.args.recalculate, self.args.statistics)
+
+        # Create statistics plots
+        if self.args.plot is not None:
+            controller.create_statistics_plot(self.args.plot)
+
+        # Process attack(s) with given attack params
+        if self.args.attack is not None:
+            # If attack is present, load attack with params
+            controller.process_attacks(self.args.attack)
+
+        # Parameter -q without arguments was given -> go into query loop
+        if self.args.query == [None]:
+            controller.enter_query_mode()
+        # Parameter -q with arguments was given -> process query
+        elif self.args.query is not None:
+            controller.process_db_queries(self.args.query, True)
+
 def main(args):
     """
     Creates a new CLI object and invokes the arguments parsing.

+ 6 - 2
code/ID2TLib/AttackController.py

@@ -32,8 +32,12 @@ class AttackController:
         attack_module = importlib.import_module("Attack." + attack_name)
         attack_class = getattr(attack_module, attack_name)
 
-        # Set current attack
-        self.current_attack = attack_class(self.statistics, self.base_pcap)
+        # Instantiate the desired attack
+        self.current_attack = attack_class()
+        # Initialize the parameters of the attack with defaults or user supplied values.
+        self.current_attack.set_statistics(self.statistics)
+        self.current_attack.init_params()
+        # Record the attack
         self.added_attacks.append(self.current_attack)
 
     def process_attack(self, attack: str, params: str):