1
2
3
4
5 import datetime
6 import logging
7 import random
8 import re
9 import requests
10 import ssl
11 import time
12
13 from lib.cuckoo.common.abstracts import Machinery
14 from lib.cuckoo.common.exceptions import CuckooMachineError
15 from lib.cuckoo.common.exceptions import CuckooDependencyError
16 from lib.cuckoo.common.exceptions import CuckooCriticalError
17
18 try:
19 from pyVim.connect import SmartConnection
20 HAVE_PYVMOMI = True
21 except ImportError:
22 HAVE_PYVMOMI = False
23
24 log = logging.getLogger(__name__)
25 logging.getLogger("requests").setLevel(logging.WARNING)
26
27
29 """vSphere/ESXi machinery class based on pyVmomi Python SDK."""
30
31
32 RUNNING = "poweredOn"
33 POWEROFF = "poweredOff"
34 SUSPENDED = "suspended"
35 ABORTED = "aborted"
36
45
47 """Read configuration.
48 @param module_name: module name.
49 """
50 super(vSphere, self)._initialize(module_name)
51
52
53 random.seed()
54
56 """Runs checks against virtualization software when a machine manager
57 is initialized.
58 @raise CuckooCriticalError: if a misconfiguration or unsupported state
59 is found.
60 """
61 self.connect_opts = {}
62
63 if self.options.vsphere.host:
64 self.connect_opts["host"] = self.options.vsphere.host
65 else:
66 raise CuckooCriticalError(
67 "vSphere host address setting not found, please add it "
68 "to the config file."
69 )
70
71 if self.options.vsphere.port:
72 self.connect_opts["port"] = self.options.vsphere.port
73 else:
74 raise CuckooCriticalError(
75 "vSphere port setting not found, please add it to the "
76 "config file."
77 )
78
79 if self.options.vsphere.user:
80 self.connect_opts["user"] = self.options.vsphere.user
81 else:
82 raise CuckooCriticalError(
83 "vSphere username setting not found, please add it to "
84 "the config file."
85 )
86
87 if self.options.vsphere.pwd:
88 self.connect_opts["pwd"] = self.options.vsphere.pwd
89 else:
90 raise CuckooCriticalError(
91 "vSphere password setting not found, please add it to "
92 "the config file."
93 )
94
95
96 if self.options.vsphere.unverified_ssl:
97 sslContext = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
98 sslContext.verify_mode = ssl.CERT_NONE
99 self.connect_opts["sslContext"] = sslContext
100 log.warn("Turning off SSL certificate verification!")
101
102
103
104 try:
105 with SmartConnection(**self.connect_opts) as conn:
106 for machine in self.machines():
107 if not machine.snapshot:
108 raise CuckooCriticalError(
109 "Snapshot name not specified for machine %s, "
110 "please add it to the config file." %
111 machine.label
112 )
113
114 vm = self._get_virtual_machine_by_label(conn, machine.label)
115 if not vm:
116 raise CuckooCriticalError(
117 "Unable to find machine %s on vSphere host, "
118 "please update your configuration." %
119 machine.label
120 )
121
122 state = self._get_snapshot_power_state(vm, machine.snapshot)
123 if not state:
124 raise CuckooCriticalError(
125 "Unable to find snapshot %s for machine %s, "
126 "please update your configuration." %
127 (machine.snapshot, machine.label)
128 )
129
130 if state != self.RUNNING:
131 raise CuckooCriticalError(
132 "Snapshot for machine %s not in powered-on "
133 "state, please create one." % machine.label
134 )
135 except CuckooCriticalError as e:
136 raise e
137 except Exception as e:
138 raise CuckooCriticalError(
139 "Couldn't connect to vSphere host: %s" % e
140 )
141
142 super(vSphere, self)._initialize_check()
143
144 - def start(self, label, task):
159
160 - def stop(self, label):
173
190
192 """List virtual machines on vSphere host"""
193 ret = []
194 with SmartConnection(**self.connect_opts) as conn:
195 for vm in self._get_virtual_machines(conn):
196 ret.append(vm.summary.config.name)
197 return ret
198
200 """Get power state of vm from vSphere host.
201 @param label: virtual machine name
202 @raise CuckooMachineError: if error getting status or machine not found
203 """
204 with SmartConnection(**self.connect_opts) as conn:
205 vm = self._get_virtual_machine_by_label(conn, label)
206 if not vm:
207 raise CuckooMachineError(
208 "Machine %s not found on server" % label
209 )
210
211 status = vm.runtime.powerState
212 self.set_status(label, status)
213 return status
214
216 """Iterate over all VirtualMachine managed objects on vSphere host"""
217 def traverseDCFolders(conn, nodes, path=""):
218 for node in nodes:
219 if hasattr(node, "childEntity"):
220 for child, childpath in traverseDCFolders(conn, node.childEntity, path + node.name + "/"):
221 yield child, childpath
222 else:
223 yield node, path + node.name
224
225 def traverseVMFolders(conn, nodes):
226 for node in nodes:
227 if hasattr(node, "childEntity"):
228 for child in traverseVMFolders(conn, node.childEntity):
229 yield child
230 else:
231 yield node
232
233 self.VMtoDC = {}
234
235 for dc, dcpath in traverseDCFolders(conn, conn.content.rootFolder.childEntity):
236 for vm in traverseVMFolders(conn, dc.vmFolder.childEntity):
237 self.VMtoDC[vm.summary.config.name] = dcpath
238 yield vm
239
245
252
254 """Return the power state for a named VirtualMachineSnapshot object"""
255 for ss in self._traverseSnapshots(vm.snapshot.rootSnapshotList):
256 if ss.name == name:
257 return ss.state
258
274
294
314
316 """Download snapshot file from host to local path"""
317
318
319 snapshot = self._get_snapshot_by_name(vm, name)
320 if not snapshot:
321 raise CuckooMachineError(
322 "Snapshot %s for machine %s not found" %
323 (name, vm.summary.config.name)
324 )
325
326 memorykey = datakey = filespec = None
327 for s in vm.layoutEx.snapshot:
328 if s.key == snapshot:
329 memorykey = s.memoryKey
330 datakey = s.dataKey
331 break
332
333 for f in vm.layoutEx.file:
334 if f.key == memorykey and (f.type == "snapshotMemory" or
335 f.type == "suspendMemory"):
336 filespec = f.name
337 break
338
339 if not filespec:
340 for f in vm.layoutEx.file:
341 if f.key == datakey and f.type == "snapshotData":
342 filespec = f.name
343 break
344
345 if not filespec:
346 raise CuckooMachineError("Could not find snapshot memory file")
347
348 log.info("Downloading memory dump %s to %s", filespec, path)
349
350
351 datastore, filepath = re.match(r"\[([^\]]*)\] (.*)", filespec).groups()
352
353
354 params = {
355 "dsName": datastore,
356 "dcPath": self.VMtoDC.get(vm.summary.config.name, "ha-datacenter")
357 }
358 headers = {
359 "Cookie": conn._stub.cookie,
360 }
361 url = "https://%s:%s/folder/%s" % (
362 self.connect_opts["host"], self.connect_opts["port"], filepath
363 )
364
365
366 try:
367 response = requests.get(url, params=params, headers=headers,
368 verify=False, stream=True)
369
370 response.raise_for_status()
371
372 with open(path, "wb") as localfile:
373 for chunk in response.iter_content(16*1024):
374 localfile.write(chunk)
375
376 except Exception as e:
377 raise CuckooMachineError(
378 "Error downloading memory dump %s: %s" %
379 (filespec, e)
380 )
381
383 """Power off a virtual machine"""
384 log.info("Powering off virtual machine %s", vm.summary.config.name)
385 task = vm.PowerOffVM_Task()
386 try:
387 self._wait_task(task)
388 except CuckooMachineError as e:
389 log.error("PowerOffVM: %s", e)
390
392 """Wait for a task to complete with timeout"""
393 limit = datetime.timedelta(
394 seconds=int(self.options_globals.timeouts.vm_state)
395 )
396 start = datetime.datetime.utcnow()
397
398 while True:
399 if task.info.state == "error":
400 raise CuckooMachineError("Task error")
401
402 if task.info.state == "success":
403 break
404
405 if datetime.datetime.utcnow() - start > limit:
406 raise CuckooMachineError("Task timed out")
407
408 time.sleep(1)
409
411 """Recursive depth-first traversal of snapshot tree"""
412 for node in root:
413 if len(node.childSnapshotList) > 0:
414 for child in self._traverseSnapshots(node.childSnapshotList):
415 yield child
416 yield node
417