Example #1
0
        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());
        }
Example #2
0
        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);
        }
Example #3
0
        public bool VerifyHashTree()
        {
            if (!IsDataIntegrityEnabled)
            {
                return(true);
            }

            _io.Stream.Position = (long)HashTreeOffset;
            byte[] hash = HashUtils.ComputeSha256(_io.Reader.ReadBytes((int)PAGE_SIZE));
            if (!Header.TopHashBlockHash.IsEqualTo(hash))
            {
                return(false);
            }

            if (HashTreeLevels == 1)
            {
                return(true);
            }

            var   blocksPerLevel   = 0xAA;
            ulong topHashTreeBlock = 0;
            uint  hashTreeLevel    = 1;

            while (hashTreeLevel < HashTreeLevels)
            {
                uint dataBlockNum = 0;
                if (Header.NumberOfHashedPages != 0)
                {
                    while (dataBlockNum < Header.NumberOfHashedPages)
                    {
                        _io.Stream.Position = (long)CalculateHashEntryOffsetForBlock(dataBlockNum, hashTreeLevel - 1);
                        byte[] blockHash = HashUtils.ComputeSha256(_io.Reader.ReadBytes((int)PAGE_SIZE));
                        Array.Resize(ref blockHash, (int)HASH_ENTRY_LENGTH);

                        var upperHashBlockOffset = CalculateHashEntryOffsetForBlock(dataBlockNum, hashTreeLevel);
                        topHashTreeBlock    = XvdMath.OffsetToPageNumber(upperHashBlockOffset - HashTreeOffset);
                        _io.Stream.Position = (long)upperHashBlockOffset;

                        byte[] expectedHash = _io.Reader.ReadBytes((int)HASH_ENTRY_LENGTH);
                        if (!expectedHash.IsEqualTo(blockHash))
                        {
                            // wrong hash
                            return(false);
                        }
                        dataBlockNum += (uint)blocksPerLevel;
                    }
                }
                hashTreeLevel++;
                blocksPerLevel = blocksPerLevel * 0xAA;
            }
            if (topHashTreeBlock != 0)
            {
                Console.WriteLine(@"Top level hash page calculated to be at {0}, should be 0!", topHashTreeBlock);
            }
            return(true);
        }
Example #4
0
        internal bool RemoveData(ulong offset, ulong numPages)
        {
            var page   = XvdMath.OffsetToPageNumber(offset);
            var length = numPages * PAGE_SIZE;

            _io.Stream.Position = (long)offset;
            if (!_io.DeleteBytes((long)length))
            {
                return(false);
            }

            if (!IsXvcFile)
            {
                return(true);
            }

            if (XvcInfo.InitialPlayOffset > offset)
            {
                XvcInfo.InitialPlayOffset -= length;
            }

            if (XvcInfo.PreviewOffset > offset)
            {
                XvcInfo.PreviewOffset -= length;
            }

            for (int i = 0; i < RegionHeaders.Count; i++)
            {
                var region = RegionHeaders[i];
                region.Hash = 0; // ???

                if (offset >= region.Offset && region.Offset + region.Length > offset)
                {
                    region.Length -= length; // offset is part of region, reduce length
                }
                else if (region.Offset > offset)
                {
                    region.Offset -= length; // offset is before region, reduce offset
                }
                RegionHeaders[i] = region;   // region is a copy instead of a reference due to it being a struct, so we have to replace the original data ourselves
            }

            for (int i = 0; i < UpdateSegments.Count; i++)
            {
                var segment = UpdateSegments[i];
                if (segment.PageNum < page)
                {
                    continue;
                }

                segment.PageNum  -= (uint)numPages;
                UpdateSegments[i] = segment;
            }

            return(true);
        }
Example #5
0
        internal bool AddData(ulong offset, ulong numPages)
        {
            var page   = XvdMath.OffsetToPageNumber(offset);
            var length = numPages * PAGE_SIZE;

            _io.Stream.Position = (long)offset;
            if (!_io.AddBytes((long)length))
            {
                return(false);
            }

            if (!IsXvcFile)
            {
                return(true);
            }

            if (XvcInfo.InitialPlayOffset > offset)
            {
                XvcInfo.InitialPlayOffset += length;
            }

            if (XvcInfo.PreviewOffset > offset)
            {
                XvcInfo.PreviewOffset += length;
            }

            for (int i = 0; i < RegionHeaders.Count; i++)
            {
                var region = RegionHeaders[i];
                region.Hash = 0; // ???

                if (offset >= region.Offset && region.Offset + region.Length > offset)
                {
                    region.Length += length; // offset is part of region, add to length
                }
                else if (region.Offset > offset)
                {
                    region.Offset += length; // offset is before region, add to offset
                }
                RegionHeaders[i] = region;
            }

            for (int i = 0; i < UpdateSegments.Count; i++)
            {
                var segment = UpdateSegments[i];
                if (segment.PageNum < page)
                {
                    continue;
                }

                segment.PageNum  += (uint)numPages;
                UpdateSegments[i] = segment;
            }

            return(true);
        }
Example #6
0
        internal bool CryptSectionXts(bool encrypt, byte[] key, uint headerId, ulong offset, ulong length)
        {
            var   startPage = XvdMath.OffsetToPageNumber(offset - UserDataOffset);
            ulong numPages  = XvdMath.BytesToPages(length);

            // Pre-read data unit numbers to minimize needing to seek around the file
            List <uint> dataUnits = null;

            if (IsDataIntegrityEnabled)
            {
                dataUnits = new List <uint>();
                for (uint page = 0; page < numPages; page++)
                {
                    // fetch dataUnit from hash table entry for this page
                    // TODO: seems we'll have to insert dataUnit when re-adding hashtables...

                    // last 4 bytes of hash entry = dataUnit
                    _io.Stream.Position = (long)CalculateHashEntryOffsetForBlock(startPage + page, 0) + 0x14;
                    dataUnits.Add(_io.Reader.ReadUInt32());
                }
            }

            var tweakAesKey = new byte[0x10];
            var dataAesKey  = new byte[0x10];

            var tweak = new byte[0x10];

            // Split tweak- / Data AES key
            Array.Copy(key, tweakAesKey, 0x10);
            Array.Copy(key, 0x10, dataAesKey, 0, 0x10);

            // Copy VDUID and header Id as tweak
            var headerIdBytes = BitConverter.GetBytes(headerId);

            Array.Copy(Header.VDUID, 0, tweak, 0x8, 0x8);
            Array.Copy(headerIdBytes, 0, tweak, 0x4, 0x4);

            var cipher = new AesXtsTransform(tweak, dataAesKey, tweakAesKey, encrypt);

            // Perform crypto!
            _io.Stream.Position = (long)offset;
            for (uint page = 0; page < numPages; page++)
            {
                var transformedData = new byte[PAGE_SIZE];

                var pageOffset = _io.Stream.Position;
                var origData   = _io.Reader.ReadBytes((int)PAGE_SIZE);

                cipher.TransformDataUnit(origData, 0, origData.Length, transformedData, 0, dataUnits?[(int)page] ?? page);

                _io.Stream.Position = pageOffset;
                _io.Writer.Write(transformedData);
            }

            return(true);
        }
Example #7
0
        uint[] GetAllBATEntries()
        {
            var batEntryCount = XvdMath.BytesToBlocks(Header.DriveSize);

            uint[] BatEntries = new uint[batEntryCount];

            for (ulong i = 0; i < batEntryCount; i++)
            {
                BatEntries[i] = ReadBat(i);
            }

            return(BatEntries);
        }
Example #8
0
        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);
        }
Example #9
0
        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());
        }
Example #10
0
 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");
     }
 }
Example #11
0
        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));
        }
Example #12
0
        public bool VerifyXvcHash(bool rehash = false)
        {
            if (!IsXvcFile)
            {
                return(true);
            }

            ulong hashTreeSize = HashTreePageCount * PAGE_SIZE;

            var ms   = new MemoryStream();
            var msIo = new IO(ms);

            msIo.Writer.WriteStruct(XvcInfo);

            // fix region headers to match pre-hashtable
            for (int i = 0; i < XvcInfo.RegionCount; i++)
            {
                var region = RegionHeaders[i];
                region.Hash = 0;

                if (IsDataIntegrityEnabled)
                {
                    if (HashTreeOffset >= region.Offset && region.Offset + region.Length > HashTreeOffset)
                    {
                        region.Length -= hashTreeSize;
                    }
                    else if (region.Offset > HashTreeOffset)
                    {
                        region.Offset -= hashTreeSize;
                    }
                }

                msIo.Writer.WriteStruct(region);
            }

            for (int i = 0; i < XvcInfo.UpdateSegmentCount; i++)
            {
                var segment = UpdateSegments[i];

                var hashTreeEnd = XvdMath.BytesToPages(HashTreeOffset) + HashTreePageCount;
                if (segment.PageNum >= hashTreeEnd)
                {
                    segment.PageNum -= (uint)HashTreePageCount;
                }

                segment.Hash = 0;

                msIo.Writer.WriteStruct(segment);
            }

            if (RegionSpecifiers != null)
            {
                for (int i = 0; i < XvcInfo.RegionSpecifierCount; i++)
                {
                    msIo.Writer.WriteStruct(RegionSpecifiers[i]);
                }
            }

            if (Header.XvcDataLength > msIo.Stream.Length)
            {
                msIo.Stream.SetLength(Header.XvcDataLength);
            }

            if (IsDataIntegrityEnabled)
            {
                // remove hash table offset from the special regions
                if (XvcInfo.InitialPlayOffset > HashTreeOffset)
                {
                    msIo.Stream.Position = 0xD28;
                    msIo.Writer.Write(XvcInfo.InitialPlayOffset - hashTreeSize);
                }

                if (XvcInfo.PreviewOffset > HashTreeOffset)
                {
                    msIo.Stream.Position = 0xD40;
                    msIo.Writer.Write(XvcInfo.PreviewOffset - hashTreeSize);
                }
            }

            byte[] xvcData = ms.ToArray();
            msIo.Dispose();
            byte[] hash    = HashUtils.ComputeSha256(xvcData);
            bool   isValid = Header.OriginalXvcDataHash.IsEqualTo(hash);

            if (rehash)
            {
                Header.OriginalXvcDataHash = hash;
            }

            return(isValid); //todo: investigate why this gets the correct hash for dev XVCs but fails for retail ones, might be to do with retail XVC data having a content ID that doesn't match with VDUID/UDUID
        }
Example #13
0
        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());
        }