In this article we will shed light on REVEN Axion's customisation possibilities by describing step by step how to create a simple plugin.

Percent plugin screenshot.

Percent plugin in action on push edi.

We will walk you through:

  • The specification of our plugin
  • The basics of plugin API for REVEN Axion
  • The implementation of the plugin's core mechanics

You can download the complete percent plugin file here.

Other plugin examples are available in REVEN documentation at http://doc.tetrane.com/latest/examples.html

Plugin specification

  1. When triggered from a selected instruction, jump to the matching one
  2. Select matching instructions as follows:
    • push: next instruction reading pushed value (may not be a pop).
    • pop: previous instruction writting popped value (may not be a push).
    • sysenter/sysexit
    • call/ret
    • int/iret
    • leave/push ebp

Note that the matching instruction may not exist or may not have been recorded in current trace.

The plugin codename will be percent (referring to vi's '%' for "goto match")

Getting called by REVEN Axion

Axion plugin basics:

  • REVEN Axion will call plugin's axion_callback() function if it exists when the shortcut associated with a plugin is triggered (multiple commands by plugin is still achievable through register_command from the plugin API).
  • REVEN Axion will load any plugin in its autoload directories (user's default autoload directory is $HOME/.config/tetrane/axion-plugins/autoload).
  • To reload a plugin you can just type axion.plugins().reload_plugin('my_plugin_name') in REVEN Axion's python console instead of restarting the application.

Getting started with a first dummy plugin stub:

  1. Create a file my_percent.py in your autoload directory

    def axion_callback():
        print "percent plugin called"
    
  2. Launch axion, define a shortcut (using menu Actions > Edit shortcuts or F11 by default) for your brand new plugin.

    shortcuts

  3. Try to trigger your plugin by calling its shortcut. See resulting output in the Python console widget

    console_output_trigger

Retrieving the selected instruction

Now that our plugin can be called, let's retrieve the selected instruction from REVEN Axion's API.

from axion_api import axion

def axion_callback():
    run, seq, instr = axion.selected_sequence()

    # run: current run name as a string
    # seq: index of selected sequence in current run
    # instr: index of selected instruction in selected sequence.
    msg = "Calling percent plugin on instruction at [%s@%d:%d]" % (run, seq, instr)

    # output to python console
    print msg
    # log message to status bar
    axion.status_message(msg, 100000)

Tip

Use axion.status_message(msg, duration_ms = 5000) instead of print to have your logs printed in the status bar.

console_output_selected

Once we find a matching instruction we will need to select it in REVEN Axion's view, which can be done by using:

axion.select_sequence(run_name, sequence_identifier, instruction_index)

Using REVEN API

REVEN Axion's client may not have all information required to resolve our matching instruction in memory. Hence we will connect to the REVEN project instance to query any missing information.

APIs summary

APIs summary

  • Axion is connected to a REVEN project instance through REVEN's network interface.
  • Plugin can communicate with REVEN Axion through its plugin API.
  • Plugin can create its own connection to REVEN project instance to garther information not loaded by REVEN Axion as any standalone Python script.

Connecting to current REVEN project instance

We connect to the current REVEN project instance through REVEN's Python API and create an execution point object from the selected instruction.

import reven

def axion_callback():
    run, seq, instr = axion.selected_sequence()

    # get current connection info
    host, port = axion.connection_info()
    # connect to reven using its python API (used by reven scripts)
    client = reven.reven_connection(host.encode(), port)
    # create execution point common in reven scripts
    point = reven.execution_point(run.encode(), seq, instr)

Warning

  • Do not forget to import reven.
  • Why encode() ? The plugin API, which relies on PythonQt (the axion object), uses utf-8 strings (thanks to Qt) while REVEN's Python API is using vanilla python strings.

Now that we have our execution point, we can query more data from REVEN project instance. Let's define a function get_matching_instruction(client, point) which will return the matching instruction's execution point or None.

None is returned if no matching instruction exists or if the matching execution point is not present in current execution trace.

This gives us the following skeleton:

from axion_api import axion
import reven

def get_matching_instruction(client, point):
    # to be implemented:
    # - retrieve context
    # - ss register heuristic
    # - esp register heursitic
    # - memory heuristic

def axion_callback():
    run, seq, instr = axion.selected_sequence()
    host, port = axion.connection_info()
    client = reven.reven_connection(host.encode(), port)
    point = reven.execution_point(run.encode(), seq, instr)

    result = get_matching_instruction(client, point)

    if result == None or not result.valid():
        print "No matching instruction recorded"
        return

    # select matching instruction
    axion.select_sequence(result.run_name, result.sequence_identifier, result.instruction_index)

Trivial heuristic monitoring stack segment

This will handle sysenter/sysexit and int/iret.

If the stack segment register ss is modified by the selected instruction, we will consider the next instruction writing ss as the matching one.

def get_matching_instruction(client, point):
    # create a range of size 1 to query context before and after selected instruction
    point_range = reven.execution_range(point.run_name, point.sequence_identifier, 1,
                                        point.instruction_index)
    # empty vector of logical address range to ignore memory
    context = client.run_get_running_context_between(point_range,
                                                     reven.vector_of_logical_address_range())

    # ss value before selected instruction
    ss_before = context.before.numeric_registers['ss'].value
    # ss value after selected instruction
    ss_after = context.after.numeric_registers['ss'].value

    if ss_before != ss_after:
        # ss is modified by instruction
        # search next write for ss and return corresponding execution point
        return client.run_search_next_register_use(point, forward=(ss_before > ss_after),
                                                   read=False, write=True, register_name="ss")

Test it in REVEN Axion:

int 0x80 selected

Jumping from int 0x80

jumped from int 0x80 to matching instruction, iretd selected

Landed on matching iretd

going back to first int 0x80 for it is matching iretd

Going back to previous int 0x80

Monitoring the stack pointer

This will handle push/pop and call/ret.

# retrieving esp value before and after selected instruction
esp_before = context.before.numeric_registers['esp'].value
esp_after = context.after.numeric_registers['esp'].value

if esp_before != esp_after:
    if esp_before > esp_after:
        # push-like instruction
        stack = reven.logical_address(ss_after, esp_after)
        # search next read of pushed value
        return client.run_search_next_memory_use(point, forward=True,
                      read=True, write=False, address=stack)

    if esp_before < esp_after:
        # pop-like instruction
        stack = reven.logical_address(ss_before, esp_before)
        # search previous write of popped value
        return client.run_search_next_memory_use(point, forward=False,
                      read=False, write=True, address=stack)

Going further: memory based heuristic

def pick_memory_access(client, point):
    "Return first non null logical address accessed by instruction at given execution point or None"
    null_logical = reven.logical_address(0, 0)
    result = None

    accesses = client.memory_get_history_instruction(point)
    for access in accesses:
        if access.logical != null_logical:
            if not (result and result.write and not access.write):
                result = access
    return result

    def get_maching_instruction(client, point):
        # skipping context retrieval and previous heuristics
        # ...

        access = pick_memory_access(client, point)
        if access != None:
            if access.write:
                return client.run_search_next_memory_use(point, forward=True,
                              read=True, write=False, address=access.logical)

            if access.read:
                return client.run_search_next_memory_use(point, forward=False,
                              read=False, write=True, address=access.logical)

Pedantic instruction matching

With the current implementation, if a pushed value is read before the pop, the match would be the first reading instruction and not the actual pop. To get the true pop - if it exists - we could monitor esp value by channeling run_search_next_register_use calls to find a greater or equal value for esp. This way, we would even be able to find a pop disguised as an esp increment (add esp, 0x4).

Conclusion

In this article, we've seen how one can easily implement simple heuristics to add specific functionality to REVEN Axion, thanks to its Python API.

This widget did not require a graphical interface, but had it been the case we could have created our own custom widgets using PythonQt and connected them to REVEN Axion.


Comments

comments powered by Disqus