public ulong[] VerifyDataHashTree(bool rehash = false) { ulong dataBlockCount = XvdMath.OffsetToPageNumber((ulong)_io.Stream.Length - UserDataOffset); var invalidBlocks = new List <ulong>(); for (ulong i = 0; i < dataBlockCount; i++) { var hashEntryOffset = CalculateHashEntryOffsetForBlock(i, 0); _io.Stream.Position = (long)hashEntryOffset; byte[] oldhash = _io.Reader.ReadBytes((int)DataHashEntryLength); var dataToHashOffset = XvdMath.PageNumberToOffset(i) + UserDataOffset; _io.Stream.Position = (long)dataToHashOffset; byte[] data = _io.Reader.ReadBytes((int)PAGE_SIZE); byte[] hash = HashUtils.ComputeSha256(data); Array.Resize(ref hash, (int)DataHashEntryLength); if (hash.IsEqualTo(oldhash)) { continue; } invalidBlocks.Add(i); if (!rehash) { continue; } _io.Stream.Position = (long)hashEntryOffset; _io.Writer.Write(hash); } return(invalidBlocks.ToArray()); }
public bool VirtualToLogicalDriveOffset(ulong virtualOffset, out ulong logicalOffset) { logicalOffset = 0; if (virtualOffset >= Header.DriveSize) { throw new InvalidOperationException( $"Virtual offset 0x{virtualOffset:X} is outside drivedata length 0x{Header.DriveSize:X}"); } if (Header.Type > XvdType.Dynamic) { throw new NotSupportedException($"Xvd type {Header.Type} is unhandled"); } if (Header.Type == XvdType.Dynamic) { var dataStartOffset = virtualOffset + XvdMath.PageNumberToOffset(Header.NumberOfMetadataPages); var pageNumber = XvdMath.OffsetToPageNumber(dataStartOffset); var inBlockOffset = XvdMath.InBlockOffset(dataStartOffset); var firstDynamicPage = XvdMath.QueryFirstDynamicPage(Header.NumberOfMetadataPages); if (pageNumber >= firstDynamicPage) { var firstDynamicPageBytes = XvdMath.PageNumberToOffset(firstDynamicPage); var blockNumber = XvdMath.OffsetToBlockNumber(dataStartOffset - firstDynamicPageBytes); ulong allocatedBlock = ReadBat(blockNumber); if (allocatedBlock == INVALID_SECTOR) { return(false); } dataStartOffset = XvdMath.PageNumberToOffset(allocatedBlock) + inBlockOffset; pageNumber = XvdMath.OffsetToPageNumber(dataStartOffset); } var dataBackingBlockNum = XvdMath.ComputeDataBackingPageNumber(Header.Type, HashTreeLevels, HashTreePageCount, pageNumber); logicalOffset = XvdMath.PageNumberToOffset(dataBackingBlockNum); logicalOffset += XvdMath.InPageOffset(dataStartOffset); logicalOffset += XvdMath.PageNumberToOffset(Header.EmbeddedXvdPageCount); logicalOffset += Header.MutableDataLength; logicalOffset += XVD_HEADER_INCL_SIGNATURE_SIZE; logicalOffset += PAGE_SIZE; } else { // Xvd type fixed logicalOffset = virtualOffset; logicalOffset += XvdMath.PageNumberToOffset(Header.EmbeddedXvdPageCount); logicalOffset += Header.MutableDataLength; logicalOffset += XvdMath.PageNumberToOffset(Header.NumberOfMetadataPages); logicalOffset += XVD_HEADER_INCL_SIGNATURE_SIZE; logicalOffset += PAGE_SIZE; } return(true); }
byte[] ReadDynamic(int count) { int positionInBuffer = 0; int bytesRemaining = count; byte[] destBuffer = new byte[count]; while (positionInBuffer < count) { byte[] data = new byte[0]; if (Position < StaticDataLength) { // Read a chunk from non-dynamic area, next iteration will read dynamic data int maxReadLength = (int)(StaticDataLength - Position); int length = bytesRemaining > maxReadLength ? maxReadLength : bytesRemaining; data = InternalRead(length); } else { // Lookup block allocation table for real data offset var targetVirtualOffset = (ulong)(Position - StaticDataLength); ulong blockNumber = XvdMath.OffsetToBlockNumber(targetVirtualOffset); long inBlockOffset = (long)XvdMath.InBlockOffset(targetVirtualOffset); int maxReadLength = (int)(XvdFile.BLOCK_SIZE - inBlockOffset); int length = bytesRemaining > maxReadLength ? maxReadLength : bytesRemaining; var targetPage = _xvdFile.ReadBat(blockNumber); if (targetPage == XvdFile.INVALID_SECTOR) { data = new byte[length]; // Advance stream position cause we are not actually reading data Position += length; } else { long targetPhysicalOffset = DynamicBaseOffset + (long)XvdMath.PageNumberToOffset(targetPage) + inBlockOffset; data = InternalReadAbsolute(targetPhysicalOffset, length); } } Array.Copy(data, 0, destBuffer, positionInBuffer, data.Length); positionInBuffer += data.Length; bytesRemaining -= data.Length; } return(destBuffer); }
public string ToString(bool formatted) { var b = new StringBuilder(); b.AppendLine("XvcUpdateSegment"); b.AppendLine(); string fmt = formatted ? " " : ""; b.AppendLineSpace(fmt + $"PageNum: 0x{PageNum:X} (@ 0x{XvdMath.PageNumberToOffset(PageNum)})"); b.AppendLineSpace(fmt + $"Hash: 0x{Hash:X}"); return(b.ToString()); }
ulong CalculateStaticDataLength() { if (Header.Type == XvdType.Dynamic) { var smallestPage = GetAllBATEntries().Min(); return(XvdMath.PageNumberToOffset(smallestPage) - XvdMath.PageNumberToOffset(Header.DynamicHeaderPageCount) - XvdMath.PageNumberToOffset(Header.XvcInfoPageCount)); } else if (Header.Type == XvdType.Fixed) { return(0); } else { throw new InvalidProgramException("Unsupported XvdType"); } }
public ulong CalculateHashEntryOffsetForBlock(ulong blockNum, uint hashLevel) { var hashBlock = XvdMath.CalculateHashBlockNumForBlockNum(Header.Type, HashTreeLevels, Header.NumberOfHashedPages, blockNum, hashLevel, out var entryNum); return(HashTreeOffset + XvdMath.PageNumberToOffset(hashBlock) + (entryNum * HASH_ENTRY_LENGTH)); }
public string ToString(bool formatted) { var b = new StringBuilder(); string fmt = formatted ? " " : ""; b.AppendLine("XvdMiscInfo:"); b.AppendLineSpace(fmt + $"Page Count: 0x{Header.NumberOfHashedPages:X}"); b.AppendLineSpace(fmt + $"Embedded XVD Offset: 0x{EmbeddedXvdOffset:X}"); b.AppendLineSpace(fmt + $"MDU Offset: 0x{MduOffset:X}"); b.AppendLineSpace(fmt + $"HashTree Offset: 0x{HashTreeOffset:X}"); b.AppendLineSpace(fmt + $"User Data Offset: 0x{UserDataOffset:X}"); b.AppendLineSpace(fmt + $"XVC Data Offset: 0x{XvcInfoOffset:X}"); b.AppendLineSpace(fmt + $"Dynamic Header Offset: 0x{DynamicHeaderOffset:X}"); b.AppendLineSpace(fmt + $"Drive Data Offset: 0x{DriveDataOffset:X}"); if (IsDataIntegrityEnabled) { b.AppendLineSpace(fmt + $"Hash Tree Page Count: 0x{HashTreePageCount:X}"); b.AppendLineSpace(fmt + $"Hash Tree Levels: 0x{HashTreeLevels:X}"); b.AppendLineSpace(fmt + $"Hash Tree Valid: {HashTreeValid}"); if (!DisableDataHashChecking) { b.AppendLineSpace(fmt + $"Data Hash Tree Valid: {DataHashTreeValid}"); } } if (IsXvcFile) { b.AppendLineSpace(fmt + $"XVC Data Hash Valid: {XvcDataHashValid}"); } b.AppendLine(); b.Append(Header.ToString(formatted)); if (IsXvcFile && XvcInfo.ContentID != null) { b.AppendLine(); bool xvcKeyFound = GetXvcKey(0, out var decryptKey); if (xvcKeyFound) { b.AppendLine($"Decrypt key for xvc keyslot 0: {decryptKey.ToHexString()}"); b.AppendLine("(key is wrong though until the obfuscation/encryption on it is figured out)"); b.AppendLine(); } b.AppendLine(XvcInfo.ToString(formatted)); } if (RegionHeaders != null) { for (int i = 0; i < RegionHeaders.Count; i++) { b.AppendLine(); string presenceInfo = ""; if (RegionPresenceInfo != null && RegionPresenceInfo.Count > i) { var presenceFlags = RegionPresenceInfo[i]; presenceInfo = " ("; presenceInfo += (presenceFlags.HasFlag(XvcRegionPresenceInfo.IsPresent) ? "present" : "not present") + ", "; presenceInfo += presenceFlags.HasFlag(XvcRegionPresenceInfo.IsAvailable) ? "available" : "unavailable"; if (((int)presenceFlags & 0xF0) != 0) { presenceInfo += $", on disc {(int)presenceFlags >> 4}"; } presenceInfo += ")"; } b.AppendLine($"Region {i}{presenceInfo}"); b.Append(RegionHeaders[i].ToString(formatted)); } } if (UpdateSegments != null && UpdateSegments.Count > 0) { // have to add segments to a seperate List so we can store the index of them... var segments = new List <Tuple <int, XvcUpdateSegment> >(); for (int i = 0; i < UpdateSegments.Count; i++) { if (UpdateSegments[i].Hash == 0) { break; } segments.Add(Tuple.Create(i, UpdateSegments[i])); } b.AppendLine(); b.AppendLine("Update Segments:"); b.AppendLine(); b.AppendLine(segments.ToStringTable( new[] { "Id", "PageNum (Offset)", "Hash" }, a => a.Item1, a => $"0x{a.Item2.PageNum:X} (0x{XvdMath.PageNumberToOffset(a.Item2.PageNum):X})", a => $"0x{a.Item2.Hash:X}")); } if (RegionSpecifiers != null && RegionSpecifiers.Count > 0) { // have to add specifiers to a seperate List so we can store the index of them... var specs = new List <Tuple <int, XvcRegionSpecifier> >(); for (int i = 0; i < RegionSpecifiers.Count; i++) { specs.Add(Tuple.Create(i, RegionSpecifiers[i])); } b.AppendLine(); b.AppendLine("Region Specifiers:"); b.AppendLine(); b.AppendLine(specs.ToStringTable( new[] { "Id", "RegionId", "Key", "Value" }, a => a.Item1, a => $"0x{a.Item2.RegionId:X}", a => a.Item2.Key, a => a.Item2.Value)); } if (!IsEncrypted) { b.AppendLine(); try { b.Append(Filesystem.ToString(formatted)); } catch (Exception e) { b.AppendLine($"Failed to get XvdFilesystem info, error: {e}"); } } else { b.AppendLine($"Cannot get XvdFilesystem from encrypted package"); } return(b.ToString()); }