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.
Files changed (91) hide show
  1. {dissect_hypervisor-3.16.dev1/dissect.hypervisor.egg-info → dissect_hypervisor-3.16.dev3}/PKG-INFO +1 -2
  2. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/dissect/hypervisor/__init__.py +0 -3
  3. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/dissect/hypervisor/disk/vmdk.py +33 -74
  4. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3/dissect.hypervisor.egg-info}/PKG-INFO +1 -2
  5. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/dissect.hypervisor.egg-info/SOURCES.txt +0 -7
  6. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/dissect.hypervisor.egg-info/entry_points.txt +0 -1
  7. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/dissect.hypervisor.egg-info/requires.txt +0 -1
  8. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/pyproject.toml +0 -2
  9. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/conftest.py +0 -5
  10. dissect_hypervisor-3.16.dev3/tests/test_vmdk.py +20 -0
  11. dissect_hypervisor-3.16.dev1/dissect/hypervisor/backup/c_vma.py +0 -60
  12. dissect_hypervisor-3.16.dev1/dissect/hypervisor/backup/vma.py +0 -269
  13. dissect_hypervisor-3.16.dev1/dissect/hypervisor/backup/xva.py +0 -136
  14. dissect_hypervisor-3.16.dev1/dissect/hypervisor/tools/vma.py +0 -173
  15. dissect_hypervisor-3.16.dev1/tests/__init__.py +0 -0
  16. dissect_hypervisor-3.16.dev1/tests/data/test.vma.gz +0 -0
  17. dissect_hypervisor-3.16.dev1/tests/test_vma.py +0 -64
  18. dissect_hypervisor-3.16.dev1/tests/test_vmdk.py +0 -93
  19. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/COPYRIGHT +0 -0
  20. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/LICENSE +0 -0
  21. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/MANIFEST.in +0 -0
  22. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/README.md +0 -0
  23. {dissect_hypervisor-3.16.dev1/dissect/hypervisor/backup → dissect_hypervisor-3.16.dev3/dissect/hypervisor/descriptor}/__init__.py +0 -0
  24. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/dissect/hypervisor/descriptor/c_hyperv.py +0 -0
  25. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/dissect/hypervisor/descriptor/hyperv.py +0 -0
  26. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/dissect/hypervisor/descriptor/ovf.py +0 -0
  27. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/dissect/hypervisor/descriptor/pvs.py +0 -0
  28. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/dissect/hypervisor/descriptor/vbox.py +0 -0
  29. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/dissect/hypervisor/descriptor/vmx.py +0 -0
  30. {dissect_hypervisor-3.16.dev1/dissect/hypervisor/descriptor → dissect_hypervisor-3.16.dev3/dissect/hypervisor/disk}/__init__.py +0 -0
  31. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/dissect/hypervisor/disk/c_hdd.py +0 -0
  32. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/dissect/hypervisor/disk/c_qcow2.py +0 -0
  33. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/dissect/hypervisor/disk/c_vdi.py +0 -0
  34. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/dissect/hypervisor/disk/c_vhd.py +0 -0
  35. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/dissect/hypervisor/disk/c_vhdx.py +0 -0
  36. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/dissect/hypervisor/disk/c_vmdk.py +0 -0
  37. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/dissect/hypervisor/disk/hdd.py +0 -0
  38. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/dissect/hypervisor/disk/qcow2.py +0 -0
  39. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/dissect/hypervisor/disk/vdi.py +0 -0
  40. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/dissect/hypervisor/disk/vhd.py +0 -0
  41. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/dissect/hypervisor/disk/vhdx.py +0 -0
  42. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/dissect/hypervisor/exceptions.py +0 -0
  43. {dissect_hypervisor-3.16.dev1/dissect/hypervisor/disk → dissect_hypervisor-3.16.dev3/dissect/hypervisor/tools}/__init__.py +0 -0
  44. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/dissect/hypervisor/tools/envelope.py +0 -0
  45. {dissect_hypervisor-3.16.dev1/dissect/hypervisor/tools → dissect_hypervisor-3.16.dev3/dissect/hypervisor/util}/__init__.py +0 -0
  46. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/dissect/hypervisor/util/envelope.py +0 -0
  47. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/dissect/hypervisor/util/vmtar.py +0 -0
  48. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/dissect.hypervisor.egg-info/dependency_links.txt +0 -0
  49. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/dissect.hypervisor.egg-info/top_level.txt +0 -0
  50. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/setup.cfg +0 -0
  51. {dissect_hypervisor-3.16.dev1/dissect/hypervisor/util → dissect_hypervisor-3.16.dev3/tests}/__init__.py +0 -0
  52. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/data/differencing.avhdx.gz +0 -0
  53. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/data/dynamic.vhd.gz +0 -0
  54. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/data/dynamic.vhdx.gz +0 -0
  55. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/data/encrypted.vmx +0 -0
  56. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/data/encryption.info +0 -0
  57. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/data/expanding.hdd/DiskDescriptor.xml +0 -0
  58. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/data/expanding.hdd/expanding.hdd +0 -0
  59. {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
  60. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/data/fixed.vhd.gz +0 -0
  61. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/data/fixed.vhdx.gz +0 -0
  62. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/data/local.tgz.ve +0 -0
  63. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/data/plain.hdd/DiskDescriptor.xml +0 -0
  64. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/data/plain.hdd/plain.hdd +0 -0
  65. {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
  66. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/data/sesparse.vmdk.gz +0 -0
  67. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/data/split.hdd/DiskDescriptor.xml +0 -0
  68. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/data/split.hdd/split.hdd +0 -0
  69. {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
  70. {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
  71. {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
  72. {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
  73. {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
  74. {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
  75. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/data/test.VMRS +0 -0
  76. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/data/test.vgz +0 -0
  77. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/data/test.vmcx +0 -0
  78. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/docs/Makefile +0 -0
  79. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/docs/conf.py +0 -0
  80. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/docs/index.rst +0 -0
  81. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/test_envelope.py +0 -0
  82. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/test_hdd.py +0 -0
  83. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/test_hyperv.py +0 -0
  84. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/test_ovf.py +0 -0
  85. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/test_pvs.py +0 -0
  86. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/test_vbox.py +0 -0
  87. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/test_vhd.py +0 -0
  88. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/test_vhdx.py +0 -0
  89. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/test_vmtar.py +0 -0
  90. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tests/test_vmx.py +0 -0
  91. {dissect_hypervisor-3.16.dev1 → dissect_hypervisor-3.16.dev3}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dissect.hypervisor
3
- Version: 3.16.dev1
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"
@@ -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
  ]
@@ -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 extent in self.descriptor.extents:
66
- if extent.type in ["SPARSE", "VMFSSPARSE", "SESPARSE"]:
67
- sdisk_fh = path.with_name(extent.filename).open("rb")
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 extent.type in ["VMFS", "FLAT"]:
70
- rdisk_fh = path.with_name(extent.filename).open("rb")
71
- self.disks.append(RawDisk(rdisk_fh, extent.sectors * SECTOR_SIZE))
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: str) -> DiskDescriptor:
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: list[ExtentDescriptor] = []
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
- # Extent descriptors can have up to seven values according to libvmdk documentation.
462
- parts = line.split(" ", maxsplit=6)
463
-
464
- if len(parts) < 3:
465
- log.warning("Unexpected ExtentDescriptor format in vmdk config: %s, ignoring", line)
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) -> str:
485
- str_template = textwrap.dedent(
486
- """\
487
- # Disk DescriptorFile
488
- version=1
489
- {}
490
-
491
- # Extent Description
492
- {}
441
+ def __str__(self):
442
+ str_template = """\
443
+ # Disk DescriptorFile
444
+ version=1
445
+ {}
493
446
 
494
- # The Disk Data Base
495
- #DDB
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 != "version":
503
- descriptor_settings.append(f"{setting}={value}")
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 = "\n".join(map(str, self.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(f'{setting} = "{value}"')
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)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dissect.hypervisor
3
- Version: 3.16.dev1
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
@@ -1,3 +1,2 @@
1
1
  [console_scripts]
2
2
  envelope-decrypt = dissect.hypervisor.tools.envelope:main
3
- vma-extract = dissect.hypervisor.tools.vma:main
@@ -9,4 +9,3 @@ dissect.util<4.0.dev,>=3.0.dev
9
9
 
10
10
  [full]
11
11
  pycryptodome
12
- rich
@@ -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