dissect.hypervisor 3.15.dev2__tar.gz → 3.16__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.15.dev2/dissect.hypervisor.egg-info → dissect_hypervisor-3.16}/PKG-INFO +1 -2
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/dissect/hypervisor/__init__.py +0 -3
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16/dissect.hypervisor.egg-info}/PKG-INFO +1 -2
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/dissect.hypervisor.egg-info/SOURCES.txt +0 -7
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/dissect.hypervisor.egg-info/entry_points.txt +0 -1
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/dissect.hypervisor.egg-info/requires.txt +0 -1
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/pyproject.toml +0 -3
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/tests/conftest.py +0 -5
- dissect_hypervisor-3.15.dev2/dissect/hypervisor/backup/c_vma.py +0 -60
- dissect_hypervisor-3.15.dev2/dissect/hypervisor/backup/vma.py +0 -269
- dissect_hypervisor-3.15.dev2/dissect/hypervisor/backup/xva.py +0 -136
- dissect_hypervisor-3.15.dev2/dissect/hypervisor/tools/vma.py +0 -173
- dissect_hypervisor-3.15.dev2/tests/__init__.py +0 -0
- dissect_hypervisor-3.15.dev2/tests/data/test.vma.gz +0 -0
- dissect_hypervisor-3.15.dev2/tests/test_vma.py +0 -64
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/COPYRIGHT +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/LICENSE +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/MANIFEST.in +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/README.md +0 -0
- {dissect_hypervisor-3.15.dev2/dissect/hypervisor/backup → dissect_hypervisor-3.16/dissect/hypervisor/descriptor}/__init__.py +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/dissect/hypervisor/descriptor/c_hyperv.py +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/dissect/hypervisor/descriptor/hyperv.py +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/dissect/hypervisor/descriptor/ovf.py +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/dissect/hypervisor/descriptor/pvs.py +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/dissect/hypervisor/descriptor/vbox.py +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/dissect/hypervisor/descriptor/vmx.py +0 -0
- {dissect_hypervisor-3.15.dev2/dissect/hypervisor/descriptor → dissect_hypervisor-3.16/dissect/hypervisor/disk}/__init__.py +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/dissect/hypervisor/disk/c_hdd.py +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/dissect/hypervisor/disk/c_qcow2.py +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/dissect/hypervisor/disk/c_vdi.py +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/dissect/hypervisor/disk/c_vhd.py +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/dissect/hypervisor/disk/c_vhdx.py +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/dissect/hypervisor/disk/c_vmdk.py +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/dissect/hypervisor/disk/hdd.py +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/dissect/hypervisor/disk/qcow2.py +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/dissect/hypervisor/disk/vdi.py +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/dissect/hypervisor/disk/vhd.py +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/dissect/hypervisor/disk/vhdx.py +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/dissect/hypervisor/disk/vmdk.py +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/dissect/hypervisor/exceptions.py +0 -0
- {dissect_hypervisor-3.15.dev2/dissect/hypervisor/disk → dissect_hypervisor-3.16/dissect/hypervisor/tools}/__init__.py +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/dissect/hypervisor/tools/envelope.py +0 -0
- {dissect_hypervisor-3.15.dev2/dissect/hypervisor/tools → dissect_hypervisor-3.16/dissect/hypervisor/util}/__init__.py +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/dissect/hypervisor/util/envelope.py +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/dissect/hypervisor/util/vmtar.py +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/dissect.hypervisor.egg-info/dependency_links.txt +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/dissect.hypervisor.egg-info/top_level.txt +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/setup.cfg +0 -0
- {dissect_hypervisor-3.15.dev2/dissect/hypervisor/util → dissect_hypervisor-3.16/tests}/__init__.py +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/tests/data/differencing.avhdx.gz +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/tests/data/dynamic.vhd.gz +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/tests/data/dynamic.vhdx.gz +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/tests/data/encrypted.vmx +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/tests/data/encryption.info +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/tests/data/expanding.hdd/DiskDescriptor.xml +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/tests/data/expanding.hdd/expanding.hdd +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/tests/data/expanding.hdd/expanding.hdd.0.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/tests/data/fixed.vhd.gz +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/tests/data/fixed.vhdx.gz +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/tests/data/local.tgz.ve +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/tests/data/plain.hdd/DiskDescriptor.xml +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/tests/data/plain.hdd/plain.hdd +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/tests/data/plain.hdd/plain.hdd.0.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/tests/data/sesparse.vmdk.gz +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/tests/data/split.hdd/DiskDescriptor.xml +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/tests/data/split.hdd/split.hdd +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/tests/data/split.hdd/split.hdd.0.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/tests/data/split.hdd/split.hdd.1.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/tests/data/split.hdd/split.hdd.2.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/tests/data/split.hdd/split.hdd.3.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/tests/data/split.hdd/split.hdd.4.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/tests/data/split.hdd/split.hdd.5.{5fbaabe3-6958-40ff-92a7-860e329aab41}.hds.gz +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/tests/data/test.VMRS +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/tests/data/test.vgz +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/tests/data/test.vmcx +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/tests/docs/Makefile +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/tests/docs/conf.py +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/tests/docs/index.rst +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/tests/test_envelope.py +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/tests/test_hdd.py +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/tests/test_hyperv.py +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/tests/test_ovf.py +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/tests/test_pvs.py +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/tests/test_vbox.py +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/tests/test_vhd.py +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/tests/test_vhdx.py +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/tests/test_vmdk.py +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/tests/test_vmtar.py +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/tests/test_vmx.py +0 -0
- {dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/tox.ini +0 -0
{dissect_hypervisor-3.15.dev2/dissect.hypervisor.egg-info → dissect_hypervisor-3.16}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: dissect.hypervisor
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.16
|
|
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
|
]
|
{dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16/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.
|
|
3
|
+
Version: 3.16
|
|
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.15.dev2 → dissect_hypervisor-3.16}/dissect.hypervisor.egg-info/SOURCES.txt
RENAMED
|
@@ -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]
|
|
@@ -66,4 +64,3 @@ license-files = ["LICENSE", "COPYRIGHT"]
|
|
|
66
64
|
include = ["dissect.*"]
|
|
67
65
|
|
|
68
66
|
[tool.setuptools_scm]
|
|
69
|
-
local_scheme = "no-local-version"
|
|
@@ -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")
|
|
@@ -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
|
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
import hashlib
|
|
2
|
-
import tarfile
|
|
3
|
-
from bisect import bisect_right
|
|
4
|
-
from xml.etree import ElementTree
|
|
5
|
-
|
|
6
|
-
from dissect.util.stream import AlignedStream
|
|
7
|
-
|
|
8
|
-
BLOCK_SIZE = 1024 * 1024
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class XVA:
|
|
12
|
-
"""XVA reader.
|
|
13
|
-
|
|
14
|
-
XenCenter export format. Basically a tar file with "blocks" of 1MB.
|
|
15
|
-
"""
|
|
16
|
-
|
|
17
|
-
def __init__(self, fh):
|
|
18
|
-
# We don't have to cache tar members, tarfile already does that for us
|
|
19
|
-
self.tar = tarfile.open(fileobj=fh)
|
|
20
|
-
self._ova = None
|
|
21
|
-
|
|
22
|
-
@property
|
|
23
|
-
def ova(self):
|
|
24
|
-
if not self._ova:
|
|
25
|
-
ova_member = self.tar.getmember("ova.xml")
|
|
26
|
-
ova_fh = self.tar.extractfile(ova_member)
|
|
27
|
-
self._ova = ElementTree.fromstring(ova_fh.read())
|
|
28
|
-
return self._ova
|
|
29
|
-
|
|
30
|
-
def disks(self):
|
|
31
|
-
return [
|
|
32
|
-
el.text
|
|
33
|
-
for el in self.ova.findall(
|
|
34
|
-
"*//member/name[.='VDI']/../..//name[.='type']/..value[.='Disk']/../..//name[.='VDI']/../value"
|
|
35
|
-
)
|
|
36
|
-
]
|
|
37
|
-
|
|
38
|
-
def open(self, ref, verify=False):
|
|
39
|
-
size = int(
|
|
40
|
-
self.ova.find(f"*//member/name[.='id']/../value[.='{ref}']/../..//name[.='virtual_size']/../value").text
|
|
41
|
-
)
|
|
42
|
-
return XVAStream(self, ref, size, verify)
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
class XVAStream(AlignedStream):
|
|
46
|
-
"""XVA stream.
|
|
47
|
-
|
|
48
|
-
XenServer usually just streams an XVA file right into an output file, so our use-case requires a bit
|
|
49
|
-
more trickery. We generally don't stream directly into an output file, but try to create a file-like
|
|
50
|
-
object for other code to use.
|
|
51
|
-
|
|
52
|
-
The numbers for the block files (weirdly) don't represent offsets. It's possible for a block file
|
|
53
|
-
to be 0 sized, in which case you should "add" that block to the stream, and continue on to the next.
|
|
54
|
-
The next block might have a number + 1 of what your current offset is, but it will still contain the
|
|
55
|
-
data for that current offset. For this reason we build a lookup list with offsets.
|
|
56
|
-
"""
|
|
57
|
-
|
|
58
|
-
def __init__(self, xva, ref, size, verify=False):
|
|
59
|
-
self.xva = xva
|
|
60
|
-
self.ref = ref
|
|
61
|
-
self.verify = verify
|
|
62
|
-
|
|
63
|
-
index = 0
|
|
64
|
-
offset = 0
|
|
65
|
-
self._lookup = []
|
|
66
|
-
self._members = []
|
|
67
|
-
for block_index, block_member, checksum_member in _iter_block_files(xva, ref):
|
|
68
|
-
if block_index > index + 1:
|
|
69
|
-
skipped = block_index - (index + 1)
|
|
70
|
-
offset += skipped * BLOCK_SIZE
|
|
71
|
-
|
|
72
|
-
if block_member.size != 0:
|
|
73
|
-
self._lookup.append(offset)
|
|
74
|
-
self._members.append((block_member, checksum_member))
|
|
75
|
-
|
|
76
|
-
offset += block_member.size
|
|
77
|
-
|
|
78
|
-
index = block_index
|
|
79
|
-
|
|
80
|
-
super().__init__(size, align=BLOCK_SIZE)
|
|
81
|
-
|
|
82
|
-
def _read(self, offset, length):
|
|
83
|
-
result = []
|
|
84
|
-
|
|
85
|
-
while length > 0:
|
|
86
|
-
# This method is probably sub-optimal, but it's fairly low effort and we rarely encounter XVA anyway
|
|
87
|
-
block_idx = bisect_right(self._lookup, offset)
|
|
88
|
-
nearest_offset = self._lookup[block_idx - 1]
|
|
89
|
-
|
|
90
|
-
if offset >= nearest_offset + BLOCK_SIZE:
|
|
91
|
-
result.append(b"\x00" * BLOCK_SIZE)
|
|
92
|
-
else:
|
|
93
|
-
block_member, checksum_member = self._members[block_idx - 1]
|
|
94
|
-
buf = self.xva.tar.extractfile(block_member).read()
|
|
95
|
-
|
|
96
|
-
if self.verify:
|
|
97
|
-
if checksum_member is None:
|
|
98
|
-
raise ValueError(f"No checksum for {block_member.name}")
|
|
99
|
-
|
|
100
|
-
if (
|
|
101
|
-
checksum_member.name.endswith("checksum")
|
|
102
|
-
and hashlib.sha1(buf).hexdigest() != self.xva.tar.extractfile(checksum_member).read().decode()
|
|
103
|
-
):
|
|
104
|
-
raise ValueError(f"Invalid checksum for {checksum_member.name}")
|
|
105
|
-
else:
|
|
106
|
-
raise NotImplementedError(f"Unsupported checksum: {checksum_member.name}")
|
|
107
|
-
|
|
108
|
-
result.append(buf)
|
|
109
|
-
|
|
110
|
-
offset += BLOCK_SIZE
|
|
111
|
-
length -= BLOCK_SIZE
|
|
112
|
-
|
|
113
|
-
return b"".join(result)
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
def _iter_block_files(xva, ref):
|
|
117
|
-
member_index = None
|
|
118
|
-
block_member = None
|
|
119
|
-
checksum_member = None
|
|
120
|
-
|
|
121
|
-
for member in xva.tar.getmembers():
|
|
122
|
-
if not member.name.startswith(ref):
|
|
123
|
-
continue
|
|
124
|
-
|
|
125
|
-
index = int(member.name.split("/")[-1].split(".")[0])
|
|
126
|
-
if member_index is None:
|
|
127
|
-
member_index = index
|
|
128
|
-
|
|
129
|
-
if member_index != index:
|
|
130
|
-
yield (member_index, block_member, checksum_member)
|
|
131
|
-
member_index = index
|
|
132
|
-
|
|
133
|
-
if member.name.endswith(("checksum", "xxhash")):
|
|
134
|
-
checksum_member = member
|
|
135
|
-
else:
|
|
136
|
-
block_member = member
|
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
import argparse
|
|
2
|
-
import logging
|
|
3
|
-
import sys
|
|
4
|
-
from pathlib import Path
|
|
5
|
-
|
|
6
|
-
from dissect.hypervisor.backup.c_vma import c_vma
|
|
7
|
-
from dissect.hypervisor.backup.vma import VMA, _iter_mask
|
|
8
|
-
|
|
9
|
-
try:
|
|
10
|
-
from rich.logging import RichHandler
|
|
11
|
-
from rich.progress import (
|
|
12
|
-
BarColumn,
|
|
13
|
-
DownloadColumn,
|
|
14
|
-
Progress,
|
|
15
|
-
TextColumn,
|
|
16
|
-
TimeRemainingColumn,
|
|
17
|
-
TransferSpeedColumn,
|
|
18
|
-
)
|
|
19
|
-
|
|
20
|
-
progress = Progress(
|
|
21
|
-
TextColumn("[bold blue]{task.fields[filename]}", justify="right"),
|
|
22
|
-
BarColumn(bar_width=None),
|
|
23
|
-
"[progress.percentage]{task.percentage:>3.1f}%",
|
|
24
|
-
"•",
|
|
25
|
-
DownloadColumn(),
|
|
26
|
-
"•",
|
|
27
|
-
TransferSpeedColumn(),
|
|
28
|
-
"•",
|
|
29
|
-
TimeRemainingColumn(),
|
|
30
|
-
transient=True,
|
|
31
|
-
)
|
|
32
|
-
except ImportError:
|
|
33
|
-
RichHandler = logging.StreamHandler
|
|
34
|
-
|
|
35
|
-
class Progress:
|
|
36
|
-
def __init__(self):
|
|
37
|
-
self.filename = None
|
|
38
|
-
self.total = None
|
|
39
|
-
|
|
40
|
-
self._task_id = 0
|
|
41
|
-
self._info = {}
|
|
42
|
-
|
|
43
|
-
def __enter__(self):
|
|
44
|
-
pass
|
|
45
|
-
|
|
46
|
-
def __exit__(self, *args, **kwargs):
|
|
47
|
-
sys.stderr.write("\n")
|
|
48
|
-
sys.stderr.flush()
|
|
49
|
-
|
|
50
|
-
def add_task(self, name, filename, total, **kwargs):
|
|
51
|
-
task_id = self._task_id
|
|
52
|
-
self._task_id += 1
|
|
53
|
-
|
|
54
|
-
self._info[task_id] = {"filename": filename, "total": total, "position": 0}
|
|
55
|
-
|
|
56
|
-
return task_id
|
|
57
|
-
|
|
58
|
-
def update(self, task_id, advance):
|
|
59
|
-
self._info[task_id]["position"] += advance
|
|
60
|
-
self.draw()
|
|
61
|
-
|
|
62
|
-
def draw(self):
|
|
63
|
-
infos = []
|
|
64
|
-
for info in self._info.values():
|
|
65
|
-
infos.append(f"{info['filename']} {(info['position'] / info['total']) * 100:0.2f}%")
|
|
66
|
-
sys.stderr.write("\r" + " | ".join(infos))
|
|
67
|
-
sys.stderr.flush()
|
|
68
|
-
|
|
69
|
-
progress = Progress()
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
log = logging.getLogger(__name__)
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
def setup_logging(logger, verbosity):
|
|
76
|
-
if verbosity == 1:
|
|
77
|
-
level = logging.ERROR
|
|
78
|
-
elif verbosity == 2:
|
|
79
|
-
level = logging.WARNING
|
|
80
|
-
elif verbosity == 3:
|
|
81
|
-
level = logging.INFO
|
|
82
|
-
elif verbosity >= 4:
|
|
83
|
-
level = logging.DEBUG
|
|
84
|
-
else:
|
|
85
|
-
level = logging.CRITICAL
|
|
86
|
-
|
|
87
|
-
handler = RichHandler()
|
|
88
|
-
handler.setFormatter(logging.Formatter("%(message)s"))
|
|
89
|
-
handler.setLevel(level)
|
|
90
|
-
logger.addHandler(handler)
|
|
91
|
-
logger.setLevel(level)
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
def main():
|
|
95
|
-
parser = argparse.ArgumentParser(description="VMA extractor")
|
|
96
|
-
parser.add_argument("input", type=Path, help="path to vma file")
|
|
97
|
-
parser.add_argument("-o", "--output", type=Path, required=True, help="path to output directory")
|
|
98
|
-
parser.add_argument("-v", "--verbose", action="count", default=3, help="increase output verbosity")
|
|
99
|
-
args = parser.parse_args()
|
|
100
|
-
|
|
101
|
-
setup_logging(log, args.verbose)
|
|
102
|
-
|
|
103
|
-
in_file = args.input.resolve()
|
|
104
|
-
if not in_file.exists():
|
|
105
|
-
log.error("Input file does not exist: %s", in_file)
|
|
106
|
-
parser.exit()
|
|
107
|
-
|
|
108
|
-
out_dir = args.output.resolve()
|
|
109
|
-
if not out_dir.exists():
|
|
110
|
-
log.error("Output path does not exist: %s", out_dir)
|
|
111
|
-
parser.exit()
|
|
112
|
-
|
|
113
|
-
if not out_dir.is_dir():
|
|
114
|
-
log.error("Output path is not a directory: %s", out_dir)
|
|
115
|
-
parser.exit()
|
|
116
|
-
|
|
117
|
-
with in_file.open("rb") as fh:
|
|
118
|
-
vma = VMA(fh)
|
|
119
|
-
|
|
120
|
-
log.info("Extracting config files")
|
|
121
|
-
for config_name, config_data in vma.configs().items():
|
|
122
|
-
out_file = out_dir.joinpath(config_name)
|
|
123
|
-
|
|
124
|
-
log.info("%s -> %s (%d bytes)", config_name, out_file, len(config_data))
|
|
125
|
-
out_file.write_bytes(config_data)
|
|
126
|
-
|
|
127
|
-
log.info("Extracting device data")
|
|
128
|
-
tasks = {}
|
|
129
|
-
handles = {}
|
|
130
|
-
for device in vma.devices():
|
|
131
|
-
task_id = progress.add_task("extract", filename=device.name, total=device.size)
|
|
132
|
-
tasks[device.id] = task_id
|
|
133
|
-
handles[device.id] = out_dir.joinpath(device.name).open("wb")
|
|
134
|
-
|
|
135
|
-
with progress:
|
|
136
|
-
try:
|
|
137
|
-
for extent in vma.extents():
|
|
138
|
-
vma.fh.seek(extent.data_offset)
|
|
139
|
-
for block_info in extent.header.blockinfo:
|
|
140
|
-
cluster_num = block_info & 0xFFFFFFFF
|
|
141
|
-
dev_id = (block_info >> 32) & 0xFF
|
|
142
|
-
mask = block_info >> (32 + 16)
|
|
143
|
-
|
|
144
|
-
if dev_id == 0:
|
|
145
|
-
continue
|
|
146
|
-
|
|
147
|
-
fh_out = handles[dev_id]
|
|
148
|
-
fh_out.seek(cluster_num * c_vma.VMA_CLUSTER_SIZE)
|
|
149
|
-
|
|
150
|
-
if mask == 0xFFFF:
|
|
151
|
-
fh_out.write(vma.fh.read(c_vma.VMA_CLUSTER_SIZE))
|
|
152
|
-
elif mask == 0:
|
|
153
|
-
fh_out.write(b"\x00" * c_vma.VMA_CLUSTER_SIZE)
|
|
154
|
-
else:
|
|
155
|
-
for allocated, count in _iter_mask(mask, 16):
|
|
156
|
-
if allocated:
|
|
157
|
-
fh_out.write(vma.fh.read(count * c_vma.VMA_BLOCK_SIZE))
|
|
158
|
-
else:
|
|
159
|
-
fh_out.write(b"\x00" * count * c_vma.VMA_BLOCK_SIZE)
|
|
160
|
-
|
|
161
|
-
progress.update(tasks[dev_id], advance=c_vma.VMA_CLUSTER_SIZE)
|
|
162
|
-
except Exception:
|
|
163
|
-
log.exception("Exception during extraction")
|
|
164
|
-
finally:
|
|
165
|
-
for handle in handles.values():
|
|
166
|
-
handle.close()
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
if __name__ == "__main__":
|
|
170
|
-
try:
|
|
171
|
-
sys.exit(main())
|
|
172
|
-
except KeyboardInterrupt:
|
|
173
|
-
pass
|
|
File without changes
|
|
Binary file
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import hashlib
|
|
2
|
-
|
|
3
|
-
from dissect.hypervisor.backup.vma import VMA, _iter_clusters
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def test_vma(simple_vma):
|
|
7
|
-
vma = VMA(simple_vma)
|
|
8
|
-
|
|
9
|
-
assert vma.version == 1
|
|
10
|
-
assert str(vma.uuid) == "04fc12eb-0fed-4322-9aaa-f4e412f68096"
|
|
11
|
-
|
|
12
|
-
assert vma.blob_string(1) == "qemu-server.conf"
|
|
13
|
-
assert len(vma.blob_data(20)) == 417
|
|
14
|
-
assert vma.blob_string(439) == "drive-scsi0"
|
|
15
|
-
|
|
16
|
-
assert vma.config("qemu-server.conf") == vma.blob_data(20)
|
|
17
|
-
assert len(vma.configs()) == 1
|
|
18
|
-
|
|
19
|
-
assert len(vma.devices()) == 1
|
|
20
|
-
|
|
21
|
-
device = vma.device(1)
|
|
22
|
-
assert device.id == 1
|
|
23
|
-
assert device.name == "drive-scsi0"
|
|
24
|
-
assert device.size == 10737418240
|
|
25
|
-
|
|
26
|
-
extents = list(vma.extents())
|
|
27
|
-
# The test data is just a small piece of a real VMA file
|
|
28
|
-
assert len(extents) == 2
|
|
29
|
-
|
|
30
|
-
assert list(_iter_clusters(vma, device.id, 0, 23)) == [
|
|
31
|
-
(0, 65535, 13312),
|
|
32
|
-
(1, 0, 78848),
|
|
33
|
-
(2, 0, 78848),
|
|
34
|
-
(3, 0, 78848),
|
|
35
|
-
(4, 0, 78848),
|
|
36
|
-
(5, 0, 78848),
|
|
37
|
-
(6, 0, 78848),
|
|
38
|
-
(7, 0, 78848),
|
|
39
|
-
(8, 0, 78848),
|
|
40
|
-
(9, 0, 78848),
|
|
41
|
-
(10, 0, 78848),
|
|
42
|
-
(11, 0, 78848),
|
|
43
|
-
(12, 0, 78848),
|
|
44
|
-
(13, 0, 78848),
|
|
45
|
-
(14, 0, 78848),
|
|
46
|
-
(15, 0, 78848),
|
|
47
|
-
(16, 65535, 79360),
|
|
48
|
-
(17, 65535, 144896),
|
|
49
|
-
(18, 65535, 210432),
|
|
50
|
-
(19, 65535, 275968),
|
|
51
|
-
(20, 65535, 341504),
|
|
52
|
-
(21, 65535, 407040),
|
|
53
|
-
(22, 65535, 472576),
|
|
54
|
-
]
|
|
55
|
-
|
|
56
|
-
stream = device.open()
|
|
57
|
-
buf = stream.read(65536)
|
|
58
|
-
assert hashlib.sha256(buf).hexdigest() == "cf4adcf1933a8c9a0a3ff5588e1400e6beea8a32212b3a35ba08c7b08e4e6b1f"
|
|
59
|
-
|
|
60
|
-
buf = stream.read(65536 * 15)
|
|
61
|
-
assert buf.strip(b"\x00") == b""
|
|
62
|
-
|
|
63
|
-
buf = stream.read(65536 * 7)
|
|
64
|
-
assert hashlib.sha256(buf).hexdigest() == "8c989a3aa590795fa919ccb7d1f28651c85f8a0d9ba00ab22cdd9fb760fa7955"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/dissect/hypervisor/descriptor/c_hyperv.py
RENAMED
|
File without changes
|
{dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/dissect/hypervisor/descriptor/hyperv.py
RENAMED
|
File without changes
|
{dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/dissect/hypervisor/descriptor/ovf.py
RENAMED
|
File without changes
|
{dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/dissect/hypervisor/descriptor/pvs.py
RENAMED
|
File without changes
|
{dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/dissect/hypervisor/descriptor/vbox.py
RENAMED
|
File without changes
|
{dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/dissect/hypervisor/descriptor/vmx.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/dissect/hypervisor/tools/envelope.py
RENAMED
|
File without changes
|
|
File without changes
|
{dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/dissect/hypervisor/util/envelope.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/dissect.hypervisor.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
{dissect_hypervisor-3.15.dev2/dissect/hypervisor/util → dissect_hypervisor-3.16/tests}/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/tests/data/expanding.hdd/DiskDescriptor.xml
RENAMED
|
File without changes
|
{dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/tests/data/expanding.hdd/expanding.hdd
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/tests/data/plain.hdd/DiskDescriptor.xml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dissect_hypervisor-3.15.dev2 → dissect_hypervisor-3.16}/tests/data/split.hdd/DiskDescriptor.xml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|