Making your own REVEN Axion plugin step by step
In this article we will shed light on REVEN Axion’s customisation possibilities by describing step by step how to create a simple plugin.
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. Percent plugin screenshot
Other plugin examples are available in REVEN documentation
- When triggered from a selected instruction, jump to the matching one
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).
- 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
REVEN Axion plugin basics:
- REVEN Axion will call plugin’s
REVEN Axion_callback()function if it exists when the shortcut associated with a plugin is triggered (multiple commands by plugin is still achievable through
register_commandfrom the plugin API).
- REVEN Axion will load any plugin in its autoload directories (user’s default autoload directory is
- To reload a plugin you can just type
REVEN 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:
- Create a file
my_percent.pyin your autoload directory
def REVEN Axion_callback(): print "percent plugin called"
- Launch REVEN Axion, define a shortcut (using menu
Actions > Edit shortcutsor
F11by default) for your brand new plugin.
- Try to trigger your plugin by calling its shortcut. See resulting output in the Python console widget
Retrieving the selected instruction
Now that our plugin can be called, let’s retrieve the selected instruction from REVEN Axion’s API.
from REVEN Axion_api import REVEN Axion def REVEN Axion_callback(): run, seq, instr = REVEN 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 REVEN Axion.status_message(msg, 100000)
REVEN Axion.status_message(msg, duration_ms = 5000) instead of
Once we find a matching instruction we will need to select it in REVEN Axion’s view, which can be done by using:
REVEN 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.
- REVEN 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 REVEN Axion_callback(): run, seq, instr = REVEN Axion.selected_sequence() # get current connection info host, port = REVEN 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)
- Do not forget to import REVEN.
encode()? The plugin API, which relies on PythonQt (the
REVEN Axionobject), 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 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 REVEN Axion_api import REVEN Axion import REVEN def get_matching_instruction(client, point): # to be implemented: # - retrieve context # - ss register heuristic # - esp register heursitic # - memory heuristic def REVEN Axion_callback(): run, seq, instr = REVEN Axion.selected_sequence() host, port = REVEN 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 REVEN 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
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:
Landed on matching
Going back to previous
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).
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.