in net/JetBrains.FormatRipper/src/MachO/MachOSignatureInjector.cs [179:349]
private static unsafe long TransferSectionSignature(Stream sourceStream, StreamRange sourceStreamRange, Stream outputStream, MH magic, IMachOSectionSignatureTransferData? sectionSignature)
{
if (sectionSignature == null)
throw new SignatureInjectionException($"Cannot transfer the signature for section: it is not signed in the original file");
long outputStreamInitialPosition = outputStream.Position;
long PositionFromStart() => outputStream.Position - outputStreamInitialPosition;
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;
uint rawMagic = MemoryUtil.GetLeU4((uint)magic);
StreamUtil.WriteBytes(outputStream, (byte*)&rawMagic, sizeof(uint));
uint ncmds;
uint sizeofcmds;
bool signatureLoadCommandMissing = false;
if (magic is MH.MH_MAGIC_64 or MH.MH_CIGAM_64)
{
mach_header_64 mh;
StreamUtil.ReadBytes(sourceStream, (byte*)&mh, sizeof(mach_header_64));
ncmds = GetU4(mh.ncmds);
sizeofcmds = GetU4(mh.sizeofcmds);
if (ncmds == sectionSignature.NumberOfLoadCommands - 1)
{
mh.ncmds = GetU4(sectionSignature.NumberOfLoadCommands);
mh.sizeofcmds = GetU4(sectionSignature.SizeOfLoadCommands);
signatureLoadCommandMissing = true;
}
else if (ncmds != sectionSignature.NumberOfLoadCommands)
{
throw new SignatureInjectionException($"Target file has wrong number of load commands. Expected {sectionSignature.NumberOfLoadCommands} or {sectionSignature.NumberOfLoadCommands - 1}, but found {ncmds}");
}
StreamUtil.WriteBytes(outputStream, (byte*)&mh, sizeof(mach_header_64));
}
else if (magic is MH.MH_MAGIC or MH.MH_CIGAM)
{
mach_header mh;
StreamUtil.ReadBytes(sourceStream, (byte*)&mh, sizeof(mach_header));
ncmds = GetU4(mh.ncmds);
sizeofcmds = GetU4(mh.sizeofcmds);
if (ncmds == sectionSignature.NumberOfLoadCommands - 1)
{
mh.ncmds = GetU4(sectionSignature.NumberOfLoadCommands);
mh.sizeofcmds = GetU4(sectionSignature.SizeOfLoadCommands);
signatureLoadCommandMissing = true;
}
else if (ncmds != sectionSignature.NumberOfLoadCommands)
{
throw new SignatureInjectionException($"Target file has wrong number of load commands. Expected {sectionSignature.NumberOfLoadCommands} or {sectionSignature.NumberOfLoadCommands - 1}, but found {ncmds}");
}
StreamUtil.WriteBytes(outputStream, (byte*)&mh, sizeof(mach_header));
}
else
{
throw new FormatException($"Invalid Mach-O magic: {magic.ToString("X")}");
}
uint oldSignatureOffset = 0;
uint commandNumber = 0;
fixed (byte* buf = StreamUtil.ReadBytes(sourceStream, checked((int)sizeofcmds)))
{
for (var cmdPtr = buf; commandNumber++ < ncmds;)
{
load_command lc;
MemoryUtil.CopyBytes(cmdPtr, (byte*)&lc, sizeof(load_command));
uint cmdSize = GetU4(lc.cmdsize);
var payloadLcPtr = cmdPtr + sizeof(load_command);
var commandType = (LC)GetU4(lc.cmd);
if (commandNumber == sectionSignature.LastLinkeditCommandNumber)
{
if (commandType == LC.LC_SEGMENT_64)
{
segment_command_64 sc;
MemoryUtil.CopyBytes(payloadLcPtr, (byte*)&sc, sizeof(segment_command_64));
sc.vmsize = GetU8(sectionSignature.LastLinkeditVmSize64);
sc.filesize = GetU8(sectionSignature.LastLinkeditFileSize64);
MemoryUtil.CopyBytes((byte*)&sc, payloadLcPtr, sizeof(segment_command_64));
}
else if (commandType == LC.LC_SEGMENT)
{
segment_command sc;
MemoryUtil.CopyBytes(payloadLcPtr, (byte*)&sc, sizeof(segment_command));
sc.vmsize = GetU4(sectionSignature.LastLinkeditVmSize32);
sc.filesize = GetU4(sectionSignature.LastLinkeditFileSize32);
MemoryUtil.CopyBytes((byte*)&sc, payloadLcPtr, sizeof(segment_command));
}
else
{
throw new SignatureInjectionException($"Error injecting signature. Load command number {sectionSignature.LastLinkeditCommandNumber} was supposed to be either LC_SEGMENT or LC_SEGMENT_64, but {commandType.ToString()} was found");
}
}
if (commandType == LC.LC_CODE_SIGNATURE)
{
linkedit_data_command ldc;
MemoryUtil.CopyBytes(payloadLcPtr, (byte*)&ldc, sizeof(linkedit_data_command));
oldSignatureOffset = GetU4(ldc.dataoff);
ldc.dataoff = GetU4(sectionSignature.LinkEditDataOffset);
ldc.datasize = GetU4(sectionSignature.LinkEditDataSize);
MemoryUtil.CopyBytes((byte*)&ldc, payloadLcPtr, sizeof(linkedit_data_command));
}
cmdPtr += cmdSize;
}
StreamUtil.WriteBytes(outputStream, buf, checked((int)sizeofcmds));
}
if (signatureLoadCommandMissing)
{
load_command signatureLc = new load_command()
{
cmd = GetU4((uint)LC.LC_CODE_SIGNATURE),
cmdsize = GetU4(sectionSignature.LcCodeSignatureSize),
};
StreamUtil.WriteBytes(outputStream, (byte*)&signatureLc, sizeof(load_command));
linkedit_data_command signatureLdc = new linkedit_data_command()
{
dataoff = GetU4(sectionSignature.LinkEditDataOffset),
datasize = GetU4(sectionSignature.LinkEditDataSize),
};
StreamUtil.WriteBytes(outputStream, (byte*)&signatureLdc, sizeof(linkedit_data_command));
sourceStream.Seek(sizeof(load_command) + sizeof(linkedit_data_command), SeekOrigin.Current);
}
long end = oldSignatureOffset != 0 ? sourceStreamRange.Position + oldSignatureOffset : sourceStreamRange.Position + sourceStreamRange.Size;
StreamUtil.CopyBytes(sourceStream, outputStream, end - sourceStream.Position);
if (PositionFromStart() < sectionSignature.LinkEditDataOffset)
{
long paddingBytes = sectionSignature.LinkEditDataOffset - PositionFromStart();
if (paddingBytes > MaxPaddingBytes)
throw new SignatureInjectionException($"Too many padding bytes required between payload and signature. Expected no more than {MaxPaddingBytes} bytes, but required {paddingBytes} bytes. Seems that the donor and the acceptor files are incompatible.");
byte[] padding = new byte[paddingBytes];
outputStream.Write(padding, 0, padding.Length);
}
if (PositionFromStart() == sectionSignature.LinkEditDataOffset)
{
outputStream.Write(sectionSignature.SignatureBlob, 0, sectionSignature.SignatureBlob.Length);
}
else
{
throw new SignatureInjectionException($"Failed to inject signature due to invalid output stream position. Expected {sectionSignature.LinkEditDataOffset}, got {PositionFromStart()}");
}
return PositionFromStart();
}