From e94c9ef0f31358b604b2da09993a2772a610503b Mon Sep 17 00:00:00 2001 From: Natalia Portillo Date: Mon, 29 Aug 2016 03:38:18 +0100 Subject: [PATCH] Adds support for VHDX images, closes #42 --- DiscImageChef.DiscImages/ChangeLog | 6 + .../DiscImageChef.DiscImages.csproj | 1 + DiscImageChef.DiscImages/VHDX.cs | 1098 +++++++++++++++++ README.md | 1 + TODO | 1 - 5 files changed, 1106 insertions(+), 1 deletion(-) create mode 100644 DiscImageChef.DiscImages/VHDX.cs diff --git a/DiscImageChef.DiscImages/ChangeLog b/DiscImageChef.DiscImages/ChangeLog index b5c18d3d..7194c583 100644 --- a/DiscImageChef.DiscImages/ChangeLog +++ b/DiscImageChef.DiscImages/ChangeLog @@ -1,3 +1,9 @@ +2016-08-29 Natalia Portillo + + * VHDX.cs: + * DiscImageChef.DiscImages.csproj: Adds support for VHDX + images, closes #42 + 2016-08-27 Natalia Portillo * VDI.cs: diff --git a/DiscImageChef.DiscImages/DiscImageChef.DiscImages.csproj b/DiscImageChef.DiscImages/DiscImageChef.DiscImages.csproj index aa532cd9..b69517e9 100644 --- a/DiscImageChef.DiscImages/DiscImageChef.DiscImages.csproj +++ b/DiscImageChef.DiscImages/DiscImageChef.DiscImages.csproj @@ -57,6 +57,7 @@ + diff --git a/DiscImageChef.DiscImages/VHDX.cs b/DiscImageChef.DiscImages/VHDX.cs new file mode 100644 index 00000000..d935bb4f --- /dev/null +++ b/DiscImageChef.DiscImages/VHDX.cs @@ -0,0 +1,1098 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : VHDX.cs +// Author(s) : Natalia Portillo +// +// Component : Disc image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Manages Connectix and Microsoft Virtual PC disk images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2016 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; +using DiscImageChef.CommonTypes; +using DiscImageChef.Console; + +namespace DiscImageChef.ImagePlugins +{ + public class VHDX : ImagePlugin + { + #region Internal Structures + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct VHDXIdentifier + { + /// + /// Signature, + /// + public ulong signature; + /// + /// UTF-16 string containing creator + /// + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 512)] + public byte[] creator; + } + + struct VHDXHeader + { + /// + /// Signature, + /// + public uint signature; + /// + /// CRC-32C of whole 4096 bytes header with this field set to 0 + /// + public uint checksum; + /// + /// Sequence number + /// + public ulong sequence; + /// + /// Unique identifier for file contents, must be changed on first write to metadata + /// + public Guid fileWriteGuid; + /// + /// Unique identifier for disk contents, must be changed on first write to metadata or data + /// + public Guid dataWriteGuid; + /// + /// Unique identifier for log entries + /// + public Guid logGuid; + /// + /// Version of log format + /// + public ushort logVersion; + /// + /// Version of VHDX format + /// + public ushort version; + /// + /// Length in bytes of the log + /// + public uint logLength; + /// + /// Offset from image start to the log + /// + public ulong logOffset; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4016)] + public byte[] reserved; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct VHDXRegionTableHeader + { + /// + /// Signature, + /// + public uint signature; + /// + /// CRC-32C of whole 64Kb table with this field set to 0 + /// + public uint checksum; + /// + /// How many entries follow this table + /// + public uint entries; + /// + /// Reserved + /// + public uint reserved; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct VHDXRegionTableEntry + { + /// + /// Object identifier + /// + public Guid guid; + /// + /// Offset in image of the object + /// + public ulong offset; + /// + /// Length in bytes of the object + /// + public uint length; + /// + /// Flags + /// + public uint flags; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct VHDXMetadataTableHeader + { + /// + /// Signature + /// + public ulong signature; + /// + /// Reserved + /// + public ushort reserved; + /// + /// How many entries are in the table + /// + public ushort entries; + /// + /// Reserved + /// + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)] + public uint[] reserved2; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct VHDXMetadataTableEntry + { + /// + /// Metadata ID + /// + public Guid itemId; + /// + /// Offset relative to start of metadata region + /// + public uint offset; + /// + /// Length in bytes + /// + public uint length; + /// + /// Flags + /// + public uint flags; + /// + /// Reserved + /// + public uint reserved; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct VHDXFileParameters + { + /// + /// Block size in bytes + /// + public uint blockSize; + /// + /// Flags + /// + public uint flags; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct VHDXParentLocatorHeader + { + /// + /// Type of parent virtual disk + /// + public Guid locatorType; + public ushort reserved; + /// + /// How many KVPs are in this parent locator + /// + public ushort keyValueCount; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct VHDXParentLocatorEntry + { + /// + /// Offset from metadata to key + /// + public uint keyOffset; + /// + /// Offset from metadata to value + /// + public uint valueOffset; + /// + /// Size of key + /// + public ushort keyLength; + /// + /// Size of value + /// + public ushort valueLength; + } + + #endregion + + #region Internal Constants + + const ulong VHDXSignature = 0x656C696678646876; + const uint VHDXHeaderSig = 0x64616568; + const uint VHDXRegionSig = 0x69676572; + const ulong VHDXMetadataSig = 0x617461646174656D; + readonly Guid BATGuid = new Guid("2DC27766-F623-4200-9D64-115E9BFD4A08"); + readonly Guid MetadataGuid = new Guid("8B7CA206-4790-4B9A-B8FE-575F050F886E"); + readonly Guid FileParametersGuid = new Guid("CAA16737-FA36-4D43-B3B6-33F0AA44E76B"); + readonly Guid VirtualDiskSizeGuid = new Guid("2FA54224-CD1B-4876-B211-5DBED83BF4B8"); + readonly Guid Page83DataGuid = new Guid("BECA12AB-B2E6-4523-93EF-C309E000C746"); + readonly Guid LogicalSectorSizeGuid = new Guid("8141BF1D-A96F-4709-BA47-F233A8FAAB5F"); + readonly Guid PhysicalSectorSizeGuid = new Guid("CDA348C7-445D-4471-9CC9-E9885251C556"); + readonly Guid ParentLocatorGuid = new Guid("A8D35F2D-B30B-454D-ABF7-D3D84834AB0C"); + readonly Guid ParentTypeVHDXGuid = new Guid("B04AEFB7-D19E-4A81-B789-25B8E9445913"); + + const string ParentLinkageKey = "parent_linkage"; + const string ParentLinkage2Key = "parent_linkage2"; + const string RelativePathKey = "relative_path"; + const string VolumePathKey = "volume_path"; + const string AbsoluteWin32PathKey = "absolute_win32_path"; + + const uint RegionFlagsRequired = 0x01; + + const uint MetadataFlagsUser = 0x01; + const uint MetadataFlagsVirtual = 0x02; + const uint MetadataFlagsRequired = 0x04; + + const uint FileFlagsLeaveAllocated = 0x01; + const uint FileFlagsHasParent = 0x02; + + /// Block has never been stored on this image, check parent + const ulong PayloadBlockNotPresent = 0x00; + /// Block was stored on this image and is removed, return whatever data you wish + const ulong PayloadBlockUndefined = 0x01; + /// Block is filled with zeroes + const ulong PayloadBlockZero = 0x02; + /// All sectors in this block were UNMAPed/TRIMed, return zeroes + const ulong PayloadBlockUnmapper = 0x03; + /// Block is present on this image + const ulong PayloadBlockFullyPresent = 0x06; + /// Block is present on image but there may be sectors present on parent image + const ulong PayloadBlockPartiallyPresent = 0x07; + + const ulong SectorBitmapNotPresent = 0x00; + const ulong SectorBitmapPresent = 0x06; + + const ulong BATFileOffsetMask = 0xFFFFFFFFFFFC0000; + const ulong BATFlagsMask = 0x7; + const ulong BATReservedMask = 0x3FFF8; + + #endregion + + #region Internal variables + + ulong VirtualDiskSize; + Guid Page83Data; + uint LogicalSectorSize; + uint PhysicalSectorSize; + + VHDXIdentifier vhdxId; + VHDXHeader vHdr; + VHDXRegionTableHeader vRegHdr; + VHDXRegionTableEntry[] vRegs; + VHDXMetadataTableHeader vMetHdr; + VHDXMetadataTableEntry[] vMets; + VHDXFileParameters vFileParms; + VHDXParentLocatorHeader vParHdr; + VHDXParentLocatorEntry[] vPars; + + long batOffset; + long metadataOffset; + + long chunkRatio; + ulong dataBlocks; + + ulong[] blockAllocationTable; + ulong[] sectorBitmapPointers; + byte[] sectorBitmap; + ImagePlugin parentImage; + bool hasParent; + FileStream imageStream; + + const int MaxCacheSize = 16777216; + int maxBlockCache; + int maxSectorCache; + + Dictionary sectorCache; + Dictionary blockCache; + + #endregion + + public VHDX() + { + Name = "Microsoft VHDX"; + PluginUUID = new Guid("536B141B-D09C-4799-AB70-34631286EB9D"); + ImageInfo = new ImageInfo(); + ImageInfo.readableSectorTags = new List(); + ImageInfo.readableMediaTags = new List(); + ImageInfo.imageHasPartitions = false; + ImageInfo.imageHasSessions = false; + ImageInfo.imageVersion = null; + ImageInfo.imageApplication = null; + ImageInfo.imageApplicationVersion = null; + ImageInfo.imageCreator = null; + ImageInfo.imageComments = null; + ImageInfo.mediaManufacturer = null; + ImageInfo.mediaModel = null; + ImageInfo.mediaSerialNumber = null; + ImageInfo.mediaBarcode = null; + ImageInfo.mediaPartNumber = null; + ImageInfo.mediaSequence = 0; + ImageInfo.lastMediaSequence = 0; + ImageInfo.driveManufacturer = null; + ImageInfo.driveModel = null; + ImageInfo.driveSerialNumber = null; + ImageInfo.driveFirmwareRevision = null; + } + + #region public methods + + public override bool IdentifyImage(string imagePath) + { + FileStream stream = new FileStream(imagePath, FileMode.Open, FileAccess.Read); + stream.Seek(0, SeekOrigin.Begin); + + if(stream.Length < 512) + return false; + + byte[] vhdxId_b = new byte[Marshal.SizeOf(vhdxId)]; + stream.Read(vhdxId_b, 0, Marshal.SizeOf(vhdxId)); + vhdxId = new VHDXIdentifier(); + IntPtr idPtr = Marshal.AllocHGlobal(Marshal.SizeOf(vhdxId)); + Marshal.Copy(vhdxId_b, 0, idPtr, Marshal.SizeOf(vhdxId)); + vhdxId = (VHDXIdentifier)Marshal.PtrToStructure(idPtr, typeof(VHDXIdentifier)); + Marshal.FreeHGlobal(idPtr); + + return vhdxId.signature == VHDXSignature; + } + + public override bool OpenImage(string imagePath) + { + FileStream stream = new FileStream(imagePath, FileMode.Open, FileAccess.Read); + stream.Seek(0, SeekOrigin.Begin); + + if(stream.Length < 512) + return false; + + byte[] vhdxId_b = new byte[Marshal.SizeOf(vhdxId)]; + stream.Read(vhdxId_b, 0, Marshal.SizeOf(vhdxId)); + vhdxId = new VHDXIdentifier(); + IntPtr idPtr = Marshal.AllocHGlobal(Marshal.SizeOf(vhdxId)); + Marshal.Copy(vhdxId_b, 0, idPtr, Marshal.SizeOf(vhdxId)); + vhdxId = (VHDXIdentifier)Marshal.PtrToStructure(idPtr, typeof(VHDXIdentifier)); + Marshal.FreeHGlobal(idPtr); + + if(vhdxId.signature != VHDXSignature) + return false; + + ImageInfo.imageApplication = Encoding.Unicode.GetString(vhdxId.creator); + + stream.Seek(64 * 1024, SeekOrigin.Begin); + byte[] vHdr_b = new byte[Marshal.SizeOf(vHdr)]; + stream.Read(vHdr_b, 0, Marshal.SizeOf(vHdr)); + vHdr = new VHDXHeader(); + IntPtr headerPtr = Marshal.AllocHGlobal(Marshal.SizeOf(vHdr)); + Marshal.Copy(vHdr_b, 0, headerPtr, Marshal.SizeOf(vHdr)); + vHdr = (VHDXHeader)Marshal.PtrToStructure(headerPtr, typeof(VHDXHeader)); + Marshal.FreeHGlobal(headerPtr); + + if(vHdr.signature != VHDXHeaderSig) + { + stream.Seek(128 * 1024, SeekOrigin.Begin); + vHdr_b = new byte[Marshal.SizeOf(vHdr)]; + stream.Read(vHdr_b, 0, Marshal.SizeOf(vHdr)); + vHdr = new VHDXHeader(); + headerPtr = Marshal.AllocHGlobal(Marshal.SizeOf(vHdr)); + Marshal.Copy(vHdr_b, 0, headerPtr, Marshal.SizeOf(vHdr)); + vHdr = (VHDXHeader)Marshal.PtrToStructure(headerPtr, typeof(VHDXHeader)); + Marshal.FreeHGlobal(headerPtr); + + if(vHdr.signature != VHDXHeaderSig) + throw new ImageNotSupportedException("VHDX header not found"); + } + + stream.Seek(192 * 1024, SeekOrigin.Begin); + byte[] vRegTable_b = new byte[Marshal.SizeOf(vRegHdr)]; + stream.Read(vRegTable_b, 0, Marshal.SizeOf(vRegHdr)); + vRegHdr = new VHDXRegionTableHeader(); + IntPtr vRegTabPtr = Marshal.AllocHGlobal(Marshal.SizeOf(vRegHdr)); + Marshal.Copy(vRegTable_b, 0, vRegTabPtr, Marshal.SizeOf(vRegHdr)); + vRegHdr = (VHDXRegionTableHeader)Marshal.PtrToStructure(vRegTabPtr, typeof(VHDXRegionTableHeader)); + Marshal.FreeHGlobal(vRegTabPtr); + + if(vRegHdr.signature != VHDXRegionSig) + { + stream.Seek(256 * 1024, SeekOrigin.Begin); + vRegTable_b = new byte[Marshal.SizeOf(vRegHdr)]; + stream.Read(vRegTable_b, 0, Marshal.SizeOf(vRegHdr)); + vRegHdr = new VHDXRegionTableHeader(); + vRegTabPtr = Marshal.AllocHGlobal(Marshal.SizeOf(vRegHdr)); + Marshal.Copy(vRegTable_b, 0, vRegTabPtr, Marshal.SizeOf(vRegHdr)); + vRegHdr = (VHDXRegionTableHeader)Marshal.PtrToStructure(vRegTabPtr, typeof(VHDXRegionTableHeader)); + Marshal.FreeHGlobal(vRegTabPtr); + + if(vRegHdr.signature != VHDXRegionSig) + throw new ImageNotSupportedException("VHDX region table not found"); + } + + vRegs = new VHDXRegionTableEntry[vRegHdr.entries]; + for(int i = 0; i < vRegs.Length; i++) + { + byte[] vReg_b = new byte[Marshal.SizeOf(vRegs[i])]; + stream.Read(vReg_b, 0, Marshal.SizeOf(vRegs[i])); + vRegs[i] = new VHDXRegionTableEntry(); + IntPtr vRegPtr = Marshal.AllocHGlobal(Marshal.SizeOf(vRegs[i])); + Marshal.Copy(vReg_b, 0, vRegPtr, Marshal.SizeOf(vRegs[i])); + vRegs[i] = (VHDXRegionTableEntry)Marshal.PtrToStructure(vRegPtr, typeof(VHDXRegionTableEntry)); + Marshal.FreeHGlobal(vRegPtr); + + if(vRegs[i].guid == BATGuid) + batOffset = (long)vRegs[i].offset; + else if(vRegs[i].guid == MetadataGuid) + metadataOffset = (long)vRegs[i].offset; + else if((vRegs[i].flags & RegionFlagsRequired) == RegionFlagsRequired) + throw new ImageNotSupportedException(string.Format("Found unsupported and required region Guid {0}, not proceeding with image.", vRegs[i].guid)); + } + + if(batOffset == 0) + throw new Exception("BAT not found, cannot continue."); + + if(metadataOffset == 0) + throw new Exception("Metadata not found, cannot continue."); + + uint fileParamsOff = 0, vdSizeOff = 0, p83Off = 0, logOff = 0, physOff = 0, parentOff = 0; + + stream.Seek(metadataOffset, SeekOrigin.Begin); + byte[] metTable_b = new byte[Marshal.SizeOf(vMetHdr)]; + stream.Read(metTable_b, 0, Marshal.SizeOf(vMetHdr)); + vMetHdr = new VHDXMetadataTableHeader(); + IntPtr metTablePtr = Marshal.AllocHGlobal(Marshal.SizeOf(vMetHdr)); + Marshal.Copy(metTable_b, 0, metTablePtr, Marshal.SizeOf(vMetHdr)); + vMetHdr = (VHDXMetadataTableHeader)Marshal.PtrToStructure(metTablePtr, typeof(VHDXMetadataTableHeader)); + Marshal.FreeHGlobal(metTablePtr); + + vMets = new VHDXMetadataTableEntry[vMetHdr.entries]; + for(int i = 0; i < vMets.Length; i++) + { + byte[] vMet_b = new byte[Marshal.SizeOf(vMets[i])]; + stream.Read(vMet_b, 0, Marshal.SizeOf(vMets[i])); + vMets[i] = new VHDXMetadataTableEntry(); + IntPtr vMetPtr = Marshal.AllocHGlobal(Marshal.SizeOf(vMets[i])); + Marshal.Copy(vMet_b, 0, vMetPtr, Marshal.SizeOf(vMets[i])); + vMets[i] = (VHDXMetadataTableEntry)Marshal.PtrToStructure(vMetPtr, typeof(VHDXMetadataTableEntry)); + Marshal.FreeHGlobal(vMetPtr); + + if(vMets[i].itemId == FileParametersGuid) + fileParamsOff = vMets[i].offset; + else if(vMets[i].itemId == VirtualDiskSizeGuid) + vdSizeOff = vMets[i].offset; + else if(vMets[i].itemId == Page83DataGuid) + p83Off = vMets[i].offset; + else if(vMets[i].itemId == LogicalSectorSizeGuid) + logOff = vMets[i].offset; + else if(vMets[i].itemId == PhysicalSectorSizeGuid) + physOff = vMets[i].offset; + else if(vMets[i].itemId == ParentLocatorGuid) + parentOff = vMets[i].offset; + else if((vMets[i].flags & MetadataFlagsRequired) == MetadataFlagsRequired) + throw new ImageNotSupportedException(string.Format("Found unsupported and required metadata Guid {0}, not proceeding with image.", vMets[i].itemId)); + } + + byte[] tmp; + + if(fileParamsOff != 0) + { + stream.Seek(fileParamsOff + metadataOffset, SeekOrigin.Begin); + tmp = new byte[8]; + stream.Read(tmp, 0, 8); + vFileParms = new VHDXFileParameters(); + vFileParms.blockSize = BitConverter.ToUInt32(tmp, 0); + vFileParms.flags = BitConverter.ToUInt32(tmp, 4); + } + else + throw new Exception("File parameters not found."); + + if(vdSizeOff != 0) + { + stream.Seek(vdSizeOff + metadataOffset, SeekOrigin.Begin); + tmp = new byte[8]; + stream.Read(tmp, 0, 8); + VirtualDiskSize = BitConverter.ToUInt64(tmp, 0); + } + else + throw new Exception("Virtual disk size not found."); + + if(p83Off != 0) + { + stream.Seek(p83Off + metadataOffset, SeekOrigin.Begin); + tmp = new byte[16]; + stream.Read(tmp, 0, 16); + Page83Data = new Guid(tmp); + } + + if(logOff != 0) + { + stream.Seek(logOff + metadataOffset, SeekOrigin.Begin); + tmp = new byte[4]; + stream.Read(tmp, 0, 4); + LogicalSectorSize = BitConverter.ToUInt32(tmp, 0); + } + else + throw new Exception("Logical sector size not found."); + + if(physOff != 0) + { + stream.Seek(physOff + metadataOffset, SeekOrigin.Begin); + tmp = new byte[4]; + stream.Read(tmp, 0, 4); + PhysicalSectorSize = BitConverter.ToUInt32(tmp, 0); + } + else + throw new Exception("Physical sector size not found."); + + if(parentOff != 0 && (vFileParms.flags & FileFlagsHasParent) == FileFlagsHasParent) + { + stream.Seek(parentOff + metadataOffset, SeekOrigin.Begin); + byte[] vParHdr_b = new byte[Marshal.SizeOf(vMetHdr)]; + stream.Read(vParHdr_b, 0, Marshal.SizeOf(vMetHdr)); + vParHdr = new VHDXParentLocatorHeader(); + IntPtr vParHdrPtr = Marshal.AllocHGlobal(Marshal.SizeOf(vMetHdr)); + Marshal.Copy(vParHdr_b, 0, vParHdrPtr, Marshal.SizeOf(vMetHdr)); + vParHdr = (VHDXParentLocatorHeader)Marshal.PtrToStructure(vParHdrPtr, typeof(VHDXParentLocatorHeader)); + Marshal.FreeHGlobal(vParHdrPtr); + + if(vParHdr.locatorType != ParentTypeVHDXGuid) + throw new ImageNotSupportedException(string.Format("Found unsupported and required parent locator type {0}, not proceeding with image.", vParHdr.locatorType)); + + vPars = new VHDXParentLocatorEntry[vParHdr.keyValueCount]; + for(int i = 0; i < vPars.Length; i++) + { + byte[] vPar_b = new byte[Marshal.SizeOf(vPars[i])]; + stream.Read(vPar_b, 0, Marshal.SizeOf(vPars[i])); + vPars[i] = new VHDXParentLocatorEntry(); + IntPtr vParPtr = Marshal.AllocHGlobal(Marshal.SizeOf(vPars[i])); + Marshal.Copy(vPar_b, 0, vParPtr, Marshal.SizeOf(vPars[i])); + vPars[i] = (VHDXParentLocatorEntry)Marshal.PtrToStructure(vParPtr, typeof(VHDXParentLocatorEntry)); + Marshal.FreeHGlobal(vParPtr); + + } + } + else if((vFileParms.flags & FileFlagsHasParent) == FileFlagsHasParent) + throw new Exception("Parent locator not found."); + + if((vFileParms.flags & FileFlagsHasParent) == FileFlagsHasParent && vParHdr.locatorType == ParentTypeVHDXGuid) + { + parentImage = new VHDX(); + bool parentWorks = false; + + foreach(VHDXParentLocatorEntry parentEntry in vPars) + { + stream.Seek(parentEntry.keyOffset + metadataOffset, SeekOrigin.Begin); + byte[] tmpKey = new byte[parentEntry.keyLength]; + stream.Read(tmpKey, 0, tmpKey.Length); + string entryType = Encoding.Unicode.GetString(tmpKey); + + if(string.Compare(entryType, RelativePathKey, StringComparison.OrdinalIgnoreCase) == 0) + { + stream.Seek(parentEntry.valueOffset + metadataOffset, SeekOrigin.Begin); + byte[] tmpVal = new byte[parentEntry.valueLength]; + stream.Read(tmpVal, 0, tmpVal.Length); + string entryValue = Encoding.Unicode.GetString(tmpVal); + + try + { + if(parentImage.OpenImage(entryValue)) + { + parentWorks = true; + break; + } + } + catch { parentWorks = false; } + + string relEntry = Path.Combine(Path.GetDirectoryName(imagePath), entryValue); + + try + { + if(parentImage.OpenImage(relEntry)) + { + parentWorks = true; + break; + } + } + catch { continue; } + } + else if(string.Compare(entryType, VolumePathKey, StringComparison.OrdinalIgnoreCase) == 0 || + string.Compare(entryType, AbsoluteWin32PathKey, StringComparison.OrdinalIgnoreCase) == 0) + { + stream.Seek(parentEntry.valueOffset + metadataOffset, SeekOrigin.Begin); + byte[] tmpVal = new byte[parentEntry.valueLength]; + stream.Read(tmpVal, 0, tmpVal.Length); + string entryValue = Encoding.Unicode.GetString(tmpVal); + + try + { + if(parentImage.OpenImage(entryValue)) + { + parentWorks = true; + break; + } + } + catch { continue; } + } + } + + if(!parentWorks) + throw new Exception("Image is differential but parent cannot be opened."); + + hasParent = true; + } + + chunkRatio = (long)((Math.Pow(2, 23) * LogicalSectorSize) / vFileParms.blockSize); + dataBlocks = VirtualDiskSize / vFileParms.blockSize; + if((VirtualDiskSize % vFileParms.blockSize) > 0) + dataBlocks++; + + long batEntries; + if(hasParent) + { + long sectorBitmapBlocks = (long)dataBlocks / chunkRatio; + if((dataBlocks % (ulong)chunkRatio) > 0) + sectorBitmapBlocks++; + sectorBitmapPointers = new ulong[sectorBitmapBlocks]; + + batEntries = sectorBitmapBlocks * (chunkRatio - 1); + } + else + batEntries = (long)(dataBlocks + ((dataBlocks - 1) / (ulong)chunkRatio)); + + DicConsole.DebugWriteLine("VHDX plugin", "Reading BAT"); + + long readChunks = 0; + blockAllocationTable = new ulong[dataBlocks]; + byte[] BAT_b = new byte[batEntries * 8]; + stream.Seek(batOffset, SeekOrigin.Begin); + stream.Read(BAT_b, 0, BAT_b.Length); + + ulong skipSize = 0; + for(ulong i = 0; i < dataBlocks; i++) + { + if(readChunks == chunkRatio) + { + if(hasParent) + sectorBitmapPointers[skipSize / 8] = BitConverter.ToUInt64(BAT_b, (int)(i * 8 + skipSize)); + + readChunks = 0; + skipSize += 8; + } + else + { + blockAllocationTable[i] = BitConverter.ToUInt64(BAT_b, (int)(i * 8 + skipSize)); + readChunks++; + } + } + + if(hasParent) + { + DicConsole.DebugWriteLine("VHDX plugin", "Reading Sector Bitmap"); + + MemoryStream sectorBmpMs = new MemoryStream(); + foreach(ulong pt in sectorBitmapPointers) + { + if((pt & BATFlagsMask) == SectorBitmapNotPresent) + { + sectorBmpMs.Write(new byte[1048576], 0, 1048576); + } + else if((pt & BATFlagsMask) == SectorBitmapPresent) + { + stream.Seek((long)((pt & BATFileOffsetMask) * 1048576), SeekOrigin.Begin); + byte[] bmp = new byte[1048576]; + stream.Read(bmp, 0, bmp.Length); + sectorBmpMs.Write(bmp, 0, bmp.Length); + } + else if((pt & BATFlagsMask) != 0) + throw new ImageNotSupportedException(string.Format("Unsupported sector bitmap block flags (0x{0:X16}) found, not proceeding.", pt & BATFlagsMask)); + } + sectorBitmap = sectorBmpMs.ToArray(); + sectorBmpMs.Close(); + } + + maxBlockCache = (int)(MaxCacheSize / vFileParms.blockSize); + maxSectorCache = (int)(MaxCacheSize / LogicalSectorSize); + + imageStream = stream; + + sectorCache = new Dictionary(); + blockCache = new Dictionary(); + + FileInfo fi = new FileInfo(imagePath); + ImageInfo.imageCreationTime = fi.CreationTimeUtc; + ImageInfo.imageLastModificationTime = fi.LastWriteTimeUtc; + ImageInfo.imageName = Path.GetFileNameWithoutExtension(imagePath); + ImageInfo.sectorSize = LogicalSectorSize; + ImageInfo.xmlMediaType = XmlMediaType.BlockMedia; + ImageInfo.mediaType = MediaType.GENERIC_HDD; + ImageInfo.imageSize = VirtualDiskSize; + ImageInfo.sectors = ImageInfo.imageSize / ImageInfo.sectorSize; + ImageInfo.driveSerialNumber = Page83Data.ToString(); + + // TODO: Separate image application from version, need several samples. + + return true; + } + + bool CheckBitmap(ulong sectorAddress) + { + long index = (long)(sectorAddress / 8); + int shift = (int)(sectorAddress % 8); + byte val = (byte)(1 << shift); + + if(index > sectorBitmap.LongLength) + return false; + + return ((sectorBitmap[index] & val) == val); + } + + public override bool ImageHasPartitions() + { + return false; + } + + public override ulong GetImageSize() + { + return ImageInfo.imageSize; + } + + public override ulong GetSectors() + { + return ImageInfo.sectors; + } + + public override uint GetSectorSize() + { + return ImageInfo.sectorSize; + } + + public override string GetImageFormat() + { + return "VHDX"; + } + + public override string GetImageVersion() + { + return ImageInfo.imageVersion; + } + + public override string GetImageApplication() + { + return ImageInfo.imageApplication; + } + + public override string GetImageApplicationVersion() + { + return ImageInfo.imageApplicationVersion; + } + + public override string GetImageCreator() + { + return ImageInfo.imageCreator; + } + + public override DateTime GetImageCreationTime() + { + return ImageInfo.imageCreationTime; + } + + public override DateTime GetImageLastModificationTime() + { + return ImageInfo.imageLastModificationTime; + } + + public override string GetImageName() + { + return ImageInfo.imageName; + } + + public override MediaType GetMediaType() + { + return MediaType.GENERIC_HDD; + } + + public override byte[] ReadSector(ulong sectorAddress) + { + if(sectorAddress > ImageInfo.sectors - 1) + throw new ArgumentOutOfRangeException(nameof(sectorAddress), string.Format("Sector address {0} not found", sectorAddress)); + + byte[] sector; + + if(sectorCache.TryGetValue(sectorAddress, out sector)) + return sector; + + ulong index = (sectorAddress * LogicalSectorSize) / vFileParms.blockSize; + ulong secOff = (sectorAddress * LogicalSectorSize) % vFileParms.blockSize; + + ulong blkPtr = blockAllocationTable[index]; + ulong blkFlags = blkPtr & BATFlagsMask; + + if((blkPtr & BATReservedMask) != 0) + throw new ImageNotSupportedException(string.Format("Unknown flags (0x{0:X16}) set in block pointer", blkPtr & BATReservedMask)); + + if((blkFlags & BATFlagsMask) == PayloadBlockNotPresent) + return hasParent ? parentImage.ReadSector(sectorAddress) : new byte[LogicalSectorSize]; + + if((blkFlags & BATFlagsMask) == PayloadBlockUndefined || + (blkFlags & BATFlagsMask) == PayloadBlockZero || + (blkFlags & BATFlagsMask) == PayloadBlockUnmapper) + return new byte[LogicalSectorSize]; + + bool partialBlock; + partialBlock = !((blkFlags & BATFlagsMask) == PayloadBlockFullyPresent); + partialBlock = (blkFlags & BATFlagsMask) == PayloadBlockPartiallyPresent; + + if(partialBlock && hasParent && !CheckBitmap(sectorAddress)) + return parentImage.ReadSector(sectorAddress); + + byte[] block; + + if(!blockCache.TryGetValue(blkPtr & BATFileOffsetMask, out block)) + { + block = new byte[vFileParms.blockSize]; + imageStream.Seek((long)((blkPtr & BATFileOffsetMask)), SeekOrigin.Begin); + imageStream.Read(block, 0, block.Length); + + if(blockCache.Count >= maxBlockCache) + blockCache.Clear(); + + blockCache.Add(blkPtr & BATFileOffsetMask, block); + } + + sector = new byte[LogicalSectorSize]; + Array.Copy(block, (int)secOff, sector, 0, sector.Length); + + if(sectorCache.Count >= maxSectorCache) + sectorCache.Clear(); + + sectorCache.Add(sectorAddress, sector); + + return sector; + } + + public override byte[] ReadSectors(ulong sectorAddress, uint length) + { + if(sectorAddress > ImageInfo.sectors - 1) + throw new ArgumentOutOfRangeException(nameof(sectorAddress), string.Format("Sector address {0} not found", sectorAddress)); + + if(sectorAddress + length > ImageInfo.sectors) + throw new ArgumentOutOfRangeException(nameof(length), string.Format("Requested more sectors ({0}) than available ({1})", sectorAddress + length, ImageInfo.sectors)); + + MemoryStream ms = new MemoryStream(); + + for(uint i = 0; i < length; i++) + { + byte[] sector = ReadSector(sectorAddress + i); + ms.Write(sector, 0, sector.Length); + } + + return ms.ToArray(); + } + + #endregion + + #region private methods + + static uint VHDXChecksum(byte[] data) + { + uint checksum = 0; + foreach(byte b in data) + checksum += b; + return ~checksum; + } + + #endregion + + #region Unsupported features + + public override string GetImageComments() + { + return null; + } + + public override byte[] ReadDiskTag(MediaTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public override byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public override byte[] ReadSector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public override byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public override byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public override byte[] ReadSectors(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public override byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public override byte[] ReadSectorLong(ulong sectorAddress) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public override byte[] ReadSectorLong(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public override byte[] ReadSectorsLong(ulong sectorAddress, uint length) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public override byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public override string GetMediaManufacturer() + { + return null; + } + + public override string GetMediaModel() + { + return null; + } + + public override string GetMediaSerialNumber() + { + return null; + } + + public override string GetMediaBarcode() + { + return null; + } + + public override string GetMediaPartNumber() + { + return null; + } + + public override int GetMediaSequence() + { + return 0; + } + + public override int GetLastDiskSequence() + { + return 0; + } + + public override string GetDriveManufacturer() + { + return null; + } + + public override string GetDriveModel() + { + return null; + } + + public override string GetDriveSerialNumber() + { + return null; + } + + public override List GetPartitions() + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public override List GetTracks() + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public override List GetSessionTracks(Session session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public override List GetSessionTracks(ushort session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public override List GetSessions() + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public override bool? VerifySector(ulong sectorAddress) + { + return null; + } + + public override bool? VerifySector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public override bool? VerifySectors(ulong sectorAddress, uint length, out List FailingLBAs, out List UnknownLBAs) + { + FailingLBAs = new List(); + UnknownLBAs = new List(); + for(ulong i = 0; i < ImageInfo.sectors; i++) + UnknownLBAs.Add(i); + return null; + } + + public override bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List FailingLBAs, out List UnknownLBAs) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public override bool? VerifyMediaImage() + { + return null; + } + + #endregion + } +} + diff --git a/README.md b/README.md index 4c56a560..e60f9a05 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,7 @@ Supported disk image formats * QEMU Enhanced Disk (QED) * Parallels Hard Disk Image (HDD) version 2 * VirtualBox Disk Image (VDI) +* Microsoft VHDX Supported partitioning schemes ============================== diff --git a/TODO b/TODO index 24af7305..d2d2302d 100644 --- a/TODO +++ b/TODO @@ -9,7 +9,6 @@ --- Add support for Apple NDIF images --- Add support for Apple UDIF images --- Add support for XPACK images ---- Add support for VHDX images --- Add support for VMWare images Filesystem plugins: