-
Notifications
You must be signed in to change notification settings - Fork 27
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 = TrueTo 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
MDKeyand inherit fromCSharpDKey - Decrypter modules should always have class type
MDecrypterand inherit fromCSharpDecrypter - Executor modules should always have class type
MExecutorand inherit fromCSharpExecutor - PreModules modules should always have class type
MPreModuleand inherit fromCSharpPreModule - PostModules modules should always have class type
MPostModuleand inherit fromCSharpPostModule - PostModules modules should always have class type
MPostModuleand inherit fromCSharpPostModule - Runner modules should always have class type
MRunnerand 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
CSharpDKeyfrombase.output.dkey.csharp_dkey - Decrypter modules must import
CSharpDecrypterfrombase.output.decrypter.csharp_decrypter - Executor modules must import
CSharpExecutorfrombase.output.executor.csharp_executor - PreModule modules must import
CSharpPreModulefrombase.output.premodule.csharp_premodule - PostModule modules must import
CSharpPostModulefrombase.output.postmodule.csharp_postmodule - Runner modules must import
ShellcodeRunnerfrombase.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 = TrueTemplate
{
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