REVEN-Axion 2017v1.4.0
percent.py

The percent script simulates the % key in VI command mode, more stack memory items. Basically this means that for a push, this makes you jump to the associated pop. For a call, this makes you jump to the corresponding ret, and so on.

NOTE: This example make use of the low-level api.

This will demonstrate the use of the following services:

It is built as an Axion script, so this also shows the use of a callback in Axion.

1 from axion_api import axion
2 import reven_api
3 from PythonQt import Qt
4 
5 
6 def register_command(callback, desc, name, shortcut):
7  action = Qt.QAction(desc, axion)
8  axion.shortcuts().register_action(action, name, Qt.QKeySequence(shortcut))
9  action.connect("triggered(bool)", callback)
10 
11 def reven_connection_from_axion(axion):
12  host, port = axion.connection_info()
13  if port == 0 or not host:
14  return None
15  return reven_api.reven_connection(host.encode(), port)
16 
17 def is_memory_history_enabled(client):
18  return 'memory_range_history' in [ i.name for i in client.engine_get_current_preset() if i.properties.enabled ]
19 
20 
21 def pick_memory_access(client, point):
22  "Return first non null logical address accessed by instruction at given execution point or None"
23 
24  null_logical = reven_api.logical_address(0, 0)
25  result = None
26 
27  accesses = client.memory_get_history_instruction(point)
28  for access in accesses:
29  if access.logical != null_logical:
30  if not (result and result.write and not access.write):
31  result = access
32  return result
33 
34 def get_matching_instruction(client, point):
35  "Return execution point of matching instruction for given execution point or None if not found."
36 
37  # create a range of size 1 to query context before and after selected instruction
38  point_range = reven_api.execution_range(point.run_name, point.sequence_identifier, 1, point.instruction_index)
39  # give an empty vector of logical address range to ignore memory for we don't nedd any memory here
40  context = client.run_get_running_context_between(point_range, reven_api.vector_of_logical_address_range())
41 
42  # ss values before and after selected instruction
43  ss_before = context.before.numeric_registers['ss'].value
44  ss_after = context.after.numeric_registers['ss'].value
45 
46  # ss basic heuristic to handle sysenter/sysexit
47  if ss_before != ss_after:
48  # ss is modified by instruction, search next or previous write for ss and return corresponding execution point
49  return client.run_search_next_register_use(point, forward=(ss_before > ss_after), read=False, write=True, register_name="ss")
50 
51  # memory heuristic
52  access = pick_memory_access(client, point)
53  if access != None:
54  if access.write:
55  return client.run_search_next_memory_use(point, forward=True, read=True, write=False, address=access.logical, size=access.size)
56 
57  if access.read:
58  return client.run_search_next_memory_use(point, forward=False, read=False, write=True, address=access.logical, size=access.size)
59 
60  # esp values before and after selected instruction
61  esp_before = context.before.numeric_registers['esp'].value
62  esp_after = context.after.numeric_registers['esp'].value
63 
64  # esp heuristic
65  if esp_before != esp_after:
66  if esp_before > esp_after:
67  stack = reven_api.logical_address(ss_after, esp_after)
68  return client.run_search_next_memory_use(point, forward=True, read=True, write=False, address=stack, size=4)
69 
70  if esp_before < esp_after:
71  stack = reven_api.logical_address(ss_before, esp_before)
72  return client.run_search_next_memory_use(point, forward=False, read=False, write=True, address=stack, size=4)
73 
74 def trigger_percent():
75  "Callback to call when this plugin is enabled."
76 
77  # get current selected instruction from axion
78  run, seq, instr = axion.selected_sequence()
79 
80  # connect to Reven server project instance for latter service calls
81  client = reven_connection_from_axion(axion)
82 
83  # get reven execution point corresponding to selected instruction (for reven service calls take execution point)
84  point = reven_api.execution_point(run.encode(), seq, instr)
85 
86  # retrieve data from Reven and use custom heuristic to find an instruction matching the current one
87  result = get_matching_instruction(client, point)
88 
89  if result == None or not result.valid():
90  axion.status_message("No matching instruction recorded for [%s@%d:%d]" % (run, seq, instr))
91  else:
92  # select matching instruction in axion
93  axion.select_sequence(result.run_name, result.sequence_identifier, result.instruction_index)
94  axion.status_message("Jumped to matching instruction at [%s@%d:%d] from [%s@%d:%d]" % (result.run_name, result.sequence_identifier, result.instruction_index, run, seq, instr))
95 
96 
97 def get_disabled_callback(reason):
98  "Generate a callback printing reason for having disabled this plugin in status bar"
99 
100  def disabled():
101  axion.status_message("Percent plugin is disabled (%s)" % reason)
102 
103  return disabled
104 
105 def try_enable():
106  "Try to enable this plugin, check connection and memory history availability"
107 
108  global current_callback
109 
110  client = reven_connection_from_axion(axion)
111  if not client:
112  current_callback = get_disabled_callback("Not connected to Reven server")
113  return
114 
115  if not is_memory_history_enabled(client) :
116  reason = "requires inspector memory range history"
117  current_callback = get_disabled_callback(reason)
118  axion.log_warning("Percent plugin disabled (%s)" % reason)
119  else:
120  current_callback = trigger_percent
121 
122 
123 # DO NOT simply assign axion_callback to callback_enabled/disabled (would break with register_command's call)
124 def axion_callback():
125  "Actual callback auto-called by axion when this plugin is triggered"
126 
127  current_callback()
128 
129 def axion_connected():
130  "Callback auto-called by axion when connected to a new server"
131 
132  try_enable()
133 
134 def axion_disconnected():
135  "Callback auto-called by axion when disconnected from current server"
136 
137  current_callback = get_disabled_callback("Not connected to Reven server")
138 
139 
140 current_callback = get_disabled_callback("Not initialised")
141 register_command(axion_callback, "percent", "plugin.percent", "%")
142 try_enable()