dissect.hypervisor 3.16.dev1__tar.gz → 3.16.dev3__tar.gz
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-3.16.dev1/dissect.hypervisor.egg-info → dissect_hypervisor-3.16.dev3}/PKG-INFO +1 -2
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/dissect/hypervisor/__init__.py +0 -3
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/dissect/hypervisor/disk/vmdk.py +33 -74
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3/dissect.hypervisor.egg-info}/PKG-INFO +1 -2
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/dissect.hypervisor.egg-info/SOURCES.txt +0 -7
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/dissect.hypervisor.egg-info/entry_points.txt +0 -1
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/dissect.hypervisor.egg-info/requires.txt +0 -1
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/pyproject.toml +0 -2
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/conftest.py +0 -5
- dissect_hypervisor-3.16.dev3/tests/test_vmdk.py +20 -0
- dissect_hypervisor-3.16.dev1/dissect/hypervisor/backup/c_vma.py +0 -60
- dissect_hypervisor-3.16.dev1/dissect/hypervisor/backup/vma.py +0 -269
- dissect_hypervisor-3.16.dev1/dissect/hypervisor/backup/xva.py +0 -136
- dissect_hypervisor-3.16.dev1/dissect/hypervisor/tools/vma.py +0 -173
- dissect_hypervisor-3.16.dev1/tests/__init__.py +0 -0
- dissect_hypervisor-3.16.dev1/tests/data/test.vma.gz +0 -0
- dissect_hypervisor-3.16.dev1/tests/test_vma.py +0 -64
- dissect_hypervisor-3.16.dev1/tests/test_vmdk.py +0 -93
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/COPYRIGHT +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/LICENSE +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/MANIFEST.in +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/README.md +0 -0
- {dissect_hypervisor-3.16.dev1/dissect/hypervisor/backup → dissect_hypervisor-3.16.dev3/dissect/hypervisor/descriptor}/__init__.py +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/dissect/hypervisor/descriptor/c_hyperv.py +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/dissect/hypervisor/descriptor/hyperv.py +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/dissect/hypervisor/descriptor/ovf.py +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/dissect/hypervisor/descriptor/pvs.py +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/dissect/hypervisor/descriptor/vbox.py +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/dissect/hypervisor/descriptor/vmx.py +0 -0
- {dissect_hypervisor-3.16.dev1/dissect/hypervisor/descriptor → dissect_hypervisor-3.16.dev3/dissect/hypervisor/disk}/__init__.py +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/dissect/hypervisor/disk/c_hdd.py +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/dissect/hypervisor/disk/c_qcow2.py +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/dissect/hypervisor/disk/c_vdi.py +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/dissect/hypervisor/disk/c_vhd.py +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/dissect/hypervisor/disk/c_vhdx.py +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/dissect/hypervisor/disk/c_vmdk.py +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/dissect/hypervisor/disk/hdd.py +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/dissect/hypervisor/disk/qcow2.py +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/dissect/hypervisor/disk/vdi.py +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/dissect/hypervisor/disk/vhd.py +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/dissect/hypervisor/disk/vhdx.py +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/dissect/hypervisor/exceptions.py +0 -0
- {dissect_hypervisor-3.16.dev1/dissect/hypervisor/disk → dissect_hypervisor-3.16.dev3/dissect/hypervisor/tools}/__init__.py +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/dissect/hypervisor/tools/envelope.py +0 -0
- {dissect_hypervisor-3.16.dev1/dissect/hypervisor/tools → dissect_hypervisor-3.16.dev3/dissect/hypervisor/util}/__init__.py +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/dissect/hypervisor/util/envelope.py +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/dissect/hypervisor/util/vmtar.py +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/dissect.hypervisor.egg-info/dependency_links.txt +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/dissect.hypervisor.egg-info/top_level.txt +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/setup.cfg +0 -0
- {dissect_hypervisor-3.16.dev1/dissect/hypervisor/util → dissect_hypervisor-3.16.dev3/tests}/__init__.py +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/data/differencing.avhdx.gz +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/data/dynamic.vhd.gz +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/data/dynamic.vhdx.gz +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/data/encrypted.vmx +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/data/encryption.info +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/data/expanding.hdd/DiskDescriptor.xml +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/data/expanding.hdd/expanding.hdd +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/data/expanding.hdd/expanding.hdd.0.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/data/fixed.vhd.gz +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/data/fixed.vhdx.gz +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/data/local.tgz.ve +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/data/plain.hdd/DiskDescriptor.xml +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/data/plain.hdd/plain.hdd +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/data/plain.hdd/plain.hdd.0.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/data/sesparse.vmdk.gz +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/data/split.hdd/DiskDescriptor.xml +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/data/split.hdd/split.hdd +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/data/split.hdd/split.hdd.0.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/data/split.hdd/split.hdd.1.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/data/split.hdd/split.hdd.2.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/data/split.hdd/split.hdd.3.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/data/split.hdd/split.hdd.4.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/data/split.hdd/split.hdd.5.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/data/test.VMRS +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/data/test.vgz +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/data/test.vmcx +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/docs/Makefile +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/docs/conf.py +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/docs/index.rst +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/test_envelope.py +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/test_hdd.py +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/test_hyperv.py +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/test_ovf.py +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/test_pvs.py +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/test_vbox.py +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/test_vhd.py +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/test_vhdx.py +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/test_vmtar.py +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/test_vmx.py +0 -0
- {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tox.ini +0 -0
{dissect_hypervisor-3.16.dev1/dissect.hypervisor.egg-info → dissect_hypervisor-3.16.dev3}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: dissect.hypervisor
|
|
3
|
-
Version: 3.16.
|
|
3
|
+
Version: 3.16.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: Affero General Public License v3
|
|
@@ -27,7 +27,6 @@ Requires-Dist: dissect.cstruct<5,>=4
|
|
|
27
27
|
Requires-Dist: dissect.util<4,>=3
|
|
28
28
|
Provides-Extra: full
|
|
29
29
|
Requires-Dist: pycryptodome; extra == "full"
|
|
30
|
-
Requires-Dist: rich; extra == "full"
|
|
31
30
|
Provides-Extra: dev
|
|
32
31
|
Requires-Dist: dissect.hypervisor[full]; extra == "dev"
|
|
33
32
|
Requires-Dist: dissect.cstruct<5.0.dev,>=4.0.dev; extra == "dev"
|
{dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/dissect/hypervisor/__init__.py
RENAMED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
from dissect.hypervisor.backup import vma, xva
|
|
2
1
|
from dissect.hypervisor.descriptor import hyperv, ovf, pvs, vbox, vmx
|
|
3
2
|
from dissect.hypervisor.disk import hdd, qcow2, vdi, vhd, vhdx, vmdk
|
|
4
3
|
from dissect.hypervisor.util import envelope, vmtar
|
|
@@ -14,9 +13,7 @@ __all__ = [
|
|
|
14
13
|
"vdi",
|
|
15
14
|
"vhd",
|
|
16
15
|
"vhdx",
|
|
17
|
-
"vma",
|
|
18
16
|
"vmdk",
|
|
19
17
|
"vmtar",
|
|
20
18
|
"vmx",
|
|
21
|
-
"xva",
|
|
22
19
|
]
|
{dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/dissect/hypervisor/disk/vmdk.py
RENAMED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
1
|
import ctypes
|
|
4
2
|
import io
|
|
5
3
|
import logging
|
|
@@ -7,7 +5,6 @@ import os
|
|
|
7
5
|
import textwrap
|
|
8
6
|
import zlib
|
|
9
7
|
from bisect import bisect_right
|
|
10
|
-
from dataclasses import dataclass
|
|
11
8
|
from functools import lru_cache
|
|
12
9
|
from pathlib import Path
|
|
13
10
|
|
|
@@ -62,13 +59,13 @@ class VMDK(AlignedStream):
|
|
|
62
59
|
if self.descriptor.attr["parentCID"] != "ffffffff":
|
|
63
60
|
self.parent = open_parent(path.parent, self.descriptor.attr["parentFileNameHint"])
|
|
64
61
|
|
|
65
|
-
for
|
|
66
|
-
if
|
|
67
|
-
sdisk_fh = path.with_name(
|
|
62
|
+
for _, size, extent_type, filename in self.descriptor.extents:
|
|
63
|
+
if extent_type in ["SPARSE", "VMFSSPARSE", "SESPARSE"]:
|
|
64
|
+
sdisk_fh = path.with_name(filename).open("rb")
|
|
68
65
|
self.disks.append(SparseDisk(sdisk_fh, parent=self.parent))
|
|
69
|
-
elif
|
|
70
|
-
rdisk_fh = path.with_name(
|
|
71
|
-
self.disks.append(RawDisk(rdisk_fh,
|
|
66
|
+
elif extent_type in ["VMFS", "FLAT"]:
|
|
67
|
+
rdisk_fh = path.with_name(filename).open("rb")
|
|
68
|
+
self.disks.append(RawDisk(rdisk_fh, size * SECTOR_SIZE))
|
|
72
69
|
|
|
73
70
|
elif magic in (COWD_MAGIC, VMDK_MAGIC, SESPARSE_MAGIC):
|
|
74
71
|
sparse_disk = SparseDisk(fh)
|
|
@@ -401,37 +398,8 @@ class SparseExtentHeader:
|
|
|
401
398
|
return getattr(self.hdr, attr)
|
|
402
399
|
|
|
403
400
|
|
|
404
|
-
@dataclass
|
|
405
|
-
class ExtentDescriptor:
|
|
406
|
-
access_mode: str
|
|
407
|
-
sectors: int
|
|
408
|
-
type: str
|
|
409
|
-
filename: str | None = None
|
|
410
|
-
start_sector: int | None = None
|
|
411
|
-
partition_uuid: str | None = None
|
|
412
|
-
device_identifier: str | None = None
|
|
413
|
-
|
|
414
|
-
def __post_init__(self) -> None:
|
|
415
|
-
self._raw = " ".join(map(str, [v for v in self.__dict__.values() if v is not None]))
|
|
416
|
-
self.sectors = int(self.sectors)
|
|
417
|
-
|
|
418
|
-
if self.filename:
|
|
419
|
-
self.filename = self.filename.strip('"')
|
|
420
|
-
|
|
421
|
-
if self.start_sector:
|
|
422
|
-
self.start_sector = int(self.start_sector)
|
|
423
|
-
|
|
424
|
-
def __repr__(self) -> str:
|
|
425
|
-
return f"<ExtentDescriptor {self._raw}>"
|
|
426
|
-
|
|
427
|
-
def __str__(self) -> str:
|
|
428
|
-
return self._raw
|
|
429
|
-
|
|
430
|
-
|
|
431
401
|
class DiskDescriptor:
|
|
432
|
-
def __init__(
|
|
433
|
-
self, attr: dict, extents: list[ExtentDescriptor], disk_db: dict, sectors: int, raw_config: str | None = None
|
|
434
|
-
):
|
|
402
|
+
def __init__(self, attr, extents, disk_db, sectors, raw_config=None):
|
|
435
403
|
self.attr = attr
|
|
436
404
|
self.extents = extents
|
|
437
405
|
self.ddb = disk_db
|
|
@@ -439,15 +407,9 @@ class DiskDescriptor:
|
|
|
439
407
|
self.raw = raw_config
|
|
440
408
|
|
|
441
409
|
@classmethod
|
|
442
|
-
def parse(cls, vmdk_config
|
|
443
|
-
"""Return :class:`DiskDescriptor` based on the provided ``vmdk_config``.
|
|
444
|
-
|
|
445
|
-
Resources:
|
|
446
|
-
- https://github.com/libyal/libvmdk/blob/main/documentation/VMWare%20Virtual%20Disk%20Format%20(VMDK).asciidoc
|
|
447
|
-
""" # noqa: E501
|
|
448
|
-
|
|
410
|
+
def parse(cls, vmdk_config):
|
|
449
411
|
descriptor_settings = {}
|
|
450
|
-
extents
|
|
412
|
+
extents = []
|
|
451
413
|
disk_db = {}
|
|
452
414
|
sectors = 0
|
|
453
415
|
|
|
@@ -458,16 +420,11 @@ class DiskDescriptor:
|
|
|
458
420
|
continue
|
|
459
421
|
|
|
460
422
|
if line.startswith("RW ") or line.startswith("RDONLY ") or line.startswith("NOACCESS "):
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
continue
|
|
467
|
-
|
|
468
|
-
extent = ExtentDescriptor(*parts)
|
|
469
|
-
sectors += extent.sectors
|
|
470
|
-
extents.append(extent)
|
|
423
|
+
access_type, size, extent_type, filename = line.split(" ", 3)
|
|
424
|
+
filename = filename.strip('"')
|
|
425
|
+
size = int(size)
|
|
426
|
+
sectors += size
|
|
427
|
+
extents.append([access_type, size, extent_type, filename])
|
|
471
428
|
continue
|
|
472
429
|
|
|
473
430
|
setting, _, value = line.partition("=")
|
|
@@ -481,33 +438,35 @@ class DiskDescriptor:
|
|
|
481
438
|
|
|
482
439
|
return cls(descriptor_settings, extents, disk_db, sectors, vmdk_config)
|
|
483
440
|
|
|
484
|
-
def __str__(self)
|
|
485
|
-
str_template =
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
{}
|
|
490
|
-
|
|
491
|
-
# Extent Description
|
|
492
|
-
{}
|
|
441
|
+
def __str__(self):
|
|
442
|
+
str_template = """\
|
|
443
|
+
# Disk DescriptorFile
|
|
444
|
+
version=1
|
|
445
|
+
{}
|
|
493
446
|
|
|
494
|
-
|
|
495
|
-
|
|
447
|
+
# Extent Description
|
|
448
|
+
{}
|
|
496
449
|
|
|
497
|
-
|
|
498
|
-
|
|
450
|
+
# The Disk Data Base
|
|
451
|
+
#DDB
|
|
499
452
|
|
|
453
|
+
{}"""
|
|
454
|
+
str_template = textwrap.dedent(str_template)
|
|
500
455
|
descriptor_settings = []
|
|
501
456
|
for setting, value in self.attr.items():
|
|
502
|
-
if setting
|
|
503
|
-
|
|
457
|
+
if setting == "version":
|
|
458
|
+
continue
|
|
459
|
+
descriptor_settings.append("{}={}".format(setting, value))
|
|
504
460
|
descriptor_settings = "\n".join(descriptor_settings)
|
|
505
461
|
|
|
506
|
-
extents =
|
|
462
|
+
extents = []
|
|
463
|
+
for access_type, size, extent_type, filename in self.extents:
|
|
464
|
+
extents.append('{} {} {} "{}"'.format(access_type, size, extent_type, filename))
|
|
465
|
+
extents = "\n".join(extents)
|
|
507
466
|
|
|
508
467
|
disk_db = []
|
|
509
468
|
for setting, value in self.ddb.items():
|
|
510
|
-
disk_db.append(
|
|
469
|
+
disk_db.append('{} = "{}"'.format(setting, value))
|
|
511
470
|
disk_db = "\n".join(disk_db)
|
|
512
471
|
|
|
513
472
|
return str_template.format(descriptor_settings, extents, disk_db)
|
{dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3/dissect.hypervisor.egg-info}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: dissect.hypervisor
|
|
3
|
-
Version: 3.16.
|
|
3
|
+
Version: 3.16.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: Affero General Public License v3
|
|
@@ -27,7 +27,6 @@ Requires-Dist: dissect.cstruct<5,>=4
|
|
|
27
27
|
Requires-Dist: dissect.util<4,>=3
|
|
28
28
|
Provides-Extra: full
|
|
29
29
|
Requires-Dist: pycryptodome; extra == "full"
|
|
30
|
-
Requires-Dist: rich; extra == "full"
|
|
31
30
|
Provides-Extra: dev
|
|
32
31
|
Requires-Dist: dissect.hypervisor[full]; extra == "dev"
|
|
33
32
|
Requires-Dist: dissect.cstruct<5.0.dev,>=4.0.dev; extra == "dev"
|
|
@@ -12,10 +12,6 @@ dissect.hypervisor.egg-info/requires.txt
|
|
|
12
12
|
dissect.hypervisor.egg-info/top_level.txt
|
|
13
13
|
dissect/hypervisor/__init__.py
|
|
14
14
|
dissect/hypervisor/exceptions.py
|
|
15
|
-
dissect/hypervisor/backup/__init__.py
|
|
16
|
-
dissect/hypervisor/backup/c_vma.py
|
|
17
|
-
dissect/hypervisor/backup/vma.py
|
|
18
|
-
dissect/hypervisor/backup/xva.py
|
|
19
15
|
dissect/hypervisor/descriptor/__init__.py
|
|
20
16
|
dissect/hypervisor/descriptor/c_hyperv.py
|
|
21
17
|
dissect/hypervisor/descriptor/hyperv.py
|
|
@@ -38,7 +34,6 @@ dissect/hypervisor/disk/vhdx.py
|
|
|
38
34
|
dissect/hypervisor/disk/vmdk.py
|
|
39
35
|
dissect/hypervisor/tools/__init__.py
|
|
40
36
|
dissect/hypervisor/tools/envelope.py
|
|
41
|
-
dissect/hypervisor/tools/vma.py
|
|
42
37
|
dissect/hypervisor/util/__init__.py
|
|
43
38
|
dissect/hypervisor/util/envelope.py
|
|
44
39
|
dissect/hypervisor/util/vmtar.py
|
|
@@ -52,7 +47,6 @@ tests/test_pvs.py
|
|
|
52
47
|
tests/test_vbox.py
|
|
53
48
|
tests/test_vhd.py
|
|
54
49
|
tests/test_vhdx.py
|
|
55
|
-
tests/test_vma.py
|
|
56
50
|
tests/test_vmdk.py
|
|
57
51
|
tests/test_vmtar.py
|
|
58
52
|
tests/test_vmx.py
|
|
@@ -67,7 +61,6 @@ tests/data/local.tgz.ve
|
|
|
67
61
|
tests/data/sesparse.vmdk.gz
|
|
68
62
|
tests/data/test.VMRS
|
|
69
63
|
tests/data/test.vgz
|
|
70
|
-
tests/data/test.vma.gz
|
|
71
64
|
tests/data/test.vmcx
|
|
72
65
|
tests/data/expanding.hdd/DiskDescriptor.xml
|
|
73
66
|
tests/data/expanding.hdd/expanding.hdd
|
|
@@ -39,7 +39,6 @@ repository = "https://github.com/fox-it/dissect.hypervisor"
|
|
|
39
39
|
[project.optional-dependencies]
|
|
40
40
|
full = [
|
|
41
41
|
"pycryptodome",
|
|
42
|
-
"rich",
|
|
43
42
|
]
|
|
44
43
|
dev = [
|
|
45
44
|
"dissect.hypervisor[full]",
|
|
@@ -48,7 +47,6 @@ dev = [
|
|
|
48
47
|
]
|
|
49
48
|
|
|
50
49
|
[project.scripts]
|
|
51
|
-
vma-extract = "dissect.hypervisor.tools.vma:main"
|
|
52
50
|
envelope-decrypt = "dissect.hypervisor.tools.envelope:main"
|
|
53
51
|
|
|
54
52
|
[tool.black]
|
|
@@ -79,11 +79,6 @@ def split_hdd() -> Iterator[str]:
|
|
|
79
79
|
yield absolute_path("data/split.hdd")
|
|
80
80
|
|
|
81
81
|
|
|
82
|
-
@pytest.fixture
|
|
83
|
-
def simple_vma() -> Iterator[BinaryIO]:
|
|
84
|
-
yield from open_file_gz("data/test.vma.gz")
|
|
85
|
-
|
|
86
|
-
|
|
87
82
|
@pytest.fixture
|
|
88
83
|
def envelope() -> Iterator[BinaryIO]:
|
|
89
84
|
yield from open_file("data/local.tgz.ve")
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from dissect.hypervisor.disk.c_vmdk import c_vmdk
|
|
2
|
+
from dissect.hypervisor.disk.vmdk import VMDK
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def test_vmdk_sesparse(sesparse_vmdk):
|
|
6
|
+
vmdk = VMDK(sesparse_vmdk)
|
|
7
|
+
|
|
8
|
+
disk = vmdk.disks[0]
|
|
9
|
+
|
|
10
|
+
assert disk.is_sesparse
|
|
11
|
+
assert disk._grain_directory_size == 0x20000
|
|
12
|
+
assert disk._grain_table_size == 0x1000
|
|
13
|
+
assert disk._grain_entry_type == c_vmdk.uint64
|
|
14
|
+
assert disk._grain_directory[0] == 0x1000000000000000
|
|
15
|
+
|
|
16
|
+
header = disk.header
|
|
17
|
+
assert header.magic == c_vmdk.SESPARSE_CONST_HEADER_MAGIC
|
|
18
|
+
assert header.version == 0x200000001
|
|
19
|
+
|
|
20
|
+
assert vmdk.read(0x1000000) == b"a" * 0x1000000
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
from dissect.cstruct import cstruct
|
|
2
|
-
|
|
3
|
-
vma_def = """
|
|
4
|
-
#define VMA_BLOCK_BITS 12
|
|
5
|
-
#define VMA_BLOCK_SIZE (1 << VMA_BLOCK_BITS)
|
|
6
|
-
#define VMA_CLUSTER_BITS (VMA_BLOCK_BITS + 4)
|
|
7
|
-
#define VMA_CLUSTER_SIZE (1 << VMA_CLUSTER_BITS)
|
|
8
|
-
|
|
9
|
-
#define VMA_EXTENT_HEADER_SIZE 512
|
|
10
|
-
#define VMA_BLOCKS_PER_EXTENT 59
|
|
11
|
-
#define VMA_MAX_CONFIGS 256
|
|
12
|
-
|
|
13
|
-
#define VMA_MAX_EXTENT_SIZE (VMA_EXTENT_HEADER_SIZE + VMA_CLUSTER_SIZE * VMA_BLOCKS_PER_EXTENT)
|
|
14
|
-
|
|
15
|
-
/* File Format Definitions */
|
|
16
|
-
|
|
17
|
-
struct VmaDeviceInfoHeader {
|
|
18
|
-
uint32 devname_ptr; /* offset into blob_buffer table */
|
|
19
|
-
uint32 reserved0;
|
|
20
|
-
uint64 size; /* device size in bytes */
|
|
21
|
-
uint64 reserved1;
|
|
22
|
-
uint64 reserved2;
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
struct VmaHeader {
|
|
26
|
-
char magic[4];
|
|
27
|
-
uint32 version;
|
|
28
|
-
char uuid[16];
|
|
29
|
-
int64 ctime;
|
|
30
|
-
char md5sum[16];
|
|
31
|
-
|
|
32
|
-
uint32 blob_buffer_offset;
|
|
33
|
-
uint32 blob_buffer_size;
|
|
34
|
-
uint32 header_size;
|
|
35
|
-
|
|
36
|
-
char _reserved1[1984];
|
|
37
|
-
|
|
38
|
-
uint32 config_names[VMA_MAX_CONFIGS]; /* offset into blob_buffer table */
|
|
39
|
-
uint32 config_data[VMA_MAX_CONFIGS]; /* offset into blob_buffer table */
|
|
40
|
-
|
|
41
|
-
char _reserved2[4];
|
|
42
|
-
|
|
43
|
-
VmaDeviceInfoHeader dev_info[256];
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
struct VmaExtentHeader {
|
|
47
|
-
char magic[4];
|
|
48
|
-
uint16 reserved1;
|
|
49
|
-
uint16 block_count;
|
|
50
|
-
char uuid[16];
|
|
51
|
-
char md5sum[16];
|
|
52
|
-
uint64 blockinfo[VMA_BLOCKS_PER_EXTENT];
|
|
53
|
-
};
|
|
54
|
-
"""
|
|
55
|
-
|
|
56
|
-
c_vma = cstruct(endian=">").load(vma_def)
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
VMA_MAGIC = b"VMA\x00"
|
|
60
|
-
VMA_EXTENT_MAGIC = b"VMAE"
|
|
@@ -1,269 +0,0 @@
|
|
|
1
|
-
# References:
|
|
2
|
-
# - https://git.proxmox.com/?p=pve-qemu.git;a=blob;f=vma_spec.txt
|
|
3
|
-
# - https://lists.gnu.org/archive/html/qemu-devel/2013-02/msg03667.html
|
|
4
|
-
|
|
5
|
-
import hashlib
|
|
6
|
-
import struct
|
|
7
|
-
from collections import defaultdict
|
|
8
|
-
from functools import lru_cache
|
|
9
|
-
from uuid import UUID
|
|
10
|
-
|
|
11
|
-
from dissect.util import ts
|
|
12
|
-
from dissect.util.stream import AlignedStream
|
|
13
|
-
|
|
14
|
-
from dissect.hypervisor.backup.c_vma import VMA_EXTENT_MAGIC, VMA_MAGIC, c_vma
|
|
15
|
-
from dissect.hypervisor.exceptions import InvalidHeaderError
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class VMA:
|
|
19
|
-
"""Proxmox VMA.
|
|
20
|
-
|
|
21
|
-
Parse and provide a readable object for devices in a Proxmox VMA backup file.
|
|
22
|
-
VMA is designed to be streamed for extraction, so we need to do some funny stuff to create a readable
|
|
23
|
-
object from it. Performance is not optimal, so it's generally advised to extract a VMA instead.
|
|
24
|
-
The vma-extract utility can be used for that.
|
|
25
|
-
"""
|
|
26
|
-
|
|
27
|
-
def __init__(self, fh):
|
|
28
|
-
self.fh = fh
|
|
29
|
-
|
|
30
|
-
offset = fh.tell()
|
|
31
|
-
self.header = c_vma.VmaHeader(fh)
|
|
32
|
-
if self.header.magic != VMA_MAGIC:
|
|
33
|
-
raise InvalidHeaderError("Invalid VMA header magic")
|
|
34
|
-
|
|
35
|
-
fh.seek(offset)
|
|
36
|
-
header_data = bytearray(fh.read(self.header.header_size))
|
|
37
|
-
header_data[32:48] = b"\x00" * 16
|
|
38
|
-
if hashlib.md5(header_data).digest() != self.header.md5sum:
|
|
39
|
-
raise InvalidHeaderError("Invalid VMA checksum")
|
|
40
|
-
|
|
41
|
-
self.version = self.header.version
|
|
42
|
-
self.uuid = UUID(bytes=self.header.uuid)
|
|
43
|
-
|
|
44
|
-
blob_start = self.header.blob_buffer_offset
|
|
45
|
-
blob_end = self.header.blob_buffer_offset + self.header.blob_buffer_size
|
|
46
|
-
self._blob = memoryview(bytes(header_data))[blob_start:blob_end]
|
|
47
|
-
|
|
48
|
-
blob_offset = 1
|
|
49
|
-
self._blob_data = {}
|
|
50
|
-
while blob_offset + 2 <= self.header.blob_buffer_size:
|
|
51
|
-
# The header is in big endian, but this is little...
|
|
52
|
-
size = struct.unpack("<H", self._blob[blob_offset : blob_offset + 2])[0]
|
|
53
|
-
if blob_offset + 2 + size <= blob_end:
|
|
54
|
-
self._blob_data[blob_offset] = self._blob[blob_offset + 2 : blob_offset + 2 + size].tobytes()
|
|
55
|
-
blob_offset += size + 2
|
|
56
|
-
|
|
57
|
-
self._config = {}
|
|
58
|
-
for conf_name, conf_data in zip(self.header.config_names, self.header.config_data):
|
|
59
|
-
if (conf_name, conf_data) == (0, 0):
|
|
60
|
-
continue
|
|
61
|
-
|
|
62
|
-
self._config[self.blob_string(conf_name)] = self.blob_data(conf_data)
|
|
63
|
-
|
|
64
|
-
self._devices = {}
|
|
65
|
-
for dev_id, dev_info in enumerate(self.header.dev_info):
|
|
66
|
-
if dev_id == 0 or dev_info.devname_ptr == 0:
|
|
67
|
-
continue
|
|
68
|
-
|
|
69
|
-
self._devices[dev_id] = Device(self, dev_id, self.blob_string(dev_info.devname_ptr), dev_info.size)
|
|
70
|
-
|
|
71
|
-
self._extent = lru_cache(65536)(self._extent)
|
|
72
|
-
|
|
73
|
-
@property
|
|
74
|
-
def creation_time(self):
|
|
75
|
-
return ts.from_unix(self.header.ctime)
|
|
76
|
-
|
|
77
|
-
def blob_data(self, offset):
|
|
78
|
-
if offset not in self._blob_data:
|
|
79
|
-
raise KeyError(f"No blob data for offset {offset}")
|
|
80
|
-
return self._blob_data[offset]
|
|
81
|
-
|
|
82
|
-
def blob_string(self, offset):
|
|
83
|
-
return self.blob_data(offset).decode().rstrip("\x00")
|
|
84
|
-
|
|
85
|
-
def config(self, name):
|
|
86
|
-
return self._config[name]
|
|
87
|
-
|
|
88
|
-
def configs(self):
|
|
89
|
-
return self._config
|
|
90
|
-
|
|
91
|
-
def device(self, dev_id):
|
|
92
|
-
return self._devices[dev_id]
|
|
93
|
-
|
|
94
|
-
def devices(self):
|
|
95
|
-
return list(self._devices.values())
|
|
96
|
-
|
|
97
|
-
def _extent(self, offset):
|
|
98
|
-
return Extent(self.fh, offset)
|
|
99
|
-
|
|
100
|
-
def extents(self):
|
|
101
|
-
offset = self.header.header_size
|
|
102
|
-
while True:
|
|
103
|
-
try:
|
|
104
|
-
extent = self._extent(offset)
|
|
105
|
-
except EOFError:
|
|
106
|
-
break
|
|
107
|
-
|
|
108
|
-
yield extent
|
|
109
|
-
|
|
110
|
-
offset += c_vma.VMA_EXTENT_HEADER_SIZE + extent.size
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
class Device:
|
|
114
|
-
def __init__(self, vma, dev_id, name, size):
|
|
115
|
-
self.vma = vma
|
|
116
|
-
self.id = dev_id
|
|
117
|
-
self.name = name
|
|
118
|
-
self.size = size
|
|
119
|
-
|
|
120
|
-
def __repr__(self):
|
|
121
|
-
return f"<Device id={self.id} name={self.name} size={self.size}>"
|
|
122
|
-
|
|
123
|
-
def open(self):
|
|
124
|
-
return DeviceDataStream(self)
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
class Extent:
|
|
128
|
-
def __init__(self, fh, offset):
|
|
129
|
-
self.fh = fh
|
|
130
|
-
self.offset = offset
|
|
131
|
-
self.data_offset = offset + c_vma.VMA_EXTENT_HEADER_SIZE
|
|
132
|
-
|
|
133
|
-
self.fh.seek(offset)
|
|
134
|
-
header_data = bytearray(fh.read(c_vma.VMA_EXTENT_HEADER_SIZE))
|
|
135
|
-
self.header = c_vma.VmaExtentHeader(header_data)
|
|
136
|
-
if self.header.magic != VMA_EXTENT_MAGIC:
|
|
137
|
-
raise InvalidHeaderError("Invalid VMA extent header magic")
|
|
138
|
-
|
|
139
|
-
header_data[24:40] = b"\x00" * 16
|
|
140
|
-
if hashlib.md5(header_data).digest() != self.header.md5sum:
|
|
141
|
-
raise InvalidHeaderError("Invalid VMA extent checksum")
|
|
142
|
-
|
|
143
|
-
self.uuid = UUID(bytes=self.header.uuid)
|
|
144
|
-
self.size = self.header.block_count * c_vma.VMA_BLOCK_SIZE
|
|
145
|
-
|
|
146
|
-
# Keep track of the lowest and highest cluster we have for any device
|
|
147
|
-
# We can use this to speed up extent lookup later on
|
|
148
|
-
# There are at most 59 entries, so safe to parse ahead of use
|
|
149
|
-
self._min = {}
|
|
150
|
-
self._max = {}
|
|
151
|
-
self.blocks = defaultdict(list)
|
|
152
|
-
block_offset = self.data_offset
|
|
153
|
-
for block_info in self.header.blockinfo:
|
|
154
|
-
cluster_num = block_info & 0xFFFFFFFF
|
|
155
|
-
dev_id = (block_info >> 32) & 0xFF
|
|
156
|
-
mask = block_info >> (32 + 16)
|
|
157
|
-
|
|
158
|
-
if dev_id == 0:
|
|
159
|
-
continue
|
|
160
|
-
|
|
161
|
-
if dev_id not in self._min:
|
|
162
|
-
self._min[dev_id] = cluster_num
|
|
163
|
-
self._max[dev_id] = cluster_num
|
|
164
|
-
elif cluster_num < self._min[dev_id]:
|
|
165
|
-
self._min[dev_id] = cluster_num
|
|
166
|
-
elif cluster_num > self._max[dev_id]:
|
|
167
|
-
self._max[dev_id] = cluster_num
|
|
168
|
-
|
|
169
|
-
self.blocks[dev_id].append((cluster_num, mask, block_offset))
|
|
170
|
-
|
|
171
|
-
if mask == 0xFFFF:
|
|
172
|
-
block_offset += 16 * c_vma.VMA_BLOCK_SIZE
|
|
173
|
-
elif mask == 0:
|
|
174
|
-
pass
|
|
175
|
-
else:
|
|
176
|
-
block_offset += bin(mask).count("1") * c_vma.VMA_BLOCK_SIZE
|
|
177
|
-
|
|
178
|
-
def __repr__(self):
|
|
179
|
-
return f"<Extent offset=0x{self.offset:x} size=0x{self.size:x}>"
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
class DeviceDataStream(AlignedStream):
|
|
183
|
-
def __init__(self, device):
|
|
184
|
-
self.device = device
|
|
185
|
-
self.vma = device.vma
|
|
186
|
-
super().__init__(size=device.size, align=c_vma.VMA_CLUSTER_SIZE)
|
|
187
|
-
|
|
188
|
-
def _read(self, offset, length):
|
|
189
|
-
cluster_offset = offset // c_vma.VMA_CLUSTER_SIZE
|
|
190
|
-
cluster_count = (length + c_vma.VMA_CLUSTER_SIZE - 1) // c_vma.VMA_CLUSTER_SIZE
|
|
191
|
-
block_count = (length + c_vma.VMA_BLOCK_SIZE - 1) // c_vma.VMA_BLOCK_SIZE
|
|
192
|
-
|
|
193
|
-
result = []
|
|
194
|
-
for _, mask, block_offset in _iter_clusters(self.vma, self.device.id, cluster_offset, cluster_count):
|
|
195
|
-
read_count = min(block_count, 16)
|
|
196
|
-
|
|
197
|
-
# Optimize reading fully set and fully sparse masks
|
|
198
|
-
if mask == 0xFFFF:
|
|
199
|
-
self.vma.fh.seek(block_offset)
|
|
200
|
-
result.append(self.vma.fh.read(c_vma.VMA_BLOCK_SIZE * read_count))
|
|
201
|
-
elif mask == 0:
|
|
202
|
-
result.append(b"\x00" * read_count * c_vma.VMA_BLOCK_SIZE)
|
|
203
|
-
else:
|
|
204
|
-
self.vma.fh.seek(block_offset)
|
|
205
|
-
for allocated, count in _iter_mask(mask, read_count):
|
|
206
|
-
if allocated:
|
|
207
|
-
result.append(self.vma.fh.read(c_vma.VMA_BLOCK_SIZE * count))
|
|
208
|
-
else:
|
|
209
|
-
result.append(b"\x00" * count * c_vma.VMA_BLOCK_SIZE)
|
|
210
|
-
|
|
211
|
-
block_count -= read_count
|
|
212
|
-
if block_count == 0:
|
|
213
|
-
break
|
|
214
|
-
|
|
215
|
-
return b"".join(result)
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
def _iter_clusters(vma, dev_id, cluster, count):
|
|
219
|
-
# Find clusters and starting offsets in all extents
|
|
220
|
-
temp = {}
|
|
221
|
-
end = cluster + count
|
|
222
|
-
|
|
223
|
-
for extent in vma.extents():
|
|
224
|
-
if dev_id not in extent.blocks:
|
|
225
|
-
continue
|
|
226
|
-
|
|
227
|
-
if end < extent._min[dev_id] or cluster > extent._max[dev_id]:
|
|
228
|
-
continue
|
|
229
|
-
|
|
230
|
-
for cluster_num, mask, block_offset in extent.blocks[dev_id]:
|
|
231
|
-
if cluster_num == cluster:
|
|
232
|
-
yield cluster_num, mask, block_offset
|
|
233
|
-
cluster += 1
|
|
234
|
-
|
|
235
|
-
while cluster in temp:
|
|
236
|
-
yield temp[cluster]
|
|
237
|
-
del temp[cluster]
|
|
238
|
-
cluster += 1
|
|
239
|
-
elif cluster < cluster_num <= end:
|
|
240
|
-
temp[cluster_num] = (cluster_num, mask, block_offset)
|
|
241
|
-
|
|
242
|
-
if cluster == end:
|
|
243
|
-
break
|
|
244
|
-
|
|
245
|
-
if cluster == end:
|
|
246
|
-
break
|
|
247
|
-
|
|
248
|
-
while cluster in temp:
|
|
249
|
-
yield temp[cluster]
|
|
250
|
-
del temp[cluster]
|
|
251
|
-
cluster += 1
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
def _iter_mask(mask, length):
|
|
255
|
-
# Yield consecutive bitmask values
|
|
256
|
-
current_status = mask & 1
|
|
257
|
-
current_count = 0
|
|
258
|
-
|
|
259
|
-
for bit_idx in range(length):
|
|
260
|
-
status = (mask & (1 << bit_idx)) >> bit_idx
|
|
261
|
-
if status == current_status:
|
|
262
|
-
current_count += 1
|
|
263
|
-
else:
|
|
264
|
-
yield current_status, current_count
|
|
265
|
-
current_status = status
|
|
266
|
-
current_count = 1
|
|
267
|
-
|
|
268
|
-
if current_count:
|
|
269
|
-
yield current_status, current_count
|