Unit - Abstract Challenge Solution

Units are the part of Katana which actually performs the evaluation on a given target. They are defined as subclasses of the katana.unit.Unit class, and must implement three methods at a minimum to begin functioning.

Units are automatically loaded from the katana/units directory, and optionally other directories specified at runtime by the Finder object below. This object is created by the Manager, and will search a directory for valid unit objects.

You can also register unit classes manually with the Finder if needed.

class katana.unit.Unit(manager: katana.manager.Manager, target: katana.target.Target)

Bases: object

Abstract the interface with a specific unit of evaluation for CTF challenges. This class must implement the evaluate and validate methods in order to be used with Katana.

Units attempt to solve very basic and targeted CTF challenges and provide data which could either contain a flag or another challenge. If the data contains a flag, evaluation will be halted. If it doesn’t, the data may be used to bootstrap further Unit scanning.

When implementing a new unit, keep in mind that any serious processing should not occur until the Unit.evaluate method. This method is executed within the context of a thread. Executing intensive checks in other methods could indadvertedly slow down Katana.

Property PRIORITY:
 This is a priority from 0 ( highest priority) to 100 (lowest priority). Priorities are also scaled proportionally to parents in order to ensure children have higher priorities.
Property RECURSE_SELF:
 Specifies whether this unit can recurse into itself.
Property NO_RECURSE:
 Indicates if recursion is allowed at all for this unit
Property DEPENDENCIES:
 A list of system binary dependencies we rely on (e.g. ["steghide"])
Property STRICT_FLAGS:
 If specified, a flag must match the entire data string (not some sub-string within it).
Property GROUPS:
 a list of groups this unit belongs to. This is useful for queuing or excluding only certain groups. By convention, this normally at least contains the package name (e.g. “crypto” or “stego”). However, it can theoretically contain any name you would like.
Property BLOCKED_GROUPS:
 a list of groups or unit names which this unit cannot recurse into.

Here’s an example of a very basic unit class:

class Unit(katana.unit.Unit):
    
    # Higher priority than normal
    PRIORITY = 25
    # Groups we belong to
    GROUPS = ["web", "bruteforce"]

    def __init__(manager: katana.manager.Manager, target: katana.target.Target):
        super(Unit, self).__init__(manager, target)
        if not target.is_url:
            raise NotApplicable("not a url")
    
    def evaluate(self, case):
        # Do something with this URL
        return
PRIORITY = 50
RECURSE_SELF = False
NO_RECURSE = False
PROTECTED_RECURSE = False
DEPENDENCIES = []
STRICT_FLAGS = False
GROUPS = []
BLOCKED_GROUPS = []
classmethod get_name() → str

By default, we assume the unit name is the same as the containing module. This can be overridden, but should not conflict with other units.

classmethod validate(manager)

Checks that required configuration values are available in the manager configuration file. This should be called via super prior to subclass implementation, as it ensures the section for this unit is added.

can_recurse(unit_class: Type[katana.unit.Unit]) → bool

Checks recursion rules and returns whether or not recursion is allowed into the given unit class. This unit has already been matched to a given recursion target from this unit.

Direct indicates whether this is the direct child or an ancestor of self.

Parameters:unit_class – The child we are thinking recursing into
is_complete() → bool

Returns true if either this unit or the origin target has completed

enumerate()

Yield cases for evaluation given the target and manager configuration. This allows units with multiple possible evaluations (such as password guesser’s) to take advantage of the parallelism of Katana without further coding. By default, this method yields a single None value which will be passed as case in the Unit.evaluate method below. You must yield at least one value before returning, or evaluate will never run.

evaluate(case: Any)

Run unit tasks given case which was returned from Unit.enumerate. This could happen in any thread or process of execution and should be stateless.

get_output_dir()

Find the output directory for this unit. This will return the directory where artifacts are expected to be stored in this context and also ensure it exists

generate_artifact(name: Optional[str] = None, mode: str = 'w', create: bool = True, asdir: bool = False) → Tuple[str, IO]

Generate a new artifact, and return the path and open file handle. The artifact is not automatically registered with the manager, since it is initially empty. You should register any artifacts which contain useful data based on your unit (using self.manager.register_artifact)

family_tree() → Generator[Unit, None, None]

A generator which yields all parent units

get(name: str, default: Optional[str] = None) → str

Get a configuration value with a default. If a value was specified under the DEFAULT section, it will be returned before the default value specified here. :param name: name of the parameter :param default: default value :return: the value or the default value

getb(name: str, default: Optional[bool] = None) → bool

same as get, but returns a boolean value

geti(name: str, default: Optional[int] = None) → int

same as get but returns an integer value

classmethod check_deps()

The default dependency check will make sure that every item in self.DEPENDENCIES exists as an external executable in the current environment, and raise a NotApplicable exception otherwise. You likely won’t need to override this, but you can if you’d like.

class katana.unit.Finder(manager: katana.manager.Manager, use_default: bool = True)

Bases: object

Utilize python dynamic introspection and loading to locate units either within the default unit list bundled with Katana or in a custom location.

Note

This code will automatically load all *.py scripts underneath the specified unit directory and look for a Unit class. This could be dangerous. Don’t put random scripts in this directory.

validate() → None

Validate the manager configuration for each unit. Units without proper configuration will raise an exception which will be fed up to the user. Each unit accepts configuration items under it’s own section if required (e.g. [katana.units.crypto.caeser])

find(directory: str, prefix: str) → Generator[Type[katana.unit.Unit], None, None]

Locate units which conform to the Katana unit specification with the given directory. All python source file within the directory will end up being executed. A valid unit definition contains a Unit class which subclasses katana.unit.Unit and implements Unit.evaluate and Unit.validate.

register(unit: Type[katana.unit.Unit])

Register a unit to be used during analysis

match(target: katana.target.Target, scale: float = 1.0) → Generator[katana.unit.Unit, None, None]

Match the given target to one or more units that have previously been enumerated with the Finder.find method. This tests that the unit itself is applicable to the target in order to find specific applicable units

exception katana.unit.NotApplicable

Bases: Exception

Indicates the Unit which was created is not applicable to the given target, and the unit is in an undefined state.

exception katana.unit.MissingDependency

Bases: Exception

Indicates the unit was missing a dependency, and cannot be loaded. The message content is the name of the missing dependency

class katana.unit.NoneUnit(manager: katana.manager.Manager, target: katana.target.Target)

Bases: katana.unit.Unit

classmethod get_name() → str

By default, we assume the unit name is the same as the containing module. This can be overridden, but should not conflict with other units.

class katana.unit.FileUnit(manager: katana.manager.Manager, target: katana.target.Target, keywords=None)

Bases: katana.unit.Unit

This unit base class requires that the given target be a file, and also optionally have a libmagic signature which contains one of a specified set of keywords. To use this unit, you simply pass a special keywords argument to its constructor in your unit subclass:

# A unit that requires a file containing some sort of image
class Unit(units.FileUnit):
    def __init__(self, manager, target):
        super(Unit, self).__init__(manager, target, keywords=['image'])
class katana.unit.PrintableDataUnit(manager: katana.manager.Manager, target: katana.target.Target)

Bases: katana.unit.Unit

This unit base class ensures that the target content contains only printable data (that is, data which is not binary/is readable).

class katana.unit.NotEnglishUnit(manager: katana.manager.Manager, target: katana.target.Target)

Bases: katana.unit.Unit

This unit base class ensures that the target content contains mostly non-english text.

class katana.unit.NotEnglishAndPrintableUnit(manager: katana.manager.Manager, target: katana.target.Target)

Bases: katana.unit.Unit

This unit base class ensures that the target content is printable, and is also not english text (e.g. base64 data, white space, etc.)

class katana.unit.RegexUnit(manager: katana.manager.Manager, target: katana.target.Target)

Bases: katana.unit.Unit

Utilizes a regular expression pattern to locate matching sections of the input data. The Unit will raise NotApplicable if the target has no matches.

enumerate()

Yield’s all the match objects