Skip to content

analysis plugin development

Jörg Stucke edited this page Aug 24, 2018 · 21 revisions

Analysis Plug-In Development Guide

Analysis plug-ins can work on the binaries, as well as on resutls of other plug-ins. The plug-ins can be executed just on the outer firmware container as well as on each single extracted file. If it is executed on each extracted file, a result summary can be recursivly propagated to the parent files.

ℹ️ You can copy our minimal functional hello world plug-in to start your own.

Write an Analysis Plug-In

FACT detects plug-ins automatically as long as they are stored in src/plugins/analysis.

A plug-in consists of folders and files following the following template.

.
├── __init__.py
├── install.sh [OPTIONAL]
├── code
│   ├── __init__.py
│   └── PLUGIN_NAME.py
├── internal [OPTIONAL]
│   └── ADDITIONAL_SOURCES_OR_CODE
├── signatures [OPTIONAL: JUST FOR YARA PLUGINS]
├── test
│   ├── __init__.py
│   ├── test_PLUGIN_NAME.py
│   └── data [OPTIONAL]
│       └── SOME DATA FILES TO TEST
└── view 
    └── PLUGIN_NAME.html [OPTIONAL]

Each file is described below.

./install.sh

install.sh is an optional file that can provide additional bash code that should be run on install. install.sh is automatically triggered by FACT's installation script.

A basic install.sh shall look like this.

#!/usr/bin/env bash

# change cwd to current file's directory
cd "$( dirname "${BASH_SOURCE[0]}" )" 

echo "------------------------------------"
echo "       SOME MEANINGFUL TITLE        "
echo "------------------------------------"

[YOUR CODE HERE]
	
exit 0

Do not forget the "exit 0" at the end! If you miss it and something goes wrong, the installer will end-up in an infinite loop!

./code/PLUGIN_NAME.py

This is the actual plug-in code. You should use the following self explanatory template to write your plug-in.

from analysis.PluginBase import AnalysisBasePlugin
 

class AnalysisPlugin(AnalysisBasePlugin):
    '''
    Some Description
    '''
    NAME = 'PLUGIN_NAME'
    DESCRIPTION = 'a short description of the plugin'
    MIME_BLACKLIST = [optional list of MIME types that should be ignored by the plugin]
    DEPENDENCIES = [LIST_OF_PLUG-IN_NAMES_THAT_THIS_PLUGIN_RELYS ON] 
    VERSION = 'x.x'
 
   def __init__(self, plugin_adminstrator, config=None, recursive=True):
        '''
        recursive flag: If True recursively analyze included files
        '''
        self.config = config
 
        # additional init stuff can go here
 
        super().__init__(plugin_adminstrator, config=config, recursive=recursive, plugin_path=__file__)
 
    def process_object(self, file_object):
        '''
        This function must be implemented by the plug-in.
        Analysis result must be a dict stored in "file_object.processed_analysis[self.NAME]"
        CAUTION: Dict keys must be strings!
        If you want to propagate results to parent objects store a list of strings in
        "file_object.processed_analysis[self.NAME]['summary']".

        File's binary is available via "file_object.binary".
        File's local storage path is available via "file_object.file_path".
        Results of other plug-ins can be accesd via "file_object.processed_analysis['PLUGIN_NAME']".
        Do not forget to add these plug-ins to "DEPENDENCIES".
        '''
        return file_object

If your plug-in provides configuration options, you can add them to src/main.cfg in a section named as your plug-in. You can access a field (e.g. "option_a") of your configuration with the following Code:

self.config.get(self.NAME, "option_a")

Instead of the optional MIME_BLACKLIST, you can also use MIME_WHITELIST to specify a list of allowed MIME types.

👍 Hint: All analysis plug-ins have an optional parameter "threads" implemented in the "BasePlugin" class. If you do not set this parameter, there will be just one instance of your plug-in.

./internal

The optional folder internal can provide additional code or other stuff needed for the plugin.
You can use the following code to get the absolute path of the internal directory:

import os
from common_helper_files import get_dir_of_file


INTERNAL_DIRECTORY_PATH = os.path.join(get_dir_of_file(__file__), '../internal')

If you want to load python libraries/files from the internal directory you can use the following code.

import os
import sys
from common_helper_files import get_dir_of_file


INTERNAL_DIRECTORY_PATH = os.path.join(get_dir_of_file(__file__), '../internal')

sys.path.append(INTERNAL_DIRECTORY_PATH)
from YOUR_PYTHON_FILE_IN_INTERNAL_DIRECTORY import WHATEVER

./test/test_PLUGIN_NAME.py

This file includes your test code. Since analysis plug-ins use a complex multithreading aprroach, you might want to use this template to simplify your testing. The AnalysisPluginTest class used by the template handles all the multithreadding stuff and checks some general stuff (e.g. plugin registration success).

from test.unit.analysis.analysis_plugin_test_class import AnalysisPluginTest
 
from ..code.YOUR_PLUGIN import AnalysisPlugin
 
 
class test_analysis_plugin_YOUR_PLUGIN_NAME(AnalysisPluginTest):
 
    PLUGIN_NAME = 'YOUR_PLUGIN_NAME'
 
    def setUp(self):
        super().setUp()
        config = self.init_basic_config()
        # additional config can go here
        # additional setup can go here
        self.analysis_plugin = AnalysisPlugin(self, config=config)
     
    def tearDown(self):
        super().tearDown()
        # additional tearDown can go here
 
    def test_your_test_code(self):
        # your test code

./test/data

This folder shall contain any additional files, that are needed for your tests. You can address this folder using the following code in your test code file.

import os
from common_helper_files import get_dir_of_file

TEST_DATA_DIR = os.path.join(get_dir_of_file(__file__), 'data')

./view/PLUGIN_NAME.html

This optional file can can contain a customized view utilizing Jinja2 template features. In general a standard template is used to show the results of an analysis. This standard view just generates a table from the result dictonary including all entries.

Yara Plug-In Development

Many analysis are based on simple pattern matching. Therefore FACT provides a special "YaraBasePlugin" utilizing the Yara pattern matching system.

Store your Yara rule files to the signatures directory. Use the following plug-in template instead of the original template shown above.

from analysis.YaraPluginBase import YaraBasePlugin


class AnalysisPlugin(YaraBasePlugin):
    '''
    A Short description
    '''
    NAME = 'PLUGIN_NAME'
    DESCRIPTION = 'a short description of the plugin'
    DEPENDENCIES = [LIST_OF_PLUG-IN_NAMES_THAT_THIS_PLUGIN_RELYS ON]
    VERSION = 'x.x'
    FILE = __file__

    def __init__(self, plugin_adminstrator, config=None, recursive=True):
        super().__init__(plugin_adminstrator, config=config, recursive=recursive, plugin_path=__file__)