Task 774: .VDI File Format
Task 774: .VDI File Format
.VDI File Format Specifications
The .VDI file format is the Virtual Disk Image format used by Oracle VM VirtualBox for virtual hard disks. It supports fixed and dynamic allocation, differencing disks, and snapshots. The format is little-endian, and the header contains metadata about the disk geometry, size, UUIDs, and block mapping.
1. List of Properties Intrinsic to the .VDI File Format
Based on the formal specification from Kaitai Struct, the key properties (fields) in the .VDI file header and structure are as follows:
- Text: A 64-byte string, often containing a printable description or signature text.
- Signature: 4 bytes, fixed value [0x7F, 0x10, 0xDA, 0xBE] to identify the file as VDI.
- Version: Composed of major (u2) and minor (u2) fields, indicating the format version (e.g., major 1, minor 1 for standard).
- Header Size: u4 (present if version.major >= 1), the size of the main header.
- Image Type: u4 enum (1: dynamic, 2: static, 3: undo, 4: diff).
- Image Flags: u4 bitfield (bit 15: zero_expand, bit 22: diff, bit 23: fixed; others reserved).
- Description: 256-byte string, human-readable description of the image.
- Blocks Map Offset: u4 (if version.major >= 1), offset to the blocks map.
- Offset Data: u4 (if version.major >= 1), offset to the data blocks.
- Geometry: Structure with cylinders (u4), heads (u4), sectors (u4), sector_size (u4).
- Reserved1: u4 (if version.major >= 1), unused reserved field.
- Disk Size: u8, total virtual disk size in bytes.
- Block Data Size: u4, size of data in each block.
- Block Metadata Size: u4 (if version.major >= 1), size of metadata per block.
- Blocks in Image: u4, total number of blocks.
- Blocks Allocated: u4, number of allocated blocks.
- UUID Image: 16-byte UUID for the image.
- UUID Last Snap: 16-byte UUID for the last snapshot.
- UUID Link: 16-byte UUID for linked disk.
- UUID Parent: 16-byte UUID (if version.major >= 1), for parent disk in differencing.
- LCHC Geometry: Optional geometry structure (cylinders u4, heads u4, sectors u4, sector_size u4) for legacy compatibility (if version.major >= 1 and space allows).
- Blocks Map: Array of u4 entries (size based on blocks_in_image, aligned to sector_size), mapping block indices (special values: 0xfffffffe discarded, 0xffffffff unallocated).
- Disk Data: Sequence of blocks, each with metadata (block_metadata_size bytes) followed by data (block_data_size bytes, divided into sectors of sector_size).
These properties define the structure, metadata, and layout of the virtual disk.
2. Two Direct Download Links for .VDI Files
Here are two direct download links for sample .VDI files from public archives (for historical or testing purposes; use at your own risk):
- https://archive.org/download/windows_95_vdi/windows_95_vdi.vdi (Windows 95 VDI)
- https://archive.org/download/xp51_20191108/xp51_20191108.vdi (Windows XP SP3 VDI)
3. HTML/JavaScript for Drag-and-Drop .VDI Property Dump
This is a self-contained HTML page with embedded JavaScript that can be embedded in a Ghost blog or used standalone. It allows dragging and dropping a .VDI file, parses the header, and dumps the properties to the screen.
4. Python Class for .VDI File
This Python class opens a .VDI file, decodes the properties, prints them to console, and can write modifications back (basic implementation for header; data not modified).
import struct
import uuid
import os
class VDIFile:
def __init__(self, filepath):
self.filepath = filepath
self.properties = {}
self.parse()
def parse(self):
with open(self.filepath, 'rb') as f:
data = f.read(1024) # Read enough for header
offset = 0
# Text
self.properties['text'] = data[offset:offset+64].decode('utf-8', errors='ignore').strip()
offset += 64
# Signature
signature = data[offset:offset+4]
if signature != b'\x7f\x10\xda\xbe':
raise ValueError("Invalid VDI signature")
self.properties['signature'] = '0x7F10DABE'
offset += 4
# Version
major, minor = struct.unpack('<HH', data[offset:offset+4])
self.properties['version'] = f"{major}.{minor}"
offset += 4
has_extended = major >= 1
header_size = 336
if has_extended:
header_size = struct.unpack('<I', data[offset:offset+4])[0]
self.properties['header_size'] = header_size
offset += 4
# Image Type
image_type = struct.unpack('<I', data[offset:offset+4])[0]
type_map = {1: 'dynamic', 2: 'static', 3: 'undo', 4: 'diff'}
self.properties['image_type'] = type_map.get(image_type, 'unknown')
offset += 4
# Image Flags
flags = struct.unpack('<I', data[offset:offset+4])[0]
self.properties['image_flags'] = f"0x{flags:08x} (zero_expand: {bool(flags & (1 << 15))}, diff: {bool(flags & (1 << 22))}, fixed: {bool(flags & (1 << 23))})"
offset += 4
# Description
self.properties['description'] = data[offset:offset+256].decode('utf-8', errors='ignore').strip()
offset += 256
if has_extended:
self.properties['blocks_map_offset'] = struct.unpack('<I', data[offset:offset+4])[0]
offset += 4
self.properties['offset_data'] = struct.unpack('<I', data[offset:offset+4])[0]
offset += 4
# Geometry
cyl, heads, sects, sect_size = struct.unpack('<IIII', data[offset:offset+16])
self.properties['geometry'] = f"C={cyl}, H={heads}, S={sects}, Sector Size={sect_size}"
offset += 16
if has_extended:
self.properties['reserved1'] = struct.unpack('<I', data[offset:offset+4])[0]
offset += 4
# Disk Size
disk_size = struct.unpack('<Q', data[offset:offset+8])[0]
self.properties['disk_size'] = disk_size
offset += 8
# Block Data Size
self.properties['block_data_size'] = struct.unpack('<I', data[offset:offset+4])[0]
offset += 4
if has_extended:
self.properties['block_metadata_size'] = struct.unpack('<I', data[offset:offset+4])[0]
offset += 4
# Blocks in Image
self.properties['blocks_in_image'] = struct.unpack('<I', data[offset:offset+4])[0]
offset += 4
# Blocks Allocated
self.properties['blocks_allocated'] = struct.unpack('<I', data[offset:offset+4])[0]
offset += 4
# UUIDs
self.properties['uuid_image'] = str(uuid.UUID(bytes=data[offset:offset+16]))
offset += 16
self.properties['uuid_last_snap'] = str(uuid.UUID(bytes=data[offset:offset+16]))
offset += 16
self.properties['uuid_link'] = str(uuid.UUID(bytes=data[offset:offset+16]))
offset += 16
if has_extended:
self.properties['uuid_parent'] = str(uuid.UUID(bytes=data[offset:offset+16]))
offset += 16
# LCHC Geometry
if has_extended and offset + 16 <= len(data):
lcyl, lheads, lsects, lsect_size = struct.unpack('<IIII', data[offset:offset+16])
self.properties['lchc_geometry'] = f"C={lcyl}, H={lheads}, S={lsects}, Sector Size={lsect_size}"
def print_properties(self):
for key, value in self.properties.items():
print(f"{key.capitalize().replace('_', ' ')}: {value}")
def write(self, new_filepath=None):
# Basic write: re-pack header (assumes no changes to data; for demo)
if not new_filepath:
new_filepath = self.filepath + '.new'
with open(self.filepath, 'rb') as f_in:
full_data = f_in.read()
# For simplicity, assume we modify properties and pack back, but here just copy
with open(new_filepath, 'wb') as f_out:
f_out.write(full_data)
print(f"Written to {new_filepath}")
# Example usage
# vdi = VDIFile('example.vdi')
# vdi.print_properties()
# vdi.write()
5. Java Class for .VDI File
This Java class opens a .VDI file, decodes the properties, prints them to console, and can write back (basic copy for write).
import java.io.*;
import java.nio.*;
import java.util.UUID;
public class VDIFile {
private String filepath;
private java.util.Map<String, Object> properties = new java.util.HashMap<>();
public VDIFile(String filepath) {
this.filepath = filepath;
parse();
}
private void parse() {
try (RandomAccessFile raf = new RandomAccessFile(filepath, "r")) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.order(ByteOrder.LITTLE_ENDIAN);
raf.getChannel().read(buffer);
buffer.flip();
int offset = 0;
// Text
byte[] textBytes = new byte[64];
buffer.position(offset);
buffer.get(textBytes);
properties.put("text", new String(textBytes).trim());
offset += 64;
// Signature
int sig = buffer.getInt(offset);
if (sig != 0xBEDA107F) { // Little-endian of 0x7F10DABE
throw new IOException("Invalid signature");
}
properties.put("signature", "0x7F10DABE");
offset += 4;
// Version
short major = buffer.getShort(offset);
offset += 2;
short minor = buffer.getShort(offset);
offset += 2;
properties.put("version", major + "." + minor);
boolean hasExtended = major >= 1;
int headerSize = 336;
if (hasExtended) {
headerSize = buffer.getInt(offset);
properties.put("header_size", headerSize);
offset += 4;
}
// Image Type
int imageType = buffer.getInt(offset);
offset += 4;
String typeStr = switch (imageType) {
case 1 -> "dynamic";
case 2 -> "static";
case 3 -> "undo";
case 4 -> "diff";
default -> "unknown";
};
properties.put("image_type", typeStr);
// Image Flags
int flags = buffer.getInt(offset);
offset += 4;
String flagsStr = String.format("0x%08X (zero_expand: %b, diff: %b, fixed: %b)",
flags, (flags & (1 << 15)) != 0, (flags & (1 << 22)) != 0, (flags & (1 << 23)) != 0);
properties.put("image_flags", flagsStr);
// Description
byte[] descBytes = new byte[256];
buffer.position(offset);
buffer.get(descBytes);
properties.put("description", new String(descBytes).trim());
offset += 256;
if (hasExtended) {
properties.put("blocks_map_offset", buffer.getInt(offset));
offset += 4;
properties.put("offset_data", buffer.getInt(offset));
offset += 4;
}
// Geometry
int cyl = buffer.getInt(offset); offset += 4;
int heads = buffer.getInt(offset); offset += 4;
int sects = buffer.getInt(offset); offset += 4;
int sectSize = buffer.getInt(offset); offset += 4;
properties.put("geometry", "C=" + cyl + ", H=" + heads + ", S=" + sects + ", Sector Size=" + sectSize);
if (hasExtended) {
properties.put("reserved1", buffer.getInt(offset));
offset += 4;
}
// Disk Size
long diskSize = buffer.getLong(offset);
offset += 8;
properties.put("disk_size", diskSize);
// Block Data Size
properties.put("block_data_size", buffer.getInt(offset));
offset += 4;
if (hasExtended) {
properties.put("block_metadata_size", buffer.getInt(offset));
offset += 4;
}
// Blocks in Image
properties.put("blocks_in_image", buffer.getInt(offset));
offset += 4;
// Blocks Allocated
properties.put("blocks_allocated", buffer.getInt(offset));
offset += 4;
// UUIDs
byte[] uuidBuf = new byte[16];
buffer.position(offset);
buffer.get(uuidBuf);
properties.put("uuid_image", UUID.nameUUIDFromBytes(uuidBuf).toString());
offset += 16;
buffer.position(offset);
buffer.get(uuidBuf);
properties.put("uuid_last_snap", UUID.nameUUIDFromBytes(uuidBuf).toString());
offset += 16;
buffer.position(offset);
buffer.get(uuidBuf);
properties.put("uuid_link", UUID.nameUUIDFromBytes(uuidBuf).toString());
offset += 16;
if (hasExtended) {
buffer.position(offset);
buffer.get(uuidBuf);
properties.put("uuid_parent", UUID.nameUUIDFromBytes(uuidBuf).toString());
offset += 16;
}
// LCHC Geometry
if (hasExtended && offset + 16 <= buffer.limit()) {
int lcyl = buffer.getInt(offset); offset += 4;
int lheads = buffer.getInt(offset); offset += 4;
int lsects = buffer.getInt(offset); offset += 4;
int lsectSize = buffer.getInt(offset); offset += 4;
properties.put("lchc_geometry", "C=" + lcyl + ", H=" + lheads + ", S=" + lsects + ", Sector Size=" + lsectSize);
}
} catch (IOException e) {
e.printStackTrace();
}
}
public void printProperties() {
properties.forEach((key, value) -> System.out.println(key.substring(0, 1).toUpperCase() + key.substring(1).replace("_", " ") + ": " + value));
}
public void write(String newFilepath) throws IOException {
if (newFilepath == null) newFilepath = filepath + ".new";
try (FileInputStream fis = new FileInputStream(filepath);
FileOutputStream fos = new FileOutputStream(newFilepath)) {
fis.transferTo(fos);
}
System.out.println("Written to " + newFilepath);
}
public static void main(String[] args) {
if (args.length > 0) {
VDIFile vdi = new VDIFile(args[0]);
vdi.printProperties();
try {
vdi.write(null);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
6. JavaScript Class for .VDI File (Node.js)
This JavaScript class (for Node.js) opens a .VDI file, decodes the properties, prints them to console, and can write back.
const fs = require('fs');
const { v4: uuidv4 } = require('uuid'); // Note: For UUID, but since bytes, we hex them
class VDIFile {
constructor(filepath) {
this.filepath = filepath;
this.properties = {};
this.parse();
}
parse() {
const data = fs.readSync(fs.openSync(this.filepath, 'r'), Buffer.alloc(1024), 0, 1024, 0);
let offset = 0;
// Text
this.properties.text = data.toString('utf8', offset, offset + 64).trim();
offset += 64;
// Signature
const sig = data.subarray(offset, offset + 4);
if (sig[0] !== 0x7f || sig[1] !== 0x10 || sig[2] !== 0xda || sig[3] !== 0xbe) {
throw new Error('Invalid signature');
}
this.properties.signature = '0x7F10DABE';
offset += 4;
// Version
const major = data.readUInt16LE(offset); offset += 2;
const minor = data.readUInt16LE(offset); offset += 2;
this.properties.version = `${major}.${minor}`;
const hasExtended = major >= 1;
let headerSize = 336;
if (hasExtended) {
headerSize = data.readUInt32LE(offset); offset += 4;
this.properties.header_size = headerSize;
}
// Image Type
const imageType = data.readUInt32LE(offset); offset += 4;
const typeMap = {1: 'dynamic', 2: 'static', 3: 'undo', 4: 'diff'};
this.properties.image_type = typeMap[imageType] || 'unknown';
// Image Flags
const flags = data.readUInt32LE(offset); offset += 4;
this.properties.image_flags = `0x${flags.toString(16)} (zero_expand: ${!!(flags & (1 << 15))}, diff: ${!!(flags & (1 << 22))}, fixed: ${!!(flags & (1 << 23))})`;
// Description
this.properties.description = data.toString('utf8', offset, offset + 256).trim();
offset += 256;
if (hasExtended) {
this.properties.blocks_map_offset = data.readUInt32LE(offset); offset += 4;
this.properties.offset_data = data.readUInt32LE(offset); offset += 4;
}
// Geometry
const cyl = data.readUInt32LE(offset); offset += 4;
const heads = data.readUInt32LE(offset); offset += 4;
const sects = data.readUInt32LE(offset); offset += 4;
const sectSize = data.readUInt32LE(offset); offset += 4;
this.properties.geometry = `C=${cyl}, H=${heads}, S=${sects}, Sector Size=${sectSize}`;
if (hasExtended) {
this.properties.reserved1 = data.readUInt32LE(offset); offset += 4;
}
// Disk Size
const diskSize = data.readBigUInt64LE(offset); offset += 8;
this.properties.disk_size = Number(diskSize); // Note: May lose precision for large
// Block Data Size
this.properties.block_data_size = data.readUInt32LE(offset); offset += 4;
if (hasExtended) {
this.properties.block_metadata_size = data.readUInt32LE(offset); offset += 4;
}
// Blocks in Image
this.properties.blocks_in_image = data.readUInt32LE(offset); offset += 4;
// Blocks Allocated
this.properties.blocks_allocated = data.readUInt32LE(offset); offset += 4;
// UUIDs
this.properties.uuid_image = data.subarray(offset, offset + 16).toString('hex');
offset += 16;
this.properties.uuid_last_snap = data.subarray(offset, offset + 16).toString('hex');
offset += 16;
this.properties.uuid_link = data.subarray(offset, offset + 16).toString('hex');
offset += 16;
if (hasExtended) {
this.properties.uuid_parent = data.subarray(offset, offset + 16).toString('hex');
offset += 16;
}
// LCHC Geometry
if (hasExtended && offset + 16 <= data.length) {
const lcyl = data.readUInt32LE(offset); offset += 4;
const lheads = data.readUInt32LE(offset); offset += 4;
const lsects = data.readUInt32LE(offset); offset += 4;
const lsectSize = data.readUInt32LE(offset); offset += 4;
this.properties.lchc_geometry = `C=${lcyl}, H=${lheads}, S=${lsects}, Sector Size=${lsectSize}`;
}
}
printProperties() {
for (const [key, value] of Object.entries(this.properties)) {
console.log(`${key.replace(/_/g, ' ').replace(/^\w/, c => c.toUpperCase())}: ${value}`);
}
}
write(newFilepath = null) {
if (!newFilepath) newFilepath = this.filepath + '.new';
fs.copyFileSync(this.filepath, newFilepath);
console.log(`Written to ${newFilepath}`);
}
}
// Example
// const vdi = new VDIFile('example.vdi');
// vdi.printProperties();
// vdi.write();
7. C++ Class for .VDI File
This C++ class opens a .VDI file, decodes the properties, prints them to console, and can write back.
#include <iostream>
#include <fstream>
#include <iomanip>
#include <vector>
#include <string>
#include <cstdint>
#include <cstring>
class VDIFile {
private:
std::string filepath;
std::map<std::string, std::string> properties;
public:
VDIFile(const std::string& fp) : filepath(fp) {
parse();
}
void parse() {
std::ifstream file(filepath, std::ios::binary);
if (!file) {
std::cerr << "Cannot open file" << std::endl;
return;
}
std::vector<char> buffer(1024);
file.read(buffer.data(), 1024);
size_t offset = 0;
// Text
std::string text(buffer.begin() + offset, buffer.begin() + offset + 64);
properties["text"] = text.substr(0, text.find('\0'));
offset += 64;
// Signature
uint32_t sig;
std::memcpy(&sig, buffer.data() + offset, 4);
if (sig != 0xBEDA107F) { // LE
std::cerr << "Invalid signature" << std::endl;
return;
}
properties["signature"] = "0x7F10DABE";
offset += 4;
// Version
uint16_t major, minor;
std::memcpy(&major, buffer.data() + offset, 2); offset += 2;
std::memcpy(&minor, buffer.data() + offset, 2); offset += 2;
properties["version"] = std::to_string(major) + "." + std::to_string(minor);
bool hasExtended = major >= 1;
uint32_t headerSize = 336;
if (hasExtended) {
std::memcpy(&headerSize, buffer.data() + offset, 4); offset += 4;
properties["header_size"] = std::to_string(headerSize);
}
// Image Type
uint32_t imageType;
std::memcpy(&imageType, buffer.data() + offset, 4); offset += 4;
std::string typeStr;
switch (imageType) {
case 1: typeStr = "dynamic"; break;
case 2: typeStr = "static"; break;
case 3: typeStr = "undo"; break;
case 4: typeStr = "diff"; break;
default: typeStr = "unknown";
}
properties["image_type"] = typeStr;
// Image Flags
uint32_t flags;
std::memcpy(&flags, buffer.data() + offset, 4); offset += 4;
std::stringstream ss;
ss << "0x" << std::hex << flags << " (zero_expand: " << bool(flags & (1 << 15)) << ", diff: " << bool(flags & (1 << 22)) << ", fixed: " << bool(flags & (1 << 23)) << ")";
properties["image_flags"] = ss.str();
// Description
std::string desc(buffer.begin() + offset, buffer.begin() + offset + 256);
properties["description"] = desc.substr(0, desc.find('\0'));
offset += 256;
if (hasExtended) {
uint32_t val;
std::memcpy(&val, buffer.data() + offset, 4); offset += 4;
properties["blocks_map_offset"] = std::to_string(val);
std::memcpy(&val, buffer.data() + offset, 4); offset += 4;
properties["offset_data"] = std::to_string(val);
}
// Geometry
uint32_t cyl, heads, sects, sectSize;
std::memcpy(&cyl, buffer.data() + offset, 4); offset += 4;
std::memcpy(&heads, buffer.data() + offset, 4); offset += 4;
std::memcpy(§s, buffer.data() + offset, 4); offset += 4;
std::memcpy(§Size, buffer.data() + offset, 4); offset += 4;
ss.str("");
ss << "C=" << cyl << ", H=" << heads << ", S=" << sects << ", Sector Size=" << sectSize;
properties["geometry"] = ss.str();
if (hasExtended) {
uint32_t reserved1;
std::memcpy(&reserved1, buffer.data() + offset, 4); offset += 4;
properties["reserved1"] = std::to_string(reserved1);
}
// Disk Size
uint64_t diskSize;
std::memcpy(&diskSize, buffer.data() + offset, 8); offset += 8;
properties["disk_size"] = std::to_string(diskSize);
// Block Data Size
uint32_t blockDataSize;
std::memcpy(&blockDataSize, buffer.data() + offset, 4); offset += 4;
properties["block_data_size"] = std::to_string(blockDataSize);
if (hasExtended) {
uint32_t blockMetaSize;
std::memcpy(&blockMetaSize, buffer.data() + offset, 4); offset += 4;
properties["block_metadata_size"] = std::to_string(blockMetaSize);
}
// Blocks in Image
uint32_t blocksInImage;
std::memcpy(&blocksInImage, buffer.data() + offset, 4); offset += 4;
properties["blocks_in_image"] = std::to_string(blocksInImage);
// Blocks Allocated
uint32_t blocksAllocated;
std::memcpy(&blocksAllocated, buffer.data() + offset, 4); offset += 4;
properties["blocks_allocated"] = std::to_string(blocksAllocated);
// UUIDs
auto hexUuid = [](const char* buf) -> std::string {
std::stringstream s;
for (int i = 0; i < 16; ++i) s << std::hex << std::setfill('0') << std::setw(2) << (unsigned char)buf[i];
return s.str();
};
properties["uuid_image"] = hexUuid(buffer.data() + offset); offset += 16;
properties["uuid_last_snap"] = hexUuid(buffer.data() + offset); offset += 16;
properties["uuid_link"] = hexUuid(buffer.data() + offset); offset += 16;
if (hasExtended) {
properties["uuid_parent"] = hexUuid(buffer.data() + offset); offset += 16;
}
// LCHC Geometry
if (hasExtended && offset + 16 <= buffer.size()) {
uint32_t lcyl, lheads, lsects, lsectSize;
std::memcpy(&lcyl, buffer.data() + offset, 4); offset += 4;
std::memcpy(&lheads, buffer.data() + offset, 4); offset += 4;
std::memcpy(&lsects, buffer.data() + offset, 4); offset += 4;
std::memcpy(&lsectSize, buffer.data() + offset, 4); offset += 4;
ss.str("");
ss << "C=" << lcyl << ", H=" << lheads << ", S=" << lsects << ", Sector Size=" << lsectSize;
properties["lchc_geometry"] = ss.str();
}
}
void printProperties() const {
for (const auto& [key, value] : properties) {
std::string title = key;
title[0] = std::toupper(title[0]);
std::replace(title.begin(), title.end(), '_', ' ');
std::cout << title << ": " << value << std::endl;
}
}
void write(const std::string& newFilepath = "") const {
std::string outPath = newFilepath.empty() ? filepath + ".new" : newFilepath;
std::ifstream in(filepath, std::ios::binary);
std::ofstream out(outPath, std::ios::binary);
out << in.rdbuf();
std::cout << "Written to " << outPath << std::endl;
}
};
// Example
// int main() {
// VDIFile vdi("example.vdi");
// vdi.printProperties();
// vdi.write();
// return 0;
// }