Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
tests/_data/** filter=lfs diff=lfs merge=lfs -text
2 changes: 2 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
exclude .gitattributes
exclude .gitignore
recursive-exclude .github/ *
recursive-exclude tests/_data/ *
163 changes: 85 additions & 78 deletions dissect/hypervisor/disk/c_vdi.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,90 +2,97 @@

from dissect.cstruct import cstruct

# https://www.virtualbox.org/browser/vbox/trunk/src/VBox/Storage/VDICore.h
# https://forums.virtualbox.org/viewtopic.php?t=8046
# 0000 3C 3C 3C 20 53 75 6E 20 78 56 4D 20 56 69 72 74 <<< Sun xVM Virt
# 0010 75 61 6C 42 6F 78 20 44 69 73 6B 20 49 6D 61 67 ualBox Disk Imag
# 0020 65 20 3E 3E 3E 0A 00 00 00 00 00 00 00 00 00 00 e >>>
# 0030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
#
# 0040 7F 10 DA BE Image Signature
# 01 00 01 00 Version 1.1
# 90 01 00 00 Size of Header(0x190)
# 01 00 00 00 Image Type (Dynamic VDI)
# 0050 00 00 00 00 Image Flags
# 00 00 00 00 00 00 00 00 00 00 00 00 Image Description
# 0060-001F 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
# 0150 00 00 00 00
# 00 02 00 00 offsetBlocks
# 00 20 00 00 offsetData
# 00 00 00 00 #Cylinders (0)
# 0160 00 00 00 00 #Heads (0)
# 00 00 00 00 #Sectors (0)
# 00 02 00 00 SectorSize (512)
# 00 00 00 00 -- unused --
# 0170 00 00 00 78 00 00 00 00 DiskSize (Bytes)
# 00 00 10 00 BlockSize
# 00 00 00 00 Block Extra Data (0)
# 0180 80 07 00 00 #BlocksInHDD
# 0B 02 00 00 #BlocksAllocated
# 5A 08 62 27 A8 B6 69 44 UUID of this VDI
# 0190 A1 57 E2 B2 43 A5 8F CB
# 0C 5C B1 E3 C5 73 ED 40 UUID of last SNAP
# 01A0 AE F7 06 D6 20 69 0C 96
# 00 00 00 00 00 00 00 00 UUID link
# 01B0 00 00 00 00 00 00 00 00
# 00 00 00 00 00 00 00 00 UUID Parent
# 01C0 00 00 00 00 00 00 00 00
# CF 03 00 00 00 00 00 00 -- garbage / unused --
# 01D0 3F 00 00 00 00 02 00 00 00 00 00 00 00 00 00 00 -- garbage / unused --
# 01E0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -- unused --
# 01F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -- unused --

# https://github.com/VirtualBox/virtualbox/blob/main/src/VBox/Storage/VDICore.h
vdi_def = """
enum ImageType : uint32 {
Dynamic = 0x01,
Fixed = 0x02,
Undo = 0x03,
Differencing = 0x04
enum VDI_IMAGE_TYPE {
/** Normal dynamically growing base image file. */
NORMAL = 1,
/** Preallocated base image file of a fixed size. */
FIXED,
/** Dynamically growing image file for undo/commit changes support. */
UNDO,
/** Dynamically growing image file for differencing support. */
DIFF,
};

flag ImageFlags : uint32 {
None = 0x00000000,
Split2G = 0x00000001,
ZeroExpand = 0x00000002
flag VDI_IMAGE_FLAGS {
/** Fill new blocks with zeroes while expanding image file. Only valid
* for newly created images, never set for opened existing images. */
ZERO_EXPAND = 0x0100,
};

struct HeaderDescriptor {
char FileInfo[64];
uint32 Signature;
uint32 Version;
uint32 HeaderSize;
ImageType ImageType;
ImageFlags ImageFlags;
char ImageDescription[256];
uint32 BlocksOffset;
uint32 DataOffset;
uint32 NumCylinders;
uint32 NumHeads;
uint32 NumSectors;
uint32 SectorSize;
uint32 Unused1;
uint64 DiskSize;
uint32 BlockSize;
uint32 BlockExtraData;
uint32 BlocksInHDD;
uint32 BlocksAllocated;
char UUIDVDI[16];
char UUIDSNAP[16];
char UUIDLink[16];
char UUIDParent[16];
};
typedef struct VDIDISKGEOMETRY {
/** Cylinders. */
uint32_t Cylinders;
/** Heads. */
uint32_t Heads;
/** Sectors per track. */
uint32_t Sectors;
/** Sector size. (bytes per sector) */
uint32_t Sector;
} VDIDISKGEOMETRY, *PVDIDISKGEOMETRY;

typedef struct VDIPREHEADER {
/** Just text info about image type, for eyes only. */
char szFileInfo[64];
/** The image signature (VDI_IMAGE_SIGNATURE). */
uint32_t u32Signature;
/** The image version (VDI_IMAGE_VERSION). */
uint32_t u32Version;
} VDIPREHEADER, *PVDIPREHEADER;

/**
* Size of Comment field of HDD image header.
*/
#define VDI_IMAGE_COMMENT_SIZE 256

/* NOTE: All the header versions are additive, so just use the latest one. */
typedef struct VDIHEADER1PLUS {
/** Size of this structure in bytes. */
uint32_t cbHeader;
/** The image type (VDI_IMAGE_TYPE_*). */
VDI_IMAGE_TYPE u32Type;
/** Image flags (VDI_IMAGE_FLAGS_*). */
VDI_IMAGE_FLAGS fFlags;
/** Image comment. (UTF-8) */
char szComment[VDI_IMAGE_COMMENT_SIZE];
/** Offset of blocks array from the beginning of image file.
* Should be sector-aligned for HDD access optimization. */
uint32_t offBlocks;
/** Offset of image data from the beginning of image file.
* Should be sector-aligned for HDD access optimization. */
uint32_t offData;
/** Legacy image geometry (previous code stored PCHS there). */
VDIDISKGEOMETRY LegacyGeometry;
/** Was BIOS HDD translation mode, now unused. */
uint32_t u32Dummy;
/** Size of disk (in bytes). */
uint64_t cbDisk;
/** Block size. (For instance VDI_IMAGE_BLOCK_SIZE.) Should be a power of 2! */
uint32_t cbBlock;
/** Size of additional service information of every data block.
* Prepended before block data. May be 0.
* Should be a power of 2 and sector-aligned for optimization reasons. */
uint32_t cbBlockExtra;
/** Number of blocks. */
uint32_t cBlocks;
/** Number of allocated blocks. */
uint32_t cBlocksAllocated;
/** UUID of image. */
char uuidCreate[16];
/** UUID of image's last modification. */
char uuidModify[16];
/** Only for secondary images - UUID of previous image. */
char uuidLinkage[16];
/** Only for secondary images - UUID of previous image's last modification. */
char uuidParentModify[16];
/** LCHS image geometry (new field in VDI1.2 version. */
VDIDISKGEOMETRY Geometry;
} VDIHEADER1PLUS, *PVDIHEADER1PLUS;
"""

c_vdi = cstruct().load(vdi_def)

VDI_SIGNATURE = 0xBEDA107F

UNALLOCATED = -1
SPARSE = -2
VDI_IMAGE_SIGNATURE = 0xBEDA107F
VDI_IMAGE_BLOCK_FREE = ~0
VDI_IMAGE_BLOCK_ZERO = ~1
100 changes: 100 additions & 0 deletions dissect/hypervisor/disk/c_vdi.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# Generated by cstruct-stubgen
from typing import BinaryIO, Literal, TypeAlias, overload

import dissect.cstruct as __cs__

class _c_vdi(__cs__.cstruct):
VDI_IMAGE_COMMENT_SIZE: Literal[256] = ...
class VDI_IMAGE_TYPE(__cs__.Enum):
NORMAL = ...
FIXED = ...
UNDO = ...
DIFF = ...

class VDI_IMAGE_FLAGS(__cs__.Flag):
ZERO_EXPAND = ...

class VDIDISKGEOMETRY(__cs__.Structure):
Cylinders: _c_vdi.uint32
Heads: _c_vdi.uint32
Sectors: _c_vdi.uint32
Sector: _c_vdi.uint32
@overload
def __init__(
self,
Cylinders: _c_vdi.uint32 | None = ...,
Heads: _c_vdi.uint32 | None = ...,
Sectors: _c_vdi.uint32 | None = ...,
Sector: _c_vdi.uint32 | None = ...,
): ...
@overload
def __init__(self, fh: bytes | memoryview | bytearray | BinaryIO, /): ...

PVDIDISKGEOMETRY: TypeAlias = __cs__.Pointer[_c_vdi.VDIDISKGEOMETRY]
class VDIPREHEADER(__cs__.Structure):
szFileInfo: __cs__.CharArray
u32Signature: _c_vdi.uint32
u32Version: _c_vdi.uint32
@overload
def __init__(
self,
szFileInfo: __cs__.CharArray | None = ...,
u32Signature: _c_vdi.uint32 | None = ...,
u32Version: _c_vdi.uint32 | None = ...,
): ...
@overload
def __init__(self, fh: bytes | memoryview | bytearray | BinaryIO, /): ...

PVDIPREHEADER: TypeAlias = __cs__.Pointer[_c_vdi.VDIPREHEADER]
class VDIHEADER1PLUS(__cs__.Structure):
cbHeader: _c_vdi.uint32
u32Type: _c_vdi.VDI_IMAGE_TYPE
fFlags: _c_vdi.VDI_IMAGE_FLAGS
szComment: __cs__.CharArray
offBlocks: _c_vdi.uint32
offData: _c_vdi.uint32
LegacyGeometry: _c_vdi.VDIDISKGEOMETRY
u32Dummy: _c_vdi.uint32
cbDisk: _c_vdi.uint64
cbBlock: _c_vdi.uint32
cbBlockExtra: _c_vdi.uint32
cBlocks: _c_vdi.uint32
cBlocksAllocated: _c_vdi.uint32
uuidCreate: __cs__.CharArray
uuidModify: __cs__.CharArray
uuidLinkage: __cs__.CharArray
uuidParentModify: __cs__.CharArray
Geometry: _c_vdi.VDIDISKGEOMETRY
@overload
def __init__(
self,
cbHeader: _c_vdi.uint32 | None = ...,
u32Type: _c_vdi.VDI_IMAGE_TYPE | None = ...,
fFlags: _c_vdi.VDI_IMAGE_FLAGS | None = ...,
szComment: __cs__.CharArray | None = ...,
offBlocks: _c_vdi.uint32 | None = ...,
offData: _c_vdi.uint32 | None = ...,
LegacyGeometry: _c_vdi.VDIDISKGEOMETRY | None = ...,
u32Dummy: _c_vdi.uint32 | None = ...,
cbDisk: _c_vdi.uint64 | None = ...,
cbBlock: _c_vdi.uint32 | None = ...,
cbBlockExtra: _c_vdi.uint32 | None = ...,
cBlocks: _c_vdi.uint32 | None = ...,
cBlocksAllocated: _c_vdi.uint32 | None = ...,
uuidCreate: __cs__.CharArray | None = ...,
uuidModify: __cs__.CharArray | None = ...,
uuidLinkage: __cs__.CharArray | None = ...,
uuidParentModify: __cs__.CharArray | None = ...,
Geometry: _c_vdi.VDIDISKGEOMETRY | None = ...,
): ...
@overload
def __init__(self, fh: bytes | memoryview | bytearray | BinaryIO, /): ...

PVDIHEADER1PLUS: TypeAlias = __cs__.Pointer[_c_vdi.VDIHEADER1PLUS]

# Technically `c_vdi` is an instance of `_c_vdi`, but then we can't use it in type hints
c_vdi: TypeAlias = _c_vdi

VDI_IMAGE_SIGNATURE: int
VDI_IMAGE_BLOCK_FREE: int
VDI_IMAGE_BLOCK_ZERO: int
Loading