-
Notifications
You must be signed in to change notification settings - Fork 28
I. Creating Output Modules
Output modules are a bit more complicated than input modules, since they consist of a template file as well as Python class file. The template file is a templatized block of code that is rendered and mutated during the payload creation file. The Python file contains all of the information needed by DropEngine to successfully render and mutate the template file.
This section will begin by describing how to create an output module by hand from a piece of CSharp code. We'll then demonstrate how to automate this process using DropEngine's Module Maker.
To understand how this process works, it’s best to go over how to create an output module from scratch. In this example, we’ll create a decryption key (or DKey) module from a piece of CSharp code.
We begin with the following block of CSharp code, which simply returns a static decryption key.
public class DKey
{
public static string Generate()
{
byte[] dkey = { 0, 0, 0, 25 };
return Encoding.UTF8.GetString(dkey);
}
}
The first thing we need to do is convert this block of code into a Jinja template. If you’re not familiar with Jinja, check out this guide here:
For symbolic substitution to work, we need to provide DropEngine with a way of keeping track of symbol names in this file. We do this using the follow steps.
The first thing we do is substitute the name of our class with the Jinja variable special[‘class_name’]
, as shown in the example below.
using System;
public class {{ special['class_name'] }}
{
public static string Generate()
{
byte[] dkey = { 0, 0, 0, 25 };
return Encoding.UTF8.GetString(dkey);
}
}
Next, we substitute the name of our entry-point function with the Jinja variable func_name
, as shown in the next example.
using System;
public class {{ special['class_name'] }}
{
public static string {{ func_name }}()
{
byte[] dkey = { 0, 0, 0, 25 };
return Encoding.UTF8.GetString(dkey);
}
}
Next, we substitute all remaining symbols with the Jinja variable v[‘$SYMBOL’]
, where $SYMBOL is the name of symbol we are wrapping.
using System;
public class {{ special['class_name'] }}
{
public static string {{ func_name }}()
{
byte[] {{ v['dkey'] }} = {{ special['ekey_data']['val_array'] }};
return Encoding.UTF8.GetString({{ v['dkey'] }});
}
}
Finally, we remove the modules imports from the template. Save the list of imports – you’ll need them when you create the Module Class Definition in the next section.
using System;
public class {{ special['class_name'] }}
{
public static string {{ func_name }}()
{
byte[] {{ v['dkey'] }} = {{ special['ekey_data']['val_array'] }};
return Encoding.UTF8.GetString({{ v['dkey'] }});
}
}
Finally, place the resulting template file in the appropriate subdirectory of DropEngine's templates
directory.
We now have a completed Jinja template file. Next, we need to create the module class definition. We begin with the following template:
import json
import config
from base.output.example.csharp_example import CSharpExample
class MExample(CSharpExample):
def __init__(self):
super().__init__()
self.name = ''
self.mtype = ''
self.author = ''
self.description = ''
self.compatible_interfaces = [
'csharp_runner_interface',
]
self.compatible_imodules = [
'ekey_static',
]
self.functions = []
self._vars = [
'dkey',
]
self.comments = []
self.whitespaces = []
self.imports = []
self.template = ''
self.func_name = ''
self.class_name = ''
self.mutate_func_name = True
self.mutate_class_name = True
To create your Module Class Definition, you’ll need to perform the following steps.
Remember the list of symbol names that you wrapped with Jinja variables when you built the module template? DropEngine needs them to render your module into a payload component. Edit the template by adding these symbol names to as strings within self._vars
list as shown below.
import json
import config
from base.output.example.csharp_example import CSharpExample
class MExample(CSharpExample):
def __init__(self):
super().__init__()
#...snip...
self._vars = [
'dkey',
]
#...snip...
You’ll want to add the original class and entry-point function names to the self.class_name
and self.func_name
attributes, respectively (see the example below).
import json
import config
from base.output.example.csharp_example import CSharpExample
class MExample(CSharpExample):
def __init__(self):
super().__init__()
#...snip...
self.func_name = 'generate'
self.class_name = 'DKey'
#...snip...
You’ll want to specify the path to your template file (you should place the template file in one of the subdirectories of DropEngine’s template folder). You can do this by editing the value of the self.template
attribute.
import json
import config
from base.output.example.csharp_example import CSharpExample
class MExample(CSharpExample):
def __init__(self):
super().__init__()
#...snip...
self.template = 'dkeys/dkey_csharp_static.cs'
#...snip...
If you don’t want the class or function name to be mutated, you’ll need to change the values of self.mutate_func_name
and self.mutate_class_name
as shown below.
import json
import config
from base.output.example.csharp_example import CSharpExample
class MExample(CSharpExample):
def __init__(self):
super().__init__()
#...snip...
self.mutate_func_name = True
self.mutate_class_name = True
#...snip...
Remember the list of module imports that you removed from the template file? You need to add these as members of self.imports
as shown below.
import json
import config
from base.output.example.csharp_example import CSharpExample
class MExample(CSharpExample):
def __init__(self):
super().__init__()
#...snip...
self.imports = [
'System',
]
#...snip...
Ass with all modules, you will need to fill out the following metadata within the template:
-
self.name
- the name of the module -
self.mtype
- the module type (for output modules, this should be set to one of the following: decrypters, dkeys, executors, runners, premodules, postmodules) -
self.author
- the name of the module author -
self.description
- a brief description of what the module does -
compatible_interfaces
- a list of interface modules that are compatible with the module -
compatible_omodules
- a list of compatible output modules (for example, an ekey module will have a list of compatible dkey modules)
Our example with added metadata is shown below.
import json
import config
from base.output.example.csharp_example import CSharpExample
class MExample(CSharpExample):
def __init__(self):
super().__init__()
#...snip...
self.name = 'dkey_csharp_static'
self.mtype = 'dkey'
self.author = '@s0lst1c3'
self.description = 'Static dkey for testing purposes'
self.compatible_interfaces = [
'csharp_runner_interface',
]
self.compatible_imodules = [
'ekey_static',
]
#...snip...
Next, we need to modify our class definition according to the following rules:
- DKey modules should always have class type
MDKey
and inherit fromCSharpDKey
- Decrypter modules should always have class type
MDecrypter
and inherit fromCSharpDecrypter
- Executor modules should always have class type
MExecutor
and inherit fromCSharpExecutor
- PreModules modules should always have class type
MPreModule
and inherit fromCSharpPreModule
- PostModules modules should always have class type
MPostModule
and inherit fromCSharpPostModule
- PostModules modules should always have class type
MPostModule
and inherit fromCSharpPostModule
- Runner modules should always have class type
MRunner
and inherit fromShellcodeRunner
In this example, we're creating a DKey module, so our class definition should look like this:
import json
import config
from base.output.example.csharp_example import CSharpExample
class MDKey(CSharpDKey):
def __init__(self):
super().__init__()
#...snip...
Next, we need to modify the import statement for our module's parent class according to the following rules:
- DKey modules must import
CSharpDKey
frombase.output.dkey.csharp_dkey
- Decrypter modules must import
CSharpDecrypter
frombase.output.decrypter.csharp_decrypter
- Executor modules must import
CSharpExecutor
frombase.output.executor.csharp_executor
- PreModule modules must import
CSharpPreModule
frombase.output.premodule.csharp_premodule
- PostModule modules must import
CSharpPostModule
frombase.output.postmodule.csharp_postmodule
- Runner modules must import
ShellcodeRunner
frombase.output.runner.runner
In this example, we're creating a DKey module, so our import statement should look like this:
import json
import config
from base.output.dkey.csharp_dkey import CSharpDKey
class MDKey(CSharpDKey):
def __init__(self):
super().__init__()
#...snip...
We should now have a finished DKey module that looks like this:
Module Class Definition
class MDKey(CSharpDKey):
def __init__(self):
if config.debug:
print('calling MDKey.__init__()')
super().__init__()
self.name = 'dkey_csharp_static'
self.mtype = 'dkey'
self.author = '@s0lst1c3'
self.description = 'Static dkey for testing purposes'
self.compatible_interfaces = [
'csharp_runner_interface',
]
self.compatible_imodules = [
'ekey_static',
]
self.functions = []
self._vars = [
'dkey',
]
self.comments = []
self.whitespaces = []
self.imports = [
'System',
]
self.template = 'dkeys/dkey_csharp_static.cs'
self.func_name = 'generate'
self.class_name = 'DKey'
self.mutate_func_name = True
self.mutate_class_name = True
Template
{
public static string {{ func_name }}()
{
byte[] {{ v['dkey'] }} = {{ special['ekey_data']['val_array'] }};
return Encoding.UTF8.GetString({{ v['dkey'] }});
}
}
The manual approach documented in the previous section can be time consuming and labor intensive, especially for larger templates. To solve this problem, DropEngine includes a standalone tool called Module Maker that uses ANTLR4 Abstract Syntax Trees (ASTs) to automate the process of turning raw payload components into DropEngine modules.
Say we want to create a decrypter module from the following block of CSharp code:
using System;
public class TestClassName
{
public static byte[] decrypt(byte[] ct, byte[] ekey)
{
byte [] iv = { };
SHA256Managed hashstring = new SHA256Managed();
byte[] hashed_key = hashstring.ComputeHash(ekey);
using (RijndaelManaged rijAlg = new RijndaelManaged())
{
rijAlg.Key = hashed_key;
rijAlg.IV = iv;
rijAlg.Padding = PaddingMode.PKCS7;
rijAlg.BlockSize = 128;
rijAlg.Mode = CipherMode.CBC;
ICryptoTransform decryptor = rijAlg.CreateDecryptor(rijAlg.Key, rijAlg.IV);
return decryptor.TransformFinalBlock(ct, 0, ct.Length);
}
}
}
We first need to obtain a complete list of symbol names from this block of code using the csharp_ini_maker.py
script included with DropEngine. Point the script towards the code's source file using the --input-file
flag, and use the --output-file
flag to specify an output file where the symbols should be sent (see next example).
python csharp_ini_maker.py --input-file main.cs --output-file main.ini
The csharp_ini_maker.py
script will use an Abstract Syntax Tree (AST) created with ANTLR4 to enumerate all of the symbols in the source file and store them in the output file. Open up the output file, which should look something like this:
[vars]
hashed_key
rijAlg
decryptor
hashstring
iv
[methods]
decrypt
[class_decls]
TestClassName
[params]
ct
ekey
[delegates]
[imports]
System
Finally, point the csharp_module_maker.py
script included with DropEngine at both the CSharp source file and the INI file generated in the previous step as shown in the following example. Note that the --source-file
flag points to our source file, --symbol-file
points to our symbol INI file, --class-name
is used to specify the CSharp class name, and --func-name
is used to specify the entrypoint function of our original block of code. The rest of the flags are used to pass metadata. All of the flags shown in this example are mandatory.
python csharp_module_maker.py \
--source-file main.cs \
--symbol-file main.ini \
--type decrypter \
--name test_decrypter \
--author s0lst1c3 \
--description itsamodule \
--class-name TestClassName \
--func-name decrypt \
--compatible-imodules asdf
Once you've run csharp_module_maker.py
, you should have two files: a template file and a module class definition file. These files will automatically be saved to their correct locations within DropEngine's directory tree. Both files are shown below:
Template:
public class {{ special['class_name'] }}
{
public static byte[] {{ func_name }}(byte[] {{ v['ct'] }}, byte[] {{ v['ekey'] }})
{
byte [] {{ v['iv'] }} = { };
SHA256Managed {{ v['hashstring'] }} = new SHA256Managed();
byte[] {{ v['hashed_key'] }} = {{ v['hashstring'] }}.ComputeHash({{ v['ekey'] }});
using (RijndaelManaged {{ v['rijAlg'] }} = new RijndaelManaged())
{
{{ v['rijAlg'] }}.Key = {{ v['hashed_key'] }};
{{ v['rijAlg'] }}.IV = {{ v['iv'] }};
{{ v['rijAlg'] }}.Padding = PaddingMode.PKCS7;
{{ v['rijAlg'] }}.BlockSize = 128;
{{ v['rijAlg'] }}.Mode = CipherMode.CBC;
ICryptoTransform {{ v['decryptor'] }} = {{ v['rijAlg'] }}.CreateDecryptor({{ v['rijAlg'] }}.Key, {{ v['rijAlg'] }}.IV);
return {{ v['decryptor'] }}.TransformFinalBlock({{ v['ct'] }}, 0, {{ v['ct'] }}.Length);
}
}
}
Module Class Definition:
import json
import config
from base.output.decrypter.csharp_decrypter import CSharpDecrypter
class MDecrypter(CSharpDecrypter):
def __init__(self):
if config.debug:
print('calling MDecrypter.__init__()')
super().__init__()
self.name = 'test_decrypter'
self.mtype = 'decrypter'
self.author = 's0lst1c3'
self.description = 'itsamodule'
self.compatible_interfaces = [
]
self.compatible_imodules = [
'asdf',
]
self.functions = []
self._vars = [
'hashed_key',
'hashstring',
'decryptor',
'rijAlg',
'iv',
'decrypt',
'ekey',
'ct',
]
self.comments = []
self.whitespaces = []
self.imports = [
'System',
]
self.template = 'decrypters/test_decrypter.cs'
self.func_name = 'decrypt'
self.class_name = 'TestClassName'
self.mutate_func_name = True
self.mutate_class_name = True