Skip to content

Contribute

Secu edited this page Jul 25, 2023 · 12 revisions

Kraken has a modular and scalable design that allows to extend and customize its usage. This design has been chosen because the post-exploitation process depends on the needs and objectives.

This section explains how to contribute to each of Kraken's components.

Agents

TODO

Modules

Kraken modules are organized according to a hierarchy ("file-level") and a specific layout ("content-level"). The reason for maintaining this structure is to allow Kraken to be scalable and extensible, since anyone can develop one or more modules and integrate them easily.

To show an example of module-level contribution, the "mv" module will be used as an example and we will show the creation and structuring process.

Introduction

Before we start, we must be aware of the following points about the new module that we are going to incorporate:

  1. The purpose of the module (whether it re-implements a system command or is an abstract functionality).
  2. The operating systems and technologies on which it can work.
  3. The versions of the technologies for which it is available.
  4. The arguments required by the module
  5. Possible examples of use and error situations
  6. If you need any specific functionality on the client side (dispatcher)
  7. If you need a specific output format (formater)
  8. If you have any external reference

These points will be essential when developing our module, because it is all the information that must be provided when registering and integrating it with Kraken. Once we have defined these points, we can move on to the creation of the module.

For this example, the process will be divided into the three supported technologies, since the module will be created for all of them:

PHP Module Creation

We begin with the creation of the module in PHP language. The first step is to use the PHP template that Kraken provides as a base.

PHP Template Structure

Before editing, the general structure of the template will be explained. We start from the following code:

#<?php


class Module_template
{
    private $SUCC_CODE        = 0;
    private $ERR_CODE         = 1;

    private $FIELD_SEPARATOR  = ",";
    private $VALUE_SEPARATOR  = "=";

    private $RESPONSE_STATUS  = "status";
    private $RESPONSE_MESSAGE = "message";

    private $return_code;

    public function __construct($cwd)
    {
        $this->return_code = $this->SUCC_CODE;
        $cwd = $this->normalizePath($cwd);
        chdir($cwd);
    }

    private function normalizePath($currPath)
    {
        $currPath = str_replace("\"", "", $currPath);
        $currPath = str_replace("'", "", $currPath);
        $currPath = str_replace("\\", "/", $currPath);
        return $currPath;
    }

    private function parseArgs($args)
    {
        preg_match_all('/"[^"]+"|\'[^\']+\'|\S+/', $args, $matches);
        return $matches[0];
    }

    private function generateResponse($result)
    {
        $response  = "";
        $response .= $this->RESPONSE_STATUS . $this->VALUE_SEPARATOR . $this->return_code;
        $response .= $this->FIELD_SEPARATOR;
        $response .= $this->RESPONSE_MESSAGE . $this->VALUE_SEPARATOR . bin2hex($result);
        return bin2hex($response);
    }

    private function doAction($param_one, $param_two, $param_three)
    {
        $result = "";

        try
        {
            $result = "do something";
        }
        catch (Exception $e)
        {
            $result .= $e->getMessage() . PHP_EOL;
            $this->return_code = $this->ERR_CODE;
        }

        return $result;
    }

    public function execute($args)
    {
        $result = "";

        $parsed_args = $this->parseArgs(hex2bin($args));

        if (sizeof($parsed_args) == 0)
        {
            $result = "Invalid arguments provided";
            $this->return_code = $this->ERR_CODE;
        }
        else
        {
            $result .= $this->doAction($parsed_args[0], $parsed_args[1], $parsed_args[2]);
        }
        return $this->generateResponse($result);
    }
}


$cwd = '#{CWD}';
$args = '#{ARGS}';
$module = new Module_template($cwd);
print($module->execute($args));

The base PHP module is a Class, which is instantiated and invoked. The class must follow a name pattern with the format: Module_<name-of-module>. Before its instance, we can see 2 variables:

  • cwd: contains the path from where the module should be invoked (current working directory). Example: /var/www/html.
  • args: contains a representation of the arguments to be passed to the module in HexString format. Example: 2f746d702f66696c655f61202f746d702f66696c655f62.

The content of these variables is replaced by the agent before the module execution. Because, both the cwd and the module arguments, are dynamic data which the agent does not have until the moment in which the module is executed.

The class is instantiated by passing the cwd to the constructor: the first thing that is performed is to move to the target directory (this path contained in the cwd is sanitized to avoid spaces or invalid values sent by the client).

After this step, the execute() method is invoked passing the module arguments by parameter. This method is the responsible for processing the module arguments, treating them as required, invoking the do... and returning a response for the agent. Let's explain the flow of this method:

  1. The HexString of the arguments is processed by calling parseArgs() and we obtain an array with the different arguments (this processing is performed based on a regular expression).
  2. Then the necessary checks on the arguments are performed. In the case of the default template, it simply ensures the existence of arguments passed to the module.
  3. After validation, the method that will be used to perform the actions that characterize the module is invoked. This method, by convention, should follow the name pattern: do<Action> (where 'Action' is the name of the action performed by the module. An example of a name will be shown later).
  4. The do... method receives the arguments and performs the expected functionality. From this method you can create as many methods as you want to achieve its purpose. It is recommended to add an exception control to avoid unexpected situations.
  5. Finally, with the value returned by the "do..." we proceed to encapsulate it in a special response format, which the agent is able to "process". Basically the structure of the response is something like: HEXSTR(status=<RETURN_CODE>,message=HEXSTR(<OUTPUT>)). The module prints this response because the agent captures it using the output buffering functions.

And here is the general structure of the PHP module template.

PHP Module Development

Next we are going to edit this template to convert it into the PHP mv module.

First, we have to change the Class name and the "do..." method to match the naming format. The modification looks like the following:

#<?php


class Module_mv
{
    private $SUCC_CODE        = 0;
    private $ERR_CODE         = 1;

    private $FIELD_SEPARATOR  = ",";
    private $VALUE_SEPARATOR  = "=";

    private $RESPONSE_STATUS  = "status";
    private $RESPONSE_MESSAGE = "message";

    private $return_code;

    public function __construct($cwd)
    {
        $this->return_code = $this->SUCC_CODE;
        $cwd = $this->normalizePath($cwd);
        chdir($cwd);
    }

    private function normalizePath($currPath)
    {
        $currPath = str_replace("\"", "", $currPath);
        $currPath = str_replace("'", "", $currPath);
        $currPath = str_replace("\\", "/", $currPath);
        return $currPath;
    }

    private function parseArgs($args)
    {
        preg_match_all('/"[^"]+"|\'[^\']+\'|\S+/', $args, $matches);
        return $matches[0];
    }

    private function generateResponse($result)
    {
        $response  = "";
        $response .= $this->RESPONSE_STATUS . $this->VALUE_SEPARATOR . $this->return_code;
        $response .= $this->FIELD_SEPARATOR;
        $response .= $this->RESPONSE_MESSAGE . $this->VALUE_SEPARATOR . bin2hex($result);
        return bin2hex($response);
    }

    private function doMove($param_one, $param_two, $param_three)
    {
        $result = "";

        try
        {
            $result = "do something";
        }
        catch (Exception $e)
        {
            $result .= $e->getMessage() . PHP_EOL;
            $this->return_code = $this->ERR_CODE;
        }

        return $result;
    }

    public function execute($args)
    {
        $result = "";

        $parsed_args = $this->parseArgs(hex2bin($args));

        if (sizeof($parsed_args) == 0)
        {
            $result = "Invalid arguments provided";
            $this->return_code = $this->ERR_CODE;
        }
        else
        {
            $result .= $this->doMove($parsed_args[0], $parsed_args[1], $parsed_args[2]);
        }
        return $this->generateResponse($result);
    }
}


$cwd = '#{CWD}';
$args = '#{ARGS}';
$module = new Module_mv($cwd);
print($module->execute($args));

After this, I proceed to modify the execute() method to adjust the processing and validation of arguments to the case more similar to the unix command mv. It will be as follows:

#<?php


class Module_mv
{
    private $SUCC_CODE        = 0;
    private $ERR_CODE         = 1;

    private $FIELD_SEPARATOR  = ",";
    private $VALUE_SEPARATOR  = "=";

    private $RESPONSE_STATUS  = "status";
    private $RESPONSE_MESSAGE = "message";

    private $return_code;

    public function __construct($cwd)
    {
        $this->return_code = $this->SUCC_CODE;
        $cwd = $this->normalizePath($cwd);
        chdir($cwd);
    }

    private function normalizePath($currPath)
    {
        $currPath = str_replace("\"", "", $currPath);
        $currPath = str_replace("'", "", $currPath);
        $currPath = str_replace("\\", "/", $currPath);
        return $currPath;
    }

    private function parseArgs($args)
    {
        preg_match_all('/"[^"]+"|\'[^\']+\'|\S+/', $args, $matches);
        return $matches[0];
    }

    private function generateResponse($result)
    {
        $response  = "";
        $response .= $this->RESPONSE_STATUS . $this->VALUE_SEPARATOR . $this->return_code;
        $response .= $this->FIELD_SEPARATOR;
        $response .= $this->RESPONSE_MESSAGE . $this->VALUE_SEPARATOR . bin2hex($result);
        return bin2hex($response);
    }

    private function doMove($sources, $dest)
    {
        $result = "";

        try
        {
            $result = "do something";
        }
        catch (Exception $e)
        {
            $result .= $e->getMessage() . PHP_EOL;
            $this->return_code = $this->ERR_CODE;
        }

        return $result;
    }

    public function execute($args)
    {
        $result = "";

        $parsed_args = $this->parseArgs(hex2bin($args));

        if (sizeof($parsed_args) < 2)
        {
            $result = "Invalid arguments provided. Specify a source file or directory to be moved to a destination";
            $this->return_code = $this->ERR_CODE;
        }
        else
        {
            $sources = array_slice($parsed_args, 0, -1);
            $dest    = end($parsed_args);
            $result .= $this->doMove($sources, $dest);
        }
        return $this->generateResponse($result);
    }
}


$cwd = '#{CWD}';
$args = '#{ARGS}';
$module = new Module_mv($cwd);
print($module->execute($args));

Finally, in the doMove() method, I put all the logic of my module: checks, functionality, error control, etc. With these changes, the module is completed:

#<?php


class Module_mv
{
    private $SUCC_CODE        = 0;
    private $ERR_CODE         = 1;

    private $FIELD_SEPARATOR  = ",";
    private $VALUE_SEPARATOR  = "=";

    private $RESPONSE_STATUS  = "status";
    private $RESPONSE_MESSAGE = "message";

    private $return_code;

    public function __construct($cwd)
    {
        $this->return_code = $this->SUCC_CODE;
        $cwd = $this->normalizePath($cwd);
        chdir($cwd);
    }

    private function normalizePath($currPath)
    {
        $currPath = str_replace("\"", "", $currPath);
        $currPath = str_replace("'", "", $currPath);
        $currPath = str_replace("\\", "/", $currPath);
        return $currPath;
    }

    private function parseArgs($args)
    {
        preg_match_all('/"[^"]+"|\'[^\']+\'|\S+/', $args, $matches);
        return $matches[0];
    }

    private function generateResponse($result)
    {
        $response  = "";
        $response .= $this->RESPONSE_STATUS . $this->VALUE_SEPARATOR . $this->return_code;
        $response .= $this->FIELD_SEPARATOR;
        $response .= $this->RESPONSE_MESSAGE . $this->VALUE_SEPARATOR . bin2hex($result);
        return bin2hex($response);
    }

    private function doMove($sources, $dest)
    {
        $result = "";

        try
        {
            $dest = $this->normalizePath($dest);

            if ((sizeof($sources) > 1) && (@is_dir($dest) === false))
            {
                throw new Exception("mv: target '$dest' is not a directory", 1);
            }

            foreach ($sources as $source)
            {
                $source = $this->normalizePath($source);
                if (@file_exists($source) === false)
                {
                    $result .= "mv: cannot stat '$source': No such file or directory" . PHP_EOL;
                    continue;
                }

                if ((@file_exists($dest) === true) && (@is_dir($dest) === true))
                {
                    $dest_expand = $dest . DIRECTORY_SEPARATOR . basename($source);
                    if (@rename($source, $dest_expand) === false)
                    {
                        $result .= "mv: cannot move '$source' to '$dest_expand': Failed" . PHP_EOL;
                        continue;
                    }
                }
                else
                {
                    if (@rename($source, $dest) === false)
                    {
                        $result .= "mv: cannot move '$source' to '$dest': Failed" . PHP_EOL;
                        continue;
                    }
                }
            }
        }
        catch (Exception $e)
        {
            $result .= $e->getMessage() . PHP_EOL;
            $this->return_code = $this->ERR_CODE;
        }

        return $result;
    }

    public function execute($args)
    {
        $result = "";

        $parsed_args = $this->parseArgs(hex2bin($args));

        if (sizeof($parsed_args) < 2)
        {
            $result = "Invalid arguments provided. Specify a source file or directory to be moved to a destination" . PHP_EOL;
            $this->return_code = $this->ERR_CODE;
        }
        else
        {
            $sources = array_slice($parsed_args, 0, -1);
            $dest    = end($parsed_args);
            $result .= $this->doMove($sources, $dest);
        }
        return $this->generateResponse($result);
    }
}


$cwd = '#{CWD}';
$args = '#{ARGS}';
$module = new Module_mv($cwd);
print($module->execute($args));

PHP Module Versioning

After completing the development of the module, it should be placed on disk from the following steps:

  1. We situate in the root directory of the modules: modules/ (where the templates are located).
  2. We create a directory with the name of our module: mv.
  3. Inside the directory we just created, we save our module following the specific format (<name>.<lang><version>.<lang>). For this case: mv.php8.php (we define the module for the highest PHP version that we have tested its operation: 8).

Optionally, you can validate the operation of the module in different versions of the technology using the utility: check_syntax. This will allow us to detect possible errors and/or unexpected operation.

First we create the symbolic links for each version of the technology pointing to the mv.php8.php file.

ln -s mv.php8.php mv.php7.php
ln -s mv.php8.php mv.php5.6.php
ln -s mv.php8.php mv.php5.5.php
ln -s mv.php8.php mv.php5.4.php

Then we move to the utility directory and run the checker:

cd ../../utils/check_syntax/
# Remember to run the 'pull_php.sh' script to download the containers for each PHP version
python check_php.py

In the results, we can see that the mv module works for all PHP versions covered by Kraken:

[+] Module: 'mv.php5.6.php' for version '5.6' compiled successfully
[+] Module: 'mv.php5.6.php' for version '5.6' executed successfully
[+] Module: 'mv.php5.4.php' for version '5.4' compiled successfully
[+] Module: 'mv.php5.4.php' for version '5.4' executed successfully
[+] Module: 'mv.php5.5.php' for version '5.5' compiled successfully
[+] Module: 'mv.php5.5.php' for version '5.5' executed successfully
[+] Module: 'mv.php8.php' for version '8.0' compiled successfully
[+] Module: 'mv.php8.php' for version '8.0' executed successfully
[+] Module: 'mv.php8.php' for version '8.1' compiled successfully
[+] Module: 'mv.php8.php' for version '8.1' executed successfully
[+] Module: 'mv.php8.php' for version '8.2' compiled successfully
[+] Module: 'mv.php8.php' for version '8.2' executed successfully
[+] Module: 'mv.php7.php' for version '7.0' compiled successfully
[+] Module: 'mv.php7.php' for version '7.0' executed successfully
[+] Module: 'mv.php7.php' for version '7.1' compiled successfully
[+] Module: 'mv.php7.php' for version '7.1' executed successfully
[+] Module: 'mv.php7.php' for version '7.2' compiled successfully
[+] Module: 'mv.php7.php' for version '7.2' executed successfully
[+] Module: 'mv.php7.php' for version '7.3' compiled successfully
[+] Module: 'mv.php7.php' for version '7.3' executed successfully
[+] Module: 'mv.php7.php' for version '7.4' compiled successfully
[+] Module: 'mv.php7.php' for version '7.4' executed successfully

Performing this check is interesting because, if we had used a function that did not exist in an old version of PHP, this script would show us the error and we could correct it. Otherwise, we could drag an unknown bug and it would occur only in a specific environment (without giving us enough visibility to be able to identify and correct it).

PHP Module Registration

The next step is to register the module in the configuration file: modules.py. In this file we define the characteristics of our module (the ones we have discussed in the "module preparation" section). You can take as a template the structure of any existing module that most closely approximates yours. However, each field of the structure is explained below:

  • Name: this field contains the name of the module, which will be displayed in the Kraken console and help.
  • Description: a short description about the module functionality.
  • Author: the names of the module authors (one or multiple).
  • Template: name of the template in the module directory (must match with the name of module).
  • Examples: a list of examples of usage (the idea is to cover different cases).
  • Operating System: operating systems supported by the module.
  • References: references to external libraries of the module (only for .NET).
  • Arguments: list of arguments in argparser format.
  • Dispatcher: name of the component that will manage the execution and operation of the module (default: 'default').
  • Formater: name of the component that will format the module output (default: 'default').

In the case of the "mv" module, the configuration structure, in Python dictionary format, would look like this:

{
    "name" : "mv",
    "description" : "Move file/s or directory/ies to another destination",
    "author" : "@secu_x11",
    "template" : "mv",
    "examples" : [
        "mv example.txt demo.txt",
        "mv example.txt /tmp/example.txt",
        "mv example.txt /tmp",
        "mv example.txt /tmp/",
        "mv somedir otherdir",
        "mv somedir /tmp/somedir",
        "mv somedir /tmp",
        "mv somedir /tmp/",
        "mv example.txt demo.txt /tmp",
        "mv example.txt somedir /tmp"
    ],
    "so" : [
        {
            "name" : "Linux",
            "agents" : ["php"]
        },
        {
            "name" : "Windows",
            "agents" : ["php"]
        }
    ],
    "references" : [],
    "args": [
        {
            "sources": {
                "help": "Source files or directories",
                "nargs" : "*",
                "type":  str
            },
            "dest": {
                "help": "Destination file or directory",
                "nargs": 1,
                "type":  str
            }
        }
    ],
    "dispatcher" : "default",
    "formater" : "default"
}

Then, the module configuration dictionary is added to the MODULE_COMMANDS array. And with this, when relaunching Kraken, it will detect the new module and, if it fits the environment conditions, Kraken will load it to be able to use it:

(kraken) secu@x11 ~/Kraken (main)$ python kraken.py -c -m st -p utils/req2profile/examples/profile_testing_php_linux_st.json -k raw
(ST) www-data@b208172e9c50:/var/www/html$ help mv
usage: mv [sources [sources ...]] dest

Move file/s or directory/ies to another destination

positional arguments:
  sources  Source files or directories
  dest     Destination file or directory

Examples:
  mv example.txt demo.txt
  mv example.txt /tmp/example.txt
  mv example.txt /tmp
  mv example.txt /tmp/
  mv somedir otherdir
  mv somedir /tmp/somedir
  mv somedir /tmp
  mv somedir /tmp/
  mv example.txt demo.txt /tmp
  mv example.txt somedir /tmp

(ST) www-data@b208172e9c50:/var/www/html$ ls test.php

  -rw-r--r--  1      www-data  www-data  18     2023/03/08 11:06:14  test.php

(ST) www-data@b208172e9c50:/var/www/html$ mv test.php /tmp

(ST) www-data@b208172e9c50:/var/www/html$ ls /tmp/test.php

  -rw-r--r--  1      www-data  www-data  18     2023/03/08 11:06:14  test.php

(ST) www-data@b208172e9c50:/var/www/html$

Java Module Creation

Continuing with the implementation of the mv module, this time we will use the Java template to build the Java implementation.

Java Template Structure

The Java template differs in a few things with the PHP template (explained above). The code is as follows:

import java.io.*;
import java.util.*;
import java.text.*;
import java.util.regex.*;


public class Module_template
{
    private String cwd = System.getProperty("user.dir");
    private final String SUCC_CODE = "0";
    private final String ERR_CODE  = "1";
    private final String JAVA_EOL  = getLineSeparator();

    private String getLineSeparator()
    {
        String os_name = System.getProperty("os.name");
        if (os_name.startsWith("Windows"))
            return "\r\n";
        else
            return "\n";
    }

    private byte[] hex2bin(String data) throws Exception
    {
        if ((data.length() % 2) == 1)
            throw new Exception("hex2bin(): data cannot have an odd number of digits");
        
        byte[] data_bytes = new byte[data.length() / 2];
        for (int i = 0; i < data.length(); i += 2)
        {
            String hs = data.substring(i, i + 2);
            try
            {
                int val = Integer.parseInt(hs, 16);
                data_bytes[i/2] = (byte)val;
            }
            catch(Exception e)
            {
                throw new Exception("hex2bin() failed to convert hex:'" + hs + "' to byte");
            }
        }
        return data_bytes;
    }

    private String bin2hex(byte[] ba) throws Exception
    {
        try
        {
            StringBuilder sb = new StringBuilder(ba.length * 2);
            for (byte b: ba)
                sb.append(String.format("%02x", b));
            return sb.toString();
        }
        catch(Exception e)
        {
            throw new Exception("bin2hex() failed");
        }
    }

    private String hex2str(String data) throws Exception
    {
        byte[] data_bytes = hex2bin(data);
        String data_string = new String(data_bytes);
        return data_string;
    }

    private String sanitizePath(String currPath)
    {
        currPath = currPath.replace("\"", "");
        currPath = currPath.replace("'", "");
        currPath = currPath.replace("\\", "/");
        return currPath;
    }

    private String normalizePath(String currPath) throws IOException
    {
        currPath = sanitizePath(currPath);

        File filepath = new File(currPath);
        if (filepath.isAbsolute())
        {
            return filepath.getCanonicalPath();
        }
        else
        {
            File new_filepath = new File(this.cwd + File.separator + currPath);
            return new_filepath.getCanonicalPath();
        }
    }

    private String[] parseArgs(String args)
    {
        String regex = "\"[^\"]+\"|'[^']+'|\\S+";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(args);
        List<String> arguments = new ArrayList<String>();

        while (matcher.find())
            arguments.add(matcher.group(0));

        String[] arguments_arr = new String[arguments.size()];
        arguments_arr = arguments.toArray(arguments_arr);
        return arguments_arr;
    }

    private String changeCWD(String cwd) throws Exception
    {
        File target_dir = new File(cwd).getAbsoluteFile();

        if (target_dir.exists() == false)
            throw new Exception("Directory: '" + cwd + "': does not exist");

        if (target_dir.canRead() == false)
            throw new Exception("Can't move to path: '" + cwd + "': permission denied");

        if (target_dir.isDirectory() == false)
            throw new Exception("Path: '" + cwd + "': is not a directory");

        System.setProperty("user.dir", target_dir.getCanonicalPath());

        return sanitizePath(target_dir.getCanonicalPath());
    }

    private String[] doAction(String param_one, String param_two, String param_three)
    {
        String result = "";
        try
        {
            result = "do something" + JAVA_EOL;
        }
        catch(Exception ex)
        {
            return new String[]{ERR_CODE, ex.getMessage() + JAVA_EOL};
        }
        return new String[]{SUCC_CODE, result};
    }

    public String[] execute(String[] args)
    {        
        if (args.length != 1)
            return new String[]{ERR_CODE, "Invalid arguments provided. Only one directory is allowed to be moved" + JAVA_EOL};
        
        return doAction(args[0]);
    }

    public String[] go(String module_cwd, String module_args)
    {
        try
        {
            this.cwd = changeCWD(hex2str(module_cwd));
            String[] args = parseArgs(hex2str(module_args));
            return execute(args);
        }
        catch(Exception ex)
        {
            return new String[]{ERR_CODE, ex.getMessage() + JAVA_EOL};
        }
    }

    public static void main(String[] args)
    {
        Module_template m = new Module_template();
        String[] results = m.execute(args);
        System.out.println(results[1]);
        return;
    }
}

Basically, the structure and many methods are similar to the PHP template. Although it has some differences that will be seen below:

  • At the beginning of the template, a series of Java libraries necessary for the correct functioning of the module are imported. These imports are considered as "base" and must be present in all modules. Additionally, those needed for the particular operation of the module can be added later.
  • In the case of Java, unlike PHP, it has a series of additional methods for the treatment of: hexadecimal codification, change of directories, etc.
  • Java modules must implement a static method: main() as seen in the template. This method will be necessary to compile the module locally and to be able to test it.
  • Class methods are also available: go() and execute():
    • The go() method is similar to how it is used in Cobaltstrike BOFs. Let's say that it is the method that the agent invokes (after module loading) and which returns the final response. The implementation is fixed and, in principle, should not be changed.
    • This "go" method will call the execute() method, which performs the same function as in PHP modules (see the previous section).

Otherwise, the Java implementation is the same as in the PHP example.

Java Module Development

Next we are going to edit this template to convert it into the mv Java module.

First we change the Class name along with the do... as in the previous examples. We also add the checks to the execute() method:

import java.io.*;
import java.util.*;
import java.text.*;
import java.util.regex.*;


public class Module_mv
{
    private String cwd = System.getProperty("user.dir");
    private final String SUCC_CODE = "0";
    private final String ERR_CODE  = "1";
    private final String JAVA_EOL  = getLineSeparator();

    private String getLineSeparator()
    {
        String os_name = System.getProperty("os.name");
        if (os_name.startsWith("Windows"))
            return "\r\n";
        else
            return "\n";
    }

    private byte[] hex2bin(String data) throws Exception
    {
        if ((data.length() % 2) == 1)
            throw new Exception("hex2bin(): data cannot have an odd number of digits");
        
        byte[] data_bytes = new byte[data.length() / 2];
        for (int i = 0; i < data.length(); i += 2)
        {
            String hs = data.substring(i, i + 2);
            try
            {
                int val = Integer.parseInt(hs, 16);
                data_bytes[i/2] = (byte)val;
            }
            catch(Exception e)
            {
                throw new Exception("hex2bin() failed to convert hex:'" + hs + "' to byte");
            }
        }
        return data_bytes;
    }

    private String bin2hex(byte[] ba) throws Exception
    {
        try
        {
            StringBuilder sb = new StringBuilder(ba.length * 2);
            for (byte b: ba)
                sb.append(String.format("%02x", b));
            return sb.toString();
        }
        catch(Exception e)
        {
            throw new Exception("bin2hex() failed");
        }
    }

    private String hex2str(String data) throws Exception
    {
        byte[] data_bytes = hex2bin(data);
        String data_string = new String(data_bytes);
        return data_string;
    }

    private String sanitizePath(String currPath)
    {
        currPath = currPath.replace("\"", "");
        currPath = currPath.replace("'", "");
        currPath = currPath.replace("\\", "/");
        return currPath;
    }

    private String normalizePath(String currPath) throws IOException
    {
        currPath = sanitizePath(currPath);

        File filepath = new File(currPath);
        if (filepath.isAbsolute())
        {
            return filepath.getCanonicalPath();
        }
        else
        {
            File new_filepath = new File(this.cwd + File.separator + currPath);
            return new_filepath.getCanonicalPath();
        }
    }

    private String[] parseArgs(String args)
    {
        String regex = "\"[^\"]+\"|'[^']+'|\\S+";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(args);
        List<String> arguments = new ArrayList<String>();

        while (matcher.find())
            arguments.add(matcher.group(0));

        String[] arguments_arr = new String[arguments.size()];
        arguments_arr = arguments.toArray(arguments_arr);
        return arguments_arr;
    }

    private String changeCWD(String cwd) throws Exception
    {
        File target_dir = new File(cwd).getAbsoluteFile();

        if (target_dir.exists() == false)
            throw new Exception("Directory: '" + cwd + "': does not exist");

        if (target_dir.canRead() == false)
            throw new Exception("Can't move to path: '" + cwd + "': permission denied");

        if (target_dir.isDirectory() == false)
            throw new Exception("Path: '" + cwd + "': is not a directory");

        System.setProperty("user.dir", target_dir.getCanonicalPath());

        return sanitizePath(target_dir.getCanonicalPath());
    }

    private String[] doMove(String[] sources, String dest)
    {
        String result = "";
        try
        {
            result = "do something" + JAVA_EOL;
        }
        catch(Exception ex)
        {
            return new String[]{ERR_CODE, ex.getMessage() + JAVA_EOL};
        }
        return new String[]{SUCC_CODE, result};
    }

    public String[] execute(String[] args)
    {        
        if (args.length < 2)
            return new String[]{ERR_CODE, "Invalid arguments provided. Specify a source file or directory to be moved to a destination" + JAVA_EOL};
        
        String[] sources = Arrays.copyOfRange(args, 0, (args.length - 1));
        String dest      = args[(args.length - 1)];

        return doMove(sources, dest);
    }

    public String[] go(String module_cwd, String module_args)
    {
        try
        {
            this.cwd = changeCWD(hex2str(module_cwd));
            String[] args = parseArgs(hex2str(module_args));
            return execute(args);
        }
        catch(Exception ex)
        {
            return new String[]{ERR_CODE, ex.getMessage() + JAVA_EOL};
        }
    }

    public static void main(String[] args)
    {
        Module_mv m = new Module_mv();
        String[] results = m.execute(args);
        System.out.println(results[1]);
        return;
    }
}

Then, the module is completed with the functionality of the doMove() method:

import java.io.*;
import java.util.*;
import java.text.*;
import java.util.regex.*;


public class Module_mv
{
    private String cwd = System.getProperty("user.dir");
    private final String SUCC_CODE = "0";
    private final String ERR_CODE  = "1";
    private final String JAVA_EOL  = getLineSeparator();

    private String getLineSeparator()
    {
        String os_name = System.getProperty("os.name");
        if (os_name.startsWith("Windows"))
            return "\r\n";
        else
            return "\n";
    }

    private byte[] hex2bin(String data) throws Exception
    {
        if ((data.length() % 2) == 1)
            throw new Exception("hex2bin(): data cannot have an odd number of digits");
        
        byte[] data_bytes = new byte[data.length() / 2];
        for (int i = 0; i < data.length(); i += 2)
        {
            String hs = data.substring(i, i + 2);
            try
            {
                int val = Integer.parseInt(hs, 16);
                data_bytes[i/2] = (byte)val;
            }
            catch(Exception e)
            {
                throw new Exception("hex2bin() failed to convert hex:'" + hs + "' to byte");
            }
        }
        return data_bytes;
    }

    private String bin2hex(byte[] ba) throws Exception
    {
        try
        {
            StringBuilder sb = new StringBuilder(ba.length * 2);
            for (byte b: ba)
                sb.append(String.format("%02x", b));
            return sb.toString();
        }
        catch(Exception e)
        {
            throw new Exception("bin2hex() failed");
        }
    }

    private String hex2str(String data) throws Exception
    {
        byte[] data_bytes = hex2bin(data);
        String data_string = new String(data_bytes);
        return data_string;
    }

    private String sanitizePath(String currPath)
    {
        currPath = currPath.replace("\"", "");
        currPath = currPath.replace("'", "");
        currPath = currPath.replace("\\", "/");
        return currPath;
    }

    private String normalizePath(String currPath) throws IOException
    {
        currPath = sanitizePath(currPath);

        File filepath = new File(currPath);
        if (filepath.isAbsolute())
        {
            return filepath.getCanonicalPath();
        }
        else
        {
            File new_filepath = new File(this.cwd + File.separator + currPath);
            return new_filepath.getCanonicalPath();
        }
    }

    private String[] parseArgs(String args)
    {
        String regex = "\"[^\"]+\"|'[^']+'|\\S+";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(args);
        List<String> arguments = new ArrayList<String>();

        while (matcher.find())
            arguments.add(matcher.group(0));

        String[] arguments_arr = new String[arguments.size()];
        arguments_arr = arguments.toArray(arguments_arr);
        return arguments_arr;
    }

    private String changeCWD(String cwd) throws Exception
    {
        File target_dir = new File(cwd).getAbsoluteFile();

        if (target_dir.exists() == false)
            throw new Exception("Directory: '" + cwd + "': does not exist");

        if (target_dir.canRead() == false)
            throw new Exception("Can't move to path: '" + cwd + "': permission denied");

        if (target_dir.isDirectory() == false)
            throw new Exception("Path: '" + cwd + "': is not a directory");

        System.setProperty("user.dir", target_dir.getCanonicalPath());

        return sanitizePath(target_dir.getCanonicalPath());
    }

    private String[] doMove(String[] sources, String dest)
    {
        String result = "";
        try
        {
            dest = normalizePath(dest);
            File dest_file = new File(dest).getAbsoluteFile();
            if ((sources.length > 1) && (dest_file.isDirectory() == false))
                throw new Exception("mv: target '" + dest + "' is not a directory");
            
            for (String source : sources)
            {
                source = normalizePath(source);
                File source_file = new File(source);
                if (source_file.exists() == false)
                {
                    result += "mv: cannot stat '" + source + "': No such file or directory" + JAVA_EOL;
                    continue;
                }

                if ((dest_file.exists() == true) && (dest_file.isDirectory() == true))
                {
                    File new_dest_file = new File(dest + File.separator + source_file.getName());
                    if(source_file.renameTo(new_dest_file) == false)
                    {
                        result += "mv: cannot move '" + source + "' to '" + dest + "': Failed" + JAVA_EOL;
                        continue;
                    }

                    source_file.delete();
                }
                else
                {
                    if(source_file.renameTo(dest_file) == false)
                    {
                        result += "mv: cannot move '" + source + "' to '" + dest + "': Failed" + JAVA_EOL;
                        continue;
                    }

                    source_file.delete();
                }
            }
        }
        catch(Exception ex)
        {
            return new String[]{ERR_CODE, ex.getMessage() + JAVA_EOL};
        }
        return new String[]{SUCC_CODE, result};
    }

    public String[] execute(String[] args)
    {        
        if (args.length < 2)
            return new String[]{ERR_CODE, "Invalid arguments provided. Specify a source file or directory to be moved to a destination" + JAVA_EOL};
        
        String[] sources = Arrays.copyOfRange(args, 0, (args.length - 1));
        String dest      = args[(args.length - 1)];

        return doMove(sources, dest);
    }

    public String[] go(String module_cwd, String module_args)
    {
        try
        {
            this.cwd = changeCWD(hex2str(module_cwd));
            String[] args = parseArgs(hex2str(module_args));
            return execute(args);
        }
        catch(Exception ex)
        {
            return new String[]{ERR_CODE, ex.getMessage() + JAVA_EOL};
        }
    }

    public static void main(String[] args)
    {
        Module_mv m = new Module_mv();
        String[] results = m.execute(args);
        System.out.println(results[1]);
        return;
    }
}

Java Module Versioning

After completing the development of the module, we save it in the same way as we have done in the PHP version. We save the file in the path: modules/mv/mv.java17.java.

Then we create the symbolic links for each Java version:

ln -s mv.java17.java mv.java1.6.java
ln -s mv.java17.java mv.java1.7.java
ln -s mv.java17.java mv.java1.8.java
ln -s mv.java17.java mv.java9.java
ln -s mv.java17.java mv.java10.java
ln -s mv.java17.java mv.java11.java
ln -s mv.java17.java mv.java12.java
ln -s mv.java17.java mv.java13.java
ln -s mv.java17.java mv.java14.java
ln -s mv.java17.java mv.java15.java
ln -s mv.java17.java mv.java16.java

And we move to the "check_syntax" directory to launch the checker:

cd ../../utils/check_syntax/
# Remember to run 'pull_java.sh' script to download the containers for each Java version.
python check_java.py

And the result shows how the module adapts to all Java versions covered by Kraken:

[+] Module: mv.java9.java compiled successfully
[+] Module: mv.java11.java compiled successfully
[+] Module: mv.java14.java compiled successfully
[+] Module: mv.java1.8.java compiled successfully
[+] Module: mv.java16.java compiled successfully
[+] Module: mv.java12.java compiled successfully
[+] Module: mv.java15.java compiled successfully
[+] Module: mv.java1.7.java compiled successfully
[+] Module: mv.java1.6.java compiled successfully
[+] Module: mv.java10.java compiled successfully
[+] Module: mv.java17.java compiled successfully
[+] Module: mv.java13.java compiled successfully

Java Module Registration

In order to use the module, we will have to register the Java version in the mv module configuration inside the modules.py file. We simply add the java technology to the supported operating systems:

{
    "name" : "mv",
    "description" : "Move file/s or directory/ies to another destination",
    "author" : "@secu_x11",
    "template" : "mv",
    "examples" : [
        "mv example.txt demo.txt",
        "mv example.txt /tmp/example.txt",
        "mv example.txt /tmp",
        "mv example.txt /tmp/",
        "mv somedir otherdir",
        "mv somedir /tmp/somedir",
        "mv somedir /tmp",
        "mv somedir /tmp/",
        "mv example.txt demo.txt /tmp",
        "mv example.txt somedir /tmp"
    ],
    "so" : [
        {
            "name" : "Linux",
            "agents" : ["php", "java"]
        },
        {
            "name" : "Windows",
            "agents" : ["php", "java"]
        }
    ],
    "references" : [],
    "args": [
        {
            "sources": {
                "help": "Source files or directories",
                "nargs" : "*",
                "type":  str
            },
            "dest": {
                "help": "Destination file or directory",
                "nargs": 1,
                "type":  str
            }
        }
    ],
    "dispatcher" : "default",
    "formater" : "default"
}

And with this, we could run Kraken and the module would be allowed to be used in the supported versions.

To finish with the section related to this technology, I want to highlight that, if we make a change in the Java module code while we are using Kraken, we will have to recompile it to be able to use the latest version. This can be done thanks to the core command: recompile.

NET Module Creation

Finally, we will implement the mv module for the .NET technology. In the same way, we will use the .NET template that Kraken delivers as a base.

NET Template Structure

We start from the following template code:

using System;
using System.IO;
using System.Text;
using System.Security;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Runtime.InteropServices;
using System.Security.Principal;


public class Module_template
{
    public const string SUCC_CODE       = "0";
    public const string ERR_CODE        = "1";
    public const string NON_TOKEN_VALUE = "0";

    [DllImport("advapi32.dll", SetLastError = true)]
    public static extern bool ImpersonateLoggedOnUser(IntPtr hToken);

    [DllImport("advapi32.dll", SetLastError = true)]
    public static extern bool RevertToSelf();

    private void ImpersonateWithToken(string token)
    {
        int token_int = Int32.Parse(token);
        IntPtr targetToken = new IntPtr(token_int);

        string current_username = "";
        using (WindowsIdentity wid = WindowsIdentity.GetCurrent())
        {
            current_username = wid.Name;
        }

        if (!ImpersonateLoggedOnUser(targetToken))
        {
            int errorCode = Marshal.GetLastWin32Error();
            throw new Exception("ImpersonateLoggedOnUser failed with the following error: " + errorCode);
        }

        string impersonate_username = "";
        using (WindowsIdentity wid = WindowsIdentity.GetCurrent())
        {
            impersonate_username = wid.Name;
        }

        if (current_username == impersonate_username)
            throw new Exception("ImpersonateLoggedOnUser worked, but thread running as user " + current_username);
    }

    private string hex2Str(string hex)
    {
        byte[] raw = new byte[hex.Length / 2];
        for (int i = 0; i < raw.Length; i++)
        {
            raw[i] = Convert.ToByte(hex.Substring(i * 2, 2), 16);
        }
        return Encoding.ASCII.GetString(raw);
    }

    private string normalizePath(string currPath)
    {
        currPath = currPath.Replace("\"", "");
        currPath = currPath.Replace("'", "");
        currPath = currPath.Replace(@"\", "/");
        return currPath;
    }

    private string changeCWD(string cwd)
    {
        try
        {
            Directory.SetCurrentDirectory(cwd);
            return normalizePath(Directory.GetCurrentDirectory());
        }
        catch (Exception ex)
        {
            if (ex is IOException)
                throw new Exception("Path '" + cwd + "' is not a directory");
            else if (ex is DirectoryNotFoundException)
                throw new Exception("Directory '" + cwd + "' does not exist");
            else if (ex is SecurityException)
                throw new Exception("Directory '" + cwd + "' permission denied");
            else if (ex is PathTooLongException)
                throw new Exception("Path '" + cwd + "' exceed the maximum length defined by the system");
            else
                throw new Exception("Move to path '" + cwd + "' failed");
        }
    }

    private string[] parseArguments(string args)
    {
        List<string> arguments_parsed = new List<string>();
        string pattern = @"""[^""]+""|'[^']+'|\S+";
        foreach (Match m in Regex.Matches(args, pattern))
        {
            arguments_parsed.Add(m.Value);
        }
        return arguments_parsed.ToArray();
    }

    private string[] doEcho(string[] str_params)
    {
        string result = "";

        try
        {
            foreach (string p in str_params)
            {
                result += p + Environment.NewLine;
            }
        }
        catch(Exception ex)
        {
            result += ex.ToString() + Environment.NewLine;
            return new string[]{ERR_CODE, result};
        }

        return new string[]{SUCC_CODE, result};
    }

    public string[] execute(string[] args)
    {
        string result = "";
        List<string> nargs = new List<string>(args);
		
        if (nargs.Count == 0)
        {
            result = "Invalid arguments provided. Specify one or multiple strings to echo.";
            return new string[]{ERR_CODE, result};
        }

        return doEcho(nargs.ToArray());
    }

    public string[] go(string cwd, string args, string token)
    {
        string[] results = new string[]{SUCC_CODE, ""};
        string pwd = Directory.GetCurrentDirectory();

        try
        {
            if (token != NON_TOKEN_VALUE)
                ImpersonateWithToken(token);

            string new_cwd = changeCWD(cwd);
            string[] arguments_parsed = parseArguments(hex2Str(args));
            
            results = execute(arguments_parsed);
        }
        catch (Exception ex)
        {
            results[0] = ERR_CODE;
            results[1] = ex.ToString();
        }
        finally
        {
            changeCWD(pwd);
            if (token != NON_TOKEN_VALUE)
                RevertToSelf();
        }
        return results;
    }

    public static void go_dm(string cwd, string args, string token)
    {
        Module_mv m = new Module_mv();
        String[] results = m.go(cwd, args, token);
        Console.WriteLine(results[0]);
        Console.WriteLine(results[1]);
        return;
    }

    public static void Main(string[] args)
    {
        Module_template m = new Module_template();
        String[] results = m.execute(args);
        Console.WriteLine(results[0]);
        Console.WriteLine(results[1]);
        return;
    }
}

Again, the general structure matches with the other templates, but we can also see a number of differences that we are going to explain below:

  • First, we can see a number of methods and attributes related to the topic of Windows Tokens. These functionalities are designed to allow running the Kraken module by impersonating the identity and privileges of another user. This allows different functionalities that, in a non-privileged context would be impossible, be made possible thanks to the impersonation with tokens. These elements are:
    • The methods: ImpersonateLoggedOnUser() and RevertToSelf() (which are functions that are proper to unmanaged code), are used to impersonate the identity of a user, in a Thread, through an access token. After the execution of the module, it is possible to return to the previous context with the call to RevertToSelf.
    • These two functions are invoked from the "entrypoint" method: go(). So, the first thing it does, in case of passing a token different from NON_TOKEN_VALUE, is to try to perform the impersonation through the ImpersonateWithToken() method.
    • Another point of interest is the restore current working directory (cwd). This is important because, if you change the reference path from where the module has to be invoked, you must subsequently restore it. If this is not done explicitly, NET will keep this path in the execution of future modules, causing possible execution errors.

After this, there is not much more to highlight, since the rest of the functionalities match the standard specification.

NET Module Development

As in the previous cases, we edit the template to adjust it to the recreation of the mv module.

First we change the name of the Class together with the do... as in the previous examples. We take the opportunity to add the checks to the execute() method:

using System;
using System.IO;
using System.Text;
using System.Security;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Runtime.InteropServices;
using System.Security.Principal;


public class Module_mv
{
    public const string SUCC_CODE       = "0";
    public const string ERR_CODE        = "1";
    public const string NON_TOKEN_VALUE = "0";

    [DllImport("advapi32.dll", SetLastError = true)]
    public static extern bool ImpersonateLoggedOnUser(IntPtr hToken);

    [DllImport("advapi32.dll", SetLastError = true)]
    public static extern bool RevertToSelf();

    private void ImpersonateWithToken(string token)
    {
        int token_int = Int32.Parse(token);
        IntPtr targetToken = new IntPtr(token_int);

        string current_username = "";
        using (WindowsIdentity wid = WindowsIdentity.GetCurrent())
        {
            current_username = wid.Name;
        }

        if (!ImpersonateLoggedOnUser(targetToken))
        {
            int errorCode = Marshal.GetLastWin32Error();
            throw new Exception("ImpersonateLoggedOnUser failed with the following error: " + errorCode);
        }

        string impersonate_username = "";
        using (WindowsIdentity wid = WindowsIdentity.GetCurrent())
        {
            impersonate_username = wid.Name;
        }

        if (current_username == impersonate_username)
            throw new Exception("ImpersonateLoggedOnUser worked, but thread running as user " + current_username);
    }

    private string hex2Str(string hex)
    {
        byte[] raw = new byte[hex.Length / 2];
        for (int i = 0; i < raw.Length; i++)
        {
            raw[i] = Convert.ToByte(hex.Substring(i * 2, 2), 16);
        }
        return Encoding.ASCII.GetString(raw);
    }

    private string normalizePath(string currPath)
    {
        currPath = currPath.Replace("\"", "");
        currPath = currPath.Replace("'", "");
        currPath = currPath.Replace(@"\", "/");
        return currPath;
    }

    private string changeCWD(string cwd)
    {
        try
        {
            Directory.SetCurrentDirectory(cwd);
            return normalizePath(Directory.GetCurrentDirectory());
        }
        catch (Exception ex)
        {
            if (ex is IOException)
                throw new Exception("Path '" + cwd + "' is not a directory");
            else if (ex is DirectoryNotFoundException)
                throw new Exception("Directory '" + cwd + "' does not exist");
            else if (ex is SecurityException)
                throw new Exception("Directory '" + cwd + "' permission denied");
            else if (ex is PathTooLongException)
                throw new Exception("Path '" + cwd + "' exceed the maximum length defined by the system");
            else
                throw new Exception("Move to path '" + cwd + "' failed");
        }
    }

    private string[] parseArguments(string args)
    {
        List<string> arguments_parsed = new List<string>();
        string pattern = @"""[^""]+""|'[^']+'|\S+";
        foreach (Match m in Regex.Matches(args, pattern))
        {
            arguments_parsed.Add(m.Value);
        }
        return arguments_parsed.ToArray();
    }

    private string[] doMove(string[] sources, string dest)
    {
        string result = "";

        try
        {
            result += "do something" + Environment.NewLine;
        }
        catch(Exception ex)
        {
            result += ex.ToString() + Environment.NewLine;
            return new string[]{ERR_CODE, result};
        }

        return new string[]{SUCC_CODE, result};
    }

    public string[] execute(string[] args)
    {
        string result = "";
        List<string> nargs = new List<string>(args);
		
        if (nargs.Count < 2)
        {
            result = "Invalid arguments provided. Specify a source file or directory to be moved to a destination" + Environment.NewLine;
            return new string[]{ERR_CODE, result};
        }

        string[] sources = nargs.GetRange(0, (nargs.Count - 1)).ToArray();
        string dest = nargs[(nargs.Count - 1)];

        return doMove(sources, dest);
    }

    public string[] go(string cwd, string args, string token)
    {
        string[] results = new string[]{SUCC_CODE, ""};
        string pwd = Directory.GetCurrentDirectory();

        try
        {
            if (token != NON_TOKEN_VALUE)
                ImpersonateWithToken(token);

            string new_cwd = changeCWD(cwd);
            string[] arguments_parsed = parseArguments(hex2Str(args));
            
            results = execute(arguments_parsed);
        }
        catch (Exception ex)
        {
            results[0] = ERR_CODE;
            results[1] = ex.ToString();
        }
        finally
        {
            changeCWD(pwd);
            if (token != NON_TOKEN_VALUE)
                RevertToSelf();
        }
        return results;
    }

    public static void go_dm(string cwd, string args, string token)
    {
        Module_mv m = new Module_mv();
        String[] results = m.go(cwd, args, token);
        Console.WriteLine(results[0]);
        Console.WriteLine(results[1]);
        return;
    }

    public static void Main(string[] args)
    {
        Module_mv m = new Module_mv();
        String[] results = m.execute(args);
        Console.WriteLine(results[0]);
        Console.WriteLine(results[1]);
        return;
    }
}

After this, the module is completed with the functionality of the doMove() method:

using System;
using System.IO;
using System.Text;
using System.Security;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Runtime.InteropServices;
using System.Security.Principal;


public class Module_mv
{
    public const string SUCC_CODE       = "0";
    public const string ERR_CODE        = "1";
    public const string NON_TOKEN_VALUE = "0";

    [DllImport("advapi32.dll", SetLastError = true)]
    public static extern bool ImpersonateLoggedOnUser(IntPtr hToken);

    [DllImport("advapi32.dll", SetLastError = true)]
    public static extern bool RevertToSelf();

    private void ImpersonateWithToken(string token)
    {
        int token_int = Int32.Parse(token);
        IntPtr targetToken = new IntPtr(token_int);

        string current_username = "";
        using (WindowsIdentity wid = WindowsIdentity.GetCurrent())
        {
            current_username = wid.Name;
        }

        if (!ImpersonateLoggedOnUser(targetToken))
        {
            int errorCode = Marshal.GetLastWin32Error();
            throw new Exception("ImpersonateLoggedOnUser failed with the following error: " + errorCode);
        }

        string impersonate_username = "";
        using (WindowsIdentity wid = WindowsIdentity.GetCurrent())
        {
            impersonate_username = wid.Name;
        }

        if (current_username == impersonate_username)
            throw new Exception("ImpersonateLoggedOnUser worked, but thread running as user " + current_username);
    }

    private string hex2Str(string hex)
    {
        byte[] raw = new byte[hex.Length / 2];
        for (int i = 0; i < raw.Length; i++)
        {
            raw[i] = Convert.ToByte(hex.Substring(i * 2, 2), 16);
        }
        return Encoding.ASCII.GetString(raw);
    }

    private string normalizePath(string currPath)
    {
        currPath = currPath.Replace("\"", "");
        currPath = currPath.Replace("'", "");
        currPath = currPath.Replace(@"\", "/");
        return currPath;
    }

    private string changeCWD(string cwd)
    {
        try
        {
            Directory.SetCurrentDirectory(cwd);
            return normalizePath(Directory.GetCurrentDirectory());
        }
        catch (Exception ex)
        {
            if (ex is IOException)
                throw new Exception("Path '" + cwd + "' is not a directory");
            else if (ex is DirectoryNotFoundException)
                throw new Exception("Directory '" + cwd + "' does not exist");
            else if (ex is SecurityException)
                throw new Exception("Directory '" + cwd + "' permission denied");
            else if (ex is PathTooLongException)
                throw new Exception("Path '" + cwd + "' exceed the maximum length defined by the system");
            else
                throw new Exception("Move to path '" + cwd + "' failed");
        }
    }

    private string[] parseArguments(string args)
    {
        List<string> arguments_parsed = new List<string>();
        string pattern = @"""[^""]+""|'[^']+'|\S+";
        foreach (Match m in Regex.Matches(args, pattern))
        {
            arguments_parsed.Add(m.Value);
        }
        return arguments_parsed.ToArray();
    }

    private bool fileOrDirectoryExists(string filepath)
    {
        return (Directory.Exists(filepath) || File.Exists(filepath));
    }

    private string[] doMove(string[] sources, string dest)
    {
        string result = "";

        try
        {
            dest = normalizePath(dest);

            if ((sources.Length > 1) && (Directory.Exists(dest) == false))
                throw new Exception("mv: target '" + dest + "' is not a directory");

            foreach (string fsource in sources)
            {
                string source = normalizePath(fsource);
                if (fileOrDirectoryExists(source) == false)
                {
                    result += "mv: cannot stat '" + source + "': No such file or directory" + Environment.NewLine;
                    continue;
                }

                try
                {
                    if ((File.Exists(source) == true) && (Directory.Exists(dest) == true))
                    {
                        string new_dest = dest + Path.DirectorySeparatorChar + Path.GetFileName(source);
                        File.Move(source, new_dest);
                    }
                    else if ((Directory.Exists(source) == true) && (Directory.Exists(dest) == true))
                    {
                        string new_dest = dest + Path.DirectorySeparatorChar + Path.GetFileName(source);
                        Directory.Move(source, new_dest);
                    }
                    else if ((Directory.Exists(source) == true) && (Directory.Exists(dest) == false))
                    {
                        Directory.Move(source, dest);
                    }
                    else
                    {
                        File.Move(source, dest);
                    }
                }
                catch(Exception ex)
                {
                    result += "mv: cannot move '" + source + "' to '" + dest + "': " + ex.ToString() + Environment.NewLine;
                    continue;
                }
            }
        }
        catch(Exception ex)
        {
            result += ex.ToString() + Environment.NewLine;
            return new string[]{ERR_CODE, result};
        }

        return new string[]{SUCC_CODE, result};
    }

    public string[] execute(string[] args)
    {
        string result = "";
        List<string> nargs = new List<string>(args);
		
        if (nargs.Count < 2)
        {
            result = "Invalid arguments provided. Specify a source file or directory to be moved to a destination" + Environment.NewLine;
            return new string[]{ERR_CODE, result};
        }

        string[] sources = nargs.GetRange(0, (nargs.Count - 1)).ToArray();
        string dest = nargs[(nargs.Count - 1)];

        return doMove(sources, dest);
    }

    public string[] go(string cwd, string args, string token)
    {
        string[] results = new string[]{SUCC_CODE, ""};
        string pwd = Directory.GetCurrentDirectory();

        try
        {
            if (token != NON_TOKEN_VALUE)
                ImpersonateWithToken(token);

            string new_cwd = changeCWD(cwd);
            string[] arguments_parsed = parseArguments(hex2Str(args));
            
            results = execute(arguments_parsed);
        }
        catch (Exception ex)
        {
            results[0] = ERR_CODE;
            results[1] = ex.ToString();
        }
        finally
        {
            changeCWD(pwd);
            if (token != NON_TOKEN_VALUE)
                RevertToSelf();
        }
        return results;
    }

    public static void go_dm(string cwd, string args, string token)
    {
        Module_mv m = new Module_mv();
        String[] results = m.go(cwd, args, token);
        Console.WriteLine(results[0]);
        Console.WriteLine(results[1]);
        return;
    }

    public static void Main(string[] args)
    {
        Module_mv m = new Module_mv();
        String[] results = m.execute(args);
        Console.WriteLine(results[0]);
        Console.WriteLine(results[1]);
        return;
    }
}

NET Module Versioning

After completing the development of the module, we save it as we have done with the previous ones: modules/mv/mv.cs4.cs.

Next, the process will be performed on a Windows machine (since the .NET modules must be validated on Windows). From an administrative Powershell console we proceed to create the symbolic links for each .NET version:

cmd /c mklink .\mv.cs2.cs .\mv.cs4.cs
cmd /c mklink .\mv.cs3.5.cs .\mv.cs4.cs

Then, we move to the "check_syntax" directory, and run the Python script:

cd ../../utils/check_syntax/
python check_cs.py

And the result confirms the functionality of the module for all versions of NET currently supported by Kraken:

[+] Module: 'mv.cs4.cs' for version '4.0.30319' compiled successfully
[+] Module: 'mv.cs4.cs' for version '4.0.30319' executed successfully
[+] Module: 'mv.cs3.5.cs' for version '3.5' compiled successfully
[+] Module: 'mv.cs3.5.cs' for version '3.5' executed successfully
[+] Module: 'mv.cs2.cs' for version '2.0.50727' compiled successfully
[+] Module: 'mv.cs2.cs' for version '2.0.50727' executed successfully

NET Module Registration

In order to use the module, we must register the NET version in the mv module configuration inside the modules.py file. We simply add the cs technology to the supported operating systems (Windows only). It is important to highlight that, if our module needs to link some reference to some external DLL (to NET Framework), its path can be added in the references section:

{
    "name" : "mv",
    "description" : "Move file/s or directory/ies to another destination",
    "author" : "@secu_x11",
    "template" : "mv",
    "examples" : [
        "mv example.txt demo.txt",
        "mv example.txt /tmp/example.txt",
        "mv example.txt /tmp",
        "mv example.txt /tmp/",
        "mv somedir otherdir",
        "mv somedir /tmp/somedir",
        "mv somedir /tmp",
        "mv somedir /tmp/",
        "mv example.txt demo.txt /tmp",
        "mv example.txt somedir /tmp"
    ],
    "so" : [
        {
            "name" : "Linux",
            "agents" : ["php", "java"]
        },
        {
            "name" : "Windows",
            "agents" : ["php", "java", "cs"]
        }
    ],
    "references" : [],
    "args": [
        {
            "sources": {
                "help": "Source files or directories",
                "nargs" : "*",
                "type":  str
            },
            "dest": {
                "help": "Destination file or directory",
                "nargs": 1,
                "type":  str
            }
        }
    ],
    "dispatcher" : "default",
    "formater" : "default"
}

And with this, we would be able to run Kraken and the module would be allowed to be used in the supported versions.

Dispatchers

TODO

Formaters

TODO

Environments

You can add new web platforms to Kraken environments. To do so, simply follow the steps below:

  1. Create the deployment file compatible with Docker Compose.
  2. Validate and ensure the correct functioning of Kraken agents for the technology in use by the container (do not add a deployment environment in which Kraken does not working correctly).
  3. Open a pull request.

Any contribution is appreciated, since validating the tool in different environments ensures its correct operation.

Utils

Utilities are considered as "extra" Kraken tools, they do not interfere in Kraken's operation, but only serve to validate or support the different Kraken components.

For this reason, the way to contribute new utilities is very simple:

  1. You must explain the purpose of this utility and its relationship to Kraken.
  2. If the tool has dependencies or any requirements not covered by Kraken, you must specify them.
  3. A section of examples of use should be added. And it is recommended to add a small section about the different possibilities that this tool provides.
  4. Finally, open a pull request and it will be checked and merged as soon as possible.