in net/JetBrains.FormatRipper/src/MachO/MachOFile.cs [158:587]
public static unsafe MachOFile Parse(Stream stream, Mode mode = Mode.Default)
{
Section Read(StreamRange imageRange, MH magic)
{
if (magic is not (MH.MH_MAGIC or MH.MH_MAGIC_64 or MH.MH_CIGAM or MH.MH_CIGAM_64))
throw new FormatException("Unknown Mach-O magic numbers");
var isLittleEndian = magic is MH.MH_MAGIC or MH.MH_MAGIC_64;
var needSwap = BitConverter.IsLittleEndian != isLittleEndian;
uint GetU4(uint v) => needSwap ? MemoryUtil.SwapU4(v) : v;
ulong GetU8(ulong v) => needSwap ? MemoryUtil.SwapU8(v) : v;
var excludeRanges = new List<StreamRange>();
LoadCommandsInfo ReadLoadCommands(long cmdOffset, uint nCmds, uint sizeOfCmds)
{
var hasSignature = false;
SignatureType signatureType = SignatureType.None;
byte[]? codeDirectoryBlob = null;
byte[]? cmsSignatureBlob = null;
byte[]? entitlements = null;
byte[]? entitlementsDer = null;
List<HashVerificationUnit> hashVerificationUnits = new List<HashVerificationUnit>();
List<CDHash> cdHashes = new List<CDHash>();
uint commandNumber = 0;
var sectionSignatureTransferData = new MachOSectionSignatureTransferData()
{
NumberOfLoadCommands = nCmds,
SizeOfLoadCommands = sizeOfCmds,
};
fixed (byte* buf = StreamUtil.ReadBytes(stream, checked((int)sizeOfCmds)))
{
for (var cmdPtr = buf; commandNumber++ < nCmds;)
{
load_command lc;
MemoryUtil.CopyBytes(cmdPtr, (byte*)&lc, sizeof(load_command));
var payloadLcPtr = cmdPtr + sizeof(load_command);
switch ((LC)GetU4(lc.cmd))
{
case LC.LC_SEGMENT:
{
segment_command sc;
MemoryUtil.CopyBytes(payloadLcPtr, (byte*)&sc, sizeof(segment_command));
sectionSignatureTransferData.LastLinkeditCommandNumber = commandNumber;
sectionSignatureTransferData.LastLinkeditVmSize32 = GetU4(sc.vmsize);
sectionSignatureTransferData.LastLinkeditFileSize32 = GetU4(sc.filesize);
if ((mode & Mode.ComputeHashInfo) == Mode.ComputeHashInfo)
{
var segNameBuf = MemoryUtil.CopyBytes(sc.segname, 16);
var segName = new string(Encoding.UTF8.GetChars(segNameBuf, 0, MemoryUtil.GetAsciiStringZSize(segNameBuf)));
if (segName == SEG.SEG_LINKEDIT)
{
excludeRanges.Add(new StreamRange(checked(cmdOffset + (payloadLcPtr - buf) + ((byte*)&sc.vmsize - (byte*)&sc)), sizeof(uint)));
excludeRanges.Add(new StreamRange(checked(cmdOffset + (payloadLcPtr - buf) + ((byte*)&sc.filesize - (byte*)&sc)), sizeof(uint)));
}
}
break;
}
case LC.LC_SEGMENT_64:
{
segment_command_64 sc;
MemoryUtil.CopyBytes(payloadLcPtr, (byte*)&sc, sizeof(segment_command_64));
sectionSignatureTransferData.LastLinkeditCommandNumber = commandNumber;
sectionSignatureTransferData.LastLinkeditVmSize64 = GetU8(sc.vmsize);
sectionSignatureTransferData.LastLinkeditFileSize64 = GetU8(sc.filesize);
if ((mode & Mode.ComputeHashInfo) == Mode.ComputeHashInfo)
{
var segNameBuf = MemoryUtil.CopyBytes(sc.segname, 16);
var segName = new string(Encoding.UTF8.GetChars(segNameBuf, 0, MemoryUtil.GetAsciiStringZSize(segNameBuf)));
if (segName == SEG.SEG_LINKEDIT)
{
excludeRanges.Add(new StreamRange(checked(cmdOffset + (payloadLcPtr - buf) + ((byte*)&sc.vmsize - (byte*)&sc)), sizeof(ulong)));
excludeRanges.Add(new StreamRange(checked(cmdOffset + (payloadLcPtr - buf) + ((byte*)&sc.filesize - (byte*)&sc)), sizeof(ulong)));
}
}
break;
}
case LC.LC_CODE_SIGNATURE:
{
linkedit_data_command ldc;
MemoryUtil.CopyBytes(payloadLcPtr, (byte*)&ldc, sizeof(linkedit_data_command));
if ((mode & Mode.ComputeHashInfo) == Mode.ComputeHashInfo)
{
excludeRanges.Add(new StreamRange(checked(cmdOffset + (cmdPtr - buf)), GetU4(lc.cmdsize)));
excludeRanges.Add(new StreamRange(GetU4(ldc.dataoff), GetU4(ldc.datasize)));
}
sectionSignatureTransferData.LcCodeSignatureSize = GetU4(lc.cmdsize);
sectionSignatureTransferData.LinkEditDataOffset = GetU4(ldc.dataoff);
sectionSignatureTransferData.LinkEditDataSize = GetU4(ldc.datasize);
if ((mode & Mode.SignatureData) == Mode.SignatureData)
{
stream.Position = checked(imageRange.Position + GetU4(ldc.dataoff));
sectionSignatureTransferData.SignatureBlob = StreamUtil.ReadBytes(stream, checked((int)GetU4(ldc.datasize)));
stream.Position = checked(imageRange.Position + GetU4(ldc.dataoff));
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 Mach-O code embedded signature magic");
var csLength = MemoryUtil.GetBeU4(cssb.length);
if (csLength < sizeof(CS_SuperBlob))
throw new FormatException("Too small Mach-O 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 + GetU4(ldc.dataoff) + 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 Mach-O code directory signature magic");
var cscdLength = MemoryUtil.GetBeU4(cscd.length);
byte[] currentCodeDirectoryBlob = MemoryUtil.CopyBytes(csOffsetPtr, checked((int)cscdLength));
if (signatureType == SignatureType.None)
signatureType = SignatureType.AdHoc;
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 + GetU4(ldc.dataoff) + 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 Mach-O blob wrapper signature magic");
var csbLength = MemoryUtil.GetBeU4(csb.length);
if (csbLength < sizeof(CS_Blob))
throw new FormatException("Too small Mach-O cms signature blob length");
cmsSignatureBlob = MemoryUtil.CopyBytes(csOffsetPtr + sizeof(CS_Blob), checked((int)csbLength - sizeof(CS_Blob)));
signatureType = SignatureType.Regular;
}
break;
case CSSLOT.CSSLOT_ENTITLEMENTS:
{
CS_Entitlements csent;
MemoryUtil.CopyBytes(csOffsetPtr, (byte*)&csent, sizeof(CS_Entitlements));
CSMAGIC entitlementsMagic = (CSMAGIC)MemoryUtil.GetBeU4(csent.magic);
if (entitlementsMagic != CSMAGIC.CSMAGIC_EMBEDDED_ENTITLEMENTS)
throw new FormatException($"Invalid Mach-O entitlements magic. Expected {CSMAGIC.CSMAGIC_EMBEDDED_ENTITLEMENTS.ToString("X")} but got {entitlementsMagic.ToString("X")}");
uint csentLength = MemoryUtil.GetBeU4(csent.length);
entitlements = MemoryUtil.CopyBytes(csOffsetPtr + sizeof(CS_Entitlements), checked((int)csentLength - sizeof(CS_Entitlements)));
}
break;
case CSSLOT.CSSLOT_ENTITLEMENTS_DER:
{
CS_Entitlements csent;
MemoryUtil.CopyBytes(csOffsetPtr, (byte*)&csent, sizeof(CS_Entitlements));
CSMAGIC entitlementsMagic = (CSMAGIC)MemoryUtil.GetBeU4(csent.magic);
if (entitlementsMagic != CSMAGIC.CSMAGIC_EMBEDDED_ENTITLEMENTS_DER)
throw new FormatException($"Invalid Mach-O der-encoded entitlements magic. Expected {CSMAGIC.CSMAGIC_EMBEDDED_ENTITLEMENTS_DER.ToString("X")} but got {entitlementsMagic.ToString("X")}");
uint csentLength = MemoryUtil.GetBeU4(csent.length);
entitlementsDer = MemoryUtil.CopyBytes(csOffsetPtr + sizeof(CS_Entitlements), checked((int)csentLength - sizeof(CS_Entitlements)));
}
break;
}
}
}
}
}
hasSignature = true;
break;
}
cmdPtr += GetU4(lc.cmdsize);
}
if ((mode & Mode.ComputeHashInfo) == Mode.ComputeHashInfo)
if (!hasSignature)
excludeRanges.Add(new StreamRange(checked(cmdOffset + sizeOfCmds), sizeof(load_command) + sizeof(linkedit_data_command)));
}
return new(
hasSignature,
signatureType,
new SignatureData(codeDirectoryBlob, cmsSignatureBlob),
hashVerificationUnits,
cdHashes,
(mode & Mode.SignatureData) == Mode.SignatureData && signatureType != SignatureType.None ? sectionSignatureTransferData : null,
entitlements,
entitlementsDer);
}
int GetZeroPadding(bool hasCodeSignature)
{
if (hasCodeSignature)
return 0;
var count = (int)(imageRange.Size % 16);
return count == 0 ? 0 : 16 - count;
}
if (magic is MH.MH_MAGIC_64 or MH.MH_CIGAM_64)
{
mach_header_64 mh;
StreamUtil.ReadBytes(stream, (byte*)&mh, sizeof(mach_header_64));
if ((mode & Mode.ComputeHashInfo) == Mode.ComputeHashInfo)
{
excludeRanges.Add(new StreamRange(checked(sizeof(MH) + ((byte*)&mh.ncmds - (byte*)&mh)), sizeof(uint)));
excludeRanges.Add(new StreamRange(checked(sizeof(MH) + ((byte*)&mh.sizeofcmds - (byte*)&mh)), sizeof(uint)));
}
var loadCommands = ReadLoadCommands(sizeof(MH) + sizeof(mach_header_64), GetU4(mh.ncmds), GetU4(mh.sizeofcmds));
ComputeHashInfo? computeHashInfo = null;
if ((mode & Mode.ComputeHashInfo) == Mode.ComputeHashInfo)
{
StreamRangeUtil.Sort(excludeRanges);
var sortedHashIncludeRanges = StreamRangeUtil.Invert(imageRange.Size, excludeRanges);
StreamRangeUtil.MergeNeighbors(sortedHashIncludeRanges);
computeHashInfo = new ComputeHashInfo(imageRange.Position, sortedHashIncludeRanges, GetZeroPadding(loadCommands.HasSignature));
}
return new Section(
isLittleEndian,
(CPU_TYPE)GetU4(mh.cputype),
(CPU_SUBTYPE)GetU4(mh.cpusubtype),
(MH_FileType)GetU4(mh.filetype),
(MH_Flags)GetU4(mh.flags),
loadCommands.HasSignature,
loadCommands.SignatureType,
loadCommands.SignatureData,
computeHashInfo,
loadCommands.HashVerificationUnits,
loadCommands.CDHashes,
loadCommands.SectionSignatureTransferData,
loadCommands.Entitlements,
loadCommands.EntitlementsDer);
}
else
{
mach_header mh;
StreamUtil.ReadBytes(stream, (byte*)&mh, sizeof(mach_header));
if ((mode & Mode.ComputeHashInfo) == Mode.ComputeHashInfo)
{
excludeRanges.Add(new StreamRange(checked(sizeof(MH) + ((byte*)&mh.ncmds - (byte*)&mh)), sizeof(uint)));
excludeRanges.Add(new StreamRange(checked(sizeof(MH) + ((byte*)&mh.sizeofcmds - (byte*)&mh)), sizeof(uint)));
}
var loadCommands = ReadLoadCommands(sizeof(MH) + sizeof(mach_header), GetU4(mh.ncmds), GetU4(mh.sizeofcmds));
ComputeHashInfo? computeHashInfo = null;
if ((mode & Mode.ComputeHashInfo) == Mode.ComputeHashInfo)
{
StreamRangeUtil.Sort(excludeRanges);
var sortedHashIncludeRanges = StreamRangeUtil.Invert(imageRange.Size, excludeRanges);
StreamRangeUtil.MergeNeighbors(sortedHashIncludeRanges);
computeHashInfo = new ComputeHashInfo(imageRange.Position, sortedHashIncludeRanges, GetZeroPadding(loadCommands.HasSignature));
}
return new Section(
isLittleEndian,
(CPU_TYPE)GetU4(mh.cputype),
(CPU_SUBTYPE)GetU4(mh.cpusubtype),
(MH_FileType)GetU4(mh.filetype),
(MH_Flags)GetU4(mh.flags),
loadCommands.HasSignature,
loadCommands.SignatureType,
loadCommands.SignatureData,
computeHashInfo,
loadCommands.HashVerificationUnits,
loadCommands.CDHashes,
loadCommands.SectionSignatureTransferData,
loadCommands.Entitlements,
loadCommands.EntitlementsDer);
}
}
stream.Position = 0;
uint rawMagic;
StreamUtil.ReadBytes(stream, (byte*)&rawMagic, sizeof(uint));
var magic = (MH)MemoryUtil.GetLeU4(rawMagic);
if (magic is MH.FAT_MAGIC or MH.FAT_MAGIC_64 or MH.FAT_CIGAM or MH.FAT_CIGAM_64)
{
var isFatLittleEndian = magic is MH.FAT_MAGIC or MH.FAT_MAGIC_64;
var needSwap = BitConverter.IsLittleEndian != isFatLittleEndian;
uint GetU4(uint v) => needSwap ? MemoryUtil.SwapU4(v) : v;
ulong GetU8(ulong v) => needSwap ? MemoryUtil.SwapU8(v) : v;
fat_header fh;
StreamUtil.ReadBytes(stream, (byte*)&fh, sizeof(fat_header));
var nFatArch = GetU4(fh.nfat_arch);
var sections = new Section[nFatArch];
if (magic is MH.FAT_CIGAM_64 or MH.FAT_MAGIC_64)
{
var fatNodes = new fat_arch_64[checked((int)nFatArch)];
fixed (fat_arch_64* ptr = fatNodes)
StreamUtil.ReadBytes(stream, (byte*)ptr, checked((int)nFatArch * sizeof(fat_arch_64)));
for (var n = 0; n < nFatArch; n++)
{
var position = checked((long)GetU8(fatNodes[n].offset));
stream.Position = position;
uint rawSubMagic;
StreamUtil.ReadBytes(stream, (byte*)&rawSubMagic, sizeof(uint));
var subMagic = (MH)MemoryUtil.GetLeU4(rawSubMagic);
sections[n] = Read(new StreamRange(position, checked((long)GetU8(fatNodes[n].size))), subMagic);
if (sections[n].CpuType != (CPU_TYPE)GetU4(fatNodes[n].cputype))
throw new FormatException("Inconsistent cpu type in fat header");
if (sections[n].CpuSubType != (CPU_SUBTYPE)GetU4(fatNodes[n].cpusubtype))
throw new FormatException("Inconsistent cpu subtype in fat header");
}
}
else
{
var fatNodes = new fat_arch[checked((int)nFatArch)];
fixed (fat_arch* ptr = fatNodes)
StreamUtil.ReadBytes(stream, (byte*)ptr, checked((int)nFatArch * sizeof(fat_arch)));
for (var n = 0; n < nFatArch; n++)
{
var position = GetU4(fatNodes[n].offset);
stream.Position = position;
uint rawSubMagic;
StreamUtil.ReadBytes(stream, (byte*)&rawSubMagic, sizeof(uint));
var subMagic = (MH)MemoryUtil.GetLeU4(rawSubMagic);
sections[n] = Read(new StreamRange(position, GetU4(fatNodes[n].size)), subMagic);
if (sections[n].CpuType != (CPU_TYPE)GetU4(fatNodes[n].cputype))
throw new FormatException("Inconsistent cpu type in fat header");
if (sections[n].CpuSubType != (CPU_SUBTYPE)GetU4(fatNodes[n].cpusubtype))
throw new FormatException("Inconsistent cpu subtype in fat header");
}
}
return new(isFatLittleEndian, sections);
}
return new(null, new[] { Read(new StreamRange(0, stream.Length), magic) });
}