Skip to content

Creating a New Module

Dustin Born edited this page Jul 20, 2020 · 9 revisions

Introduction

AVAIN's capabilities for vulnerability analysis and penetration tests are heavily based on the modules it has. More modules result in greater analysis capabilities. Therefore, creating more modules over time is essential. The idea here is to make use of many other, already written, tools with a specific purpose by simply creating a wrapper around them to include them into AVAIN. To keep things simple, AVAIN uses Python as its main programming language, which means that the part of modules that interfaces with AVAIN's core has to be written in Python as well. However, apart from the interface, modules can make use of other software or programming languages.

Adding New Modules

Modules have to follow certain rules in order to successfully interface with AVAIN's core:

  • File names & location: New modules have to be placed into the modules subdirectory. All modules have to be prefixed with the word avain to be distinguishable from other files / scripts. Have a look at the current module structure to see some examples. As modules have to be written in Python, their file extension has to be .py.
  • Interfacing functions & result sharing: Modules are required to have a run(results: list) function that is invoked to start the module. To share one of AVAIN's dedicated shared results with the core and other modules, a module has to append them to the results list. Here, every result has to be appended to the list as a tuple of (TYPE, RESULT), where TYPE is a key or string contained in the enum defined in core/result_types.py and RESULT can either be the filepath to a JSON file or the result itself as a Python dictionary. The mentioned enum simply lists the currently available shared result types: scan result, webserver map & vuln score result. In addition, modules can implement a kill() function that is called when the module should be killed, e.g. if the user presses ctrl+c.
  • Other output files: Other module results that are not shared and are to be viewed directly by the user can be returned separately. Every such result needs to be stored in a separate file that is located within the module's (working) directory. To inform AVAIN about the existence of these other result files, the module has to store the filepaths in a global CREATED_FILES list. The filepaths can be absolute or relative to the module's directory. Entire result folders can be specified as well.
  • Important: When calling a module, AVAIN switches into the directory of the module, so that every module can run within its own environment and address other files relative to its own directory.

Module Parameters

There are two ways a module can receive user parameters from the core: configuration files ([see further below](#Using- Configuration-Files)) and global variables within the module. E.g. if a module has defined the global variable NETWORKS, the core assigns that variable its corresponding value. Currently available module parameters are:

  • VERBOSE – Specifies whether AVAIN should be verbose
  • CONFIG – The part of the config file relevant to this module
  • CORE_CONFIG – The part of the config file relevant for all modules
  • NETWORKS – A list of networks (as expressions) to scan, possibly containing wildcards and prefixes
  • OMIT_NETWORKS – &nbsp A list of network (expressions) not to scan, possibly containing wildcards and prefixes
  • PORTS – A list of ports to scan, possibly containing the prefix "T" for TCP or "U" for UDP as well as an expression listing the ports that should be scanned
  • HOSTS – A list of all (pure) host addresses to scan, not containing any wildcards or prefixes

Furthermore, to be handed any of AVAIN's dedicated shared results, modules have to define which kind of results they want to receive as well as a place to store them in. This can be done by defining the global dict INTERMEDIATE_RESULTS and putting into it the requested types of intermediate results as keys together with anything as values. The available result types are defined in core/result_types.py as enum. The shared results that are given to a module are always an aggregation of all previously returned shared results. Example:

INTERMEDIATE_RESULTS = {ResultType.SCAN: {}, ResultType.WEBSERVER_MAP: {}}  # get the current SCAN and WEBSERVER_MAP result

Alternatively to the enum keys, their values can be put into the dict of intermediate results. Example:

INTERMEDIATE_RESULTS = {"SCAN": {}, "WEBSERVER_MAP": {}}  # get the current SCAN and WEBSERVER_MAP result

Using Configuration Files

Using configuration files is rather simple. The structure of a configuration file is explained here. All a module needs to do to utilize configuration files is to define a new section in the file. E.g. if we wrote a module named abc.superscanner, the we can add to the configuration file:

[abc.superscanner]
speed = 5
aggressive = True

Doing this will cause the global CONFIG parameter to be populated with this configuration when our module abc.superscanner is invoked, i.e. the module will receive:

CONFIG = {"speed": "5", "aggressive": "True"}

The module can then access its configuration simply via this dictionary. As we can see, AVAIN automatically parses the config sections and provides every module with its configuration. However, AVAIN does not parse the configuration values, it simply reads them as a string. Every module itself is responsible for parsing its config values.

Printing

While printing text to the user via the builtin print function is possible, it is not advised because it may cause consistency issues, since the core and an invoked module run concurrently in different threads. AVAIN's utility class provides a separate thread-safe printit function that should be used instead. You can also supply a predefined color to print the given text in. Alternatively, you can also acquire & release the used mutex directly to prevent the core from printing anything.

Logging

Every module has the ability to log events. First a logger needs to be setup. This can be done simply with:

import logging
logger = logging.getLogger(__name__)

Once set up, the logger can be used like this:

logger.info("Hello World :)")

The log entries will be visible in the global avain.log logfile at the root of the output directory.

Examples

For examples and guidance it is best to look at existing modules. Rather small and overseeable modules are:

Creating Module Specific Build Scripts

In case a module requires installation of other components or resources before it can be used, it can create a avain_build.sh bash script that is automatically sourced by the main install.sh script, which is executed by the user to install AVAIN. This way, it is easy for modules to include their own installation steps into the global installation process.

Creating Module Updaters

In case a module has software or resources that need to stay up to date, it can create a separate module_updater.py file that is automatically executed if AVAIN is called with the -uM flag or if the time since the last update exceeds the configured threshold. Such updater modules have to define a run function the same way normal modules do. Also, shared results can be returned via the list parameter of the run function and other files via the global CREATED_FILES variable, just like normal modules.