// /*************************************************************************** // Aaru Data Preservation Suite // ---------------------------------------------------------------------------- // // Filename : Read.cs // Author(s) : Natalia Portillo // // Component : Disk image plugins. // // --[ Description ] ---------------------------------------------------------- // // Reads Microsoft Hyper-V 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-2025 Natalia Portillo // ****************************************************************************/ using System; using System.Collections.Generic; using System.IO; using System.Text; using Aaru.CommonTypes; using Aaru.CommonTypes.Enums; using Aaru.CommonTypes.Interfaces; using Aaru.Helpers; using Aaru.Logging; using Sentry; namespace Aaru.Images; public sealed partial class Vhdx { #region IMediaImage Members /// public ErrorNumber Open(IFilter imageFilter) { Stream stream = imageFilter.GetDataForkStream(); stream.Seek(0, SeekOrigin.Begin); if(stream.Length < 512) return ErrorNumber.InvalidArgument; var vhdxIdB = new byte[Marshal.SizeOf()]; stream.EnsureRead(vhdxIdB, 0, Marshal.SizeOf()); _id = Marshal.ByteArrayToStructureLittleEndian(vhdxIdB); if(_id.signature != VHDX_SIGNATURE) return ErrorNumber.InvalidArgument; _imageInfo.Application = Encoding.Unicode.GetString(_id.creator); stream.Seek(64 * 1024, SeekOrigin.Begin); var vHdrB = new byte[Marshal.SizeOf
()]; stream.EnsureRead(vHdrB, 0, Marshal.SizeOf
()); _vHdr = Marshal.ByteArrayToStructureLittleEndian
(vHdrB); if(_vHdr.Signature != VHDX_HEADER_SIG) { stream.Seek(128 * 1024, SeekOrigin.Begin); vHdrB = new byte[Marshal.SizeOf
()]; stream.EnsureRead(vHdrB, 0, Marshal.SizeOf
()); _vHdr = Marshal.ByteArrayToStructureLittleEndian
(vHdrB); if(_vHdr.Signature != VHDX_HEADER_SIG) { AaruLogging.Error(Localization.VHDX_header_not_found); return ErrorNumber.InvalidArgument; } } stream.Seek(192 * 1024, SeekOrigin.Begin); var vRegTableB = new byte[Marshal.SizeOf()]; stream.EnsureRead(vRegTableB, 0, Marshal.SizeOf()); _vRegHdr = Marshal.ByteArrayToStructureLittleEndian(vRegTableB); if(_vRegHdr.signature != VHDX_REGION_SIG) { stream.Seek(256 * 1024, SeekOrigin.Begin); vRegTableB = new byte[Marshal.SizeOf()]; stream.EnsureRead(vRegTableB, 0, Marshal.SizeOf()); _vRegHdr = Marshal.ByteArrayToStructureLittleEndian(vRegTableB); if(_vRegHdr.signature != VHDX_REGION_SIG) { AaruLogging.Error(Localization.VHDX_region_table_not_found); return ErrorNumber.InvalidArgument; } } _vRegs = new RegionTableEntry[_vRegHdr.entries]; for(var i = 0; i < _vRegs.Length; i++) { var vRegB = new byte[System.Runtime.InteropServices.Marshal.SizeOf(_vRegs[i])]; stream.EnsureRead(vRegB, 0, System.Runtime.InteropServices.Marshal.SizeOf(_vRegs[i])); _vRegs[i] = Marshal.ByteArrayToStructureLittleEndian(vRegB); 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 & REGION_FLAGS_REQUIRED) == REGION_FLAGS_REQUIRED) { AaruLogging.Error(string.Format(Localization .Found_unsupported_and_required_region_Guid_0_not_proceeding_with_image, _vRegs[i].guid)); return ErrorNumber.InvalidArgument; } } if(_batOffset == 0) { AaruLogging.Error(Localization.BAT_not_found_cannot_continue); return ErrorNumber.InvalidArgument; } if(_metadataOffset == 0) { AaruLogging.Error(Localization.Metadata_not_found_cannot_continue); return ErrorNumber.InvalidArgument; } uint fileParamsOff = 0, vdSizeOff = 0, p83Off = 0, logOff = 0, physOff = 0, parentOff = 0; stream.Seek(_metadataOffset, SeekOrigin.Begin); var metTableB = new byte[Marshal.SizeOf()]; stream.EnsureRead(metTableB, 0, Marshal.SizeOf()); _vMetHdr = Marshal.ByteArrayToStructureLittleEndian(metTableB); _vMets = new MetadataTableEntry[_vMetHdr.entries]; for(var i = 0; i < _vMets.Length; i++) { var vMetB = new byte[System.Runtime.InteropServices.Marshal.SizeOf(_vMets[i])]; stream.EnsureRead(vMetB, 0, System.Runtime.InteropServices.Marshal.SizeOf(_vMets[i])); _vMets[i] = Marshal.ByteArrayToStructureLittleEndian(vMetB); 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 & METADATA_FLAGS_REQUIRED) == METADATA_FLAGS_REQUIRED) { AaruLogging.Error(string.Format(Localization .Found_unsupported_and_required_metadata_Guid_0_not_proceeding_with_image, _vMets[i].itemId)); return ErrorNumber.InvalidArgument; } } byte[] tmp; if(fileParamsOff != 0) { stream.Seek(fileParamsOff + _metadataOffset, SeekOrigin.Begin); tmp = new byte[8]; stream.EnsureRead(tmp, 0, 8); _vFileParms = new FileParameters { blockSize = BitConverter.ToUInt32(tmp, 0), flags = BitConverter.ToUInt32(tmp, 4) }; } else { AaruLogging.Error(Localization.File_parameters_not_found); return ErrorNumber.InvalidArgument; } if(vdSizeOff != 0) { stream.Seek(vdSizeOff + _metadataOffset, SeekOrigin.Begin); tmp = new byte[8]; stream.EnsureRead(tmp, 0, 8); _virtualDiskSize = BitConverter.ToUInt64(tmp, 0); } else { AaruLogging.Error(Localization.Virtual_disk_size_not_found); return ErrorNumber.InvalidArgument; } if(p83Off != 0) { stream.Seek(p83Off + _metadataOffset, SeekOrigin.Begin); tmp = new byte[16]; stream.EnsureRead(tmp, 0, 16); _page83Data = new Guid(tmp); } if(logOff != 0) { stream.Seek(logOff + _metadataOffset, SeekOrigin.Begin); tmp = new byte[4]; stream.EnsureRead(tmp, 0, 4); _logicalSectorSize = BitConverter.ToUInt32(tmp, 0); } else { AaruLogging.Error(Localization.Logical_sector_size_not_found); return ErrorNumber.InvalidArgument; } if(physOff != 0) { stream.Seek(physOff + _metadataOffset, SeekOrigin.Begin); tmp = new byte[4]; stream.EnsureRead(tmp, 0, 4); _physicalSectorSize = BitConverter.ToUInt32(tmp, 0); } else { AaruLogging.Error(Localization.Physical_sector_size_not_found); return ErrorNumber.InvalidArgument; } if(parentOff != 0 && (_vFileParms.flags & FILE_FLAGS_HAS_PARENT) == FILE_FLAGS_HAS_PARENT) { stream.Seek(parentOff + _metadataOffset, SeekOrigin.Begin); var vParHdrB = new byte[Marshal.SizeOf()]; stream.EnsureRead(vParHdrB, 0, Marshal.SizeOf()); _vParHdr = Marshal.ByteArrayToStructureLittleEndian(vParHdrB); if(_vParHdr.locatorType != _parentTypeVhdxGuid) { AaruLogging.Error(string.Format(Localization .Found_unsupported_and_required_parent_locator_type_0_not_proceeding_with_image, _vParHdr.locatorType)); return ErrorNumber.NotSupported; } _vPars = new ParentLocatorEntry[_vParHdr.keyValueCount]; for(var i = 0; i < _vPars.Length; i++) { var vParB = new byte[System.Runtime.InteropServices.Marshal.SizeOf(_vPars[i])]; stream.EnsureRead(vParB, 0, System.Runtime.InteropServices.Marshal.SizeOf(_vPars[i])); _vPars[i] = Marshal.ByteArrayToStructureLittleEndian(vParB); } } else if((_vFileParms.flags & FILE_FLAGS_HAS_PARENT) == FILE_FLAGS_HAS_PARENT) { AaruLogging.Error(Localization.Parent_locator_not_found); return ErrorNumber.NoSuchFile; } if((_vFileParms.flags & FILE_FLAGS_HAS_PARENT) == FILE_FLAGS_HAS_PARENT && _vParHdr.locatorType == _parentTypeVhdxGuid) { _parentImage = new Vhdx(); var parentWorks = false; foreach(ParentLocatorEntry parentEntry in _vPars) { stream.Seek(parentEntry.keyOffset + _metadataOffset, SeekOrigin.Begin); var tmpKey = new byte[parentEntry.keyLength]; stream.EnsureRead(tmpKey, 0, tmpKey.Length); string entryType = Encoding.Unicode.GetString(tmpKey); IFilter parentFilter; if(string.Equals(entryType, RELATIVE_PATH_KEY, StringComparison.OrdinalIgnoreCase)) { stream.Seek(parentEntry.valueOffset + _metadataOffset, SeekOrigin.Begin); var tmpVal = new byte[parentEntry.valueLength]; stream.EnsureRead(tmpVal, 0, tmpVal.Length); string entryValue = Encoding.Unicode.GetString(tmpVal); try { parentFilter = PluginRegister.Singleton.GetFilter(Path.Combine(imageFilter.ParentFolder, entryValue)); if(parentFilter != null && _parentImage.Open(parentFilter) == ErrorNumber.NoError) { parentWorks = true; break; } } catch(Exception ex) { SentrySdk.CaptureException(ex); parentWorks = false; } string relEntry = Path.Combine(Path.GetDirectoryName(imageFilter.Path), entryValue); try { parentFilter = PluginRegister.Singleton.GetFilter(Path.Combine(imageFilter.ParentFolder, relEntry)); if(parentFilter == null || _parentImage.Open(parentFilter) != ErrorNumber.NoError) continue; parentWorks = true; break; } catch(Exception ex) { // ignored SentrySdk.CaptureException(ex); } } else if(string.Equals(entryType, VOLUME_PATH_KEY, StringComparison.OrdinalIgnoreCase) || string.Equals(entryType, ABSOLUTE_WIN32_PATH_KEY, StringComparison.OrdinalIgnoreCase)) { stream.Seek(parentEntry.valueOffset + _metadataOffset, SeekOrigin.Begin); var tmpVal = new byte[parentEntry.valueLength]; stream.EnsureRead(tmpVal, 0, tmpVal.Length); string entryValue = Encoding.Unicode.GetString(tmpVal); try { parentFilter = PluginRegister.Singleton.GetFilter(Path.Combine(imageFilter.ParentFolder, entryValue)); if(parentFilter == null || _parentImage.Open(parentFilter) != ErrorNumber.NoError) continue; parentWorks = true; break; } catch(Exception ex) { // ignored SentrySdk.CaptureException(ex); } } } if(!parentWorks) { AaruLogging.Error(Localization.Image_is_differential_but_parent_cannot_be_opened); return ErrorNumber.InOutError; } _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); AaruLogging.Debug(MODULE_NAME, Localization.Reading_BAT); long readChunks = 0; _blockAllocationTable = new ulong[_dataBlocks]; var batB = new byte[batEntries * 8]; stream.Seek(_batOffset, SeekOrigin.Begin); stream.EnsureRead(batB, 0, batB.Length); ulong skipSize = 0; for(ulong i = 0; i < _dataBlocks; i++) { if(readChunks == _chunkRatio) { if(_hasParent) _sectorBitmapPointers[skipSize / 8] = BitConverter.ToUInt64(batB, (int)(i * 8 + skipSize)); readChunks = 0; skipSize += 8; } else { _blockAllocationTable[i] = BitConverter.ToUInt64(batB, (int)(i * 8 + skipSize)); readChunks++; } } if(_hasParent) { AaruLogging.Debug(MODULE_NAME, Localization.Reading_Sector_Bitmap); var sectorBmpMs = new MemoryStream(); foreach(ulong pt in _sectorBitmapPointers) { switch(pt & BAT_FLAGS_MASK) { case SECTOR_BITMAP_NOT_PRESENT: sectorBmpMs.Write(new byte[1048576], 0, 1048576); break; case SECTOR_BITMAP_PRESENT: stream.Seek((long)((pt & BAT_FILE_OFFSET_MASK) * 1048576), SeekOrigin.Begin); var bmp = new byte[1048576]; stream.EnsureRead(bmp, 0, bmp.Length); sectorBmpMs.Write(bmp, 0, bmp.Length); break; default: if((pt & BAT_FLAGS_MASK) != 0) { AaruLogging.Error(string.Format(Localization .Unsupported_sector_bitmap_block_flags_0_found_not_proceeding, pt & BAT_FLAGS_MASK)); return ErrorNumber.InvalidArgument; } break; } } _sectorBitmap = sectorBmpMs.ToArray(); sectorBmpMs.Close(); } _maxBlockCache = (int)(MAX_CACHE_SIZE / _vFileParms.blockSize); _maxSectorCache = (int)(MAX_CACHE_SIZE / _logicalSectorSize); _imageStream = stream; _sectorCache = new Dictionary(); _blockCache = new Dictionary(); _imageInfo.CreationTime = imageFilter.CreationTime; _imageInfo.LastModificationTime = imageFilter.LastWriteTime; _imageInfo.MediaTitle = Path.GetFileNameWithoutExtension(imageFilter.Filename); _imageInfo.SectorSize = _logicalSectorSize; _imageInfo.MetadataMediaType = MetadataMediaType.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. _imageInfo.Cylinders = (uint)(_imageInfo.Sectors / 16 / 63); _imageInfo.Heads = 16; _imageInfo.SectorsPerTrack = 63; return ErrorNumber.NoError; } /// public ErrorNumber ReadSector(ulong sectorAddress, bool negative, out byte[] buffer, out SectorStatus sectorStatus) { buffer = null; sectorStatus = SectorStatus.NotDumped; if(negative) return ErrorNumber.NotSupported; if(sectorAddress > _imageInfo.Sectors - 1) return ErrorNumber.OutOfRange; if(_sectorCache.TryGetValue(sectorAddress, out buffer)) return ErrorNumber.NoError; sectorStatus = SectorStatus.Dumped; ulong index = sectorAddress * _logicalSectorSize / _vFileParms.blockSize; ulong secOff = sectorAddress * _logicalSectorSize % _vFileParms.blockSize; ulong blkPtr = _blockAllocationTable[index]; ulong blkFlags = blkPtr & BAT_FLAGS_MASK; if((blkPtr & BAT_RESERVED_MASK) != 0) { AaruLogging.Error($"Unknown flags (0x{blkPtr & BAT_RESERVED_MASK:X16}) set in block pointer"); return ErrorNumber.InvalidArgument; } switch(blkFlags & BAT_FLAGS_MASK) { case PAYLOAD_BLOCK_NOT_PRESENT: if(_hasParent) return _parentImage.ReadSector(sectorAddress, false, out buffer, out sectorStatus); buffer = new byte[_logicalSectorSize]; return ErrorNumber.NoError; case PAYLOAD_BLOCK_UNDEFINED: case PAYLOAD_BLOCK_ZERO: case PAYLOAD_BLOCK_UNMAPPER: buffer = new byte[_logicalSectorSize]; return ErrorNumber.NoError; } bool partialBlock = (blkFlags & BAT_FLAGS_MASK) == PAYLOAD_BLOCK_PARTIALLY_PRESENT; if(partialBlock && _hasParent && !CheckBitmap(sectorAddress)) return _parentImage.ReadSector(sectorAddress, false, out buffer, out sectorStatus); if(!_blockCache.TryGetValue(blkPtr & BAT_FILE_OFFSET_MASK, out byte[] block)) { block = new byte[_vFileParms.blockSize]; _imageStream.Seek((long)(blkPtr & BAT_FILE_OFFSET_MASK), SeekOrigin.Begin); _imageStream.EnsureRead(block, 0, block.Length); if(_blockCache.Count >= _maxBlockCache) _blockCache.Clear(); _blockCache.Add(blkPtr & BAT_FILE_OFFSET_MASK, block); } buffer = new byte[_logicalSectorSize]; Array.Copy(block, (int)secOff, buffer, 0, buffer.Length); if(_sectorCache.Count >= _maxSectorCache) _sectorCache.Clear(); _sectorCache.Add(sectorAddress, buffer); return ErrorNumber.NoError; } /// public ErrorNumber ReadSectors(ulong sectorAddress, bool negative, uint length, out byte[] buffer, out SectorStatus[] sectorStatus) { buffer = null; sectorStatus = null; if(negative) return ErrorNumber.NotSupported; if(sectorAddress > _imageInfo.Sectors - 1) return ErrorNumber.OutOfRange; if(sectorAddress + length > _imageInfo.Sectors) return ErrorNumber.OutOfRange; var ms = new MemoryStream(); sectorStatus = new SectorStatus[length]; for(uint i = 0; i < length; i++) { ErrorNumber errno = ReadSector(sectorAddress + i, false, out byte[] sector, out SectorStatus status); if(errno != ErrorNumber.NoError) return errno; ms.Write(sector, 0, sector.Length); sectorStatus[i] = status; } buffer = ms.ToArray(); return ErrorNumber.NoError; } #endregion }