2764 lines
71 KiB
C#
2764 lines
71 KiB
C#
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Text;
|
|
using System.IO;
|
|
using iPhonePackager;
|
|
using System.Diagnostics;
|
|
|
|
using System.Security.Cryptography.X509Certificates;
|
|
using System.Security.Cryptography;
|
|
using System.Security.Cryptography.Pkcs;
|
|
|
|
using SysadminsLV.Asn1Parser;
|
|
using System.Xml;
|
|
|
|
/// <summary>
|
|
/// Minimal implementation of the Mach object file format
|
|
/// See: http://developer.apple.com/library/mac/#documentation/DeveloperTools/Conceptual/MachORuntime/Reference/reference.html for format details
|
|
/// And: http://www.opensource.apple.com/source/xnu/xnu-1456.1.26/EXTERNAL_HEADERS/mach-o/loader.h for constant values
|
|
///
|
|
/// Only supports 32 bit little-endian executables, and writing back out is not fully supported.
|
|
/// A few classes have support for patching individual fields.
|
|
/// </summary>
|
|
namespace MachObjectHandling
|
|
{
|
|
// Values for CpuType
|
|
public enum CpuType
|
|
{
|
|
ARCH_ABI64 = 0x1000000,
|
|
I386 = 7,
|
|
ARM = 12,
|
|
POWERPC = 18,
|
|
ARM64 = ARCH_ABI64 | ARM,
|
|
}
|
|
|
|
public class Bits
|
|
{
|
|
public enum Num
|
|
{
|
|
_32 = 32,
|
|
_64 = 64,
|
|
}
|
|
public static int Bytes(Num num)
|
|
{
|
|
return (num == Num._64) ? sizeof(UInt64) : sizeof(UInt32);
|
|
}
|
|
}
|
|
|
|
public abstract class RWContextBase
|
|
{
|
|
protected bool bIsOutput = false;
|
|
public bool bStreamLittleEndian = true;
|
|
protected Stack<long> Positions = new Stack<long>();
|
|
protected byte[] SwapBuffer = new byte[8];
|
|
|
|
// When a file is within another file, FileOffset can be set so all external operations can act as if it is just a single file.
|
|
protected long ArchiveFileOffset;
|
|
|
|
public void OpenFatArchiveAt(long AtOffset)
|
|
{
|
|
if (ArchiveFileOffset != 0)
|
|
{
|
|
throw new InvalidDataException(String.Format("Nested OpenFatAchriveAt() not supported:{0} {1} {2}", Position, ArchiveFileOffset, AtOffset));
|
|
}
|
|
|
|
// Start at beginning of file.
|
|
PushPositionAndJump(0);
|
|
ArchiveFileOffset = AtOffset;
|
|
|
|
// Cause a seek to the ArchiveFileOffset.
|
|
Position = 0;
|
|
}
|
|
public void CloseFatArchive()
|
|
{
|
|
if (ArchiveFileOffset == 0)
|
|
{
|
|
throw new InvalidDataException(String.Format("CloseFatArchive() called without Open {0} {1}.", Position, ArchiveFileOffset));
|
|
}
|
|
ArchiveFileOffset = 0;
|
|
PopPosition();
|
|
}
|
|
|
|
public void PushPositionAndJump(long NewOffset)
|
|
{
|
|
Positions.Push(Position);
|
|
Position = NewOffset;
|
|
}
|
|
|
|
public void PushPosition()
|
|
{
|
|
Positions.Push(Position);
|
|
}
|
|
|
|
public void PopPosition()
|
|
{
|
|
Position = Positions.Pop();
|
|
}
|
|
|
|
public abstract long Position
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
public void VerifyStreamPosition(long StartingStreamPosition, long JustReadCount)
|
|
{
|
|
long ExpectedPosition = StartingStreamPosition + JustReadCount;
|
|
if (Position != ExpectedPosition)
|
|
{
|
|
throw new InvalidDataException(String.Format("Stream offset is not as expected, {0} too {1} data!",
|
|
bIsOutput ? "wrote" : "read",
|
|
(Position < ExpectedPosition) ? "little" : "much"));
|
|
}
|
|
}
|
|
|
|
public void VerifyStreamPosition(ref long StreamPosition, long JustReadCount)
|
|
{
|
|
StreamPosition += JustReadCount;
|
|
VerifyStreamPosition(StreamPosition, 0);
|
|
}
|
|
}
|
|
|
|
public class ReadingContext : RWContextBase
|
|
{
|
|
protected BinaryReader SR;
|
|
|
|
public ReadingContext(BinaryReader Stream)
|
|
{
|
|
SR = Stream;
|
|
}
|
|
|
|
public override long Position
|
|
{
|
|
get { return SR.BaseStream.Position - ArchiveFileOffset; }
|
|
set { SR.BaseStream.Position = value + ArchiveFileOffset; }
|
|
}
|
|
|
|
public UInt64 ReadUInt(Bits.Num Num)
|
|
{
|
|
if (Num == Bits.Num._64)
|
|
return ReadUInt64();
|
|
else
|
|
return ReadUInt32();
|
|
}
|
|
|
|
public UInt32 ReadUInt32()
|
|
{
|
|
if (bStreamLittleEndian == BitConverter.IsLittleEndian)
|
|
{
|
|
return SR.ReadUInt32();
|
|
}
|
|
else
|
|
{
|
|
SR.Read(SwapBuffer, 0, 4);
|
|
Array.Reverse(SwapBuffer, 0, 4);
|
|
return BitConverter.ToUInt32(SwapBuffer, 0);
|
|
}
|
|
}
|
|
|
|
public UInt64 ReadUInt64()
|
|
{
|
|
if (bStreamLittleEndian == BitConverter.IsLittleEndian)
|
|
{
|
|
return SR.ReadUInt64();
|
|
}
|
|
else
|
|
{
|
|
SR.Read(SwapBuffer, 0, 8);
|
|
Array.Reverse(SwapBuffer, 0, 8);
|
|
return BitConverter.ToUInt64(SwapBuffer, 0);
|
|
}
|
|
}
|
|
|
|
public byte[] ReadBytes(int DataSize)
|
|
{
|
|
return SR.ReadBytes(DataSize);
|
|
}
|
|
|
|
public byte[] ReadBytes(UInt32 DataSize)
|
|
{
|
|
return SR.ReadBytes((int)DataSize);
|
|
}
|
|
|
|
public byte ReadByte()
|
|
{
|
|
return SR.ReadByte();
|
|
}
|
|
|
|
public string ReadFixedASCII(int Length)
|
|
{
|
|
return Utilities.ReadFixedASCII(SR, Length);
|
|
}
|
|
|
|
public string ReadASCIIZ()
|
|
{
|
|
// Find the end of the string
|
|
long SavedPosition = Position;
|
|
while (ReadByte() != 0)
|
|
{
|
|
}
|
|
int Length = (int)(Position - SavedPosition);
|
|
Position = SavedPosition;
|
|
|
|
// Read it
|
|
return ReadFixedASCII(Length);
|
|
}
|
|
}
|
|
|
|
|
|
public class WritingPhase
|
|
{
|
|
public delegate void DelayedWriteCallback(WritingContext Context);
|
|
|
|
public Queue<DelayedWriteCallback> PendingWrites = new Queue<DelayedWriteCallback>();
|
|
|
|
public void Drain(WritingContext Context)
|
|
{
|
|
if (Config.bCodeSignVerbose)
|
|
{
|
|
Console.WriteLine("Draining phase with {0} writes", PendingWrites.Count);
|
|
}
|
|
|
|
while (PendingWrites.Count > 0)
|
|
{
|
|
if (Config.bCodeSignVerbose)
|
|
{
|
|
Console.WriteLine(" One delayed write at position = 0x{0:X}", Context.Position);
|
|
}
|
|
|
|
DelayedWriteCallback Job = PendingWrites.Dequeue();
|
|
Job.Invoke(Context);
|
|
}
|
|
|
|
if (Config.bCodeSignVerbose)
|
|
{
|
|
Console.WriteLine("Finished draining phase (curpos = 0x{0:X})", Context.Position);
|
|
}
|
|
}
|
|
|
|
public bool bWorkRemaining
|
|
{
|
|
get { return PendingWrites.Count > 0; }
|
|
}
|
|
}
|
|
|
|
public class DeferredFieldU32or64
|
|
{
|
|
// The start of the section we're counting length for
|
|
protected long AnchorPoint;
|
|
|
|
// The offset the length is stored in
|
|
public long WritePoint;
|
|
|
|
public Bits.Num AddressSize;
|
|
|
|
public DeferredFieldU32or64(WritingContext Context, long Base, Bits.Num Address)
|
|
{
|
|
AnchorPoint = Base;
|
|
WritePoint = Context.Position;
|
|
|
|
AddressSize = Address;
|
|
Context.Position += Bits.Bytes(AddressSize);
|
|
}
|
|
|
|
public void Commit(WritingContext Context)
|
|
{
|
|
long CurrentPos = Context.Position;
|
|
long Length = CurrentPos - AnchorPoint;
|
|
|
|
if (AddressSize == Bits.Num._32)
|
|
{
|
|
Debug.Assert((Length >= UInt32.MinValue) && (Length <= UInt32.MaxValue));
|
|
}
|
|
|
|
Context.PushPositionAndJump(WritePoint);
|
|
Context.WriteUInt((UInt64)Length, AddressSize);
|
|
Context.PopPosition();
|
|
}
|
|
}
|
|
|
|
public class LengthFieldU32or64 : DeferredFieldU32or64
|
|
{
|
|
public LengthFieldU32or64(WritingContext Context, long BaseOffset, Bits.Num Address)
|
|
: base(Context, BaseOffset, Address)
|
|
{
|
|
}
|
|
|
|
public void Rebase(long NewBaseOffset)
|
|
{
|
|
AnchorPoint = NewBaseOffset;
|
|
}
|
|
}
|
|
|
|
public class OffsetFieldU32or64 : DeferredFieldU32or64
|
|
{
|
|
public OffsetFieldU32or64(WritingContext Context, long BaseOffset, Bits.Num Address)
|
|
: base(Context, BaseOffset, Address)
|
|
{
|
|
}
|
|
}
|
|
|
|
public class WritingContext : RWContextBase
|
|
{
|
|
public WritingPhase CurrentPhase = new WritingPhase();
|
|
|
|
Stack<WritingPhase> PendingPhases = new Stack<WritingPhase>();
|
|
|
|
public void CreateNewPhase()
|
|
{
|
|
PendingPhases.Push(CurrentPhase);
|
|
CurrentPhase = new WritingPhase();
|
|
}
|
|
|
|
public void Flush()
|
|
{
|
|
SW.Flush();
|
|
SW.BaseStream.Flush();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Processes a phase worth of work, returning false when no work remains
|
|
/// </summary>
|
|
/// <returns>Returns true if more work remains</returns>
|
|
public bool ProcessEntirePhase()
|
|
{
|
|
CurrentPhase.Drain(this);
|
|
Debug.Assert(CurrentPhase.PendingWrites.Count == 0);
|
|
|
|
if (PendingPhases.Count > 0)
|
|
{
|
|
CurrentPhase = PendingPhases.Pop();
|
|
}
|
|
|
|
return CurrentPhase.bWorkRemaining || (PendingPhases.Count > 0);
|
|
}
|
|
|
|
public LengthFieldU32or64 WriteDeferredLength(long AlreadyCountedBytes, Bits.Num Num)
|
|
{
|
|
return new LengthFieldU32or64(this, Position - AlreadyCountedBytes, Num);
|
|
}
|
|
|
|
public void CommitDeferredField(DeferredFieldU32or64 Field)
|
|
{
|
|
Field.Commit(this);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Emits an offset field that needs to be committed once the true offset is known
|
|
/// </summary>
|
|
/// <param name="AlreadyCountedBytes">The number of bytes ago that the offset is relative to. Pass in 0 to be relative to the current output position.</param>
|
|
/// <param name="Num">Number of bits this write will be.</param>
|
|
/// <returns></returns>
|
|
public OffsetFieldU32or64 WriteDeferredOffset(long AlreadyCountedBytes, Bits.Num Num)
|
|
{
|
|
return new OffsetFieldU32or64(this, Position - AlreadyCountedBytes, Num);
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Emits an offset field that needs to be committed once the true offset is known
|
|
/// </summary>
|
|
/// <param name="AlreadyCountedBytes">The number of bytes ago that the offset is relative to. Pass in 0 to be relative to the current output position.</param>
|
|
/// <param name="Num">Number of bits this write will be.</param>
|
|
/// <returns></returns>
|
|
public OffsetFieldU32or64 WriteDeferredOffsetFrom(long BasePosition, Bits.Num Num)
|
|
{
|
|
return new OffsetFieldU32or64(this, BasePosition, Num);
|
|
}
|
|
|
|
protected BinaryWriter SW;
|
|
|
|
public WritingContext(BinaryWriter Stream)
|
|
{
|
|
bIsOutput = true;
|
|
SW = Stream;
|
|
}
|
|
|
|
public override long Position
|
|
{
|
|
get { return SW.BaseStream.Position - ArchiveFileOffset; }
|
|
set { SW.BaseStream.Position = value + ArchiveFileOffset; }
|
|
}
|
|
|
|
public void CompleteWritingAndClose()
|
|
{
|
|
while (ProcessEntirePhase())
|
|
{
|
|
}
|
|
SW.Close();
|
|
}
|
|
|
|
public void WriteZeros(long NumZeros)
|
|
{
|
|
if (NumZeros > 0)
|
|
{
|
|
byte[] ZeroList = new byte[NumZeros];
|
|
|
|
//@TODO: Should be unnecessary
|
|
for (int i = 0; i < ZeroList.Length; ++i)
|
|
{
|
|
ZeroList[i] = 0;
|
|
}
|
|
|
|
Write(ZeroList);
|
|
}
|
|
}
|
|
|
|
public void WriteUInt(UInt64 Value, Bits.Num Num)
|
|
{
|
|
if (Num == Bits.Num._64)
|
|
Write(Value);
|
|
else
|
|
Write((UInt32)Value);
|
|
}
|
|
|
|
public void Write(UInt32 Value)
|
|
{
|
|
if (bStreamLittleEndian == BitConverter.IsLittleEndian)
|
|
{
|
|
SW.Write(Value);
|
|
}
|
|
else
|
|
{
|
|
byte[] Buffer = BitConverter.GetBytes(Value);
|
|
Array.Reverse(Buffer, 0, 4);
|
|
SW.Write(Buffer, 0, 4);
|
|
}
|
|
}
|
|
|
|
public void Write(UInt64 Value)
|
|
{
|
|
if (bStreamLittleEndian == BitConverter.IsLittleEndian)
|
|
{
|
|
SW.Write(Value);
|
|
}
|
|
else
|
|
{
|
|
byte[] Buffer = BitConverter.GetBytes(Value);
|
|
Array.Reverse(Buffer, 0, 8);
|
|
SW.Write(Buffer, 0, 8);
|
|
}
|
|
}
|
|
|
|
public void Write(int Value)
|
|
{
|
|
Write((UInt32)Value);
|
|
}
|
|
|
|
public void Write(byte[] Buffer)
|
|
{
|
|
SW.Write(Buffer);
|
|
}
|
|
|
|
public void Write(byte[] Buffer, int Offset, int Length)
|
|
{
|
|
SW.Write(Buffer, Offset, Length);
|
|
}
|
|
|
|
public void Write(byte Value)
|
|
{
|
|
SW.Write(Value);
|
|
}
|
|
|
|
public void WriteFixedASCII(string Value, int BufferLength)
|
|
{
|
|
Utilities.WriteFixedASCII(SW, Value, BufferLength);
|
|
}
|
|
|
|
public void WriteAbsoluteOffsetAndDelayedData(byte[] Data, long Alignment, Bits.Num Num)
|
|
{
|
|
WriteOffsetAndDelayedData(Data, Alignment, 0, Num);
|
|
}
|
|
|
|
public void WriteOffsetAndDelayedData(byte[] Data, long Alignment, long OffsetFrom, Bits.Num Num)
|
|
{
|
|
if ((Data == null) || (Data.Length == 0))
|
|
{
|
|
SW.Write((UInt32)0);
|
|
}
|
|
else
|
|
{
|
|
OffsetFieldU32or64 Offset = WriteDeferredOffsetFrom(OffsetFrom, Num);
|
|
CurrentPhase.PendingWrites.Enqueue(delegate(WritingContext Context)
|
|
{
|
|
long ModPosition = Position % Alignment;
|
|
if (ModPosition != 0)
|
|
{
|
|
WriteZeros(Alignment - ModPosition);
|
|
}
|
|
|
|
CommitDeferredField(Offset);
|
|
Write(Data);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Information about a single section within a segment
|
|
/// Warning: Contains absolute file offsets
|
|
/// </summary>
|
|
class MachSection
|
|
{
|
|
string SectionName;
|
|
string SegmentName;
|
|
UInt64 Addr;
|
|
UInt64 Size;
|
|
//UInt32 Offset;
|
|
UInt32 LogAlignment;
|
|
UInt32 RelocationOffset;
|
|
UInt32 NumRelocations;
|
|
int Flags;
|
|
UInt32 Reserved1;
|
|
UInt32 Reserved2;
|
|
// Only in 64 bit - Docs do not say so, but header file does http://llvm.org/docs/doxygen/html/Support_2MachO_8h_source.html.
|
|
UInt32 Reserved3;
|
|
|
|
Bits.Num AddressSize;
|
|
|
|
byte[] SectionData;
|
|
|
|
// These section types have an offset of 0 and no bytes of file data
|
|
const int S_ZEROFILL = 0x1;
|
|
const int S_GB_ZEROFILL = 0xC;
|
|
|
|
public MachSection(Bits.Num Num)
|
|
{
|
|
AddressSize = Num;
|
|
}
|
|
|
|
public int SectionType
|
|
{
|
|
get { return ((int)Flags) & 0xFF; }
|
|
set
|
|
{
|
|
Flags = (Flags & ~0xFF) | (value & 0xFF);
|
|
}
|
|
}
|
|
|
|
public void Read(ReadingContext SR)
|
|
{
|
|
SectionName = SR.ReadFixedASCII(16);
|
|
SegmentName = SR.ReadFixedASCII(16);
|
|
Addr = SR.ReadUInt(AddressSize);
|
|
Size = SR.ReadUInt(AddressSize);
|
|
UInt32 FileOffset = SR.ReadUInt32();
|
|
LogAlignment = SR.ReadUInt32();
|
|
RelocationOffset = SR.ReadUInt32();
|
|
NumRelocations = SR.ReadUInt32();
|
|
Flags = (int)SR.ReadUInt32();
|
|
Reserved1 = SR.ReadUInt32();
|
|
Reserved2 = SR.ReadUInt32();
|
|
|
|
if (AddressSize == Bits.Num._64)
|
|
Reserved3 = SR.ReadUInt32();
|
|
|
|
if ((SectionType == S_ZEROFILL) || (SectionType == S_GB_ZEROFILL))
|
|
{
|
|
Debug.Assert(FileOffset == 0);
|
|
SectionData = null;
|
|
}
|
|
else
|
|
{
|
|
SR.PushPositionAndJump((long)FileOffset);
|
|
SectionData = SR.ReadBytes((int)Size);
|
|
SR.PopPosition();
|
|
}
|
|
|
|
if (Config.bCodeSignVerbose)
|
|
{
|
|
Console.WriteLine(" v Read Section '{0}' in segment '{1}' with size {2} and offset 0x{3:X} and align {4} and flags {5:X}", SectionName, SegmentName, Size, FileOffset, 1 << (byte)LogAlignment, Flags);
|
|
}
|
|
}
|
|
|
|
public void Write(WritingContext SW)
|
|
{
|
|
SW.WriteFixedASCII(SectionName, 16);
|
|
SW.WriteFixedASCII(SegmentName, 16);
|
|
SW.WriteUInt(Addr, AddressSize);
|
|
SW.WriteUInt(Size, AddressSize);
|
|
SW.WriteAbsoluteOffsetAndDelayedData(SectionData, 1 << (byte)LogAlignment, AddressSize);
|
|
SW.Write(LogAlignment);
|
|
SW.Write(RelocationOffset);
|
|
SW.Write(NumRelocations);
|
|
SW.Write((UInt32)Flags);
|
|
SW.Write(Reserved1);
|
|
SW.Write(Reserved2);
|
|
|
|
if (AddressSize == Bits.Num._64)
|
|
SW.Write(Reserved3);
|
|
}
|
|
}
|
|
|
|
public abstract class MachLoadCommand
|
|
{
|
|
public UInt32 Command;
|
|
public bool RequiredForDynamicLoad;
|
|
|
|
/// <summary>
|
|
/// The offset (in bytes) from the start of the file when loading this command from a Mach
|
|
/// Object file, or -1 if this was created in memory. Only used for special modification code
|
|
/// that modifies individual commands without fully understanding the file format, since MachO
|
|
/// files aren't designed in such a way that allows piecemeal modifications...
|
|
/// </summary>
|
|
public long StartingLoadOffset = -1;
|
|
|
|
/// <summary>
|
|
/// Information on a segment (payload type: MachLoadCommandSegment)
|
|
/// </summary>
|
|
public const UInt32 LC_SEGMENT = 0x01;
|
|
public const UInt32 LC_SEGMENT_64 = 0x19;
|
|
|
|
/// <summary>
|
|
/// Symbol and string tables (payload type: MachLoadCommandSymbolTable)
|
|
/// </summary>
|
|
public const UInt32 LC_SYMTAB = 0x02;
|
|
|
|
/// <summary>
|
|
/// Defines information about the main thread (payload type: MachLoadCommandMainThreadInfo)
|
|
/// </summary>
|
|
public const UInt32 LC_THREAD = 0x04;
|
|
public const UInt32 LC_UNIXTHREAD = 0x05;
|
|
|
|
/// <summary>
|
|
/// Information about the parts of the symbol table that are used for dynamic linking (payload type: MachLoadCommandDynamicSymbolTable)
|
|
/// </summary>
|
|
public const UInt32 LC_DYSYMTAB = 0x0B;
|
|
|
|
// Specifies a dynamically linked library (payload type: MachLoadCommandDylib)
|
|
public const UInt32 LC_LOAD_DYLIB = 0x0C;
|
|
public const UInt32 LC_ID_DYLIB = 0x0D;
|
|
|
|
/// <summary>
|
|
/// Specifies the name of the dynamic linker to use (payload type: MachLoadCommandDynamicLinkerName)
|
|
/// </summary>
|
|
public const UInt32 LC_LOAD_DYLINKER = 0x0E;
|
|
public const UInt32 LC_ID_DYLINKER = 0x0F;
|
|
|
|
/// <summary>
|
|
/// Specifies a weak dynamically linked library (payload type: MachLoadCommandReferencedDLL)
|
|
/// </summary>
|
|
public const UInt32 LC_LOAD_WEAK_DYLIB = 0x18;
|
|
|
|
/// <summary>
|
|
/// Specifies a GUID (payload type: MachLoadCommandUUID)
|
|
/// </summary>
|
|
public const UInt32 LC_UUID = 0x1B;
|
|
|
|
/// <summary>
|
|
/// Specifies the code signature information
|
|
/// </summary>
|
|
public const UInt32 LC_CODE_SIGNATURE = 0x1D;
|
|
|
|
/// <summary>
|
|
/// Specifies an encrypted segment (payload type: MachLoadCommandEncryptedSegmentInfo)
|
|
/// </summary>
|
|
public const UInt32 LC_ENCRYPTION_INFO = 0x21;
|
|
|
|
/// <summary>
|
|
/// Specifies information the dynamic loader needs to load this object (payload type: MachLoadCommandDynamicLoaderInfo)
|
|
/// </summary>
|
|
public const UInt32 LC_DYLD_INFO = 0x22;
|
|
|
|
|
|
protected abstract void PackageData(WritingContext SW);
|
|
protected abstract void UnpackageData(ReadingContext SR, int CommandSize);
|
|
|
|
public static MachLoadCommand CreateFromStream(ReadingContext SR)
|
|
{
|
|
long StartingStreamPosition = SR.Position;
|
|
|
|
// Read the code and size (common to all commands)
|
|
UInt32 CommandCode = SR.ReadUInt32();
|
|
UInt32 CommandSize = SR.ReadUInt32();
|
|
|
|
UInt32 EffectiveCommand = CommandCode & ~(1 << 31);
|
|
MachLoadCommand Result;
|
|
switch (EffectiveCommand)
|
|
{
|
|
case LC_SEGMENT:
|
|
Result = new MachLoadCommandSegment(Bits.Num._32);
|
|
break;
|
|
|
|
case LC_SEGMENT_64:
|
|
Result = new MachLoadCommandSegment(Bits.Num._64);
|
|
break;
|
|
|
|
case LC_SYMTAB:
|
|
Result = new MachLoadCommandSymbolTable();
|
|
break;
|
|
|
|
case LC_THREAD:
|
|
case LC_UNIXTHREAD:
|
|
Result = new MachLoadCommandMainThreadInfo();
|
|
break;
|
|
|
|
case LC_DYSYMTAB:
|
|
Result = new MachLoadCommandDynamicSymbolTable();
|
|
break;
|
|
|
|
case LC_LOAD_DYLIB:
|
|
case LC_LOAD_WEAK_DYLIB:
|
|
case LC_ID_DYLIB:
|
|
Result = new MachLoadCommandDylib();
|
|
break;
|
|
|
|
case LC_LOAD_DYLINKER:
|
|
case LC_ID_DYLINKER:
|
|
Result = new MachLoadCommandDynamicLinkerName();
|
|
break;
|
|
|
|
case LC_UUID:
|
|
Result = new MachLoadCommandUUID();
|
|
break;
|
|
|
|
case LC_CODE_SIGNATURE:
|
|
Result = new MachLoadCommandCodeSignature();
|
|
break;
|
|
|
|
case LC_ENCRYPTION_INFO:
|
|
Result = new MachLoadCommandEncryptedSegmentInfo();
|
|
break;
|
|
|
|
case LC_DYLD_INFO:
|
|
Result = new MachLoadCommandDynamicLoaderInfo();
|
|
break;
|
|
|
|
default:
|
|
Result = new MachLoadCommandOpaque();
|
|
break;
|
|
}
|
|
|
|
// MSB of CommandCode is LC_REQ_DYLD or 0, indicating if the command can (theoretically) be ignored
|
|
// or is critical to loading
|
|
Result.StartingLoadOffset = StartingStreamPosition;
|
|
Result.Command = EffectiveCommand;
|
|
Result.RequiredForDynamicLoad = (CommandCode >> 31) != 0;
|
|
|
|
Result.UnpackageData(SR, (int)CommandSize);
|
|
|
|
SR.VerifyStreamPosition(StartingStreamPosition, CommandSize);
|
|
return Result;
|
|
}
|
|
|
|
public void Write(WritingContext SW)
|
|
{
|
|
long StartingPosition = SW.Position;
|
|
|
|
// Write the command and length
|
|
SW.Write(Command);
|
|
LengthFieldU32or64 Length = SW.WriteDeferredLength(4, Bits.Num._32);
|
|
|
|
// Write the data
|
|
PackageData(SW);
|
|
|
|
// Write any pad bytes and commit the length
|
|
SW.WriteZeros((SW.Position - StartingPosition) % 4);
|
|
SW.CommitDeferredField(Length);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Opaque mach load command
|
|
/// </summary>
|
|
class MachLoadCommandOpaque : MachLoadCommand
|
|
{
|
|
protected byte[] MyCommandData;
|
|
|
|
protected override void PackageData(WritingContext SW)
|
|
{
|
|
SW.Write(MyCommandData, 0, MyCommandData.Length);
|
|
}
|
|
|
|
protected override void UnpackageData(ReadingContext SR, int CommandSize)
|
|
{
|
|
int DataSize = (int)CommandSize - (2 * sizeof(UInt32));
|
|
Debug.Assert(DataSize >= 0);
|
|
MyCommandData = SR.ReadBytes(DataSize);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts the N bytes at starting at Offset of the payload buffer to text (standard hex editor rules)
|
|
/// If N is 0, N is set to the size of the payload
|
|
/// </summary>
|
|
protected string PayloadToString(int N, int Offset)
|
|
{
|
|
if (N == 0)
|
|
{
|
|
N = int.MaxValue;
|
|
}
|
|
N = Math.Min(N, MyCommandData.Length - Offset);
|
|
|
|
if (N <= 0)
|
|
{
|
|
return "";
|
|
}
|
|
|
|
char[] Buffer = new char[N];
|
|
int WriteIndex = 0;
|
|
while (WriteIndex < Buffer.Length)
|
|
{
|
|
byte Value = MyCommandData[WriteIndex + Offset];
|
|
if (Value > 0)
|
|
{
|
|
Buffer[WriteIndex] = ((Value >= 0x20) && (Value < 0x7F)) ? (char)Value : '.';
|
|
WriteIndex++;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
return new String(Buffer, 0, WriteIndex);
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return String.Format("Unknown 0x{0:X} with {1} bytes of data", Command, MyCommandData.Length);
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// A blob of data in the __LINKEDIT segment
|
|
/// Warning: Contains absolute file offsets
|
|
/// </summary>
|
|
class MachLoadCommandLinkEditBlob : MachLoadCommand
|
|
{
|
|
public UInt32 BlobFileOffset;
|
|
public UInt32 BlobFileSize;
|
|
|
|
protected override void PackageData(WritingContext SW)
|
|
{
|
|
SW.Write(BlobFileOffset);
|
|
SW.Write(BlobFileSize);
|
|
}
|
|
|
|
protected override void UnpackageData(ReadingContext SR, int CommandSize)
|
|
{
|
|
BlobFileOffset = SR.ReadUInt32();
|
|
BlobFileSize = SR.ReadUInt32();
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return String.Format("Blob at offset 0x{0:X} with a size of {1} KB", BlobFileOffset, BlobFileSize / 1024.0f);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Payload for LC_SEGMENT && LC_SEGMENT_64
|
|
/// Warning: Contains absolute file offsets (what an awful file format!)
|
|
/// </summary>
|
|
class MachLoadCommandSegment : MachLoadCommand
|
|
{
|
|
public string SegmentName;
|
|
|
|
public UInt64 VirtualAddress;
|
|
public UInt64 VirtualSize;
|
|
|
|
public UInt64 FileOffset;
|
|
public UInt64 FileSize;
|
|
|
|
public UInt32 MaxProt;
|
|
public UInt32 InitProt;
|
|
public List<MachSection> Sections = new List<MachSection>();
|
|
public UInt32 Flags;
|
|
|
|
public Bits.Num AddressSize;
|
|
|
|
public MachLoadCommandSegment(Bits.Num Addressing)
|
|
{
|
|
AddressSize = Addressing;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Patches just the file length of this segment in an existing file
|
|
/// </summary>
|
|
public void PatchFileLength(WritingContext SW, UInt32 NewLength)
|
|
{
|
|
Debug.Assert(StartingLoadOffset >= 0);
|
|
|
|
if (Config.bCodeSignVerbose)
|
|
{
|
|
Console.WriteLine("ZZZZZZZZZZZZZZZZ");
|
|
Console.WriteLine(" Segment Length from 0x{0:X} to 0x{1:X} (delta {2})", FileSize, NewLength, (long)NewLength - (long)FileSize);
|
|
Console.WriteLine("ZZZZZZZZZZZZZZZZ");
|
|
}
|
|
|
|
FileSize = NewLength;
|
|
|
|
long PatchOffset = StartingLoadOffset + (2 * sizeof(UInt32)); // Command, CommandLength
|
|
PatchOffset += 16; // SegmentName
|
|
PatchOffset += 3 * Bits.Bytes(AddressSize); // VirtualAddress, VirtualSize, FileOffset
|
|
|
|
SW.PushPositionAndJump(PatchOffset);
|
|
SW.WriteUInt(FileSize, AddressSize);
|
|
SW.PopPosition();
|
|
}
|
|
|
|
protected override void PackageData(WritingContext SW)
|
|
{
|
|
// Write the segment load command
|
|
SW.WriteFixedASCII(SegmentName, 16);
|
|
SW.WriteUInt(VirtualAddress, AddressSize);
|
|
SW.WriteUInt(VirtualSize, AddressSize);
|
|
|
|
// Offset to first segment
|
|
//@TODO: These file offsets and file lengths aren't correct (compared to the original MachO's)
|
|
OffsetFieldU32or64 FileOffset = SW.WriteDeferredOffsetFrom(0, AddressSize);
|
|
LengthFieldU32or64 FileLength = SW.WriteDeferredLength(0, AddressSize);
|
|
|
|
SW.Write(MaxProt);
|
|
SW.Write(InitProt);
|
|
SW.Write(Sections.Count);
|
|
SW.Write(Flags);
|
|
|
|
// Enqueue a job to commit the file offset to the first section
|
|
SW.CurrentPhase.PendingWrites.Enqueue(delegate(WritingContext Context)
|
|
{
|
|
FileLength.Rebase(Context.Position);
|
|
FileOffset.Commit(Context);
|
|
});
|
|
|
|
// Write the sections belonging to the segment
|
|
foreach (MachSection Section in Sections)
|
|
{
|
|
Section.Write(SW);
|
|
}
|
|
|
|
// Enqueue a job to commit the length of data in all the sections
|
|
SW.CurrentPhase.PendingWrites.Enqueue(delegate(WritingContext Context)
|
|
{
|
|
FileLength.Commit(Context);
|
|
});
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return String.Format("Segment '{0}' with {1} sections (Offset 0x{2:X} Size {3})", SegmentName, Sections.Count, FileOffset, FileSize);
|
|
}
|
|
|
|
protected override void UnpackageData(ReadingContext SR, int CommandSize)
|
|
{
|
|
// Read the segment
|
|
SegmentName = SR.ReadFixedASCII(16);
|
|
VirtualAddress = SR.ReadUInt(AddressSize);
|
|
VirtualSize = SR.ReadUInt(AddressSize);
|
|
FileOffset = SR.ReadUInt(AddressSize);
|
|
FileSize = SR.ReadUInt(AddressSize);
|
|
MaxProt = SR.ReadUInt32();
|
|
InitProt = SR.ReadUInt32();
|
|
UInt32 SectionCount = SR.ReadUInt32();
|
|
Flags = SR.ReadUInt32();
|
|
|
|
// Read the segment data
|
|
//SR.PushPositionAndJump(FileOffset);
|
|
//FileData = SR.ReadBytes(FileSize);
|
|
//SR.PopPosition();
|
|
|
|
// Read the sections belonging to the segment
|
|
for (int SectionIndex = 0; SectionIndex < SectionCount; ++SectionIndex)
|
|
{
|
|
MachSection Section = new MachSection(AddressSize);
|
|
Section.Read(SR);
|
|
Sections.Add(Section);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Payload for LC_SYMTAB
|
|
/// Warning: Contains absolute offsets
|
|
/// </summary>
|
|
class MachLoadCommandSymbolTable : MachLoadCommand
|
|
{
|
|
UInt32 SymbolTableOffset;
|
|
UInt32 SymbolCount;
|
|
UInt32 StringTableOffset;
|
|
UInt32 StringTableSize;
|
|
|
|
protected override void PackageData(WritingContext SW)
|
|
{
|
|
SW.Write(SymbolTableOffset);
|
|
SW.Write(SymbolCount);
|
|
SW.Write(StringTableOffset);
|
|
SW.Write(StringTableSize);
|
|
}
|
|
|
|
protected override void UnpackageData(ReadingContext SR, int CommandSize)
|
|
{
|
|
SymbolTableOffset = SR.ReadUInt32();
|
|
SymbolCount = SR.ReadUInt32();
|
|
StringTableOffset = SR.ReadUInt32();
|
|
StringTableSize = SR.ReadUInt32();
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return String.Format("Symbol Table with {0} symbols at offset 0x{1:X} and a {2} KB string table at offset 0x{3:X}",
|
|
SymbolCount, SymbolTableOffset,
|
|
StringTableSize / 1024.0f, StringTableOffset);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Payload for LC_THREAD and LC_UNIXTHREAD
|
|
/// </summary>
|
|
class MachLoadCommandMainThreadInfo : MachLoadCommandOpaque
|
|
{
|
|
public override string ToString()
|
|
{
|
|
switch (Command)
|
|
{
|
|
case LC_THREAD:
|
|
return "Main thread configuration (no default stack)";
|
|
case LC_UNIXTHREAD:
|
|
return "Main thread configuration (with a stack)";
|
|
default:
|
|
return base.ToString();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Payload for LC_DYSYMTAB
|
|
/// Warning: Contains absolute file offsets
|
|
/// </summary>
|
|
class MachLoadCommandDynamicSymbolTable : MachLoadCommand
|
|
{
|
|
UInt32 [] Payload = new UInt32[18];
|
|
// 6,8,10,12,14,16 are offsets :(
|
|
|
|
protected override void PackageData(WritingContext SW)
|
|
{
|
|
for (int i = 0; i < Payload.Length; ++i)
|
|
{
|
|
SW.Write(Payload[i]);
|
|
}
|
|
}
|
|
|
|
protected override void UnpackageData(ReadingContext SR, int CommandSize)
|
|
{
|
|
for (int i = 0; i < Payload.Length; ++i)
|
|
{
|
|
Payload[i] = SR.ReadUInt32();
|
|
}
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return "Information on symbols needed for dynamic linking and loading";
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Payload for LC_LOAD_DYLIB, LC_LOAD_WEAK_DYLIB, and LC_ID_DYLIB
|
|
/// </summary>
|
|
class MachLoadCommandDylib : MachLoadCommandOpaque
|
|
{
|
|
public override string ToString()
|
|
{
|
|
string Result;
|
|
switch (Command)
|
|
{
|
|
case LC_LOAD_DYLIB:
|
|
Result = "Load Dynamic Library";
|
|
break;
|
|
case LC_LOAD_WEAK_DYLIB:
|
|
Result = "Load Weak Dynamic Library";
|
|
break;
|
|
case LC_ID_DYLIB:
|
|
Result = "Dynamic Library Install Name";
|
|
break;
|
|
default:
|
|
Result = base.ToString();
|
|
return Result;
|
|
}
|
|
|
|
return String.Format("{0} '{1}'", Result, PayloadToString(0, 16));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Payload for LC_LOAD_DYLINKER and LC_ID_DYLINKER
|
|
/// </summary>
|
|
class MachLoadCommandDynamicLinkerName : MachLoadCommandOpaque
|
|
{
|
|
public override string ToString()
|
|
{
|
|
return String.Format("Dynamic linker name '{0}'", PayloadToString(0, 4));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Payload for LC_UUID
|
|
/// </summary>
|
|
class MachLoadCommandUUID : MachLoadCommandOpaque
|
|
{
|
|
public override string ToString()
|
|
{
|
|
Guid ID = new Guid(MyCommandData);
|
|
return String.Format("UUID: {0}", ID);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Payload for LC_ENCRYPTION_INFO
|
|
/// </summary>
|
|
class MachLoadCommandEncryptedSegmentInfo : MachLoadCommand
|
|
{
|
|
UInt32 EncryptedFileOffset;
|
|
UInt32 EncryptedFileSize;
|
|
UInt32 EncryptionMode;
|
|
|
|
protected override void PackageData(WritingContext SW)
|
|
{
|
|
SW.Write(EncryptedFileOffset);
|
|
SW.Write(EncryptedFileSize);
|
|
SW.Write(EncryptionMode);
|
|
}
|
|
|
|
protected override void UnpackageData(ReadingContext SR, int CommandSize)
|
|
{
|
|
EncryptedFileOffset = SR.ReadUInt32();
|
|
EncryptedFileSize = SR.ReadUInt32();
|
|
EncryptionMode = SR.ReadUInt32();
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return String.Format("Encrypted segment at offset 0x{0:X} of size {1} KB in mode {2}", EncryptedFileOffset, EncryptedFileSize / 1024.0f, EncryptionMode);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Payload for LC_DYLD_INFO
|
|
/// Warning: Contains absolute file offsets
|
|
/// </summary>
|
|
class MachLoadCommandDynamicLoaderInfo : MachLoadCommand
|
|
{
|
|
UInt32[] Payload = new UInt32[10];
|
|
// 0,2,4,6,8 are offsets :(
|
|
|
|
protected override void PackageData(WritingContext SW)
|
|
{
|
|
for (int i = 0; i < Payload.Length; ++i)
|
|
{
|
|
SW.Write(Payload[i]);
|
|
}
|
|
}
|
|
|
|
|
|
protected override void UnpackageData(ReadingContext SR, int CommandSize)
|
|
{
|
|
for (int i = 0; i < Payload.Length; ++i)
|
|
{
|
|
Payload[i] = SR.ReadUInt32();
|
|
}
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return "Compressed information needed for dynamic loading";
|
|
}
|
|
}
|
|
|
|
class SigningBlob
|
|
{
|
|
const UInt32 Magic = 0xC00CDEFA;
|
|
}
|
|
|
|
public class MachObjectFile
|
|
{
|
|
public UInt32 Magic;
|
|
public UInt32 CpuType;
|
|
public UInt32 CpuSubType;
|
|
public UInt32 FileType;
|
|
// NumCommands
|
|
// SizeOfCommand
|
|
public UInt32 Flags;
|
|
public UInt32 Reserved64; // Only in mach_header_64
|
|
|
|
public List<MachLoadCommand> Commands = new List<MachLoadCommand>();
|
|
|
|
// Values for Magic
|
|
public const UInt32 MH_MAGIC = 0xFEEDFACE;
|
|
public const UInt32 MH_CIGAM = 0xCEFAEDFE;
|
|
public const UInt32 MH_MAGIC_64 = 0xFEEDFACF;
|
|
public const UInt32 MH_CIGAM_64 = 0xCFFAEDFE;
|
|
|
|
// Values for FileType
|
|
public const UInt32 MH_EXECUTE = 2;
|
|
|
|
public const int MachHeaderPad = 0x1A60;
|
|
|
|
public void Read(ReadingContext SR)
|
|
{
|
|
long ExpectedStreamPosition = 0;
|
|
int NumInt = 7;
|
|
|
|
// Read the header
|
|
Magic = SR.ReadUInt32();
|
|
CpuType = SR.ReadUInt32();
|
|
CpuSubType = SR.ReadUInt32();
|
|
FileType = SR.ReadUInt32();
|
|
UInt32 NumCommands = SR.ReadUInt32();
|
|
UInt32 SizeOfCommands = SR.ReadUInt32();
|
|
Flags = SR.ReadUInt32();
|
|
|
|
if (Magic == MH_MAGIC_64)
|
|
{
|
|
Reserved64 = SR.ReadUInt32();
|
|
NumInt++;
|
|
}
|
|
SR.VerifyStreamPosition(ref ExpectedStreamPosition, NumInt * sizeof(UInt32));
|
|
|
|
// Read the commands
|
|
Commands.Clear();
|
|
for (int LoadCommandIndex = 0; LoadCommandIndex < NumCommands; ++LoadCommandIndex)
|
|
{
|
|
MachLoadCommand Command = MachLoadCommand.CreateFromStream(SR);
|
|
Commands.Add(Command);
|
|
|
|
if (Config.bCodeSignVerbose)
|
|
{
|
|
Console.WriteLine(" Read command: {0}", Command.ToString());
|
|
}
|
|
}
|
|
SR.VerifyStreamPosition(ref ExpectedStreamPosition, SizeOfCommands);
|
|
}
|
|
|
|
public void Write(WritingContext Context)
|
|
{
|
|
// Write the header
|
|
Context.Write(Magic);
|
|
Context.Write(CpuType);
|
|
Context.Write(CpuSubType);
|
|
Context.Write(FileType);
|
|
|
|
Context.Write((UInt32)Commands.Count);
|
|
LengthFieldU32or64 SizeOfCommands = Context.WriteDeferredLength(-2 * sizeof(UInt32), Bits.Num._32); // Size of commands, after this field and the flags field
|
|
|
|
Context.Write(Flags);
|
|
if (Magic == MH_MAGIC_64)
|
|
{
|
|
Context.Write(Reserved64);
|
|
}
|
|
|
|
// Write each command (which may enqueue deferred work)
|
|
foreach (MachLoadCommand Command in Commands)
|
|
{
|
|
Command.Write(Context);
|
|
}
|
|
Context.CommitDeferredField(SizeOfCommands);
|
|
|
|
//@TODO: Figure out where this offsetting comes from
|
|
long MainStartPosition = MachHeaderPad;
|
|
Context.WriteZeros(MainStartPosition - Context.Position);
|
|
|
|
// Drain deferred work until the file is completely done
|
|
Context.CompleteWritingAndClose();
|
|
}
|
|
|
|
public void LoadFromFile(string Filename)
|
|
{
|
|
BinaryReader SR = new BinaryReader(File.OpenRead(Filename));
|
|
ReadingContext Context = new ReadingContext(SR);
|
|
Read(Context);
|
|
SR.Close();
|
|
}
|
|
|
|
public void LoadFromBytes(byte[] Data)
|
|
{
|
|
MemoryStream Stream = new MemoryStream(Data, false);
|
|
BinaryReader SR = new BinaryReader(Stream);
|
|
ReadingContext Context = new ReadingContext(SR);
|
|
Read(Context);
|
|
SR.Close();
|
|
}
|
|
|
|
public void WriteToFile(string Filename)
|
|
{
|
|
BinaryWriter SW = new BinaryWriter(File.OpenWrite(Filename));
|
|
WritingContext Context = new WritingContext(SW);
|
|
Write(Context);
|
|
SW.Close();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Blob for magic CSMAGIC_ENTITLEMENTS
|
|
/// </summary>
|
|
public class EntitlementsBlob : OpaqueBlob
|
|
{
|
|
public EntitlementsBlob()
|
|
{
|
|
MyMagic = CSMAGIC_ENTITLEMENTS;
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return "Entitlements '" + Encoding.ASCII.GetString(MyData, 0, MyData.Length) + "'";
|
|
}
|
|
|
|
public static EntitlementsBlob Create(string EntitlementsText)
|
|
{
|
|
EntitlementsBlob Result = new EntitlementsBlob();
|
|
Result.MyData = Encoding.UTF8.GetBytes(EntitlementsText);
|
|
return Result;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Blob for magic CSMAGIC_ENTITLEMENTS_DER
|
|
/// </summary>
|
|
public class EntitlementsDerBlob : OpaqueBlob
|
|
{
|
|
public EntitlementsDerBlob()
|
|
{
|
|
MyMagic = CSMAGIC_ENTITLEMENTS_DER;
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return "EntitlementsDER '" + AsnFormatter.BinaryToString(MyData) + "'";
|
|
}
|
|
|
|
public static EntitlementsDerBlob Create(string EntitlementsXmlText)
|
|
{
|
|
EntitlementsDerBlob Result = new EntitlementsDerBlob();
|
|
|
|
XmlDocument doc = new XmlDocument();
|
|
doc.LoadXml(EntitlementsXmlText);
|
|
|
|
XmlNode xmlNode = doc.FirstChild;
|
|
|
|
// eat tokens until we see a start 'plist' element.
|
|
while (xmlNode != null && (xmlNode.NodeType != XmlNodeType.Element || xmlNode.Name != "plist"))
|
|
{
|
|
xmlNode = xmlNode.NextSibling;
|
|
}
|
|
|
|
Asn1Builder SetBuilder = new Asn1Builder();
|
|
|
|
if (xmlNode != null)
|
|
{
|
|
// dict node
|
|
xmlNode = xmlNode.FirstChild;
|
|
if (xmlNode != null && xmlNode.NodeType == XmlNodeType.Element && xmlNode.Name == "dict")
|
|
{
|
|
for (int i = 0; i < xmlNode.ChildNodes.Count; i++)
|
|
{
|
|
if (xmlNode.ChildNodes[i].Name == "key")
|
|
{
|
|
Asn1Builder KeyValueBuilder = new Asn1Builder();
|
|
KeyValueBuilder.AddUTF8String(xmlNode.ChildNodes[i].InnerText);
|
|
|
|
i++;
|
|
if (i >= xmlNode.ChildNodes.Count)
|
|
{
|
|
break;
|
|
}
|
|
if (xmlNode.ChildNodes[i].Name == "string")
|
|
{
|
|
KeyValueBuilder.AddUTF8String(xmlNode.ChildNodes[i].InnerText);
|
|
}
|
|
else if (xmlNode.ChildNodes[i].Name == "true")
|
|
{
|
|
KeyValueBuilder.AddBoolean(true);
|
|
}
|
|
else if (xmlNode.ChildNodes[i].Name == "false")
|
|
{
|
|
KeyValueBuilder.AddBoolean(false);
|
|
}
|
|
else if (xmlNode.ChildNodes[i].Name == "array")
|
|
{
|
|
Asn1Builder ArrayBuilder = new Asn1Builder();
|
|
foreach (XmlNode child in xmlNode.ChildNodes[i].ChildNodes)
|
|
{
|
|
if (child.Name == "string")
|
|
{
|
|
ArrayBuilder.AddUTF8String(child.InnerText);
|
|
}
|
|
else if (child.Name == "true")
|
|
{
|
|
ArrayBuilder.AddBoolean(true);
|
|
}
|
|
else if (child.Name == "false")
|
|
{
|
|
ArrayBuilder.AddBoolean(false);
|
|
}
|
|
}
|
|
|
|
KeyValueBuilder.AddDerData(ArrayBuilder.GetEncoded());
|
|
}
|
|
|
|
SetBuilder.AddDerData(KeyValueBuilder.GetEncoded());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Config.bCodeSignVerbose)
|
|
{
|
|
Console.WriteLine("Generated Entitlements DER as {0}", AsnFormatter.BinaryToString(SetBuilder.GetEncoded(0x31)));
|
|
}
|
|
|
|
Result.MyData = SetBuilder.GetEncoded(0x31);
|
|
|
|
return Result;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Blob for magic CSMAGIC_REQUIREMENTS_TABLE
|
|
/// http://www.opensource.apple.com/source/libsecurity_codesigning/libsecurity_codesigning-36885/lib/requirement.h
|
|
///
|
|
/// Currently does not attempt to understand or parse the individual requirements and treats them as opaque data
|
|
/// </summary>
|
|
public class RequirementsBlob : SuperBlob
|
|
{
|
|
public RequirementsBlob()
|
|
{
|
|
MyMagic = CSMAGIC_REQUIREMENTS_TABLE;
|
|
}
|
|
|
|
// Valid kinds of hosts that can run the executable
|
|
const int kSecHostRequirementType = 1;
|
|
|
|
// Valid guests the executable can run
|
|
const int kSecGuestRequirementType = 2;
|
|
|
|
// Explicit requirements
|
|
const int kSecDesignatedRequirementType = 3;
|
|
|
|
// Libraries we can link against
|
|
const int kSecLibraryRequirementType = 4;
|
|
|
|
public static RequirementsBlob CreateEmpty()
|
|
{
|
|
return new RequirementsBlob();
|
|
}
|
|
|
|
protected string KeyToString(int Key)
|
|
{
|
|
switch (Key)
|
|
{
|
|
case kSecHostRequirementType:
|
|
return "kSecHostRequirementType";
|
|
case kSecGuestRequirementType:
|
|
return "kSecGuestRequirementType";
|
|
case kSecDesignatedRequirementType:
|
|
return "kSecDesignatedRequirementType";
|
|
case kSecLibraryRequirementType:
|
|
return "kSecLibraryRequirementType";
|
|
default:
|
|
return String.Format("UnknownKeyType_{0}", Key);
|
|
}
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
string Result = "Requirements table:\n";
|
|
|
|
foreach (KeyValuePair<UInt32, AbstractBlob> KVP in Table.Slots)
|
|
{
|
|
Result += String.Format(" Requirement[Type: {0}, Magic: 0x{1:X}] = {2}\n",
|
|
KeyToString((int)KVP.Key),
|
|
KVP.Value.MyMagic, KVP.Value.ToString());
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
}
|
|
|
|
public class RequirementBlob : OpaqueBlob
|
|
{
|
|
const UInt32 kOpUnknown = 1000;
|
|
const UInt32 kOpIdent = 2;
|
|
const UInt32 kOpAnchorHash = 4;
|
|
const UInt32 kOpAnd = 6;
|
|
const UInt32 kOpCertField = 11;
|
|
const UInt32 kOpCertGeneric = 14;
|
|
const UInt32 kOpGenericAnchor = 15;
|
|
|
|
public class ExpressionOp
|
|
{
|
|
public UInt32 OpVal = kOpUnknown;
|
|
|
|
public ExpressionOp() { }
|
|
public ExpressionOp(UInt32 InOpVal)
|
|
{
|
|
OpVal = InOpVal;
|
|
}
|
|
|
|
public virtual void ReadData(ReadingContext SR)
|
|
{ }
|
|
|
|
public virtual void WriteData(WritingContext SW)
|
|
{
|
|
SW.Write(OpVal);
|
|
}
|
|
|
|
public virtual void UpdateCertificateAndBundle(string CertificateName, string BundleIdentifier)
|
|
{ }
|
|
|
|
static public ExpressionOp ReadOperand(ReadingContext SR)
|
|
{
|
|
UInt32 OpVal = SR.ReadUInt32();
|
|
ExpressionOp Op = null;
|
|
switch (OpVal)
|
|
{
|
|
case kOpAnd:
|
|
Op = new AndOp();
|
|
break;
|
|
|
|
case kOpIdent:
|
|
Op = new IdentOp();
|
|
break;
|
|
|
|
case kOpGenericAnchor:
|
|
Op = new ExpressionOp(OpVal);
|
|
break;
|
|
|
|
case kOpCertField:
|
|
Op = new CertFieldOp();
|
|
break;
|
|
|
|
case kOpCertGeneric:
|
|
Op = new CertGenericOp();
|
|
break;
|
|
|
|
case kOpAnchorHash:
|
|
Op = new AnchorHashOp();
|
|
break;
|
|
|
|
default:
|
|
throw new Exception("Unknown Expression Operand: " + OpVal.ToString());
|
|
}
|
|
|
|
Op.ReadData(SR);
|
|
return Op;
|
|
}
|
|
};
|
|
|
|
class AndOp : ExpressionOp
|
|
{
|
|
public ExpressionOp Op1;
|
|
public ExpressionOp Op2;
|
|
|
|
public AndOp()
|
|
{
|
|
OpVal = kOpAnd;
|
|
}
|
|
|
|
public override void ReadData(ReadingContext SR)
|
|
{
|
|
Op1 = ExpressionOp.ReadOperand(SR);
|
|
Op2 = ExpressionOp.ReadOperand(SR);
|
|
}
|
|
|
|
public override void WriteData(WritingContext SW)
|
|
{
|
|
base.WriteData(SW);
|
|
Op1.WriteData(SW);
|
|
Op2.WriteData(SW);
|
|
}
|
|
|
|
public override void UpdateCertificateAndBundle(string CertificateName, string BundleIdentifier)
|
|
{
|
|
Op1.UpdateCertificateAndBundle(CertificateName, BundleIdentifier);
|
|
Op2.UpdateCertificateAndBundle(CertificateName, BundleIdentifier);
|
|
}
|
|
};
|
|
|
|
class IdentOp : ExpressionOp
|
|
{
|
|
public string BundleIdentifier;
|
|
|
|
public IdentOp()
|
|
{
|
|
OpVal = kOpIdent;
|
|
}
|
|
|
|
public override void ReadData(ReadingContext SR)
|
|
{
|
|
UInt32 Count = SR.ReadUInt32();
|
|
BundleIdentifier = SR.ReadFixedASCII((int)Count);
|
|
Count = 4 - Count % 4;
|
|
if (Count > 0 && Count < 4)
|
|
SR.ReadBytes(Count);
|
|
}
|
|
|
|
public override void WriteData(WritingContext SW)
|
|
{
|
|
base.WriteData(SW);
|
|
SW.Write(BundleIdentifier.Length); // bundle identifier length
|
|
SW.WriteFixedASCII(BundleIdentifier, BundleIdentifier.Length); // bundle identifier string
|
|
int Count = 4 - BundleIdentifier.Length % 4; // may need to pad to alignment of 4 bytes
|
|
if (Count > 0 && Count < 4)
|
|
SW.WriteZeros(Count);
|
|
}
|
|
|
|
public override void UpdateCertificateAndBundle(string InCertificateName, string InBundleIdentifier)
|
|
{
|
|
BundleIdentifier = InBundleIdentifier;
|
|
}
|
|
};
|
|
|
|
class CertFieldOp : ExpressionOp
|
|
{
|
|
public int CertificateIndex = 0;
|
|
public string FieldName = "subject.CN";
|
|
struct MatchSuffix
|
|
{
|
|
public UInt32 MatchOp;
|
|
public string CertificateName;
|
|
};
|
|
MatchSuffix MatchOp;
|
|
|
|
public CertFieldOp()
|
|
{
|
|
OpVal = kOpCertField;
|
|
MatchOp.MatchOp = kMatchEqual;
|
|
}
|
|
|
|
public override void ReadData(ReadingContext SR)
|
|
{
|
|
CertificateIndex = (int)SR.ReadUInt32(); // index in the mobile provision certificate list (always 0 for now)
|
|
UInt32 Count = SR.ReadUInt32();
|
|
FieldName = SR.ReadFixedASCII((int)Count);
|
|
Count = 4 - Count % 4;
|
|
if (Count > 0 && Count < 4)
|
|
SR.ReadBytes(Count);
|
|
MatchOp = new MatchSuffix();
|
|
MatchOp.MatchOp = SR.ReadUInt32(); // must equal
|
|
Count = SR.ReadUInt32();
|
|
MatchOp.CertificateName = SR.ReadFixedASCII((int)Count);
|
|
Count = 4 - Count % 4;
|
|
if (Count > 0 && Count < 4)
|
|
SR.ReadBytes(Count);
|
|
}
|
|
|
|
public override void WriteData(WritingContext SW)
|
|
{
|
|
base.WriteData(SW);
|
|
SW.Write(CertificateIndex); // index in the mobile provision certificate list (always 0 for now)
|
|
SW.Write(FieldName.Length); // field name length
|
|
SW.WriteFixedASCII(FieldName, FieldName.Length); // field name to match
|
|
int Count = 4 - FieldName.Length % 4; // may need to pad to alignment of 4 bytes
|
|
if (Count > 0 && Count < 4)
|
|
SW.WriteZeros(Count);
|
|
SW.Write(MatchOp.MatchOp); // must equal
|
|
SW.Write(MatchOp.CertificateName.Length); // length of certficate name
|
|
SW.WriteFixedASCII(MatchOp.CertificateName, MatchOp.CertificateName.Length);// certificate name to match
|
|
Count = 4 - MatchOp.CertificateName.Length % 4; // may need to pad to alignment of 4 bytes
|
|
if (Count > 0 && Count < 4)
|
|
SW.WriteZeros(Count);
|
|
}
|
|
|
|
public override void UpdateCertificateAndBundle(string InCertificateName, string InBundleIdentifier)
|
|
{
|
|
MatchOp.CertificateName = InCertificateName;
|
|
}
|
|
};
|
|
|
|
class CertGenericOp : ExpressionOp
|
|
{
|
|
public int OIDIndex = 1;
|
|
byte[] OID = new byte[] { 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x63, 0x64, 0x06, 0x02, 0x01 };
|
|
struct MatchSuffix
|
|
{
|
|
public UInt32 MatchOp;
|
|
};
|
|
MatchSuffix MatchOp;
|
|
|
|
public CertGenericOp()
|
|
{
|
|
OpVal = kOpCertGeneric;
|
|
MatchOp.MatchOp = kMatchExists;
|
|
}
|
|
|
|
public override void ReadData(ReadingContext SR)
|
|
{
|
|
OIDIndex = (int)SR.ReadUInt32(); // index of the OID value (always 1)
|
|
UInt32 Count = SR.ReadUInt32();
|
|
OID = SR.ReadBytes((int)Count);
|
|
Count = 4 - Count % 4;
|
|
if (Count > 0 && Count < 4)
|
|
SR.ReadBytes(Count);
|
|
|
|
MatchOp.MatchOp = SR.ReadUInt32(); // OID must exist
|
|
}
|
|
|
|
public override void WriteData(WritingContext SW)
|
|
{
|
|
base.WriteData(SW);
|
|
SW.Write(OIDIndex); // index of the OID value (always 1)
|
|
SW.Write(OID.Length); // length of OID
|
|
SW.Write(OID); // OID to match
|
|
int Count = 4 - OID.Length % 4; // may need to pad to alignment of 4 bytes
|
|
if (Count > 0 && Count < 4)
|
|
SW.WriteZeros(Count);
|
|
|
|
// may need to pad to alignment of 4 bytes
|
|
SW.Write(MatchOp.MatchOp); // OID must exist
|
|
}
|
|
};
|
|
|
|
class AnchorHashOp : ExpressionOp
|
|
{
|
|
byte[] Hash = new byte[] { 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x63, 0x64, 0x06, 0x02, 0x01 };
|
|
int CertificateIndex = -1;
|
|
public AnchorHashOp()
|
|
{
|
|
OpVal = kOpAnchorHash;
|
|
}
|
|
|
|
public override void ReadData(ReadingContext SR)
|
|
{
|
|
CertificateIndex = (int)SR.ReadUInt32();
|
|
UInt32 Count = SR.ReadUInt32();
|
|
Hash = SR.ReadBytes((int)Count);
|
|
Count = 4 - Count % 4;
|
|
if (Count > 0 && Count < 4)
|
|
SR.ReadBytes(Count);
|
|
}
|
|
|
|
public override void WriteData(WritingContext SW)
|
|
{
|
|
base.WriteData(SW);
|
|
SW.Write(CertificateIndex); // index of the OID value (always 1)
|
|
SW.Write(Hash.Length); // length of OID
|
|
SW.Write(Hash); // OID to match
|
|
int Count = 4 - Hash.Length % 4; // may need to pad to alignment of 4 bytes
|
|
if (Count > 0 && Count < 4)
|
|
SW.WriteZeros(Count);
|
|
}
|
|
};
|
|
|
|
const int kReqExpression = 1;
|
|
|
|
const int kMatchExists = 0;
|
|
const int kMatchEqual = 1;
|
|
|
|
string BundleIdentifier = "";
|
|
string CertificateName = "";
|
|
|
|
public ExpressionOp Expression = null;
|
|
|
|
public RequirementBlob()
|
|
{
|
|
MyMagic = CSMAGIC_REQUIREMENT;
|
|
}
|
|
|
|
public static RequirementBlob CreateFromCertificate(X509Certificate2 SigningCert, string Bundle, ExpressionOp OldReq = null)
|
|
{
|
|
RequirementBlob Blob = new RequirementBlob();
|
|
Blob.InitializeFromCert(SigningCert, Bundle, OldReq);
|
|
return Blob;
|
|
}
|
|
|
|
protected void InitializeFromCert(X509Certificate2 SigningCert, string Bundle, ExpressionOp OldReq = null)
|
|
{
|
|
BundleIdentifier = Bundle;
|
|
int StartIndex = SigningCert.SubjectName.Name.IndexOf("CN=");
|
|
int EndIndex = -1;
|
|
CertificateName = "";
|
|
if (StartIndex > -1)
|
|
{
|
|
// find the next attribute
|
|
StartIndex += 3;
|
|
char SearchChar = ',';
|
|
if (SigningCert.SubjectName.Name[StartIndex] == '\"')
|
|
{
|
|
// quotes are around the string because of special characters
|
|
StartIndex++;
|
|
SearchChar = '\"';
|
|
}
|
|
EndIndex = SigningCert.SubjectName.Name.IndexOf(SearchChar, StartIndex);
|
|
if (EndIndex == -1)
|
|
{
|
|
// must be at the end, so go to the end
|
|
EndIndex = SigningCert.SubjectName.Name.Length;
|
|
if (SearchChar == '\"')
|
|
{
|
|
EndIndex--;
|
|
}
|
|
}
|
|
// get the string
|
|
CertificateName = SigningCert.SubjectName.Name.Substring(StartIndex, EndIndex - StartIndex);
|
|
}
|
|
if (string.IsNullOrEmpty(CertificateName))
|
|
{
|
|
CertificateName = CryptoAdapter.GetFriendlyNameFromCert(SigningCert);
|
|
}
|
|
|
|
// always use the new requirements when initializing from cert
|
|
Expression = new AndOp();
|
|
(Expression as AndOp).Op1 = new IdentOp();
|
|
((Expression as AndOp).Op1 as IdentOp).BundleIdentifier = BundleIdentifier;
|
|
(Expression as AndOp).Op2 = new AndOp();
|
|
((Expression as AndOp).Op2 as AndOp).Op1 = new ExpressionOp(kOpGenericAnchor);
|
|
((Expression as AndOp).Op2 as AndOp).Op2 = new AndOp();
|
|
(((Expression as AndOp).Op2 as AndOp).Op2 as AndOp).Op1 = new CertFieldOp();
|
|
(((Expression as AndOp).Op2 as AndOp).Op2 as AndOp).Op2 = new CertGenericOp();
|
|
}
|
|
|
|
protected override void PackageData(WritingContext SW)
|
|
{
|
|
// update all of the read expressions with the certificate name and bundle identifier
|
|
Expression.UpdateCertificateAndBundle(CertificateName, BundleIdentifier);
|
|
SW.Write(kReqExpression);
|
|
Expression.WriteData(SW);
|
|
}
|
|
|
|
protected override void UnpackageData(ReadingContext SR, UInt32 Length)
|
|
{
|
|
if (Length > 0)
|
|
{
|
|
UInt32 ExpressionVal = SR.ReadUInt32();
|
|
if (ExpressionVal == kReqExpression)
|
|
{
|
|
Expression = ExpressionOp.ReadOperand(SR);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public class CodeSigningTableBlob : SuperBlob
|
|
{
|
|
public CodeSigningTableBlob()
|
|
{
|
|
MyMagic = CSMAGIC_EMBEDDED_SIGNATURE;
|
|
}
|
|
|
|
public static CodeSigningTableBlob Create()
|
|
{
|
|
CodeSigningTableBlob Blob = new CodeSigningTableBlob();
|
|
return Blob;
|
|
}
|
|
}
|
|
|
|
public abstract class AbstractBlob
|
|
{
|
|
// Key for CodeDirectory blob in superblob
|
|
public const UInt32 CSSLOT_CODEDIRECTORY = 0;
|
|
|
|
// Requirements blob
|
|
public const UInt32 CSMAGIC_REQUIREMENT = 0xFADE0C00;
|
|
|
|
// Internal requirements
|
|
public const UInt32 CSMAGIC_REQUIREMENTS_TABLE = 0xFADE0C01;
|
|
|
|
// Code directory blob (should be in a slot with key = CSSLOT_CODEDIRECTORY)
|
|
public const UInt32 CSMAGIC_CODEDIRECTORY = 0xFADE0C02;
|
|
|
|
// Superblob of all signature data (code directory, actual signature, etc...)
|
|
public const UInt32 CSMAGIC_EMBEDDED_SIGNATURE = 0xFADE0CC0;
|
|
|
|
// Entitlements
|
|
public const UInt32 CSMAGIC_ENTITLEMENTS = 0xFADE7171;
|
|
|
|
// Entitlements DER
|
|
public const UInt32 CSMAGIC_ENTITLEMENTS_DER = 0xFADE7172;
|
|
|
|
// Actual signature blob
|
|
public const UInt32 CSMAGIC_CODEDIR_SIGNATURE = 0xFADE0B01;
|
|
|
|
|
|
public UInt32 MyMagic;
|
|
|
|
protected abstract void PackageData(WritingContext SW);
|
|
protected abstract void UnpackageData(ReadingContext SR, UInt32 Length);
|
|
|
|
public static AbstractBlob CreateFromStream(ReadingContext SR)
|
|
{
|
|
// Read the magic and length (common to all blobs)
|
|
UInt32 Magic = SR.ReadUInt32();
|
|
UInt32 Length = SR.ReadUInt32();
|
|
|
|
AbstractBlob Result;
|
|
|
|
switch (Magic)
|
|
{
|
|
case CSMAGIC_CODEDIRECTORY:
|
|
Result = new CodeDirectoryBlob();
|
|
break;
|
|
case CSMAGIC_CODEDIR_SIGNATURE:
|
|
Result = new CodeDirectorySignatureBlob();
|
|
break;
|
|
case CSMAGIC_ENTITLEMENTS:
|
|
Result = new EntitlementsBlob();
|
|
break;
|
|
case CSMAGIC_ENTITLEMENTS_DER:
|
|
Result = new EntitlementsDerBlob();
|
|
break;
|
|
case CSMAGIC_REQUIREMENTS_TABLE:
|
|
Result = new RequirementsBlob();
|
|
break;
|
|
case CSMAGIC_EMBEDDED_SIGNATURE:
|
|
Result = new CodeSigningTableBlob();
|
|
break;
|
|
case CSMAGIC_REQUIREMENT:
|
|
Result = new RequirementBlob();
|
|
break;
|
|
default:
|
|
Result = new OpaqueBlob();
|
|
break;
|
|
}
|
|
Result.MyMagic = Magic;
|
|
Result.UnpackageData(SR, Length);
|
|
|
|
if (Config.bCodeSignVerbose)
|
|
{
|
|
Console.WriteLine("[Read blob with magic 0x{0:X} and length={1}]\n{2}", Magic, Length, Result.ToString());
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
public void Write(WritingContext SW)
|
|
{
|
|
SW.Write(MyMagic);
|
|
|
|
LengthFieldU32or64 Length = SW.WriteDeferredLength(4, Bits.Num._32);
|
|
PackageData(SW);
|
|
SW.CommitDeferredField(Length);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Always uses big endian!!!
|
|
/// Converts a blob back into an array of bytes (does not work if there are any file-absolute offsets, as it serializes starting at 0 of a fake stream)
|
|
/// </summary>
|
|
public byte[] GetBlobBytes()
|
|
{
|
|
MemoryStream MemoryBuffer = new MemoryStream();
|
|
BinaryWriter Writer = new BinaryWriter(MemoryBuffer);
|
|
WritingContext SW = new WritingContext(Writer);
|
|
SW.bStreamLittleEndian = false;
|
|
|
|
Write(SW);
|
|
SW.CompleteWritingAndClose();
|
|
|
|
return MemoryBuffer.ToArray();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
public class OpaqueBlob : AbstractBlob
|
|
{
|
|
protected byte[] MyData;
|
|
|
|
protected override void UnpackageData(ReadingContext SR, UInt32 Length)
|
|
{
|
|
MyData = SR.ReadBytes(Length - (2 * sizeof(UInt32)));
|
|
}
|
|
|
|
protected override void PackageData(WritingContext SW)
|
|
{
|
|
SW.Write(MyData);
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return "Opaque blob";//'" + Encoding.ASCII.GetString(MyData, 0, MyData.Length) + "'";
|
|
}
|
|
}
|
|
|
|
public abstract class SuperBlob : AbstractBlob
|
|
{
|
|
protected BlobTable Table = new BlobTable();
|
|
|
|
public AbstractBlob GetBlobByMagic(UInt32 Magic)
|
|
{
|
|
foreach (KeyValuePair<UInt32, AbstractBlob> KVP in Table.Slots)
|
|
{
|
|
if (KVP.Value.MyMagic == Magic)
|
|
{
|
|
return KVP.Value;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public AbstractBlob GetBlobByKey(UInt32 Key)
|
|
{
|
|
foreach (KeyValuePair<UInt32, AbstractBlob> KVP in Table.Slots)
|
|
{
|
|
if (KVP.Key == Key)
|
|
{
|
|
return KVP.Value;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
protected override void UnpackageData(ReadingContext SR, uint Length)
|
|
{
|
|
Table.Read(SR);
|
|
}
|
|
|
|
protected override void PackageData(WritingContext SW)
|
|
{
|
|
Table.Write(SW);
|
|
}
|
|
|
|
public void Add(UInt32 Key, AbstractBlob Value)
|
|
{
|
|
Table.Slots.Add(new KeyValuePair<UInt32, AbstractBlob>(Key, Value));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// References:
|
|
/// http://www.opensource.apple.com/source/libsecurity_utilities/libsecurity_utilities-36984/lib/blob.h
|
|
/// http://www.opensource.apple.com/source/libsecurity_utilities/libsecurity_utilities-38535/lib/superblob.h
|
|
/// http://www.opensource.apple.com/source/libsecurity_codesigning/libsecurity_codesigning-36885/lib/cscdefs.h
|
|
///
|
|
/// BlobTable:
|
|
/// U32 SlotCount
|
|
/// SerializedSlot Slots[Count]
|
|
///
|
|
/// SerializedSlot
|
|
/// KeyType Type
|
|
/// U32 Offset (relative to start of BlobTable)
|
|
///
|
|
/// </summary>
|
|
public class BlobTable
|
|
{
|
|
public List<KeyValuePair<UInt32, AbstractBlob>> Slots = new List<KeyValuePair<UInt32, AbstractBlob>>();
|
|
|
|
public AbstractBlob GetByKey(UInt32 Key)
|
|
{
|
|
foreach (KeyValuePair<UInt32, AbstractBlob> KVP in Slots)
|
|
{
|
|
if (KVP.Key == Key)
|
|
{
|
|
return KVP.Value;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads in a data blob, preserving the stream read pointer (Offset is relative to the start of the underlying stream)
|
|
/// </summary>
|
|
AbstractBlob ReadBlob(ReadingContext SR, long Offset)
|
|
{
|
|
SR.PushPositionAndJump(Offset);
|
|
AbstractBlob Result = AbstractBlob.CreateFromStream(SR);
|
|
SR.PopPosition();
|
|
|
|
return Result;
|
|
}
|
|
|
|
public void Write(WritingContext SW)
|
|
{
|
|
// Magic (written in the outer)
|
|
// Length (written in the outer)
|
|
// SlotCount
|
|
// Slot SlotTable[SlotCount]
|
|
// Each slot is Key, Offset
|
|
// <Slot-referenced data>
|
|
|
|
//WritingContext.OffsetFieldU32or64[] Offsets = new WritingContext.OffsetFieldU32or64[Slots.Count];
|
|
long StartingPosition = SW.Position - (2 * sizeof(UInt32));
|
|
|
|
// Start a phase for writing out the superblob data
|
|
SW.CreateNewPhase();
|
|
|
|
// Write the slot table, queuing up the individual slot writes
|
|
SW.Write((UInt32)Slots.Count);
|
|
|
|
foreach (KeyValuePair<UInt32, AbstractBlob> Slot in Slots)
|
|
{
|
|
SW.Write(Slot.Key);
|
|
|
|
OffsetFieldU32or64 Offset = SW.WriteDeferredOffsetFrom(StartingPosition, Bits.Num._32);
|
|
KeyValuePair<UInt32, AbstractBlob> LocalSlot = Slot;
|
|
|
|
SW.CurrentPhase.PendingWrites.Enqueue(delegate(WritingContext Context)
|
|
{
|
|
if (Config.bCodeSignVerbose)
|
|
{
|
|
Console.WriteLine("Writing a slot. Offset={0}, SlotData={1}", Offset.WritePoint, LocalSlot.ToString());
|
|
}
|
|
SW.CommitDeferredField(Offset);
|
|
LocalSlot.Value.Write(Context);
|
|
});
|
|
}
|
|
|
|
// Force evaluation of the slots
|
|
SW.ProcessEntirePhase();
|
|
}
|
|
|
|
public void Read(ReadingContext SR)
|
|
{
|
|
long BaseOffset = SR.Position - (2 * sizeof(UInt32));
|
|
|
|
UInt32 SlotCount = SR.ReadUInt32();
|
|
|
|
for (UInt32 i = 0; i < SlotCount; ++i)
|
|
{
|
|
// Read a slot
|
|
UInt32 Key = SR.ReadUInt32();
|
|
UInt32 Offset = SR.ReadUInt32();
|
|
|
|
// Read it's associated blob
|
|
AbstractBlob Blob = ReadBlob(SR, BaseOffset + Offset);
|
|
|
|
// Add it to the slot table
|
|
Slots.Add(new KeyValuePair<UInt32, AbstractBlob>(Key, Blob));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
public class CodeDirectorySignatureBlob : OpaqueBlob
|
|
{
|
|
public CodeDirectorySignatureBlob()
|
|
{
|
|
MyMagic = CSMAGIC_CODEDIR_SIGNATURE;
|
|
}
|
|
|
|
public static CodeDirectorySignatureBlob Create()
|
|
{
|
|
return new CodeDirectorySignatureBlob();
|
|
}
|
|
|
|
public void DebugCMSData(byte[] Data, string Prefix)
|
|
{
|
|
SignedCms CMS = new SignedCms();
|
|
|
|
CMS.Decode(Data);
|
|
int Version = CMS.Version;
|
|
|
|
Console.WriteLine("CMS Blob {0} Info", Prefix);
|
|
Console.WriteLine(" Version = {0}", Version);
|
|
foreach (SignerInfo si in CMS.SignerInfos)
|
|
{
|
|
Console.WriteLine(" SignerInfo {");
|
|
Console.WriteLine(" Cert issued by {0}", si.Certificate.Issuer);
|
|
foreach (CryptographicAttributeObject Attr in si.SignedAttributes)
|
|
{
|
|
foreach (AsnEncodedData AttrData in Attr.Values)
|
|
{
|
|
Pkcs9SigningTime SignTime = AttrData as Pkcs9SigningTime;
|
|
Pkcs9ContentType CType = AttrData as Pkcs9ContentType;
|
|
Pkcs9MessageDigest Digest = AttrData as Pkcs9MessageDigest;
|
|
|
|
if (SignTime != null)
|
|
Console.WriteLine(" SignedTime {0:O}", SignTime.SigningTime);
|
|
if (CType != null)
|
|
Console.WriteLine(" ContentType {0}", CType.ContentType.FriendlyName);
|
|
if (Digest != null)
|
|
Console.WriteLine(" MessageDigest h'{0}'", BitConverter.ToString(Digest.RawData).Replace("-", ""));
|
|
}
|
|
}
|
|
Console.WriteLine(" }");
|
|
}
|
|
|
|
Console.WriteLine(" Inner content: h'{0}'", BitConverter.ToString(CMS.ContentInfo.Content).Replace("-", ""));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Populates this CMS blob with the data from signing a code directory
|
|
/// </summary>
|
|
public void SignCodeDirectory(X509Certificate2 SigningCert, DateTime SigningTime, CodeDirectoryBlob CodeDirectory)
|
|
{
|
|
// Create a signer
|
|
CmsSigner Signer = new CmsSigner(SigningCert);
|
|
Signer.IncludeOption = X509IncludeOption.WholeChain;
|
|
Signer.SignerIdentifierType = SubjectIdentifierType.IssuerAndSerialNumber;
|
|
Signer.DigestAlgorithm = new Oid(CryptoConfig.MapNameToOID("sha256"), "sha256");
|
|
|
|
// A Pkcs9ContentType and Pkcs9MessageDigest will automatically be added, and it fails to
|
|
// compute a signature if they are added manually, so only the signing time needs to be added
|
|
Signer.SignedAttributes.Add(new Pkcs9SigningTime(SigningTime));
|
|
|
|
// Sign the data (in a detached manner, so only the digest of the CodeDirectory is
|
|
// stored in the CMS blob and not the whole CodeDirectory blob)
|
|
bool bDetached = true;
|
|
bool bSilent = true;
|
|
ContentInfo CodeDirContentInfo = new ContentInfo(CodeDirectory.GetBlobBytes());
|
|
SignedCms CMS = new SignedCms(CodeDirContentInfo, bDetached);
|
|
CMS.ComputeSignature(Signer, bSilent);
|
|
|
|
MyData = CMS.Encode();
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
DebugCMSData(this.MyData, "CMS blob ToString");
|
|
return "CMS Blob";
|
|
}
|
|
}
|
|
|
|
public class CodeDirectoryBlob : AbstractBlob
|
|
{
|
|
public const UInt32 cVersion2 = 0x20200;
|
|
public const UInt32 cVersion3 = 0x20400;
|
|
|
|
public UInt32 Version;
|
|
UInt32 Flags;
|
|
|
|
//UInt32 HashOffset;
|
|
byte[] Hashes;
|
|
|
|
//UInt32 IdentifierStringOffset;
|
|
string Identifier;
|
|
|
|
UInt32 SpecialSlotCount;
|
|
UInt32 CodeSlotCount;
|
|
UInt32 MainImageSignatureLimit;
|
|
byte BytesPerHash;
|
|
byte HashType;
|
|
byte Spare1;
|
|
byte LogPageSize;
|
|
UInt32 Spare2;
|
|
UInt32 ScatterCount;
|
|
|
|
string Team;
|
|
|
|
UInt32 Spare3;
|
|
UInt64 CodeLimit64;
|
|
|
|
UInt64 ExecSegBase;
|
|
UInt64 ExecSegLimit;
|
|
UInt64 ExecSegFlags;
|
|
|
|
// Special slot index for Info.plist
|
|
public const int cdInfoSlot = 1;
|
|
|
|
// Special slot index for internal requirements
|
|
public const int cdRequirementsSlot = 2;
|
|
|
|
// Special slot index for the resource directory
|
|
public const int cdResourceDirSlot = 3;
|
|
|
|
// Special slot index for the application
|
|
public const int cdApplicationSlot = 4;
|
|
|
|
// Special slot index for embedded entitlements
|
|
public const int cdEntitlementSlot = 5;
|
|
|
|
// Special slot index 6, unknown - all 0's in practice
|
|
public const int cdUnknown6Slot = 6;
|
|
|
|
// Special slot index for embedded der entitlements
|
|
public const int cdDerEntitlementSlot = 7;
|
|
|
|
// Number of special slot indexes
|
|
public const int cdSlotMax = cdDerEntitlementSlot;
|
|
|
|
/// <summary>
|
|
/// Hash provider (SHA1)
|
|
/// </summary>
|
|
protected SHA256CryptoServiceProvider HashProvider2 = new SHA256CryptoServiceProvider();
|
|
|
|
protected override void UnpackageData(ReadingContext SR, UInt32 Length)
|
|
{
|
|
long StartOfBlob = SR.Position - sizeof(UInt32) * 2;
|
|
|
|
// References:
|
|
// https://llvm.org/doxygen/BinaryFormat_2MachO_8h_source.html
|
|
// https://github.com/apple/darwin-xnu/blob/main/osfmk/kern/cs_blobs.h
|
|
Version = SR.ReadUInt32();
|
|
Flags = SR.ReadUInt32();
|
|
UInt32 HashOffset = SR.ReadUInt32();
|
|
UInt32 IdentifierStringOffset = SR.ReadUInt32();
|
|
SpecialSlotCount = SR.ReadUInt32();
|
|
CodeSlotCount = SR.ReadUInt32();
|
|
MainImageSignatureLimit = SR.ReadUInt32();
|
|
BytesPerHash = SR.ReadByte();
|
|
HashType = SR.ReadByte();
|
|
Spare1 = SR.ReadByte();
|
|
LogPageSize = SR.ReadByte();
|
|
Spare2 = SR.ReadUInt32();
|
|
ScatterCount = SR.ReadUInt32();
|
|
UInt32 TeamStringOffset = SR.ReadUInt32();
|
|
Spare3 = SR.ReadUInt32();
|
|
CodeLimit64 = SR.ReadUInt64();
|
|
ExecSegBase = SR.ReadUInt64();
|
|
ExecSegLimit = SR.ReadUInt64();
|
|
ExecSegFlags = SR.ReadUInt64();
|
|
|
|
// Read the identifier string
|
|
SR.PushPositionAndJump(StartOfBlob + IdentifierStringOffset);
|
|
Identifier = SR.ReadASCIIZ();
|
|
SR.PopPosition();
|
|
// Read the team string
|
|
SR.PushPositionAndJump(StartOfBlob + TeamStringOffset);
|
|
Team = SR.ReadASCIIZ();
|
|
SR.PopPosition();
|
|
|
|
// Read the hashes
|
|
long TotalNumHashes = SpecialSlotCount + CodeSlotCount;
|
|
Hashes = new byte[TotalNumHashes * BytesPerHash];
|
|
|
|
SR.PushPositionAndJump(StartOfBlob + HashOffset - BytesPerHash * SpecialSlotCount);
|
|
for (long i = 0; i < TotalNumHashes; ++i)
|
|
{
|
|
byte[] Hash = SR.ReadBytes(BytesPerHash);
|
|
Array.Copy(Hash, 0, Hashes, i * BytesPerHash, BytesPerHash);
|
|
}
|
|
SR.PopPosition();
|
|
|
|
|
|
if (Config.bCodeSignVerbose)
|
|
{
|
|
PrintHash("Info:", cdSlotMax - cdInfoSlot);
|
|
PrintHash("Requirements:", cdSlotMax - cdRequirementsSlot);
|
|
PrintHash("ResourceDir:", cdSlotMax - cdResourceDirSlot);
|
|
PrintHash("Application:", cdSlotMax - cdApplicationSlot);
|
|
PrintHash("Entitlements:", cdSlotMax - cdEntitlementSlot);
|
|
}
|
|
}
|
|
|
|
void PrintHash(string Prefix, int SlotIndex)
|
|
{
|
|
Console.WriteLine("{0} {1}", Prefix, BitConverter.ToString(Hashes, SlotIndex * BytesPerHash, BytesPerHash).Replace("-", ""));
|
|
}
|
|
|
|
protected override void PackageData(WritingContext SW)
|
|
{
|
|
long StartPos = SW.Position - (2 * sizeof(UInt32));
|
|
|
|
SW.Write(Version);
|
|
SW.Write(Flags);
|
|
|
|
// The hash offset is weird, it points to the first code page hash, not the start of the hashes array...
|
|
OffsetFieldU32or64 HashOffset = SW.WriteDeferredOffsetFrom(StartPos - (BytesPerHash * SpecialSlotCount), Bits.Num._32);
|
|
OffsetFieldU32or64 IdentifierStringOffset = SW.WriteDeferredOffsetFrom(StartPos, Bits.Num._32);
|
|
|
|
SW.Write(SpecialSlotCount);
|
|
SW.Write(CodeSlotCount);
|
|
SW.Write(MainImageSignatureLimit);
|
|
|
|
SW.Write(BytesPerHash);
|
|
SW.Write(HashType);
|
|
SW.Write(Spare1);
|
|
SW.Write(LogPageSize);
|
|
|
|
SW.Write(Spare2);
|
|
SW.Write(ScatterCount);
|
|
|
|
OffsetFieldU32or64 TeamStringOffset = SW.WriteDeferredOffsetFrom(StartPos, Bits.Num._32);
|
|
SW.Write(Spare3);
|
|
|
|
SW.Write(CodeLimit64);
|
|
SW.Write(ExecSegBase);
|
|
SW.Write(ExecSegLimit);
|
|
SW.Write(ExecSegFlags);
|
|
|
|
// Write the identifier
|
|
SW.CommitDeferredField(IdentifierStringOffset);
|
|
byte[] IdentifierOutput = Utilities.CreateASCIIZ(Identifier);
|
|
SW.Write(IdentifierOutput);
|
|
|
|
// Write the team identifier
|
|
SW.CommitDeferredField(TeamStringOffset);
|
|
byte[] TeamOutput = Utilities.CreateASCIIZ(Team);
|
|
SW.Write(TeamOutput);
|
|
|
|
// Write the hashes
|
|
SW.CommitDeferredField(HashOffset);
|
|
SW.Write(Hashes);
|
|
}
|
|
|
|
public CodeDirectoryBlob()
|
|
{
|
|
MyMagic = CSMAGIC_CODEDIRECTORY;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The page size in bytes
|
|
/// </summary>
|
|
public int PageSize
|
|
{
|
|
get { return 1 << LogPageSize; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generates a hash for one of the special slots and copies it into the hash array
|
|
/// </summary>
|
|
public void GenerateSpecialSlotHash(int SpecialSlotIndex, byte[] SourceData)
|
|
{
|
|
byte[] Hash = HashProvider2.ComputeHash(SourceData);
|
|
Array.Copy(
|
|
Hash, 0,
|
|
Hashes, (SpecialSlotCount - SpecialSlotIndex) * BytesPerHash,
|
|
BytesPerHash);
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Generates an empty hash (all zeros) for a special slot that isn't used (only cdApplicationSlot gets this treatment)
|
|
/// </summary>
|
|
/// <param name="SpecialSlotIndex"></param>
|
|
public void GenerateSpecialSlotHash(int SpecialSlotIndex)
|
|
{
|
|
for (int i = 0; i < BytesPerHash; ++i)
|
|
{
|
|
Hashes[(cdSlotMax - SpecialSlotIndex) * BytesPerHash + i] = 0;
|
|
}
|
|
}
|
|
|
|
public static CodeDirectoryBlob Create(string ApplicationID, string TeamID, int SignedFileLength, UInt64 InExecSegBase, UInt64 InExecSegLimit)
|
|
{
|
|
CodeDirectoryBlob Blob = new CodeDirectoryBlob();
|
|
Blob.Allocate(ApplicationID, TeamID, SignedFileLength, InExecSegBase, InExecSegLimit);
|
|
|
|
return Blob;
|
|
}
|
|
|
|
public void Allocate(string ApplicationID, string TeamID, int SignedFileLength, UInt64 InExecSegBase, UInt64 InExecSegLimit)
|
|
{
|
|
Identifier = ApplicationID;
|
|
Team = TeamID;
|
|
|
|
Version = cVersion3;
|
|
Flags = 0;
|
|
Spare1 = 0;
|
|
Spare2 = 0;
|
|
Spare3 = 0;
|
|
ScatterCount = 0;
|
|
|
|
CodeLimit64 = 0;
|
|
|
|
ExecSegBase = InExecSegBase;
|
|
ExecSegLimit = InExecSegLimit;
|
|
ExecSegFlags = 17;
|
|
|
|
// 4 KB pages
|
|
LogPageSize = 12;
|
|
int PageSize = 1 << LogPageSize;
|
|
|
|
// 32 byte SHA256 hashes
|
|
HashType = 2;
|
|
BytesPerHash = (byte)(HashProvider2.HashSize / 8);
|
|
Debug.Assert(BytesPerHash == 32);
|
|
|
|
// Allocate space for the hashes
|
|
MainImageSignatureLimit = (UInt32)SignedFileLength;
|
|
SpecialSlotCount = cdSlotMax;
|
|
CodeSlotCount = (uint)((MainImageSignatureLimit + PageSize - 1) / PageSize);
|
|
Hashes = new byte[(SpecialSlotCount + CodeSlotCount) * BytesPerHash];
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculates the hashes for every 4 KB of the main executable
|
|
/// </summary>
|
|
public void ComputeImageHashes(byte[] SignedFileData)
|
|
{
|
|
// Fill out the executable hash
|
|
for (int i = 0; i < CodeSlotCount; ++i)
|
|
{
|
|
int StartOffset = i * PageSize;
|
|
int LengthRemaining = (int)MainImageSignatureLimit - StartOffset;
|
|
byte[] PageHash = HashProvider2.ComputeHash(SignedFileData, StartOffset, Math.Min(LengthRemaining, PageSize));
|
|
Array.Copy(PageHash, 0, Hashes, (SpecialSlotCount + i) * BytesPerHash, BytesPerHash);
|
|
}
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return String.Format(
|
|
"\n CodeDirectory\n v{0:X}\n flags={1}\n Ident: {2}\n with {3}+{4} slots",
|
|
Version, Flags, Identifier,
|
|
SpecialSlotCount, CodeSlotCount) +
|
|
|
|
String.Format(
|
|
"\n SigLimit 0x{0:X}\n Hash(Len={1} Type={2} Page={3})\n Spares={4} {5}\n ScatterCount={6}\n\n",
|
|
MainImageSignatureLimit, BytesPerHash, HashType, 1 << LogPageSize, Spare1, Spare2, ScatterCount);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Payload for LC_CODE_SIGNATURE
|
|
/// </summary>
|
|
class MachLoadCommandCodeSignature : MachLoadCommandLinkEditBlob
|
|
{
|
|
public CodeSigningTableBlob Payload;
|
|
|
|
public override string ToString()
|
|
{
|
|
return "Code Signature " + base.ToString();
|
|
}
|
|
|
|
protected override void UnpackageData(ReadingContext SR, int CommandSize)
|
|
{
|
|
base.UnpackageData(SR, CommandSize);
|
|
|
|
SR.PushPositionAndJump(BlobFileOffset);
|
|
SR.bStreamLittleEndian = false;
|
|
long SavedPosition = SR.Position;
|
|
|
|
// Parse the blob
|
|
Payload = AbstractBlob.CreateFromStream(SR) as CodeSigningTableBlob;
|
|
|
|
if (Config.bCodeSignVerbose)
|
|
{
|
|
Console.WriteLine(Payload.ToString());
|
|
}
|
|
|
|
//SR.VerifyStreamPosition(SavedPosition, BlobFileSize);
|
|
|
|
SR.PopPosition();
|
|
SR.bStreamLittleEndian = true;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Patches the the blob length and offset of the code signing blob in an existing file
|
|
/// </summary>
|
|
public void PatchPositionAndSize(WritingContext SW, UInt32 NewOffset, UInt32 NewLength)
|
|
{
|
|
Debug.Assert(StartingLoadOffset >= 0);
|
|
|
|
if (Config.bCodeSignVerbose)
|
|
{
|
|
Console.WriteLine("ZZZZZZZZZZZZZZZZ");
|
|
Console.WriteLine(" Blob offset from 0x{0:X} to 0x{1:X} (delta {2})", BlobFileOffset, NewOffset, (long)NewOffset - (long)BlobFileOffset);
|
|
Console.WriteLine(" Blob size from 0x{0:X} to 0x{1:X} (delta {2})", BlobFileSize, NewLength, (long)NewLength - (long)BlobFileSize);
|
|
Console.WriteLine("ZZZZZZZZZZZZZZZZ");
|
|
}
|
|
|
|
BlobFileOffset = NewOffset;
|
|
BlobFileSize = NewLength;
|
|
|
|
long PatchOffset = StartingLoadOffset + (2 * sizeof(UInt32)); // Command, CommandLength
|
|
|
|
SW.PushPositionAndJump(PatchOffset);
|
|
SW.Write(BlobFileOffset);
|
|
SW.Write(BlobFileSize);
|
|
SW.PopPosition();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Minimal implementation of the Fat Binary object file format
|
|
/// See: https://developer.apple.com/library/mac/documentation/DeveloperTools/Conceptual/MachORuntime/Reference/reference.html#//apple_ref/doc/uid/20001298-154889
|
|
|
|
public class FatBinaryArch
|
|
{
|
|
public UInt32 CpuType;
|
|
public UInt32 CpuSubType;
|
|
public UInt32 Offset; // Offset to the beginning of the data for this CPU.
|
|
public UInt32 Size; // Size of the data for this CPU.
|
|
public UInt32 Align;
|
|
}
|
|
|
|
public class FatBinaryFile
|
|
{
|
|
public UInt32 Magic;
|
|
public UInt32 NumArchs;
|
|
|
|
// Was this really a fat binary, or are we just a wrapper for the MachObjectFile?
|
|
public bool bIsFatBinary;
|
|
|
|
public List<FatBinaryArch> Archs;
|
|
|
|
public List<MachObjectFile> MachObjectFiles = new List<MachObjectFile>();
|
|
|
|
// Values for Magic
|
|
public const UInt32 FAT_MAGIC = 0xCAFEBABE;
|
|
public const UInt32 FAT_CIGAM = 0xBEBAFECA; // For little Endian.
|
|
|
|
protected void Read(ReadingContext SR)
|
|
{
|
|
// Read the header to see if it is a FAT Binary or not.
|
|
SR.bStreamLittleEndian = false;
|
|
Magic = SR.ReadUInt32();
|
|
bIsFatBinary = (Magic == FAT_MAGIC);
|
|
|
|
if (bIsFatBinary)
|
|
{
|
|
Archs = new List<FatBinaryArch>();
|
|
NumArchs = SR.ReadUInt32();
|
|
|
|
for (int ArchIdx = 0; ArchIdx < NumArchs; ArchIdx++)
|
|
{
|
|
SR.bStreamLittleEndian = false;
|
|
|
|
FatBinaryArch Arch = new FatBinaryArch();
|
|
|
|
Arch.CpuType = SR.ReadUInt32();
|
|
Arch.CpuSubType = SR.ReadUInt32();
|
|
Arch.Offset = SR.ReadUInt32();
|
|
Arch.Size = SR.ReadUInt32();
|
|
Arch.Align = SR.ReadUInt32();
|
|
|
|
Archs.Add(Arch);
|
|
|
|
MachObjectFile Exe = new MachObjectFile();
|
|
|
|
SR.bStreamLittleEndian = true;
|
|
SR.OpenFatArchiveAt(Arch.Offset);
|
|
Exe.Read(SR);
|
|
SR.CloseFatArchive();
|
|
|
|
MachObjectFiles.Add(Exe);
|
|
}
|
|
SR.bStreamLittleEndian = true;
|
|
}
|
|
else
|
|
{
|
|
SR.bStreamLittleEndian = true;
|
|
MachObjectFile Exe = new MachObjectFile();
|
|
SR.Position = 0;
|
|
Exe.Read(SR);
|
|
MachObjectFiles.Add(Exe);
|
|
}
|
|
}
|
|
|
|
protected void Write(WritingContext Context)
|
|
{
|
|
if (bIsFatBinary)
|
|
{
|
|
// Write the header
|
|
Context.Write(Magic);
|
|
Context.Write(NumArchs);
|
|
|
|
Context.PushPosition();
|
|
|
|
foreach (FatBinaryArch Arch in Archs)
|
|
{
|
|
Context.Write(Arch.CpuType);
|
|
Context.Write(Arch.CpuSubType);
|
|
Context.Write(Arch.Offset);
|
|
Context.Write(Arch.Size);
|
|
Context.Write(Arch.Align);
|
|
}
|
|
|
|
int FileIdx = 0;
|
|
foreach (MachObjectFile MachFile in MachObjectFiles)
|
|
{
|
|
Archs[FileIdx].Offset = Convert.ToUInt32(Context.Position);
|
|
MachFile.Write(Context);
|
|
Archs[FileIdx].Size = Convert.ToUInt32(Context.Position) - Archs[FileIdx].Offset;
|
|
FileIdx++;
|
|
}
|
|
|
|
Context.PopPosition();
|
|
|
|
// Write updated header.
|
|
foreach (FatBinaryArch Arch in Archs)
|
|
{
|
|
Context.Write(Arch.CpuType);
|
|
Context.Write(Arch.CpuSubType);
|
|
Context.Write(Arch.Offset);
|
|
Context.Write(Arch.Size);
|
|
Context.Write(Arch.Align);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Should only be one...
|
|
MachObjectFiles[0].Write(Context);
|
|
}
|
|
}
|
|
|
|
public void WriteHeader(ref byte[] OutputData, uint Offset)
|
|
{
|
|
if (bIsFatBinary)
|
|
{
|
|
// Write the header
|
|
byte[] Data = BitConverter.GetBytes(Magic);
|
|
if (BitConverter.IsLittleEndian)
|
|
{
|
|
Array.Reverse(Data);
|
|
}
|
|
Data.CopyTo(OutputData, Offset);
|
|
Offset += sizeof(UInt32);
|
|
|
|
Data = BitConverter.GetBytes(NumArchs);
|
|
if (BitConverter.IsLittleEndian)
|
|
{
|
|
Array.Reverse(Data);
|
|
}
|
|
Data.CopyTo(OutputData, Offset);
|
|
Offset += sizeof(UInt32);
|
|
|
|
foreach (FatBinaryArch Arch in Archs)
|
|
{
|
|
Data = BitConverter.GetBytes(Arch.CpuType);
|
|
if (BitConverter.IsLittleEndian)
|
|
{
|
|
Array.Reverse(Data);
|
|
}
|
|
Data.CopyTo(OutputData, Offset);
|
|
Offset += sizeof(UInt32);
|
|
|
|
Data = BitConverter.GetBytes(Arch.CpuSubType);
|
|
if (BitConverter.IsLittleEndian)
|
|
{
|
|
Array.Reverse(Data);
|
|
}
|
|
Data.CopyTo(OutputData, Offset);
|
|
Offset += sizeof(UInt32);
|
|
|
|
Data = BitConverter.GetBytes(Arch.Offset);
|
|
if (BitConverter.IsLittleEndian)
|
|
{
|
|
Array.Reverse(Data);
|
|
}
|
|
Data.CopyTo(OutputData, Offset);
|
|
Offset += sizeof(UInt32);
|
|
|
|
Data = BitConverter.GetBytes(Arch.Size);
|
|
if (BitConverter.IsLittleEndian)
|
|
{
|
|
Array.Reverse(Data);
|
|
}
|
|
Data.CopyTo(OutputData, Offset);
|
|
Offset += sizeof(UInt32);
|
|
|
|
Data = BitConverter.GetBytes(Arch.Align);
|
|
if (BitConverter.IsLittleEndian)
|
|
{
|
|
Array.Reverse(Data);
|
|
}
|
|
Data.CopyTo(OutputData, Offset);
|
|
Offset += sizeof(UInt32);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void LoadFromFile(string Filename)
|
|
{
|
|
BinaryReader SR = new BinaryReader(File.OpenRead(Filename));
|
|
ReadingContext Context = new ReadingContext(SR);
|
|
Read(Context);
|
|
SR.Close();
|
|
}
|
|
|
|
public void LoadFromBytes(byte[] Data)
|
|
{
|
|
MemoryStream Stream = new MemoryStream(Data, false);
|
|
BinaryReader SR = new BinaryReader(Stream);
|
|
ReadingContext Context = new ReadingContext(SR);
|
|
Read(Context);
|
|
SR.Close();
|
|
}
|
|
|
|
public void WriteToFile(string Filename)
|
|
{
|
|
BinaryWriter SW = new BinaryWriter(File.OpenWrite(Filename));
|
|
WritingContext Context = new WritingContext(SW);
|
|
Write(Context);
|
|
SW.Close();
|
|
}
|
|
}
|
|
}
|