REVEN-Axion 2017v1.4.0
launcher_low_level.py

This shows how to connect to reven launcher in python, using launcher services from reven low level api.

You will find some helpers to:

And a sample use case using them.

1 #!/usr/bin/env python2
2 
3 # launcher_low_level.py -f /tmp/crash.html -- ~/samples/lynx32.bin -dump crash.html
4 
5 import reven_api # to connect reven launcher
6 import reven # to give a high level handler on a reven server after opening a project
7 
8 from os.path import basename
9 from os import system
10 from time import sleep
11 
12 ### App ########################################################################
13 
14 import argparse
15 
16 # default vnc password to use if not set in VM's config
17 default_vnc_password = 'passw0rd'
18 # default vnc port to use if not set in VM's config
19 default_vnc_port = 5900
20 
21 def cli_args():
22  """
23  Parse command line arguments.
24  """
25  parser = argparse.ArgumentParser()
26  # parser.add_argument('-v', '--verbose', action='store_true')
27  parser.add_argument('--vm', default='debian_jessie_auto', help="VM name to select for scenario recording")
28  parser.add_argument('--host', default='localhost', help="Reven launcher's hostname")
29  parser.add_argument('--port', type=int, default=8080, help="Reven launcher's port")
30  parser.add_argument('--user', default='reven', help="Reven username")
31  parser.add_argument('--project', default='sample_project', help="Reven project name")
32  parser.add_argument('--kill', action='store_true', help="close running project or abort previous scenario recording if any")
33  parser.add_argument('-f', '--file', action='append', metavar='FILE', dest='files', default=[], help="add input file for scenario recording")
34  parser.add_argument('binary', nargs='?', help="binary to analyse (will be uploaded as input file)")
35  parser.add_argument('args', nargs='*', help="arguments to launch binary with")
36 
37  return parser.parse_args()
38 
39 ### Launcher ###################################################################
40 
41 def connect_to_launcher(host, port):
42  """
43  Connect to Reven's launcher.
44  On error, print and exit.
45  @param host: host to connect to.
46  @param port: port to connect to.
47  @return: Handle to launcher (reven_api.launcher_connection).
48  """
49  try:
50  lc = reven_api.launcher_connection(host, port)
51  # add a hostname attribute to lc for future vnc connections
52  lc.hostname = host
53 
54  return lc
55  except RuntimeError as e:
56  print("Could not connect to launcher at %s:%s (%s)" % (host, port, e))
57  exit()
58 
59 def vm_config_by_name(launcher_connection, vm_name):
60  """
61  Get a VM config by name.
62  @param launcher_connection: launcher handle (reven_api.launcher_connection).
63  @param vm_name: Name of the config to lookup.
64  @return: VM config (reven_api.vm_info) or None if not found.
65  """
66  for vm_cfg in launcher_connection.list_vms():
67  if vm_cfg.name == vm_name:
68  return vm_cfg
69 
70 def print_available_vms(launcher_connection):
71  """
72  Print available VM configurations.
73  @param launcher_connection: launcher handle (reven_api.launcher_connection).
74  """
75  print "Available vm configs:"
76  for vm_cfg in launcher_connection.list_vms():
77  print "\t%s (%s)" % (vm_cfg.name, vm_cfg.display)
78 
79 def user_exists(launcher_connection, username):
80  """
81  @param launcher_connection: launcher handle (reven_api.launcher_connection).
82  @param username: Username to check for (str)
83  @return: True if a user with username exists.
84  """
85  return username in launcher_connection.list_users()
86 
87 def project_exists(launcher_connection, project):
88  """
89  @param launcher_connection: launcher handle (reven_api.launcher_connection).
90  @param project: Project handle to check for (reven_api.project_id)
91  @return: True if project exists.
92  """
93  return project in launcher_connection.list_projects(project.user)
94 
95 def check_project_not_running(launcher_connection, project, kill=False):
96  """
97  If a server is running for the given project, kill it or exit.
98  @param launcher_connection: launcher handle (reven_api.launcher_connection).
99  @param project: Project handle to check for (reven_api.project_id)
100  @param kill: If True will try to kill project's server if running.
101  """
102  try:
103  project_port = launcher_connection.project_details(project).reven_server.port
104  except RuntimeError:
105  # launcher_connection.project_details may legitimately raise if project is not fully set up yet.
106  return
107 
108  if project_port > 0:
109  if kill:
110  print "Stopping running project on port %s" % project_port
111  launcher_connection.server_kill(project_port)
112  else:
113  print "Project %s already running on port %s" % (project, project_port)
114  exit()
115 
116 def check_project_not_generating(launcher_connection, project, kill=False):
117  """
118  If a scenario is being recorder for the project, abort it or exit.
119  @param launcher_connection: launcher handle (reven_api.launcher_connection).
120  @param project: Project handle to check for (reven_api.project_id)
121  @param kill: If True will try to abort any current scenario generation.
122  """
123  try:
124  project_scenario = launcher_connection.project_scenario(project)
125  except RuntimeError:
126  # launcher_connection.project_scenario may legitimately raise if project is not fully set up yet.
127  return
128 
129  if project_scenario.is_generating:
130  if kill:
131  print "Aborting previous scenario generation"
132  abort_recording(launcher_connection, project)
133  else:
134  print "Project %s is already recording a scenario" % (project)
135  exit()
136 
137 ### Scenario ###################################################################
138 
139 def upload_input_files(launcher_connection, project, files):
140  """
141  Upload input files to project for scenario recording.
142  @param launcher_connection: launcher handle (reven_api.launcher_connection)
143  @param project: Project to upload files to (reven_api.project_id)
144  @param files: List of file paths to be uploaded (list(str))
145  @return: yield uploaded filepath.
146  """
147  for filepath in files:
148  launcher_connection.project_upload_file(project, filepath)
149  yield filepath
150 
151 class ScenarioConfigurator(object):
152  """
153  Helper class to configure scenario recording.
154  """
155 
156  def __init__(self, vm_cfg, binary=None, args=[], dump_hint=None):
157  """
158  @param vm_cfg: VM config to use (reven_api.vm_info)
159  @param binary: Optional binary to record, filepath must be accessible (str)
160  @param args: Arguments to launch the binary with (list(str))
161  @param dump_hint: Address or symbol in binary to start recording from (str).
162  """
163  self._sc = reven_api.scenario_generation_config()
164  self._sc.generation.enable_instruction_tracing = False
165  self._sc.generation.is_interactive = True
166 
167  self._set_vm(vm_cfg)
168  if binary:
169  self._set_binary(binary, args, dump_hint)
170 
171  def _set_vm(self, vm_cfg):
172  """
173  @param: vm_cfg: VM config to use (reven_api.vm_info)
174  """
175  self._sc.generation.vnc_password = vm_cfg.vnc_password or default_vnc_password
176  self._sc.generation.vnc_port = vm_cfg.vnc_port or default_vnc_port
177 
178  self._sc.scenario.vm_config_name = vm_cfg.name
179  self._sc.scenario.system_pdb_path = vm_cfg.pdb_path
180 
181  def _set_binary(self, binary, args=[], dump_hint=None):
182  self._sc.generation.is_interactive = False
183 
184  self._sc.scenario.binary_name = basename(binary) if binary else ''
185  self._sc.scenario.binary_arguments = ' '.join(args)
186  self._sc.scenario.binary_dump_hint = dump_hint or ''
187  self._sc.scenario.binary_dump_address = dump_hint or ''
188 
189  def validate(self):
190  """
191  @return: Scenario configuration (reven_api.scenario_generation_config)
192  """
193  return self._sc
194 
195 def stop_recording(vnc_host, vnc_port, vnc_password):
196  """
197  Gracefully stop scenario recording using VNC to send magic key sequence 'F9', 'F7'.
198  @param vnc_host: hostname to connect to.
199  @param vnc_port: port to connect to.
200  @param vnc_password: password to use.
201  """
202  from vncdotool import api as vnc
203 
204  vnc.connect('%s::%s' % (vnc_host, vnc_port), vnc_password).keyDown('f9').keyDown('f7')
205  vnc.shutdown()
206 
207 
208 def abort_recording(launcher_connection, project):
209  """
210  Abort scenario recording for given project (will discard currently recorded data).
211  @param launcher_connection: launcher handle (reven_api.launcher_connection)
212  @param project: project to abort recording for (reven_api.project_id)
213  """
214  launcher_connection.project_abort_scenario_generation(project)
215 
216 def watch_recording(launcher_connection, project):
217  """
218  @param launcher_connection: launcher handle (reven_api.launcher_connection)
219  @param project: project to watch recording (reven_api.project_id)
220  @return: yield recording progress (reven_api.scenario_generation_info)
221  """
222  recording = True
223  while recording:
224  progress = launcher_connection.project_scenario(project)
225  recording = progress.is_generating
226 
227  yield progress
228 
229 def wait_recording(launcher_connection, project, soft_timeout=10, hard_timeout=30):
230  """
231  Blocks until scenario is recorded or stopped.
232  @param launcher_connection: launcher handle (reven_api.launcher_connection)
233  @param project: project to wait recording (reven_api.project_id)
234  @param soft_timeout: Timeout in seconds before trying to stop scenario recording
235  @param hard_timeout: Timeout in seconds before trying to abort scenario recording (recorded data are discarded)
236  """
237  for i, progress in enumerate(watch_recording(launcher_connection, project)):
238  if progress.log_chunk:
239  print progress.log_chunk
240 
241  if i == soft_timeout:
242  print "Soft timeout: stopping scenario recording"
243  try:
244  stop_recording(launcher_connection.hostname, vnc_port, vnc_password)
245  except Exception as e:
246  print "Could not gracefully stop recording through vnc (%s)" % e
247  elif i == hard_timeout:
248  print "Hard timeout: aborting scenario recording"
249  abort_recording(launcher_connection, project)
250  else:
251  sleep(1)
252 
253  return progress.is_successful
254 
255 ### Reven ######################################################################
256 
257 def open_project(launcher_connection, project):
258  """
259  Open project (launch a server)
260  @param launcher_connection: launcher handle (reven_api.launcher_connection)
261  @param project: project to open (reven_api.project_id)
262  @return: handle to project (reven.Project)
263  """
264  # actually open project
265  port = launcher_connection.server_launch(project)
266  if not port:
267  print "Error server not started for project %s" % project
268 
269  print "Server started on port %s" % port
270 
271  # reven server cannnot accept connections immediatly after beeing launched
272  # try to reconnect if first attempts failed
273  client = None
274  for _ in range(10):
275  try:
276  client = reven.Project(launcher_connection.hostname, port)
277  print "Connected!"
278  break
279  except RuntimeError as e:
280  print "Could not connect (%s)" % e
281  sleep(1)
282 
283  return client
284 
285 
286 def wait_execution(reven_client):
287  """
288  Block until execution is completed.
289  @param reven_client: opened project handle (reven.Project)
290  """
291  while True:
292  progress = reven_client.execution_status()
293 
294  print "# %s/%s - %s/%s - %s" % (
295  progress.tsc, progress.last_tsc,
296  progress.point_index, progress.last_point_index,
297  progress.status)
298 
299  if not progress.is_busy:
300  break
301 
302  sleep(1)
303 
304 
305 if __name__ == '__main__':
306  # Arguments ############################################################
307 
308  args = cli_args()
309 
310  launcher_hostname = args.host
311  launcher_port = args.port
312  sample_project = reven_api.project_id(args.user, args.project)
313  sample_binary = args.binary
314  sample_binary_arguments = args.args
315  sample_input_files = args.files
316  sample_vm = args.vm
317  sample_overkill = args.kill
318 
319  sample_is_interactive = not sample_binary
320 
321  # Connect ##############################################################
322 
323  print "Connecting to %s:%s" % (launcher_hostname, launcher_port)
324 
325  lc = connect_to_launcher(launcher_hostname, launcher_port)
326 
327  # Check Vm #############################################################
328 
329  vm_cfg = vm_config_by_name(lc, sample_vm)
330  if not vm_cfg:
331  print "Vm config %s not found." % (sample_vm)
332  print_available_vms(lc)
333  exit()
334 
335  print "Using vm config %s: %s" % (sample_vm, vm_cfg.display)
336 
337  # Check project ########################################################
338 
339  if not user_exists(lc, sample_project.user):
340  print "User %s does not exist (will be created)" % sample_project.user
341 
342  if project_exists(lc, sample_project):
343  print "Project %s already exists, checking project status" % (sample_project)
344  check_project_not_running(lc, sample_project, kill=sample_overkill)
345  check_project_not_generating(lc, sample_project, kill=sample_overkill)
346  else:
347  print "Project %s does not exists, creating..." % (sample_project)
348  lc.project_create(sample_project)
349 
350  print "Saving to project %s" % (sample_project)
351 
352  # Scenario setup #######################################################
353 
354  # Input files
355 
356  if sample_binary and not sample_binary in sample_input_files:
357  print "Adding binary %s to files being uploaded" % (sample_binary)
358  sample_input_files.append(sample_binary)
359 
360  print "Uploading files: %s" % (sample_input_files)
361 
362  for f in upload_input_files(lc, sample_project, sample_input_files):
363  print "\tUploaded %s" % f
364 
365  # Recording
366 
367  print "Recording: %s" % ("<interactive>" if not sample_binary else "%s with %s" % (sample_binary, sample_binary_arguments))
368 
369  print "Configuring scenario recording"
370 
371  scenario_config = ScenarioConfigurator(vm_cfg=vm_cfg, binary=sample_binary, args=sample_binary_arguments, dump_hint=None).validate()
372 
373  print "Start scenario recording"
374 
375  vnc_port = scenario_config.generation.vnc_port
376  vnc_password = scenario_config.generation.vnc_password
377 
378  print "VNC conf %s::%s -p %s" % (lc.hostname, vnc_port, vnc_password)
379 
380  # Scenario recording ###################################################
381 
382  lc.project_generate_scenario(sample_project, scenario_config)
383 
384  is_scenario_successful = wait_recording(lc, sample_project)
385 
386  if is_scenario_successful:
387  print "Scenario recorded!"
388  else:
389  print "Error while recording scenario"
390  exit()
391 
392  # Launch Reven #########################################################
393 
394  client = open_project(lc, sample_project)
395  if not client:
396  print "Error while connecting to project"
397  exit()
398  port = client.port
399 
400  # Execution ############################################################
401 
402  print "Launching execution"
403 
404  client.start_execution()
405  wait_execution(client)
406 
407  # Launch axion #########################################################
408 
409  axion_launch_command = "axion --host %s --port %s" % (lc.hostname, port)
410  print axion_launch_command
411  system(axion_launch_command)