REVEN-Axion 2018v1.5.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 
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 
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_recording(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 recording.
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_recording:
130  if kill:
131  print "Aborting previous scenario recording"
132  abort_recording(launcher_connection, project)
133  else:
134  print "Project %s is already recording a scenario" % (project)
135  exit()
136 
137 
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_recording_config()
164  self._sc.recording.is_interactive = True
165 
166  self._set_vm(vm_cfg)
167  if binary:
168  self._set_binary(binary, args, dump_hint)
169 
170  def _set_vm(self, vm_cfg):
171  """
172  @param: vm_cfg: VM config to use (reven_api.vm_info)
173  """
174  self._sc.recording.vnc_password = vm_cfg.vnc_password or default_vnc_password
175  self._sc.recording.vnc_port = vm_cfg.vnc_port or default_vnc_port
176 
177  self._sc.scenario.vm_config_name = vm_cfg.name
178  self._sc.scenario.system_pdb_path = vm_cfg.pdb_path
179 
180  def _set_binary(self, binary, args=[], dump_hint=None):
181  self._sc.recording.is_interactive = False
182 
183  self._sc.scenario.binary_name = basename(binary) if binary else ''
184  self._sc.scenario.binary_arguments = ' '.join(args)
185  self._sc.scenario.binary_dump_hint = dump_hint or ''
186  self._sc.scenario.binary_dump_address = dump_hint or ''
187 
188  def validate(self):
189  """
190  @return: Scenario configuration (reven_api.scenario_recording_config)
191  """
192  return self._sc
193 
194 def stop_recording(vnc_host, vnc_port, vnc_password):
195  """
196  Gracefully stop scenario recording using VNC to send magic key sequence 'F9', 'F7'.
197  @param vnc_host: hostname to connect to.
198  @param vnc_port: port to connect to.
199  @param vnc_password: password to use.
200  """
201  from vncdotool import api as vnc
202 
203  vnc.connect('%s::%s' % (vnc_host, vnc_port), vnc_password).keyDown('f9').keyDown('f7')
204  vnc.shutdown()
205 
206 
207 def abort_recording(launcher_connection, project):
208  """
209  Abort scenario recording for given project (will discard currently recorded data).
210  @param launcher_connection: launcher handle (reven_api.launcher_connection)
211  @param project: project to abort recording for (reven_api.project_id)
212  """
213  launcher_connection.project_abort_scenario_recording(project)
214 
215 def watch_recording(launcher_connection, project):
216  """
217  @param launcher_connection: launcher handle (reven_api.launcher_connection)
218  @param project: project to watch recording (reven_api.project_id)
219  @return: yield recording progress (reven_api.scenario_recording_info)
220  """
221  recording = True
222  while recording:
223  progress = launcher_connection.project_scenario(project)
224  recording = progress.is_recording
225 
226  yield progress
227 
228 def wait_recording(launcher_connection, project, soft_timeout=10, hard_timeout=30):
229  """
230  Blocks until scenario is recorded or stopped.
231  @param launcher_connection: launcher handle (reven_api.launcher_connection)
232  @param project: project to wait recording (reven_api.project_id)
233  @param soft_timeout: Timeout in seconds before trying to stop scenario recording
234  @param hard_timeout: Timeout in seconds before trying to abort scenario recording (recorded data are discarded)
235  """
236  for i, progress in enumerate(watch_recording(launcher_connection, project)):
237  if progress.log_chunk:
238  print progress.log_chunk
239 
240  if i == soft_timeout:
241  print "Soft timeout: stopping scenario recording"
242  try:
243  stop_recording(launcher_connection.hostname, vnc_port, vnc_password)
244  except Exception as e:
245  print "Could not gracefully stop recording through vnc (%s)" % e
246  elif i == hard_timeout:
247  print "Hard timeout: aborting scenario recording"
248  abort_recording(launcher_connection, project)
249  else:
250  sleep(1)
251 
252  return progress.is_successful
253 
254 
255 
256 def open_project(launcher_connection, project):
257  """
258  Open project (launch a server)
259  @param launcher_connection: launcher handle (reven_api.launcher_connection)
260  @param project: project to open (reven_api.project_id)
261  @return: handle to project (reven.Project)
262  """
263  # actually open project
264  port = launcher_connection.server_launch(project)
265  if not port:
266  print "Error server not started for project %s" % project
267 
268  print "Server started on port %s" % port
269 
270  # reven server cannnot accept connections immediatly after beeing launched
271  # try to reconnect if first attempts failed
272  client = None
273  for _ in range(10):
274  try:
275  client = reven.Project(launcher_connection.hostname, port)
276  print "Connected!"
277  break
278  except RuntimeError as e:
279  print "Could not connect (%s)" % e
280  sleep(1)
281 
282  return client
283 
284 
285 def wait_execution(reven_client):
286  """
287  Block until execution is completed.
288  @param reven_client: opened project handle (reven.Project)
289  """
290  while True:
291  progress = reven_client.execution_status()
292 
293  print "# %s/%s - %s/%s - %s" % (
294  progress.tsc, progress.last_tsc,
295  progress.point_index, progress.last_point_index,
296  progress.status)
297 
298  if not progress.is_busy:
299  break
300 
301  sleep(1)
302 
303 
304 if __name__ == '__main__':
305  # Arguments ############################################################
306 
307  args = cli_args()
308 
309  launcher_hostname = args.host
310  launcher_port = args.port
311  sample_project = reven_api.project_id(args.user, args.project)
312  sample_binary = args.binary
313  sample_binary_arguments = args.args
314  sample_input_files = args.files
315  sample_vm = args.vm
316  sample_overkill = args.kill
317 
318  sample_is_interactive = not sample_binary
319 
320  # Connect ##############################################################
321 
322  print "Connecting to %s:%s" % (launcher_hostname, launcher_port)
323 
324  lc = connect_to_launcher(launcher_hostname, launcher_port)
325 
326  # Check Vm #############################################################
327 
328  vm_cfg = vm_config_by_name(lc, sample_vm)
329  if not vm_cfg:
330  print "Vm config %s not found." % (sample_vm)
331  print_available_vms(lc)
332  exit()
333 
334  print "Using vm config %s: %s" % (sample_vm, vm_cfg.display)
335 
336  # Check project ########################################################
337 
338  if not user_exists(lc, sample_project.user):
339  print "User %s does not exist (will be created)" % sample_project.user
340 
341  if project_exists(lc, sample_project):
342  print "Project %s already exists, checking project status" % (sample_project)
343  check_project_not_running(lc, sample_project, kill=sample_overkill)
344  check_project_not_recording(lc, sample_project, kill=sample_overkill)
345  else:
346  print "Project %s does not exists, creating..." % (sample_project)
347  lc.project_create(sample_project)
348 
349  print "Saving to project %s" % (sample_project)
350 
351  # Scenario setup #######################################################
352 
353  # Input files
354 
355  if sample_binary and not sample_binary in sample_input_files:
356  print "Adding binary %s to files being uploaded" % (sample_binary)
357  sample_input_files.append(sample_binary)
358 
359  print "Uploading files: %s" % (sample_input_files)
360 
361  for f in upload_input_files(lc, sample_project, sample_input_files):
362  print "\tUploaded %s" % f
363 
364  # Recording
365 
366  print "Recording: %s" % ("<interactive>" if not sample_binary else "%s with %s" % (sample_binary, sample_binary_arguments))
367 
368  print "Configuring scenario recording"
369 
370  scenario_config = ScenarioConfigurator(vm_cfg=vm_cfg, binary=sample_binary, args=sample_binary_arguments, dump_hint=None).validate()
371 
372  print "Start scenario recording"
373 
374  vnc_port = scenario_config.recording.vnc_port
375  vnc_password = scenario_config.recording.vnc_password
376 
377  print "VNC conf %s::%s -p %s" % (lc.hostname, vnc_port, vnc_password)
378 
379  # Scenario recording ###################################################
380 
381  lc.project_record_scenario(sample_project, scenario_config)
382 
383  is_scenario_successful = wait_recording(lc, sample_project)
384 
385  if is_scenario_successful:
386  print "Scenario recorded!"
387  else:
388  print "Error while recording scenario"
389  exit()
390 
391  # Launch Reven #########################################################
392 
393  client = open_project(lc, sample_project)
394  if not client:
395  print "Error while connecting to project"
396  exit()
397  port = client.port
398 
399  # Execution ############################################################
400 
401  print "Launching execution"
402 
403  client.start_execution()
404  wait_execution(client)
405 
406  # Launch axion #########################################################
407 
408  axion_launch_command = "axion --host %s --port %s" % (lc.hostname, port)
409  print axion_launch_command
410  system(axion_launch_command)