Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Code signing data parsing #265

Draft
wants to merge 9 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/macho.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
require_relative "macho/structure"
require_relative "macho/view"
require_relative "macho/headers"
require_relative "macho/code_signing"
require_relative "macho/load_commands"
require_relative "macho/sections"
require_relative "macho/macho_file"
Expand Down
234 changes: 234 additions & 0 deletions lib/macho/code_signing.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
# frozen_string_literal: true

module MachO
module CodeSigning
CS_STRUCTURES = {
Headers::CSMAGIC_REQUIREMENT => "Requirement",
Headers::CSMAGIC_REQUIREMENTS => "Requirements",
Headers::CSMAGIC_CODEDIRECTORY => "CodeDirectory",
Headers::CSMAGIC_EMBEDDED_SIGNATURE => "EmbeddedSignature",
Headers::CSMAGIC_EMBEDDED_SIGNATURE_OLD => "OldEmbeddedSignature",
Headers::CSMAGIC_DETACHED_SIGNATURE => "DetachedSignature",
Headers::CSMAGIC_ENTITLEMENT => "Entitlement",
Headers::CSMAGIC_ENTITLEMENTDER => "DEREntitlement",
Headers::CSMAGIC_BLOBWRAPPER => "BlobWrapper",
}.freeze

CS_HASHTYPES = {
1 => :CS_HASHTYPE_SHA1,
2 => :CS_HASHTYPE_SHA256,
3 => :CS_HASHTYPE_SHA256_TRUNCATED,
4 => :CS_HASHTYPE_SHA384,
}.freeze

class CSStructure < MachOStructure
attr_reader :view

def self.new_from_bin(view)
bin = view.raw_data.slice(view.offset, bytesize)
format = Utils.specialize_format(self::FORMAT, view.endianness)

new(view, *bin.unpack(format))
end

def initialize(view)
@view = view
end

def to_h
{
"view" => view.to_h,
}.merge super
end
end

class CSBlob < CSStructure
field :magic, :uint32, endian: :big
field :length, :uint32, endian: :big

def magic_sym
Headers::CS_MAGICS[magic]
end

def to_h
{
"blob" => {
"magic" => magic,
"magic_sym" => magic_sym,
"length" => length,
},
}.merge super
end
end

class SuperBlob < CSBlob
class BlobIndex < CSStructure
field :type, :uint32, endian: :big
field :offset, :uint32, endian: :big
end

field :count, :uint32, endian: :big

def self.new_from_bin(view)
bin = view.raw_data.slice(view.offset, bytesize)
format = Utils.specialize_format(self::FORMAT, view.endianness)

new(view, *bin.unpack(format))
end

def initialize(view, magic, length, count)
# TODO(ww): Check magic matches CSMAGIC_EMBEDDED_SIGNATURE (0xfade0cc0)
super(view, magic, length)
@count = count
end

def each_blob_index
count.times do |i|
index_offset = view.offset + self.class.bytesize + (BlobIndex.bytesize * i)
blob_view = MachOView.new view.raw_data, view.endianness, index_offset
yield BlobIndex.new_from_bin blob_view
end
end

def each_blob
each_blob_index do |blob_index|
blob_offset = view.offset + blob_index.offset
blob_magic = view.raw_data[blob_offset, blob_offset + 4].unpack1("L>1")

blob_klass_str = CS_STRUCTURES[blob_magic]
raise CSBlobUnknownError, blob_magic unless blob_klass_str

blob_klass = CodeSigning.const_get blob_klass_str
blob_view = MachOView.new view.raw_data, view.endianness, blob_offset
yield blob_klass.new_from_bin blob_view
end
end

def to_h
{
magic_sym.to_h => {
"count" => count,
},
}.merge super
end
end

# Represents a code signing Requirement blob.
class Requirement < CSBlob
end

# Represents a code signing Requirements vector.
class Requirements < CSBlob
end

# Represents a code signing CodeDirectory blob.
class CodeDirectory < CSBlob
attr_reader :version
attr_reader :flags
attr_reader :hash_offset
attr_reader :ident_offset
attr_reader :n_special_slots
attr_reader :n_code_slots
attr_reader :code_limit
attr_reader :hash_size
attr_reader :hash_type
attr_reader :spare1
attr_reader :page_size
attr_reader :spare2

FORMAT = "L>9C4L>1"

SIZEOF = 44

def initialize(view, magic, length, version, flags, hash_offset,
ident_offset, n_special_slots, n_code_slots, code_limit,
hash_size, hash_type, spare1, page_size, spare2)
super(view, magic, length)
@version = version
@flags = flags
@hash_offset = hash_offset
@ident_offset = ident_offset
@n_special_slots = n_special_slots
@n_code_slots = n_code_slots
@code_limit = code_limit
@hash_size = hash_size
@hash_type = hash_type
@spare1 = spare1
@page_size = page_size
@spare2 = spare2
end

def hash_type_sym
CS_HASHTYPES[hash_type]
end

# TODO(ww): Figure out a good way to handle the fields that have been
# added with different CodeDirectory versions:
# * 0x20100:
# uint32_t scatterOffset
# char end_withScatter[0]
# * 0x20200:
# uint32_t teamOffset
# char end_withTeam[0]
# * 0x20300:
# uint32_t spare3
# uint64_t codeLimit64
# char end_withCodeLimit64[0]
# * 0x20400:
# uint64_t execSegBase
# uint64_t execSegLimit
# uint64_t execSegFlags
# char end_withExecSec[0]

def to_h
{
magic_sym.to_s => {
"version" => version,
"flags" => flags,
"hash_offset" => hash_offset,
"ident_offset" => ident_offset,
"n_special_slots" => n_special_slots,
"n_code_slots" => n_code_slots,
"code_limit" => code_limit,
"hash_size" => hash_size,
"hash_type" => hash_type,
"spare1" => spare1,
"page_size" => page_size,
"spare2" => spare2,
},
}.merge super
end
end

# Represents a code signing EmbeddedSignature blob.
class EmbeddedSignature < CSBlob
end

# Maybe represents an "old" embedded signature blob.
# Not documented.
class OldEmbeddedSignature < CSBlob
end

# Represents a multi-arch collection of embedded signatures.
class DetachedSignature < CSBlob
end

# Represents a code signing Entitlement blob.
class Entitlement < CSBlob
# NOTE(ww): This appears to just have one member, data, whose length
# is defined by attr :length.
# From SecTask.c:
# const struct theBlob {
# uint32_t magic; /* kSecCodeMagicEntitlement */
# uint32_t length;
# const uint8_t data[];
# }
end

class DEREntitlement < CSBlob
end

class BlobWrapper < CSBlob
end
end
end
16 changes: 16 additions & 0 deletions lib/macho/exceptions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,22 @@ def initialize(offset)
end
end

# TODO(ww): doc
class LinkeditTypeMismatchError < MachOError
# TODO(ww): doc
def initialize(meth, lc_sym)
super "Method #{meth} can't be used on __LINKEDIT data commands of type #{lc_sym}"
end
end

# TODO(ww): doc
class CSBlobUnknownError < MachOError
# TODO(ww): doc
def initialize(magic)
super "Unknown code signing blob magic: 0x#{magic.to_s 16}"
end
end

# Raised when attempting to parse a compressed Mach-O without explicitly
# requesting decompression.
class CompressedMachOError < MachOError
Expand Down
26 changes: 26 additions & 0 deletions lib/macho/headers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -501,6 +501,32 @@ module Headers
:MH_DYLIB_IN_CACHE => 0x80000000,
}.freeze

# the blob index (presumably) for the CodeDirectory blob
# @api private
CSSLOT_CODEDIRECTORY = 0

CSMAGIC_REQUIREMENT = 0xfade0c00
CSMAGIC_REQUIREMENTS = 0xfade0c01
CSMAGIC_CODEDIRECTORY = 0xfade0c02
CSMAGIC_EMBEDDED_SIGNATURE = 0xfade0cc0
CSMAGIC_EMBEDDED_SIGNATURE_OLD = 0xfade0b02
CSMAGIC_DETACHED_SIGNATURE = 0xfade0cc0
CSMAGIC_ENTITLEMENT = 0xfade7171
CSMAGIC_ENTITLEMENTDER = 0xfade7172
CSMAGIC_BLOBWRAPPER = 0xfade0b01

CS_MAGICS = {
CSMAGIC_REQUIREMENT => :CSMAGIC_REQUIREMENT,
CSMAGIC_REQUIREMENTS => :CSMAGIC_REQUIREMENTS,
CSMAGIC_CODEDIRECTORY => :CSMAGIC_CODEDIRECTORY,
CSMAGIC_EMBEDDED_SIGNATURE => :CSMAGIC_EMBEDDED_SIGNATURE,
CSMAGIC_EMBEDDED_SIGNATURE_OLD => :CSMAGIC_EMBEDDED_SIGNATURE_OLD,
CSMAGIC_DETACHED_SIGNATURE => :CSMAGIC_DETACHED_SIGNATURE,
CSMAGIC_ENTITLEMENT => :CSMAGIC_ENTITLEMENT,
CSMAGIC_ENTITLEMENTDER => :CSMAGIC_ENTITLEMENTDER,
CSMAGIC_BLOBWRAPPER => :CSMAGIC_BLOBWRAPPER,
}.freeze

# Fat binary header structure
# @see MachO::FatArch
class FatHeader < MachOStructure
Expand Down
8 changes: 8 additions & 0 deletions lib/macho/load_commands.rb
Original file line number Diff line number Diff line change
Expand Up @@ -973,6 +973,14 @@ def to_h
"datasize" => datasize,
}.merge super
end

# TODO(ww): doc
def superblob
raise LinkeditTypeMismatchError.new(__method__, type) unless type == :LC_CODE_SIGNATURE

superblob_view = MachOView.new view.raw_data, view.endianness, dataoff
CodeSigning::SuperBlob.new_from_bin superblob_view
end
end

# A load command representing the offset to and size of an encrypted
Expand Down