net/JetBrains.FormatRipper/src/Dmg/DmgFile.cs (193 lines of code) (raw):

using System; using System.Collections.Generic; using System.IO; using JetBrains.FormatRipper.Dmg.Impl; using JetBrains.FormatRipper.Impl; using JetBrains.FormatRipper.MachO; using JetBrains.FormatRipper.MachO.Impl; namespace JetBrains.FormatRipper.Dmg { public sealed class DmgFile { public readonly bool HasSignature; public readonly SignatureData SignatureData; public readonly IEnumerable<HashVerificationUnit> HashVerificationUnits; public readonly IEnumerable<CDHash> CDHashes; public readonly IDmgSignatureTransferData? SignatureTransferData; [Flags] public enum Mode : uint { Default = 0x0, SignatureData = 0x1 } private DmgFile(bool hasSignature, SignatureData signatureData, IEnumerable<HashVerificationUnit> hashVerificationUnits, IEnumerable<CDHash> cdHashes, IDmgSignatureTransferData? signatureTransferData) { HasSignature = hasSignature; SignatureData = signatureData; SignatureTransferData = signatureTransferData; HashVerificationUnits = hashVerificationUnits; CDHashes = cdHashes; } public static unsafe bool Is(Stream stream) { if (stream.Length < sizeof(UDIF)) return false; stream.Seek(-sizeof(UDIF), SeekOrigin.End); UDIF udif; StreamUtil.ReadBytes(stream, (byte*)&udif, sizeof(UDIF)); if ((DmgMagic)MemoryUtil.GetBeU4(udif.Magic) != DmgMagic.KOLY) return false; if (MemoryUtil.GetBeU4(udif.HeaderSize) != sizeof(UDIF)) return false; ulong streamLength = checked((ulong)stream.Length); if (udif.PlistOffset == 0 || udif.PlistLength == 0) return false; if (MemoryUtil.GetBeU8(udif.PlistOffset) + MemoryUtil.GetBeU8(udif.PlistLength) > streamLength) return false; if (MemoryUtil.GetBeU8(udif.CodeSignatureOffset) + MemoryUtil.GetBeU8(udif.CodeSignatureLength) > streamLength) return false; return true; } public static unsafe DmgFile Parse(Stream stream, Mode mode = Mode.Default) { if (stream.Length < sizeof(UDIF)) throw new ArgumentException("Provided stream is too short to be a valid DMG file"); stream.Seek(-sizeof(UDIF), SeekOrigin.End); UDIF udif; StreamUtil.ReadBytes(stream, (byte*)&udif, sizeof(UDIF)); if ((DmgMagic)MemoryUtil.GetBeU4(udif.Magic) != DmgMagic.KOLY) throw new FormatException("Invalid DMG file UDIF structure magic"); ulong signatureOffset = MemoryUtil.GetBeU8(udif.CodeSignatureOffset); ulong signatureLength = MemoryUtil.GetBeU8(udif.CodeSignatureLength); if (signatureOffset + signatureLength > (ulong)stream.Length) throw new FormatException($"Invalid signature position. Signature position ({signatureOffset}) + signature length ({signatureLength}) is greater that stream length ({stream.Length})"); var hasSignature = signatureLength != 0; byte[]? codeDirectoryBlob = null; byte[]? cmsSignatureBlob = null; IDmgSignatureTransferData? signatureTransferData = null; List<HashVerificationUnit> hashVerificationUnits = new List<HashVerificationUnit>(); List<CDHash> cdHashes = new List<CDHash>(); if ((mode & Mode.SignatureData) == Mode.SignatureData && hasSignature) { var imageRange = new StreamRange(0, stream.Length); stream.Position = checked(imageRange.Position + (long)signatureOffset); signatureTransferData = new DmgSignatureTransferData() { SignatureOffset = checked((long)signatureOffset), SignatureLength = checked((long)signatureLength), SignatureBlob = StreamUtil.ReadBytes(stream, checked((int)signatureLength)), }; stream.Position = checked(imageRange.Position + (long)signatureOffset); CS_SuperBlob cssb; StreamUtil.ReadBytes(stream, (byte*)&cssb, sizeof(CS_SuperBlob)); if ((CSMAGIC)MemoryUtil.GetBeU4(cssb.magic) != CSMAGIC.CSMAGIC_EMBEDDED_SIGNATURE) throw new FormatException("Invalid DMG code embedded signature magic"); var csLength = MemoryUtil.GetBeU4(cssb.length); if (csLength < sizeof(CS_SuperBlob)) throw new FormatException("Too small DMG code signature super blob"); var csCount = MemoryUtil.GetBeU4(cssb.count); fixed (byte* scBuf = StreamUtil.ReadBytes(stream, checked((int)csLength - sizeof(CS_SuperBlob)))) { ComputeHashInfo[] specialSlotPositions = new ComputeHashInfo[CSSLOT.CSSLOT_HASHABLE_ENTRIES_MAX - 1]; for (int superBlobEntryIndex = 0; superBlobEntryIndex < csCount; superBlobEntryIndex++) { var scPtr = scBuf + superBlobEntryIndex * sizeof(CS_BlobIndex); CS_BlobIndex csbi; MemoryUtil.CopyBytes(scPtr, (byte*)&csbi, sizeof(CS_BlobIndex)); uint slotType = MemoryUtil.GetBeU4(csbi.type); if (slotType >= CSSLOT.CSSLOT_INFOSLOT && slotType <= CSSLOT.CSSLOT_LIBRARY_CONSTRAINT) { uint offset = MemoryUtil.GetBeU4(csbi.offset); var csOffsetPtr = scBuf + offset - sizeof(CS_SuperBlob); CS_Blob csb; MemoryUtil.CopyBytes(csOffsetPtr, (byte*)&csb, sizeof(CS_Blob)); specialSlotPositions[slotType - 1] = new ComputeHashInfo(0, new[] { new StreamRange(checked(imageRange.Position + (long)signatureOffset + offset), MemoryUtil.GetBeU4(csb.length)) }, 0); } } for (var scPtr = scBuf; csCount-- > 0; scPtr += sizeof(CS_BlobIndex)) { CS_BlobIndex csbi; MemoryUtil.CopyBytes(scPtr, (byte*)&csbi, sizeof(CS_BlobIndex)); uint offset = MemoryUtil.GetBeU4(csbi.offset); var csOffsetPtr = scBuf + offset - sizeof(CS_SuperBlob); uint slotType = MemoryUtil.GetBeU4(csbi.type); switch (slotType) { case CSSLOT.CSSLOT_CODEDIRECTORY: case CSSLOT.CSSLOT_ALTERNATE_CODEDIRECTORIES: case CSSLOT.CSSLOT_ALTERNATE_CODEDIRECTORIES1: case CSSLOT.CSSLOT_ALTERNATE_CODEDIRECTORIES2: case CSSLOT.CSSLOT_ALTERNATE_CODEDIRECTORIES3: case CSSLOT.CSSLOT_ALTERNATE_CODEDIRECTORIES4: { CS_CodeDirectory cscd; MemoryUtil.CopyBytes(csOffsetPtr, (byte*)&cscd, sizeof(CS_CodeDirectory)); if ((CSMAGIC)MemoryUtil.GetBeU4(cscd.magic) != CSMAGIC.CSMAGIC_CODEDIRECTORY) throw new FormatException("Invalid DMG code directory signature magic"); var cscdLength = MemoryUtil.GetBeU4(cscd.length); byte[] currentCodeDirectoryBlob = MemoryUtil.CopyBytes(csOffsetPtr, checked((int)cscdLength)); if (slotType == CSSLOT.CSSLOT_CODEDIRECTORY) codeDirectoryBlob = currentCodeDirectoryBlob; int codeSlots = checked((int)MemoryUtil.GetBeU4(cscd.nCodeSlots)); int specialSlots = checked((int)MemoryUtil.GetBeU4(cscd.nSpecialSlots)); uint zeroHashOffset = MemoryUtil.GetBeU4(cscd.hashOffset); long codeLimit = MemoryUtil.GetBeU4(cscd.codeLimit); int pageSize = cscd.pageSize > 0 ? 1 << cscd.pageSize : 0; string hashName = CS_HASHTYPE.GetHashName(cscd.hashType); var cdHash = new CDHash(hashName, new ComputeHashInfo(0, new[] { new StreamRange(checked(imageRange.Position + (long)signatureOffset + offset), cscdLength) }, 0)); cdHashes.Add(cdHash); for (int i = 0; i < codeSlots; i++) { byte[] hash = new byte[cscd.hashSize]; Array.Copy(currentCodeDirectoryBlob, checked((int)zeroHashOffset + i * cscd.hashSize), hash, 0, cscd.hashSize); long pageStart = i * pageSize; long currentPageSize; if (pageSize > 0) currentPageSize = pageStart + pageSize > codeLimit ? codeLimit - pageStart : pageSize; else currentPageSize = codeLimit - pageStart; var computeHashInfo = new ComputeHashInfo(0, new[] { new StreamRange(pageStart + imageRange.Position, currentPageSize) }, 0); hashVerificationUnits.Add(new HashVerificationUnit(hashName, hash, computeHashInfo)); } for (uint i = 1; i <= specialSlots; i++) { byte[] hash = new byte[cscd.hashSize]; Array.Copy(currentCodeDirectoryBlob, checked((int)(zeroHashOffset - i * cscd.hashSize)), hash, 0, cscd.hashSize); if (specialSlotPositions[i - 1] != null) hashVerificationUnits.Add(new HashVerificationUnit(hashName, hash, specialSlotPositions[i - 1])); } } break; case CSSLOT.CSSLOT_CMS_SIGNATURE: { CS_Blob csb; MemoryUtil.CopyBytes(csOffsetPtr, (byte*)&csb, sizeof(CS_Blob)); if ((CSMAGIC)MemoryUtil.GetBeU4(csb.magic) != CSMAGIC.CSMAGIC_BLOBWRAPPER) throw new FormatException("Invalid DMG blob wrapper signature magic"); var csbLength = MemoryUtil.GetBeU4(csb.length); if (csbLength < sizeof(CS_Blob)) throw new FormatException("Too small DMG cms signature blob length"); cmsSignatureBlob = MemoryUtil.CopyBytes(csOffsetPtr + sizeof(CS_Blob), checked((int)csbLength - sizeof(CS_Blob))); } break; } } } } return new DmgFile(hasSignature, new SignatureData(codeDirectoryBlob, cmsSignatureBlob), hashVerificationUnits, cdHashes, signatureTransferData); } } }