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
Other plugin examples are available in REVEN documentation at http://doc.tetrane.com/latest/examples.html
- 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
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:
Create a file my_percent.py in your autoload directory
def axion_callback(): print "percent plugin called"
Launch axion, define a shortcut (using menu Actions > Edit shortcuts or F11 by 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 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)
Use axion.status_message(msg, duration_ms = 5000) instead of print to have your logs printed in the status bar.
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.
- 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)
- 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:
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.