brew vendor-gems: commit updates.
This commit is contained in:
		
							parent
							
								
									6a109f77c6
								
							
						
					
					
						commit
						847b271481
					
				@ -91,7 +91,7 @@ $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-performance-1
 | 
			
		||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-rails-2.13.1/lib"
 | 
			
		||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-rspec-2.7.0/lib"
 | 
			
		||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-sorbet-0.6.5/lib"
 | 
			
		||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/ruby-macho-2.5.1/lib"
 | 
			
		||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/ruby-macho-3.0.0/lib"
 | 
			
		||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/simplecov-html-0.12.3/lib"
 | 
			
		||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/simplecov_json_formatter-0.1.3/lib"
 | 
			
		||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/simplecov-0.21.2/lib"
 | 
			
		||||
 | 
			
		||||
@ -16,7 +16,7 @@ require_relative "macho/tools"
 | 
			
		||||
# The primary namespace for ruby-macho.
 | 
			
		||||
module MachO
 | 
			
		||||
  # release version
 | 
			
		||||
  VERSION = "2.5.1"
 | 
			
		||||
  VERSION = "3.0.0"
 | 
			
		||||
 | 
			
		||||
  # Opens the given filename as a MachOFile or FatFile, depending on its magic.
 | 
			
		||||
  # @param filename [String] the file being opened
 | 
			
		||||
@ -84,7 +84,7 @@ module MachO
 | 
			
		||||
    # @param cpusubtype [Integer] the CPU sub-type of the unknown pair
 | 
			
		||||
    def initialize(cputype, cpusubtype)
 | 
			
		||||
      super "Unrecognized CPU sub-type: 0x%08<cpusubtype>x" \
 | 
			
		||||
        " (for CPU type: 0x%08<cputype>x" % { :cputype => cputype, :cpusubtype => cpusubtype }
 | 
			
		||||
            " (for CPU type: 0x%08<cputype>x" % { :cputype => cputype, :cpusubtype => cpusubtype }
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
@ -120,7 +120,7 @@ module MachO
 | 
			
		||||
    # @param actual_arity [Integer] the number of arguments received
 | 
			
		||||
    def initialize(cmd_sym, expected_arity, actual_arity)
 | 
			
		||||
      super "Expected #{expected_arity} arguments for #{cmd_sym} creation," \
 | 
			
		||||
        " got #{actual_arity}"
 | 
			
		||||
            " got #{actual_arity}"
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
@ -137,7 +137,7 @@ module MachO
 | 
			
		||||
    # @param lc [MachO::LoadCommand] the load command containing the string
 | 
			
		||||
    def initialize(lc)
 | 
			
		||||
      super "Load command #{lc.type} at offset #{lc.view.offset} contains a" \
 | 
			
		||||
        " malformed string"
 | 
			
		||||
            " malformed string"
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
@ -154,8 +154,8 @@ module MachO
 | 
			
		||||
    # @param filename [String] the filename
 | 
			
		||||
    def initialize(filename)
 | 
			
		||||
      super "Updated load commands do not fit in the header of " \
 | 
			
		||||
        "#{filename}. #{filename} needs to be relinked, possibly with " \
 | 
			
		||||
        "-headerpad or -headerpad_max_install_names"
 | 
			
		||||
            "#{filename}. #{filename} needs to be relinked, possibly with " \
 | 
			
		||||
            "-headerpad or -headerpad_max_install_names"
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
@ -207,4 +207,14 @@ module MachO
 | 
			
		||||
            " Consider merging with `fat64: true`"
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  # Raised when attempting to parse a compressed Mach-O without explicitly
 | 
			
		||||
  # requesting decompression.
 | 
			
		||||
  class CompressedMachOError < MachOError
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  # Raised when attempting to decompress a compressed Mach-O without adequate
 | 
			
		||||
  # dependencies, or on other decompression errors.
 | 
			
		||||
  class DecompressionError < MachOError
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@ -55,7 +55,7 @@ module MachO
 | 
			
		||||
      machos.each do |macho|
 | 
			
		||||
        macho_offset = Utils.round(offset, 2**macho.segment_alignment)
 | 
			
		||||
 | 
			
		||||
        raise FatArchOffsetOverflowError, macho_offset if !fat64 && macho_offset > (2**32 - 1)
 | 
			
		||||
        raise FatArchOffsetOverflowError, macho_offset if !fat64 && macho_offset > ((2**32) - 1)
 | 
			
		||||
 | 
			
		||||
        macho_pads[macho] = Utils.padding_for(offset, 2**macho.segment_alignment)
 | 
			
		||||
 | 
			
		||||
@ -96,7 +96,7 @@ module MachO
 | 
			
		||||
 | 
			
		||||
      @filename = filename
 | 
			
		||||
      @options = opts
 | 
			
		||||
      @raw_data = File.open(@filename, "rb", &:read)
 | 
			
		||||
      @raw_data = File.binread(@filename)
 | 
			
		||||
      populate_fields
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
@ -238,6 +238,8 @@ module MachO
 | 
			
		||||
    # @param options [Hash]
 | 
			
		||||
    # @option options [Boolean] :strict (true) if true, fail if one slice fails.
 | 
			
		||||
    #  if false, fail only if all slices fail.
 | 
			
		||||
    # @option options [Boolean] :uniq (false) for each slice: if true, change
 | 
			
		||||
    #  each rpath simultaneously.
 | 
			
		||||
    # @return [void]
 | 
			
		||||
    # @see MachOFile#change_rpath
 | 
			
		||||
    def change_rpath(old_path, new_path, options = {})
 | 
			
		||||
@ -268,6 +270,9 @@ module MachO
 | 
			
		||||
    # @param options [Hash]
 | 
			
		||||
    # @option options [Boolean] :strict (true) if true, fail if one slice fails.
 | 
			
		||||
    #  if false, fail only if all slices fail.
 | 
			
		||||
    # @option options [Boolean] :uniq (false) for each slice: if true, delete
 | 
			
		||||
    #  only the first runtime path that matches. if false, delete all duplicate
 | 
			
		||||
    #  paths that match.
 | 
			
		||||
    # @return void
 | 
			
		||||
    # @see MachOFile#delete_rpath
 | 
			
		||||
    def delete_rpath(path, options = {})
 | 
			
		||||
@ -291,7 +296,7 @@ module MachO
 | 
			
		||||
    # @param filename [String] the file to write to
 | 
			
		||||
    # @return [void]
 | 
			
		||||
    def write(filename)
 | 
			
		||||
      File.open(filename, "wb") { |f| f.write(@raw_data) }
 | 
			
		||||
      File.binwrite(filename, @raw_data)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Write all (fat) data to the file used to initialize the instance.
 | 
			
		||||
@ -301,7 +306,7 @@ module MachO
 | 
			
		||||
    def write!
 | 
			
		||||
      raise MachOError, "no initial file to write to" if filename.nil?
 | 
			
		||||
 | 
			
		||||
      File.open(@filename, "wb") { |f| f.write(@raw_data) }
 | 
			
		||||
      File.binwrite(@filename, @raw_data)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # @return [Hash] a hash representation of this {FatFile}
 | 
			
		||||
@ -37,6 +37,18 @@ module MachO
 | 
			
		||||
    # @api private
 | 
			
		||||
    MH_CIGAM_64 = 0xcffaedfe
 | 
			
		||||
 | 
			
		||||
    # compressed mach-o magic
 | 
			
		||||
    # @api private
 | 
			
		||||
    COMPRESSED_MAGIC = 0x636f6d70 # "comp"
 | 
			
		||||
 | 
			
		||||
    # a compressed mach-o slice, using LZSS for compression
 | 
			
		||||
    # @api private
 | 
			
		||||
    COMP_TYPE_LZSS = 0x6c7a7373 # "lzss"
 | 
			
		||||
 | 
			
		||||
    # a compressed mach-o slice, using LZVN ("FastLib") for compression
 | 
			
		||||
    # @api private
 | 
			
		||||
    COMP_TYPE_FASTLIB = 0x6c7a766e # "lzvn"
 | 
			
		||||
 | 
			
		||||
    # association of magic numbers to string representations
 | 
			
		||||
    # @api private
 | 
			
		||||
    MH_MAGICS = {
 | 
			
		||||
@ -433,6 +445,11 @@ module MachO
 | 
			
		||||
    # @api private
 | 
			
		||||
    MH_KEXT_BUNDLE = 0xb
 | 
			
		||||
 | 
			
		||||
    # a set of Mach-Os, running in the same userspace, sharing a linkedit.  The kext collection files are an example
 | 
			
		||||
    # of this object type
 | 
			
		||||
    # @api private
 | 
			
		||||
    MH_FILESET = 0xc
 | 
			
		||||
 | 
			
		||||
    # association of filetypes to Symbol representations
 | 
			
		||||
    # @api private
 | 
			
		||||
    MH_FILETYPES = {
 | 
			
		||||
@ -447,6 +464,7 @@ module MachO
 | 
			
		||||
      MH_DYLIB_STUB => :dylib_stub,
 | 
			
		||||
      MH_DSYM => :dsym,
 | 
			
		||||
      MH_KEXT_BUNDLE => :kext_bundle,
 | 
			
		||||
      MH_FILESET => :fileset,
 | 
			
		||||
    }.freeze
 | 
			
		||||
 | 
			
		||||
    # association of mach header flag symbols to values
 | 
			
		||||
@ -478,6 +496,9 @@ module MachO
 | 
			
		||||
      :MH_HAS_TLV_DESCRIPTORS => 0x800000,
 | 
			
		||||
      :MH_NO_HEAP_EXECUTION => 0x1000000,
 | 
			
		||||
      :MH_APP_EXTENSION_SAFE => 0x02000000,
 | 
			
		||||
      :MH_NLIST_OUTOFSYNC_WITH_DYLDINFO => 0x04000000,
 | 
			
		||||
      :MH_SIM_SUPPORT => 0x08000000,
 | 
			
		||||
      :MH_DYLIB_IN_CACHE => 0x80000000,
 | 
			
		||||
    }.freeze
 | 
			
		||||
 | 
			
		||||
    # Fat binary header structure
 | 
			
		||||
@ -724,6 +745,11 @@ module MachO
 | 
			
		||||
        filetype == Headers::MH_KEXT_BUNDLE
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # @return [Boolean] whether or not the file is of type `MH_FILESET`
 | 
			
		||||
      def fileset?
 | 
			
		||||
        filetype == Headers::MH_FILESET
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # @return [Boolean] true if the Mach-O has 32-bit magic, false otherwise
 | 
			
		||||
      def magic32?
 | 
			
		||||
        Utils.magic32?(magic)
 | 
			
		||||
@ -785,5 +811,88 @@ module MachO
 | 
			
		||||
        }.merge super
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Prelinked kernel/"kernelcache" header structure
 | 
			
		||||
    class PrelinkedKernelHeader < MachOStructure
 | 
			
		||||
      # @return [Integer] the magic number for a compressed header ({COMPRESSED_MAGIC})
 | 
			
		||||
      attr_reader :signature
 | 
			
		||||
 | 
			
		||||
      # @return [Integer] the type of compression used
 | 
			
		||||
      attr_reader :compress_type
 | 
			
		||||
 | 
			
		||||
      # @return [Integer] a checksum for the uncompressed data
 | 
			
		||||
      attr_reader :adler32
 | 
			
		||||
 | 
			
		||||
      # @return [Integer] the size of the uncompressed data, in bytes
 | 
			
		||||
      attr_reader :uncompressed_size
 | 
			
		||||
 | 
			
		||||
      # @return [Integer] the size of the compressed data, in bytes
 | 
			
		||||
      attr_reader :compressed_size
 | 
			
		||||
 | 
			
		||||
      # @return [Integer] the version of the prelink format
 | 
			
		||||
      attr_reader :prelink_version
 | 
			
		||||
 | 
			
		||||
      # @return [void]
 | 
			
		||||
      attr_reader :reserved
 | 
			
		||||
 | 
			
		||||
      # @return [void]
 | 
			
		||||
      attr_reader :platform_name
 | 
			
		||||
 | 
			
		||||
      # @return [void]
 | 
			
		||||
      attr_reader :root_path
 | 
			
		||||
 | 
			
		||||
      # @see MachOStructure::FORMAT
 | 
			
		||||
      # @api private
 | 
			
		||||
      FORMAT = "L>6a40a64a256"
 | 
			
		||||
 | 
			
		||||
      # @see MachOStructure::SIZEOF
 | 
			
		||||
      # @api private
 | 
			
		||||
      SIZEOF = 384
 | 
			
		||||
 | 
			
		||||
      # @api private
 | 
			
		||||
      def initialize(signature, compress_type, adler32, uncompressed_size, compressed_size, prelink_version, reserved, platform_name, root_path)
 | 
			
		||||
        super()
 | 
			
		||||
 | 
			
		||||
        @signature = signature
 | 
			
		||||
        @compress_type = compress_type
 | 
			
		||||
        @adler32 = adler32
 | 
			
		||||
        @uncompressed_size = uncompressed_size
 | 
			
		||||
        @compressed_size = compressed_size
 | 
			
		||||
        @prelink_version = prelink_version
 | 
			
		||||
        @reserved = reserved.unpack("L>10")
 | 
			
		||||
        @platform_name = platform_name
 | 
			
		||||
        @root_path = root_path
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # @return [Boolean] whether this prelinked kernel supports KASLR
 | 
			
		||||
      def kaslr?
 | 
			
		||||
        prelink_version >= 1
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # @return [Boolean] whether this prelinked kernel is compressed with LZSS
 | 
			
		||||
      def lzss?
 | 
			
		||||
        compress_type == COMP_TYPE_LZSS
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # @return [Boolean] whether this prelinked kernel is compressed with LZVN
 | 
			
		||||
      def lzvn?
 | 
			
		||||
        compress_type == COMP_TYPE_FASTLIB
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # @return [Hash] a hash representation of this {PrelinkedKernelHeader}
 | 
			
		||||
      def to_h
 | 
			
		||||
        {
 | 
			
		||||
          "signature" => signature,
 | 
			
		||||
          "compress_type" => compress_type,
 | 
			
		||||
          "adler32" => adler32,
 | 
			
		||||
          "uncompressed_size" => uncompressed_size,
 | 
			
		||||
          "compressed_size" => compressed_size,
 | 
			
		||||
          "prelink_version" => prelink_version,
 | 
			
		||||
          "reserved" => reserved,
 | 
			
		||||
          "platform_name" => platform_name,
 | 
			
		||||
          "root_path" => root_path,
 | 
			
		||||
        }.merge super
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@ -63,7 +63,8 @@ module MachO
 | 
			
		||||
      0x31 => :LC_NOTE,
 | 
			
		||||
      0x32 => :LC_BUILD_VERSION,
 | 
			
		||||
      (0x33 | LC_REQ_DYLD) => :LC_DYLD_EXPORTS_TRIE,
 | 
			
		||||
      (0x34 | LC_REQ_DYLD) => :LD_DYLD_CHAINED_FIXUPS,
 | 
			
		||||
      (0x34 | LC_REQ_DYLD) => :LC_DYLD_CHAINED_FIXUPS,
 | 
			
		||||
      (0x35 | LC_REQ_DYLD) => :LC_FILESET_ENTRY,
 | 
			
		||||
    }.freeze
 | 
			
		||||
 | 
			
		||||
    # association of symbol representations to load command constants
 | 
			
		||||
@ -150,7 +151,8 @@ module MachO
 | 
			
		||||
      :LC_NOTE => "NoteCommand",
 | 
			
		||||
      :LC_BUILD_VERSION => "BuildVersionCommand",
 | 
			
		||||
      :LC_DYLD_EXPORTS_TRIE => "LinkeditDataCommand",
 | 
			
		||||
      :LD_DYLD_CHAINED_FIXUPS => "LinkeditDataCommand",
 | 
			
		||||
      :LC_DYLD_CHAINED_FIXUPS => "LinkeditDataCommand",
 | 
			
		||||
      :LC_FILESET_ENTRY => "FilesetEntryCommand",
 | 
			
		||||
    }.freeze
 | 
			
		||||
 | 
			
		||||
    # association of segment name symbols to names
 | 
			
		||||
@ -173,6 +175,7 @@ module MachO
 | 
			
		||||
      :SG_FVMLIB => 0x2,
 | 
			
		||||
      :SG_NORELOC => 0x4,
 | 
			
		||||
      :SG_PROTECTED_VERSION_1 => 0x8,
 | 
			
		||||
      :SG_READ_ONLY => 0x10,
 | 
			
		||||
    }.freeze
 | 
			
		||||
 | 
			
		||||
    # The top-level Mach-O load command structure.
 | 
			
		||||
@ -1794,5 +1797,48 @@ module MachO
 | 
			
		||||
        }.merge super
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # A load command containing a description of a Mach-O that is a constituent of a fileset.
 | 
			
		||||
    # Each entry is further described by its own Mach header.
 | 
			
		||||
    # Corresponds to LC_FILESET_ENTRY.
 | 
			
		||||
    class FilesetEntryCommand < LoadCommand
 | 
			
		||||
      # @return [Integer] the virtual memory address of the entry
 | 
			
		||||
      attr_reader :vmaddr
 | 
			
		||||
 | 
			
		||||
      # @return [Integer] the file offset of the entry
 | 
			
		||||
      attr_reader :fileoff
 | 
			
		||||
 | 
			
		||||
      # @return [LCStr] the entry's ID
 | 
			
		||||
      attr_reader :entry_id
 | 
			
		||||
 | 
			
		||||
      # @return [void]
 | 
			
		||||
      attr_reader :reserved
 | 
			
		||||
 | 
			
		||||
      # @see MachOStructure::FORMAT
 | 
			
		||||
      # @api private
 | 
			
		||||
      FORMAT = "L=2Q=2L=2"
 | 
			
		||||
 | 
			
		||||
      # @see MachOStructure::SIZEOF
 | 
			
		||||
      # @api private
 | 
			
		||||
      SIZEOF = 28
 | 
			
		||||
 | 
			
		||||
      def initialize(view, cmd, cmdsize, vmaddr, fileoff, entry_id, reserved)
 | 
			
		||||
        super(view, cmd, cmdsize)
 | 
			
		||||
        @vmaddr = vmaddr
 | 
			
		||||
        @fileoff = fileoff
 | 
			
		||||
        @entry_id = LCStr.new(self, entry_id)
 | 
			
		||||
        @reserved = reserved
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # @return [Hash] a hash representation of this {FilesetEntryCommand}
 | 
			
		||||
      def to_h
 | 
			
		||||
        {
 | 
			
		||||
          "vmaddr" => vmaddr,
 | 
			
		||||
          "fileoff" => fileoff,
 | 
			
		||||
          "entry_id" => entry_id,
 | 
			
		||||
          "reserved" => reserved,
 | 
			
		||||
        }.merge super
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@ -34,7 +34,11 @@ module MachO
 | 
			
		||||
    # @param bin [String] a binary string containing raw Mach-O data
 | 
			
		||||
    # @param opts [Hash] options to control the parser with
 | 
			
		||||
    # @option opts [Boolean] :permissive whether to ignore unknown load commands
 | 
			
		||||
    # @option opts [Boolean] :decompress whether to decompress, if capable
 | 
			
		||||
    # @return [MachOFile] a new MachOFile
 | 
			
		||||
    # @note The `:decompress` option relies on non-default dependencies. Compression
 | 
			
		||||
    #  is only used in niche Mach-Os, so leaving this disabled is a reasonable default for
 | 
			
		||||
    #  virtually all normal uses.
 | 
			
		||||
    def self.new_from_bin(bin, **opts)
 | 
			
		||||
      instance = allocate
 | 
			
		||||
      instance.initialize_from_bin(bin, opts)
 | 
			
		||||
@ -46,13 +50,17 @@ module MachO
 | 
			
		||||
    # @param filename [String] the Mach-O file to load from
 | 
			
		||||
    # @param opts [Hash] options to control the parser with
 | 
			
		||||
    # @option opts [Boolean] :permissive whether to ignore unknown load commands
 | 
			
		||||
    # @option opts [Boolean] :decompress whether to decompress, if capable
 | 
			
		||||
    # @raise [ArgumentError] if the given file does not exist
 | 
			
		||||
    # @note The `:decompress` option relies on non-default dependencies. Compression
 | 
			
		||||
    #  is only used in niche Mach-Os, so leaving this disabled is a reasonable default for
 | 
			
		||||
    #  virtually all normal uses.
 | 
			
		||||
    def initialize(filename, **opts)
 | 
			
		||||
      raise ArgumentError, "#{filename}: no such file" unless File.file?(filename)
 | 
			
		||||
 | 
			
		||||
      @filename = filename
 | 
			
		||||
      @options = opts
 | 
			
		||||
      @raw_data = File.open(@filename, "rb", &:read)
 | 
			
		||||
      @raw_data = File.binread(@filename)
 | 
			
		||||
      populate_fields
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
@ -152,8 +160,8 @@ module MachO
 | 
			
		||||
    #  the instance fields
 | 
			
		||||
    # @raise [OffsetInsertionError] if the offset is not in the load command region
 | 
			
		||||
    # @raise [HeaderPadError] if the new command exceeds the header pad buffer
 | 
			
		||||
    # @note Calling this method with an arbitrary offset in the load command
 | 
			
		||||
    #  region **will leave the object in an inconsistent state**.
 | 
			
		||||
    # @note Calling this method with an arbitrary offset in the load command region
 | 
			
		||||
    # **will leave the object in an inconsistent state**.
 | 
			
		||||
    def insert_command(offset, lc, options = {})
 | 
			
		||||
      context = LoadCommands::LoadCommand::SerializationContext.context_for(self)
 | 
			
		||||
      cmd_raw = lc.serialize(context)
 | 
			
		||||
@ -196,7 +204,7 @@ module MachO
 | 
			
		||||
    # Appends a new load command to the Mach-O.
 | 
			
		||||
    # @param lc [LoadCommands::LoadCommand] the load command being added
 | 
			
		||||
    # @param options [Hash]
 | 
			
		||||
    # @option options [Boolean] :repopulate (true) whether or not to repopulate
 | 
			
		||||
    # @option f [Boolean] :repopulate (true) whether or not to repopulate
 | 
			
		||||
    #  the instance fields
 | 
			
		||||
    # @return [void]
 | 
			
		||||
    # @see #insert_command
 | 
			
		||||
@ -368,20 +376,20 @@ module MachO
 | 
			
		||||
    #  file.change_rpath("/usr/lib", "/usr/local/lib")
 | 
			
		||||
    # @param old_path [String] the old runtime path
 | 
			
		||||
    # @param new_path [String] the new runtime path
 | 
			
		||||
    # @param _options [Hash]
 | 
			
		||||
    # @param options [Hash]
 | 
			
		||||
    # @option options [Boolean] :uniq (false) if true, change duplicate
 | 
			
		||||
    #  rpaths simultaneously.
 | 
			
		||||
    # @return [void]
 | 
			
		||||
    # @raise [RpathUnknownError] if no such old runtime path exists
 | 
			
		||||
    # @raise [RpathExistsError] if the new runtime path already exists
 | 
			
		||||
    # @note `_options` is currently unused and is provided for signature
 | 
			
		||||
    #  compatibility with {MachO::FatFile#change_rpath}
 | 
			
		||||
    def change_rpath(old_path, new_path, _options = {})
 | 
			
		||||
    def change_rpath(old_path, new_path, options = {})
 | 
			
		||||
      old_lc = command(:LC_RPATH).find { |r| r.path.to_s == old_path }
 | 
			
		||||
      raise RpathUnknownError, old_path if old_lc.nil?
 | 
			
		||||
      raise RpathExistsError, new_path if rpaths.include?(new_path)
 | 
			
		||||
 | 
			
		||||
      new_lc = LoadCommands::LoadCommand.create(:LC_RPATH, new_path)
 | 
			
		||||
 | 
			
		||||
      delete_rpath(old_path)
 | 
			
		||||
      delete_rpath(old_path, options)
 | 
			
		||||
      insert_command(old_lc.view.offset, new_lc)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
@ -409,13 +417,18 @@ module MachO
 | 
			
		||||
    #  file.delete_rpath("/lib")
 | 
			
		||||
    #  file.rpaths # => []
 | 
			
		||||
    # @param path [String] the runtime path to delete
 | 
			
		||||
    # @param _options [Hash]
 | 
			
		||||
    # @param options [Hash]
 | 
			
		||||
    # @option options [Boolean] :uniq (false) if true, also delete
 | 
			
		||||
    #  duplicates of the requested path. If false, delete the first
 | 
			
		||||
    #  instance (by offset) of the requested path.
 | 
			
		||||
    # @return void
 | 
			
		||||
    # @raise [RpathUnknownError] if no such runtime path exists
 | 
			
		||||
    # @note `_options` is currently unused and is provided for signature
 | 
			
		||||
    #  compatibility with {MachO::FatFile#delete_rpath}
 | 
			
		||||
    def delete_rpath(path, _options = {})
 | 
			
		||||
      rpath_cmds = command(:LC_RPATH).select { |r| r.path.to_s == path }
 | 
			
		||||
    def delete_rpath(path, options = {})
 | 
			
		||||
      uniq = options.fetch(:uniq, false)
 | 
			
		||||
      search_method = uniq ? :select : :find
 | 
			
		||||
 | 
			
		||||
      # Cast rpath_cmds into an Array so we can handle the uniq and non-uniq cases the same way
 | 
			
		||||
      rpath_cmds = Array(command(:LC_RPATH).method(search_method).call { |r| r.path.to_s == path })
 | 
			
		||||
      raise RpathUnknownError, path if rpath_cmds.empty?
 | 
			
		||||
 | 
			
		||||
      # delete the commands in reverse order, offset descending.
 | 
			
		||||
@ -426,7 +439,7 @@ module MachO
 | 
			
		||||
    # @param filename [String] the file to write to
 | 
			
		||||
    # @return [void]
 | 
			
		||||
    def write(filename)
 | 
			
		||||
      File.open(filename, "wb") { |f| f.write(@raw_data) }
 | 
			
		||||
      File.binwrite(filename, @raw_data)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Write all Mach-O data to the file used to initialize the instance.
 | 
			
		||||
@ -436,7 +449,7 @@ module MachO
 | 
			
		||||
    def write!
 | 
			
		||||
      raise MachOError, "no initial file to write to" if @filename.nil?
 | 
			
		||||
 | 
			
		||||
      File.open(@filename, "wb") { |f| f.write(@raw_data) }
 | 
			
		||||
      File.binwrite(@filename, @raw_data)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # @return [Hash] a hash representation of this {MachOFile}
 | 
			
		||||
@ -458,6 +471,9 @@ module MachO
 | 
			
		||||
      # the smallest Mach-O header is 28 bytes
 | 
			
		||||
      raise TruncatedFileError if @raw_data.size < 28
 | 
			
		||||
 | 
			
		||||
      magic = @raw_data[0..3].unpack1("N")
 | 
			
		||||
      populate_prelinked_kernel_header if Utils.compressed_magic?(magic)
 | 
			
		||||
 | 
			
		||||
      magic = populate_and_check_magic
 | 
			
		||||
      mh_klass = Utils.magic32?(magic) ? Headers::MachHeader : Headers::MachHeader64
 | 
			
		||||
      mh = mh_klass.new_from_bin(endianness, @raw_data[0, mh_klass.bytesize])
 | 
			
		||||
@ -469,6 +485,48 @@ module MachO
 | 
			
		||||
      mh
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Read a compressed Mach-O header and check its validity, as well as whether we're able
 | 
			
		||||
    # to parse it.
 | 
			
		||||
    # @return [void]
 | 
			
		||||
    # @raise [CompressedMachOError] if we weren't asked to perform decompression
 | 
			
		||||
    # @raise [DecompressionError] if decompression is impossible or fails
 | 
			
		||||
    # @api private
 | 
			
		||||
    def populate_prelinked_kernel_header
 | 
			
		||||
      raise CompressedMachOError unless options.fetch(:decompress, false)
 | 
			
		||||
 | 
			
		||||
      @plh = Headers::PrelinkedKernelHeader.new_from_bin :big, @raw_data[0, Headers::PrelinkedKernelHeader.bytesize]
 | 
			
		||||
 | 
			
		||||
      raise DecompressionError, "unsupported compression type: LZSS" if @plh.lzss?
 | 
			
		||||
      raise DecompressionError, "unknown compression type: 0x#{plh.compress_type.to_s 16}" unless @plh.lzvn?
 | 
			
		||||
 | 
			
		||||
      decompress_macho_lzvn
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Attempt to decompress a Mach-O file from the data specified in a prelinked kernel header.
 | 
			
		||||
    # @return [void]
 | 
			
		||||
    # @raise [DecompressionError] if decompression is impossible or fails
 | 
			
		||||
    # @api private
 | 
			
		||||
    # @note This method rewrites the internal state of {MachOFile} to pretend as if it was never
 | 
			
		||||
    #  compressed to begin with, allowing all other APIs to transparently act on compressed Mach-Os.
 | 
			
		||||
    def decompress_macho_lzvn
 | 
			
		||||
      begin
 | 
			
		||||
        require "lzfse"
 | 
			
		||||
      rescue LoadError
 | 
			
		||||
        raise DecompressionError, "LZVN required but the optional 'lzfse' gem is not installed"
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # From this point onwards, the internal buffer of this MachOFile refers to the decompressed
 | 
			
		||||
      # contents specified by the prelinked kernel header.
 | 
			
		||||
      begin
 | 
			
		||||
        @raw_data = LZFSE.lzvn_decompress @raw_data.slice(Headers::PrelinkedKernelHeader.bytesize, @plh.compressed_size)
 | 
			
		||||
        # Sanity checks.
 | 
			
		||||
        raise DecompressionError if @raw_data.size != @plh.uncompressed_size
 | 
			
		||||
        # TODO: check the adler32 CRC in @plh
 | 
			
		||||
      rescue LZFSE::DecodeError
 | 
			
		||||
        raise DecompressionError, "LZVN decompression failed"
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Read just the file's magic number and check its validity.
 | 
			
		||||
    # @return [Integer] the magic
 | 
			
		||||
    # @raise [MagicError] if the magic is not valid Mach-O magic
 | 
			
		||||
@ -553,8 +611,8 @@ module MachO
 | 
			
		||||
      segments.each do |seg|
 | 
			
		||||
        seg.sections.each do |sect|
 | 
			
		||||
          next if sect.empty?
 | 
			
		||||
          next if sect.flag?(:S_ZEROFILL)
 | 
			
		||||
          next if sect.flag?(:S_THREAD_LOCAL_ZEROFILL)
 | 
			
		||||
          next if sect.type?(:S_ZEROFILL)
 | 
			
		||||
          next if sect.type?(:S_THREAD_LOCAL_ZEROFILL)
 | 
			
		||||
          next unless sect.offset < offset
 | 
			
		||||
 | 
			
		||||
          offset = sect.offset
 | 
			
		||||
@ -4,24 +4,24 @@ module MachO
 | 
			
		||||
  # Classes and constants for parsing sections in Mach-O binaries.
 | 
			
		||||
  module Sections
 | 
			
		||||
    # type mask
 | 
			
		||||
    SECTION_TYPE = 0x000000ff
 | 
			
		||||
    SECTION_TYPE_MASK = 0x000000ff
 | 
			
		||||
 | 
			
		||||
    # attributes mask
 | 
			
		||||
    SECTION_ATTRIBUTES = 0xffffff00
 | 
			
		||||
    SECTION_ATTRIBUTES_MASK = 0xffffff00
 | 
			
		||||
 | 
			
		||||
    # user settable attributes mask
 | 
			
		||||
    SECTION_ATTRIBUTES_USR = 0xff000000
 | 
			
		||||
    SECTION_ATTRIBUTES_USR_MASK = 0xff000000
 | 
			
		||||
 | 
			
		||||
    # system settable attributes mask
 | 
			
		||||
    SECTION_ATTRIBUTES_SYS = 0x00ffff00
 | 
			
		||||
    SECTION_ATTRIBUTES_SYS_MASK = 0x00ffff00
 | 
			
		||||
 | 
			
		||||
    # maximum specifiable section alignment, as a power of 2
 | 
			
		||||
    # @note see `MAXSECTALIGN` macro in `cctools/misc/lipo.c`
 | 
			
		||||
    MAX_SECT_ALIGN = 15
 | 
			
		||||
 | 
			
		||||
    # association of section flag symbols to values
 | 
			
		||||
    # association of section type symbols to values
 | 
			
		||||
    # @api private
 | 
			
		||||
    SECTION_FLAGS = {
 | 
			
		||||
    SECTION_TYPES = {
 | 
			
		||||
      :S_REGULAR => 0x0,
 | 
			
		||||
      :S_ZEROFILL => 0x1,
 | 
			
		||||
      :S_CSTRING_LITERALS => 0x2,
 | 
			
		||||
@ -44,6 +44,12 @@ module MachO
 | 
			
		||||
      :S_THREAD_LOCAL_VARIABLES => 0x13,
 | 
			
		||||
      :S_THREAD_LOCAL_VARIABLE_POINTERS => 0x14,
 | 
			
		||||
      :S_THREAD_LOCAL_INIT_FUNCTION_POINTERS => 0x15,
 | 
			
		||||
      :S_INIT_FUNC_OFFSETS => 0x16,
 | 
			
		||||
    }.freeze
 | 
			
		||||
 | 
			
		||||
    # association of section attribute symbols to values
 | 
			
		||||
    # @api private
 | 
			
		||||
    SECTION_ATTRIBUTES = {
 | 
			
		||||
      :S_ATTR_PURE_INSTRUCTIONS => 0x80000000,
 | 
			
		||||
      :S_ATTR_NO_TOC => 0x40000000,
 | 
			
		||||
      :S_ATTR_STRIP_STATIC_SYMS => 0x20000000,
 | 
			
		||||
@ -56,6 +62,13 @@ module MachO
 | 
			
		||||
      :S_ATTR_LOC_RELOC => 0x00000100,
 | 
			
		||||
    }.freeze
 | 
			
		||||
 | 
			
		||||
    # association of section flag symbols to values
 | 
			
		||||
    # @api private
 | 
			
		||||
    SECTION_FLAGS = {
 | 
			
		||||
      **SECTION_TYPES,
 | 
			
		||||
      **SECTION_ATTRIBUTES,
 | 
			
		||||
    }.freeze
 | 
			
		||||
 | 
			
		||||
    # association of section name symbols to names
 | 
			
		||||
    # @api private
 | 
			
		||||
    SECTION_NAMES = {
 | 
			
		||||
@ -147,6 +160,33 @@ module MachO
 | 
			
		||||
        size.zero?
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # @return [Integer] the raw numeric type of this section
 | 
			
		||||
      def type
 | 
			
		||||
        flags & SECTION_TYPE_MASK
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # @example
 | 
			
		||||
      #  puts "this section is regular" if sect.type?(:S_REGULAR)
 | 
			
		||||
      # @param type_sym [Symbol] a section type symbol
 | 
			
		||||
      # @return [Boolean] whether this section is of the given type
 | 
			
		||||
      def type?(type_sym)
 | 
			
		||||
        type == SECTION_TYPES[type_sym]
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # @return [Integer] the raw numeric attributes of this section
 | 
			
		||||
      def attributes
 | 
			
		||||
        flags & SECTION_ATTRIBUTES_MASK
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # @example
 | 
			
		||||
      #  puts "pure instructions" if sect.attribute?(:S_ATTR_PURE_INSTRUCTIONS)
 | 
			
		||||
      # @param attr_sym [Symbol] a section attribute symbol
 | 
			
		||||
      # @return [Boolean] whether this section is of the given type
 | 
			
		||||
      def attribute?(attr_sym)
 | 
			
		||||
        !!(attributes & SECTION_ATTRIBUTES[attr_sym])
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # @deprecated Use {#type?} or {#attribute?} instead.
 | 
			
		||||
      # @example
 | 
			
		||||
      #  puts "this section is regular" if sect.flag?(:S_REGULAR)
 | 
			
		||||
      # @param flag [Symbol] a section flag symbol
 | 
			
		||||
@ -51,6 +51,8 @@ module MachO
 | 
			
		||||
    # @param options [Hash]
 | 
			
		||||
    # @option options [Boolean] :strict (true) whether or not to fail loudly
 | 
			
		||||
    #  with an exception if the change cannot be performed
 | 
			
		||||
    # @option options [Boolean] :uniq (false) whether or not to change duplicate
 | 
			
		||||
    #  rpaths simultaneously
 | 
			
		||||
    # @return [void]
 | 
			
		||||
    def self.change_rpath(filename, old_path, new_path, options = {})
 | 
			
		||||
      file = MachO.open(filename)
 | 
			
		||||
@ -80,6 +82,8 @@ module MachO
 | 
			
		||||
    # @param options [Hash]
 | 
			
		||||
    # @option options [Boolean] :strict (true) whether or not to fail loudly
 | 
			
		||||
    #  with an exception if the change cannot be performed
 | 
			
		||||
    # @option options [Boolean] :uniq (false) whether or not to delete duplicate
 | 
			
		||||
    #  rpaths simultaneously
 | 
			
		||||
    # @return [void]
 | 
			
		||||
    def self.delete_rpath(filename, old_path, options = {})
 | 
			
		||||
      file = MachO.open(filename)
 | 
			
		||||
@ -121,5 +121,12 @@ module MachO
 | 
			
		||||
    def self.big_magic?(num)
 | 
			
		||||
      [Headers::MH_MAGIC, Headers::MH_MAGIC_64].include? num
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Compares the given number to the known magic number for a compressed Mach-O slice.
 | 
			
		||||
    # @param num [Integer] the number being checked
 | 
			
		||||
    # @return [Boolean] whether `num` is a valid compressed header magic number
 | 
			
		||||
    def self.compressed_magic?(num)
 | 
			
		||||
      num == Headers::COMPRESSED_MAGIC
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user