2014-12-07 19:09:38 -05:00
// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
2014-03-14 14:13:41 -04:00
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 ;
/// <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 , 4 ) ;
SW . Write ( Buffer , 0 , 4 ) ;
}
}
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_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 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 ;
// 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_REQUIREMENTS_TABLE :
Result = new RequirementsBlob ( ) ;
break ;
case CSMAGIC_EMBEDDED_SIGNATURE :
Result = new CodeSigningTableBlob ( ) ;
break ;
case CSMAGIC_REQUIREMENT :
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 ( "SHA1" ) , "SHA1" ) ;
// 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
{
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 ;
// 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 ;
// Number of special slot indexes
public const int cdSlotMax = cdEntitlementSlot ;
/// <summary>
/// Hash provider (SHA1)
/// </summary>
protected SHA1CryptoServiceProvider HashProvider = new SHA1CryptoServiceProvider ( ) ;
protected override void UnpackageData ( ReadingContext SR , UInt32 Length )
{
long StartOfBlob = SR . Position - sizeof ( UInt32 ) * 2 ;
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 ( ) ;
// Read the identifier string
SR . PushPositionAndJump ( StartOfBlob + IdentifierStringOffset ) ;
Identifier = 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 ) ;
// Write the identifier
SW . CommitDeferredField ( IdentifierStringOffset ) ;
byte [ ] IdentifierOutput = Utilities . CreateASCIIZ ( Identifier ) ;
SW . Write ( IdentifierOutput ) ;
// 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 = HashProvider . 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 , int SignedFileLength )
{
CodeDirectoryBlob Blob = new CodeDirectoryBlob ( ) ;
Blob . Allocate ( ApplicationID , SignedFileLength ) ;
return Blob ;
}
public void Allocate ( string ApplicationID , int SignedFileLength )
{
Identifier = ApplicationID ;
Version = 0x20100 ;
Flags = 0 ;
Spare1 = 0 ;
Spare2 = 0 ;
ScatterCount = 0 ;
// 4 KB pages
LogPageSize = 12 ;
int PageSize = 1 < < LogPageSize ;
// 20 byte SHA1 hashes
HashType = 1 ;
BytesPerHash = ( byte ) ( HashProvider . HashSize / 8 ) ;
Debug . Assert ( BytesPerHash = = 20 ) ;
// 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 = HashProvider . 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 ) ;
}
}
2015-01-28 10:47:54 -05:00
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 ) ;
}
}
}
2014-03-14 14:13:41 -04:00
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 ( ) ;
}
}
}