dissect.hypervisor 3.21.dev2__py3-none-any.whl → 3.21.dev3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dissect/hypervisor/descriptor/vbox.py +211 -7
- {dissect_hypervisor-3.21.dev2.dist-info → dissect_hypervisor-3.21.dev3.dist-info}/METADATA +1 -1
- {dissect_hypervisor-3.21.dev2.dist-info → dissect_hypervisor-3.21.dev3.dist-info}/RECORD +8 -8
- {dissect_hypervisor-3.21.dev2.dist-info → dissect_hypervisor-3.21.dev3.dist-info}/WHEEL +1 -1
- {dissect_hypervisor-3.21.dev2.dist-info → dissect_hypervisor-3.21.dev3.dist-info}/entry_points.txt +0 -0
- {dissect_hypervisor-3.21.dev2.dist-info → dissect_hypervisor-3.21.dev3.dist-info}/licenses/COPYRIGHT +0 -0
- {dissect_hypervisor-3.21.dev2.dist-info → dissect_hypervisor-3.21.dev3.dist-info}/licenses/LICENSE +0 -0
- {dissect_hypervisor-3.21.dev2.dist-info → dissect_hypervisor-3.21.dev3.dist-info}/top_level.txt +0 -0
|
@@ -1,22 +1,226 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from functools import cached_property
|
|
3
5
|
from typing import TYPE_CHECKING, TextIO
|
|
6
|
+
from uuid import UUID
|
|
4
7
|
|
|
5
8
|
from defusedxml import ElementTree
|
|
6
9
|
|
|
7
10
|
if TYPE_CHECKING:
|
|
8
|
-
from collections.abc import Iterator
|
|
9
11
|
from xml.etree.ElementTree import Element
|
|
10
12
|
|
|
13
|
+
NS = "{http://www.virtualbox.org/}"
|
|
14
|
+
|
|
11
15
|
|
|
12
16
|
class VBox:
|
|
13
|
-
|
|
17
|
+
"""VirtualBox XML descriptor parser.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
fh: A file-like object of the VirtualBox XML descriptor.
|
|
21
|
+
"""
|
|
14
22
|
|
|
15
23
|
def __init__(self, fh: TextIO):
|
|
16
24
|
self._xml: Element = ElementTree.fromstring(fh.read())
|
|
25
|
+
if self._xml.tag != f"{NS}VirtualBox":
|
|
26
|
+
raise ValueError("Invalid VirtualBox XML descriptor: root element is not VirtualBox")
|
|
27
|
+
|
|
28
|
+
if (machine := self._xml.find(f"./{NS}Machine")) is None:
|
|
29
|
+
raise ValueError("Invalid VirtualBox XML descriptor: no Machine element found")
|
|
30
|
+
|
|
31
|
+
if machine.find(f"./{NS}Hardware") is None:
|
|
32
|
+
raise ValueError("Invalid VirtualBox XML descriptor: no Hardware element found")
|
|
33
|
+
|
|
34
|
+
self.machine = Machine(self, machine)
|
|
35
|
+
|
|
36
|
+
def __repr__(self) -> str:
|
|
37
|
+
return f"<VBox uuid={self.uuid} name={self.name}>"
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def uuid(self) -> UUID | None:
|
|
41
|
+
"""The VM UUID."""
|
|
42
|
+
return self.machine.uuid
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def name(self) -> str | None:
|
|
46
|
+
"""The VM name."""
|
|
47
|
+
return self.machine.name
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def media(self) -> dict[UUID, HardDisk]:
|
|
51
|
+
"""The media (disks) registry."""
|
|
52
|
+
return self.machine.media
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def hardware(self) -> Hardware:
|
|
56
|
+
"""The current machine hardware state."""
|
|
57
|
+
return self.machine.hardware
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def snapshots(self) -> dict[UUID, Snapshot]:
|
|
61
|
+
"""All snapshots."""
|
|
62
|
+
return self.machine.snapshots
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class Machine:
|
|
66
|
+
def __init__(self, vbox: VBox, element: Element):
|
|
67
|
+
self.vbox = vbox
|
|
68
|
+
self.element = element
|
|
69
|
+
|
|
70
|
+
def __repr__(self) -> str:
|
|
71
|
+
return f"<Machine uuid={self.uuid} name={self.name}>"
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def uuid(self) -> UUID:
|
|
75
|
+
"""The machine UUID."""
|
|
76
|
+
return UUID(self.element.get("uuid").strip("{}"))
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def name(self) -> str:
|
|
80
|
+
"""The machine name."""
|
|
81
|
+
return self.element.get("name")
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def current_snapshot(self) -> UUID | None:
|
|
85
|
+
"""The current snapshot UUID."""
|
|
86
|
+
if (value := self.element.get("currentSnapshot")) is not None:
|
|
87
|
+
return UUID(value.strip("{}"))
|
|
88
|
+
return None
|
|
89
|
+
|
|
90
|
+
@cached_property
|
|
91
|
+
def media(self) -> dict[UUID, HardDisk]:
|
|
92
|
+
"""The media (disks) registry."""
|
|
93
|
+
result = {}
|
|
94
|
+
|
|
95
|
+
stack = [(None, element) for element in self.element.find(f"./{NS}MediaRegistry/{NS}HardDisks")]
|
|
96
|
+
while stack:
|
|
97
|
+
parent, element = stack.pop()
|
|
98
|
+
hdd = HardDisk(self, element, parent)
|
|
99
|
+
result[hdd.uuid] = hdd
|
|
100
|
+
|
|
101
|
+
stack.extend([(hdd, child) for child in element.findall(f"./{NS}HardDisk")])
|
|
102
|
+
|
|
103
|
+
return result
|
|
104
|
+
|
|
105
|
+
@cached_property
|
|
106
|
+
def hardware(self) -> Hardware:
|
|
107
|
+
"""The machine hardware state."""
|
|
108
|
+
return Hardware(self.vbox, self.element.find(f"./{NS}Hardware"))
|
|
109
|
+
|
|
110
|
+
@cached_property
|
|
111
|
+
def snapshots(self) -> dict[UUID, Snapshot]:
|
|
112
|
+
"""All snapshots."""
|
|
113
|
+
result = {}
|
|
114
|
+
|
|
115
|
+
if (element := self.element.find(f"./{NS}Snapshot")) is None:
|
|
116
|
+
return result
|
|
117
|
+
|
|
118
|
+
stack = [(None, element)]
|
|
119
|
+
while stack:
|
|
120
|
+
parent, element = stack.pop()
|
|
121
|
+
snapshot = Snapshot(self.vbox, element, parent)
|
|
122
|
+
result[snapshot.uuid] = snapshot
|
|
123
|
+
|
|
124
|
+
if (snapshots := element.find(f"./{NS}Snapshots")) is not None:
|
|
125
|
+
stack.extend([(snapshot, child) for child in list(snapshots)])
|
|
126
|
+
|
|
127
|
+
return result
|
|
128
|
+
|
|
129
|
+
@property
|
|
130
|
+
def parent(self) -> Snapshot | None:
|
|
131
|
+
if (uuid := self.current_snapshot) is not None:
|
|
132
|
+
return self.vbox.snapshots[uuid]
|
|
133
|
+
return None
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class HardDisk:
|
|
137
|
+
def __init__(self, vbox: VBox, element: Element, parent: HardDisk | None = None):
|
|
138
|
+
self.vbox = vbox
|
|
139
|
+
self.element = element
|
|
140
|
+
self.parent = parent
|
|
141
|
+
|
|
142
|
+
def __repr__(self) -> str:
|
|
143
|
+
return f"<HardDisk uuid={self.uuid} location={self.location}>"
|
|
144
|
+
|
|
145
|
+
@property
|
|
146
|
+
def uuid(self) -> UUID:
|
|
147
|
+
"""The disk UUID."""
|
|
148
|
+
return UUID(self.element.get("uuid").strip("{}"))
|
|
149
|
+
|
|
150
|
+
@property
|
|
151
|
+
def location(self) -> str:
|
|
152
|
+
"""The disk location."""
|
|
153
|
+
return self.element.get("location")
|
|
154
|
+
|
|
155
|
+
@property
|
|
156
|
+
def type(self) -> str | None:
|
|
157
|
+
"""The disk type."""
|
|
158
|
+
return self.element.get("type")
|
|
159
|
+
|
|
160
|
+
@property
|
|
161
|
+
def format(self) -> str:
|
|
162
|
+
"""The disk format."""
|
|
163
|
+
return self.element.get("format")
|
|
164
|
+
|
|
165
|
+
@cached_property
|
|
166
|
+
def properties(self) -> dict[str, str]:
|
|
167
|
+
"""The disk properties."""
|
|
168
|
+
return {prop.get("name"): prop.get("value") for prop in self.element.findall(f"./{NS}Property")}
|
|
169
|
+
|
|
170
|
+
@property
|
|
171
|
+
def is_encrypted(self) -> bool:
|
|
172
|
+
"""Whether the disk is encrypted."""
|
|
173
|
+
disk = self
|
|
174
|
+
while disk is not None:
|
|
175
|
+
if "CRYPT/KeyId" in disk.properties or "CRYPT/KeyStore" in disk.properties:
|
|
176
|
+
return True
|
|
177
|
+
disk = disk.parent
|
|
178
|
+
|
|
179
|
+
return False
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
class Snapshot:
|
|
183
|
+
def __init__(self, vbox: VBox, element: Element, parent: Snapshot | Machine | None = None):
|
|
184
|
+
self.vbox = vbox
|
|
185
|
+
self.element = element
|
|
186
|
+
self.parent = parent
|
|
187
|
+
|
|
188
|
+
def __repr__(self) -> str:
|
|
189
|
+
return f"<Snapshot uuid={self.uuid} name={self.name}>"
|
|
190
|
+
|
|
191
|
+
@property
|
|
192
|
+
def uuid(self) -> UUID:
|
|
193
|
+
"""The snapshot UUID."""
|
|
194
|
+
return UUID(self.element.get("uuid").strip("{}"))
|
|
195
|
+
|
|
196
|
+
@property
|
|
197
|
+
def name(self) -> str:
|
|
198
|
+
"""The snapshot name."""
|
|
199
|
+
return self.element.get("name")
|
|
200
|
+
|
|
201
|
+
@property
|
|
202
|
+
def ts(self) -> datetime:
|
|
203
|
+
"""The snapshot timestamp."""
|
|
204
|
+
return datetime.strptime(self.element.get("timeStamp"), "%Y-%m-%dT%H:%M:%S%z")
|
|
205
|
+
|
|
206
|
+
@cached_property
|
|
207
|
+
def hardware(self) -> Hardware:
|
|
208
|
+
"""The snapshot hardware state."""
|
|
209
|
+
return Hardware(self.vbox, self.element.find(f"./{NS}Hardware"))
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
class Hardware:
|
|
213
|
+
def __init__(self, vbox: VBox, element: Element):
|
|
214
|
+
self.vbox = vbox
|
|
215
|
+
self.element = element
|
|
216
|
+
|
|
217
|
+
def __repr__(self) -> str:
|
|
218
|
+
return f"<Hardware disks={len(self.disks)}>"
|
|
17
219
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
220
|
+
@property
|
|
221
|
+
def disks(self) -> list[HardDisk]:
|
|
222
|
+
"""All attached hard disks."""
|
|
223
|
+
images = self.element.findall(
|
|
224
|
+
f"./{NS}StorageControllers/{NS}StorageController/{NS}AttachedDevice[@type='HardDisk']/{NS}Image"
|
|
225
|
+
)
|
|
226
|
+
return [self.vbox.media[UUID(image.get("uuid").strip("{}"))] for image in images]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dissect.hypervisor
|
|
3
|
-
Version: 3.21.
|
|
3
|
+
Version: 3.21.dev3
|
|
4
4
|
Summary: A Dissect module implementing parsers for various hypervisor disk, backup and configuration files
|
|
5
5
|
Author-email: Dissect Team <dissect@fox-it.com>
|
|
6
6
|
License-Expression: AGPL-3.0-or-later
|
|
@@ -5,7 +5,7 @@ dissect/hypervisor/descriptor/c_hyperv.py,sha256=l_iW7CjK4DGJkg8NzNINFltA-txEPh-
|
|
|
5
5
|
dissect/hypervisor/descriptor/hyperv.py,sha256=B71-TKGpndRfKHSRSaSDA1ak6rQiNlDLs97burrJwkw,17643
|
|
6
6
|
dissect/hypervisor/descriptor/ovf.py,sha256=9TbUgY1F11EnSgUQYrkaJawGJeD-Swr1xHcH39Hpttc,1922
|
|
7
7
|
dissect/hypervisor/descriptor/pvs.py,sha256=Ek8_m5PsyAWSFMD-q_E2j1fRqq5bb8g8ZiahlQSBcvI,701
|
|
8
|
-
dissect/hypervisor/descriptor/vbox.py,sha256=
|
|
8
|
+
dissect/hypervisor/descriptor/vbox.py,sha256=Kzls0YJsn02gFtMNFcAuShClTgji7qg5T7GzBzmoWrs,6650
|
|
9
9
|
dissect/hypervisor/descriptor/vmx.py,sha256=6xhX7RlDqyn9XBkiq2NnQ5LrLL0nXwiPGOPy3WVVAtI,12647
|
|
10
10
|
dissect/hypervisor/disk/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
11
|
dissect/hypervisor/disk/asif.py,sha256=-HIXnoG8uFGTFj58kTRayVUqWtIPPKF04xMpiOYBL18,10840
|
|
@@ -30,10 +30,10 @@ dissect/hypervisor/tools/vmtar.py,sha256=bAf_rEhqUmeI8my22p7L9uV3SgB5C_61YQKfD3t
|
|
|
30
30
|
dissect/hypervisor/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
31
31
|
dissect/hypervisor/util/envelope.py,sha256=EhZjZh3EezqxkJ84OgqpptHIhpVWPcUkFMjdYU-iAnU,10442
|
|
32
32
|
dissect/hypervisor/util/vmtar.py,sha256=pktC1mZIhh1E2uSyb2Z_pvn9H9LebJ6hTmZlYcdGrOA,4038
|
|
33
|
-
dissect_hypervisor-3.21.
|
|
34
|
-
dissect_hypervisor-3.21.
|
|
35
|
-
dissect_hypervisor-3.21.
|
|
36
|
-
dissect_hypervisor-3.21.
|
|
37
|
-
dissect_hypervisor-3.21.
|
|
38
|
-
dissect_hypervisor-3.21.
|
|
39
|
-
dissect_hypervisor-3.21.
|
|
33
|
+
dissect_hypervisor-3.21.dev3.dist-info/licenses/COPYRIGHT,sha256=EOOoIwk_inOMUD4c1ylpzMtYLjGzmc-MLEVAEdLLr20,305
|
|
34
|
+
dissect_hypervisor-3.21.dev3.dist-info/licenses/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
|
|
35
|
+
dissect_hypervisor-3.21.dev3.dist-info/METADATA,sha256=4cSxyF19taf_XVCWW8l7_DavK5BkDtvUIhl2N3bHzLo,3470
|
|
36
|
+
dissect_hypervisor-3.21.dev3.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
37
|
+
dissect_hypervisor-3.21.dev3.dist-info/entry_points.txt,sha256=kW36GnJ3G1dSRYFL4r8Bj32Al_CkGqST3bdHvo3pyiY,120
|
|
38
|
+
dissect_hypervisor-3.21.dev3.dist-info/top_level.txt,sha256=Mn-CQzEYsAbkxrUI0TnplHuXnGVKzxpDw_po_sXpvv4,8
|
|
39
|
+
dissect_hypervisor-3.21.dev3.dist-info/RECORD,,
|
{dissect_hypervisor-3.21.dev2.dist-info → dissect_hypervisor-3.21.dev3.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{dissect_hypervisor-3.21.dev2.dist-info → dissect_hypervisor-3.21.dev3.dist-info}/licenses/COPYRIGHT
RENAMED
|
File without changes
|
{dissect_hypervisor-3.21.dev2.dist-info → dissect_hypervisor-3.21.dev3.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|
{dissect_hypervisor-3.21.dev2.dist-info → dissect_hypervisor-3.21.dev3.dist-info}/top_level.txt
RENAMED
|
File without changes
|