Class: MachO::MachOFile

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
Defined in:
lib/macho/macho_file.rb

Overview

Represents a Mach-O file, which contains a header and load commands as well as binary executable instructions. Mach-O binaries are architecture specific.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(filename) ⇒ MachOFile

Creates a new FatFile from the given filename.

Parameters:

  • filename (String)

    the Mach-O file to load from

Raises:

  • (ArgumentError)

    if the given file does not exist



41
42
43
44
45
46
47
# File 'lib/macho/macho_file.rb', line 41

def initialize(filename)
  raise ArgumentError, "#{filename}: no such file" unless File.file?(filename)

  @filename = filename
  @raw_data = File.open(@filename, "rb", &:read)
  populate_fields
end

Instance Attribute Details

#endiannessSymbol (readonly)

Returns the endianness of the file, :big or :little.

Returns:

  • (Symbol)

    the endianness of the file, :big or :little



17
18
19
# File 'lib/macho/macho_file.rb', line 17

def endianness
  @endianness
end

#filenameString

Returns the filename loaded from, or nil if loaded from a binary string.

Returns:

  • (String)

    the filename loaded from, or nil if loaded from a binary string



14
15
16
# File 'lib/macho/macho_file.rb', line 14

def filename
  @filename
end

#headerHeaders::MachHeader, Headers::MachHeader64 (readonly)

Returns:



21
22
23
# File 'lib/macho/macho_file.rb', line 21

def header
  @header
end

#load_commandsArray<LoadCommands::LoadCommand> (readonly)

Note:

load commands are provided in order of ascending offset.

Returns an array of the file's load commands.

Returns:



26
27
28
# File 'lib/macho/macho_file.rb', line 26

def load_commands
  @load_commands
end

Class Method Details

.new_from_bin(bin) ⇒ MachOFile

Creates a new MachOFile instance from a binary string.

Parameters:

  • bin (String)

    a binary string containing raw Mach-O data

Returns:



31
32
33
34
35
36
# File 'lib/macho/macho_file.rb', line 31

def self.new_from_bin(bin)
  instance = allocate
  instance.initialize_from_bin(bin)

  instance
end

Instance Method Details

#add_command(lc, options = {}) ⇒ void

Note:

This is public, but methods like #add_rpath should be preferred. Setting repopulate to false will leave the instance in an inconsistent state unless #populate_fields is called immediately afterwards.

This method returns an undefined value.

Appends a new load command to the Mach-O.

Parameters:

Options Hash (options):

  • :repopulate (Boolean) — default: true

    whether or not to repopulate the instance fields

See Also:



200
201
202
# File 'lib/macho/macho_file.rb', line 200

def add_command(lc, options = {})
  insert_command(header.class.bytesize + sizeofcmds, lc, options)
end

#add_rpath(path, _options = {}) ⇒ void

Note:

_options is currently unused and is provided for signature compatibility with FatFile#add_rpath

This method returns an undefined value.

Add the given runtime path to the Mach-O.

Examples:

file.rpaths # => ["/lib"]
file.add_rpath("/usr/lib")
file.rpaths # => ["/lib", "/usr/lib"]

Parameters:

  • path (String)

    the new runtime path

  • _options (Hash) (defaults to: {})

Raises:



366
367
368
369
370
371
# File 'lib/macho/macho_file.rb', line 366

def add_rpath(path, _options = {})
  raise RpathExistsError, path if rpaths.include?(path)

  rpath_cmd = LoadCommands::LoadCommand.create(:LC_RPATH, path)
  add_command(rpath_cmd)
end

#alignmentInteger

Returns the file's internal alignment.

Returns:

  • (Integer)

    the file's internal alignment



98
99
100
101
# File 'lib/macho/macho_file.rb', line 98

def_delegators :header, :magic, :ncmds, :sizeofcmds, :flags, :object?,
:executable?, :fvmlib?, :core?, :preload?, :dylib?,
:dylinker?, :bundle?, :dsym?, :kext?, :magic32?, :magic64?,
:alignment

#bundle?Boolean

Returns whether or not the file is of type MH_BUNDLE.

Returns:

  • (Boolean)

    whether or not the file is of type MH_BUNDLE



98
99
100
101
# File 'lib/macho/macho_file.rb', line 98

def_delegators :header, :magic, :ncmds, :sizeofcmds, :flags, :object?,
:executable?, :fvmlib?, :core?, :preload?, :dylib?,
:dylinker?, :bundle?, :dsym?, :kext?, :magic32?, :magic64?,
:alignment

#change_dylib_id(new_id, _options = {}) ⇒ void Also known as: dylib_id=

Note:

_options is currently unused and is provided for signature compatibility with FatFile#change_dylib_id

This method returns an undefined value.

Changes the Mach-O's dylib ID to new_id. Does nothing if not a dylib.

Examples:

file.change_dylib_id("libFoo.dylib")

Parameters:

  • new_id (String)

    the dylib's new ID

  • _options (Hash) (defaults to: {})

Raises:

  • (ArgumentError)

    if new_id is not a String



276
277
278
279
280
281
282
283
284
285
286
287
288
289
# File 'lib/macho/macho_file.rb', line 276

def change_dylib_id(new_id, _options = {})
  raise ArgumentError, "new ID must be a String" unless new_id.is_a?(String)
  return unless dylib?

  old_lc = command(:LC_ID_DYLIB).first
  raise DylibIdMissingError unless old_lc

  new_lc = LoadCommands::LoadCommand.create(:LC_ID_DYLIB, new_id,
                                            old_lc.timestamp,
                                            old_lc.current_version,
                                            old_lc.compatibility_version)

  replace_command(old_lc, new_lc)
end

#change_install_name(old_name, new_name, _options = {}) ⇒ void Also known as: change_dylib

Note:

_options is currently unused and is provided for signature compatibility with FatFile#change_install_name

This method returns an undefined value.

Changes the shared library old_name to new_name

Examples:

file.change_install_name("abc.dylib", "def.dylib")

Parameters:

  • old_name (String)

    the shared library's old name

  • new_name (String)

    the shared library's new name

  • _options (Hash) (defaults to: {})

Raises:



313
314
315
316
317
318
319
320
321
322
323
# File 'lib/macho/macho_file.rb', line 313

def change_install_name(old_name, new_name, _options = {})
  old_lc = dylib_load_commands.find { |d| d.name.to_s == old_name }
  raise DylibUnknownError, old_name if old_lc.nil?

  new_lc = LoadCommands::LoadCommand.create(old_lc.type, new_name,
                                            old_lc.timestamp,
                                            old_lc.current_version,
                                            old_lc.compatibility_version)

  replace_command(old_lc, new_lc)
end

#change_rpath(old_path, new_path, _options = {}) ⇒ void

Note:

_options is currently unused and is provided for signature compatibility with FatFile#change_rpath

This method returns an undefined value.

Changes the runtime path old_path to new_path

Examples:

file.change_rpath("/usr/lib", "/usr/local/lib")

Parameters:

  • old_path (String)

    the old runtime path

  • new_path (String)

    the new runtime path

  • _options (Hash) (defaults to: {})

Raises:



344
345
346
347
348
349
350
351
352
353
# File 'lib/macho/macho_file.rb', line 344

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)
  insert_command(old_lc.view.offset, new_lc)
end

#command(name) ⇒ Array<LoadCommands::LoadCommand> Also known as: []

All load commands of a given name.

Examples:

file.command("LC_LOAD_DYLIB")
file[:LC_LOAD_DYLIB]

Parameters:

  • name (String, Symbol)

    the load command ID

Returns:



130
131
132
# File 'lib/macho/macho_file.rb', line 130

def command(name)
  load_commands.select { |lc| lc.type == name.to_sym }
end

#core?Boolean

Returns whether or not the file is of type MH_CORE.

Returns:

  • (Boolean)

    whether or not the file is of type MH_CORE



98
99
100
101
# File 'lib/macho/macho_file.rb', line 98

def_delegators :header, :magic, :ncmds, :sizeofcmds, :flags, :object?,
:executable?, :fvmlib?, :core?, :preload?, :dylib?,
:dylinker?, :bundle?, :dsym?, :kext?, :magic32?, :magic64?,
:alignment

#cpusubtypeSymbol

Returns a symbol representation of the Mach-O's CPU subtype.

Returns:

  • (Symbol)

    a symbol representation of the Mach-O's CPU subtype



119
120
121
# File 'lib/macho/macho_file.rb', line 119

def cpusubtype
  Headers::CPU_SUBTYPES[header.cputype][header.cpusubtype]
end

#cputypeSymbol

Returns a symbol representation of the Mach-O's CPU type.

Returns:

  • (Symbol)

    a symbol representation of the Mach-O's CPU type



114
115
116
# File 'lib/macho/macho_file.rb', line 114

def cputype
  Headers::CPU_TYPES[header.cputype]
end

#delete_command(lc, options = {}) ⇒ void

Note:

This is public, but methods like #delete_rpath should be preferred. Setting repopulate to false will leave the instance in an inconsistent state unless #populate_fields is called immediately afterwards.

This method returns an undefined value.

Delete a load command from the Mach-O.

Parameters:

Options Hash (options):

  • :repopulate (Boolean) — default: true

    whether or not to repopulate the instance fields



214
215
216
217
218
219
220
221
222
223
224
225
226
# File 'lib/macho/macho_file.rb', line 214

def delete_command(lc, options = {})
  @raw_data.slice!(lc.view.offset, lc.cmdsize)

  # update Mach-O header fields to account for deleted load command
  update_ncmds(ncmds - 1)
  update_sizeofcmds(sizeofcmds - lc.cmdsize)

  # pad the space after the load commands to preserve offsets
  null_pad = "\x00" * lc.cmdsize
  @raw_data.insert(header.class.bytesize + sizeofcmds - lc.cmdsize, null_pad)

  populate_fields if options.fetch(:repopulate, true)
end

#delete_rpath(path, _options = {}) ⇒ Object

Note:

_options is currently unused and is provided for signature compatibility with FatFile#delete_rpath

Delete the given runtime path from the Mach-O.

Examples:

file.rpaths # => ["/lib"]
file.delete_rpath("/lib")
file.rpaths # => []

Parameters:

  • path (String)

    the runtime path to delete

  • _options (Hash) (defaults to: {})

Returns:

  • void

Raises:



384
385
386
387
388
389
390
391
392
393
# File 'lib/macho/macho_file.rb', line 384

def delete_rpath(path, _options = {})
  rpath_cmds = command(:LC_RPATH).select { |r| r.path.to_s == path }
  raise RpathUnknownError, path if rpath_cmds.empty?

  # delete the commands in reverse order, offset descending. this
  # allows us to defer (expensive) field population until the very end
  rpath_cmds.reverse_each { |cmd| delete_command(cmd, :repopulate => false) }

  populate_fields
end

#dsym?Boolean

Returns whether or not the file is of type MH_DSYM.

Returns:

  • (Boolean)

    whether or not the file is of type MH_DSYM



98
99
100
101
# File 'lib/macho/macho_file.rb', line 98

def_delegators :header, :magic, :ncmds, :sizeofcmds, :flags, :object?,
:executable?, :fvmlib?, :core?, :preload?, :dylib?,
:dylinker?, :bundle?, :dsym?, :kext?, :magic32?, :magic64?,
:alignment

#dylib?Boolean

Returns whether or not the file is of type MH_DYLIB.

Returns:

  • (Boolean)

    whether or not the file is of type MH_DYLIB



98
99
100
101
# File 'lib/macho/macho_file.rb', line 98

def_delegators :header, :magic, :ncmds, :sizeofcmds, :flags, :object?,
:executable?, :fvmlib?, :core?, :preload?, :dylib?,
:dylinker?, :bundle?, :dsym?, :kext?, :magic32?, :magic64?,
:alignment

#dylib_idString?

The Mach-O's dylib ID, or nil if not a dylib.

Examples:

file.dylib_id # => 'libBar.dylib'

Returns:

  • (String, nil)

    the Mach-O's dylib ID



259
260
261
262
263
264
265
# File 'lib/macho/macho_file.rb', line 259

def dylib_id
  return unless dylib?

  dylib_id_cmd = command(:LC_ID_DYLIB).first

  dylib_id_cmd.name.to_s
end

#dylib_load_commandsArray<LoadCommands::DylibCommand>

All load commands responsible for loading dylibs.

Returns:



240
241
242
# File 'lib/macho/macho_file.rb', line 240

def dylib_load_commands
  load_commands.select { |lc| LoadCommands::DYLIB_LOAD_COMMANDS.include?(lc.type) }
end

#dylinker?Boolean

Returns whether or not the file is of type MH_DYLINKER.

Returns:

  • (Boolean)

    whether or not the file is of type MH_DYLINKER



98
99
100
101
# File 'lib/macho/macho_file.rb', line 98

def_delegators :header, :magic, :ncmds, :sizeofcmds, :flags, :object?,
:executable?, :fvmlib?, :core?, :preload?, :dylib?,
:dylinker?, :bundle?, :dsym?, :kext?, :magic32?, :magic64?,
:alignment

#executable?Boolean

Returns whether or not the file is of type MH_EXECUTE.

Returns:

  • (Boolean)

    whether or not the file is of type MH_EXECUTE



98
99
100
101
# File 'lib/macho/macho_file.rb', line 98

def_delegators :header, :magic, :ncmds, :sizeofcmds, :flags, :object?,
:executable?, :fvmlib?, :core?, :preload?, :dylib?,
:dylinker?, :bundle?, :dsym?, :kext?, :magic32?, :magic64?,
:alignment

#filetypeSymbol

Returns a string representation of the Mach-O's filetype.

Returns:

  • (Symbol)

    a string representation of the Mach-O's filetype



109
110
111
# File 'lib/macho/macho_file.rb', line 109

def filetype
  Headers::MH_FILETYPES[header.filetype]
end

#flagsInteger

Returns the header flags associated with the Mach-O.

Returns:

  • (Integer)

    the header flags associated with the Mach-O



98
99
100
101
# File 'lib/macho/macho_file.rb', line 98

def_delegators :header, :magic, :ncmds, :sizeofcmds, :flags, :object?,
:executable?, :fvmlib?, :core?, :preload?, :dylib?,
:dylinker?, :bundle?, :dsym?, :kext?, :magic32?, :magic64?,
:alignment

#fvmlib?Boolean

Returns whether or not the file is of type MH_FVMLIB.

Returns:

  • (Boolean)

    whether or not the file is of type MH_FVMLIB



98
99
100
101
# File 'lib/macho/macho_file.rb', line 98

def_delegators :header, :magic, :ncmds, :sizeofcmds, :flags, :object?,
:executable?, :fvmlib?, :core?, :preload?, :dylib?,
:dylinker?, :bundle?, :dsym?, :kext?, :magic32?, :magic64?,
:alignment

#initialize_from_bin(bin) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Initializes a new MachOFile instance from a binary string.

See Also:



52
53
54
55
56
# File 'lib/macho/macho_file.rb', line 52

def initialize_from_bin(bin)
  @filename = nil
  @raw_data = bin
  populate_fields
end

#insert_command(offset, lc, options = {}) ⇒ Object

Note:

Calling this method with an arbitrary offset in the load command region will leave the object in an inconsistent state.

Inserts a load command at the given offset.

Parameters:

  • offset (Integer)

    the offset to insert at

  • lc (LoadCommands::LoadCommand)

    the load command to insert

  • options (Hash) (defaults to: {})

Options Hash (options):

  • :repopulate (Boolean) — default: true

    whether or not to repopulate the instance fields

Raises:



146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/macho/macho_file.rb', line 146

def insert_command(offset, lc, options = {})
  context = LoadCommands::LoadCommand::SerializationContext.context_for(self)
  cmd_raw = lc.serialize(context)

  if offset < header.class.bytesize || offset + cmd_raw.bytesize > low_fileoff
    raise OffsetInsertionError, offset
  end

  new_sizeofcmds = sizeofcmds + cmd_raw.bytesize

  if header.class.bytesize + new_sizeofcmds > low_fileoff
    raise HeaderPadError, @filename
  end

  # update Mach-O header fields to account for inserted load command
  update_ncmds(ncmds + 1)
  update_sizeofcmds(new_sizeofcmds)

  @raw_data.insert(offset, cmd_raw)
  @raw_data.slice!(header.class.bytesize + new_sizeofcmds, cmd_raw.bytesize)

  populate_fields if options.fetch(:repopulate, true)
end

#kext?Boolean

Returns whether or not the file is of type MH_KEXT_BUNDLE.

Returns:

  • (Boolean)

    whether or not the file is of type MH_KEXT_BUNDLE



98
99
100
101
# File 'lib/macho/macho_file.rb', line 98

def_delegators :header, :magic, :ncmds, :sizeofcmds, :flags, :object?,
:executable?, :fvmlib?, :core?, :preload?, :dylib?,
:dylinker?, :bundle?, :dsym?, :kext?, :magic32?, :magic64?,
:alignment

#linked_dylibsArray<String>

All shared libraries linked to the Mach-O.

Returns:

  • (Array<String>)

    an array of all shared libraries



295
296
297
298
299
300
301
# File 'lib/macho/macho_file.rb', line 295

def linked_dylibs
  # Some linkers produce multiple `LC_LOAD_DYLIB` load commands for the same
  # library, but at this point we're really only interested in a list of
  # unique libraries this Mach-O file links to, thus: `uniq`. (This is also
  # for consistency with `FatFile` that merges this list across all archs.)
  dylib_load_commands.map(&:name).map(&:to_s).uniq
end

#magicInteger

Returns the magic number.

Returns:

  • (Integer)

    the magic number



98
99
100
101
# File 'lib/macho/macho_file.rb', line 98

def_delegators :header, :magic, :ncmds, :sizeofcmds, :flags, :object?,
:executable?, :fvmlib?, :core?, :preload?, :dylib?,
:dylinker?, :bundle?, :dsym?, :kext?, :magic32?, :magic64?,
:alignment

#magic32?Boolean

Returns true if the Mach-O has 32-bit magic, false otherwise.

Returns:

  • (Boolean)

    true if the Mach-O has 32-bit magic, false otherwise



98
99
100
101
# File 'lib/macho/macho_file.rb', line 98

def_delegators :header, :magic, :ncmds, :sizeofcmds, :flags, :object?,
:executable?, :fvmlib?, :core?, :preload?, :dylib?,
:dylinker?, :bundle?, :dsym?, :kext?, :magic32?, :magic64?,
:alignment

#magic64?Boolean

Returns true if the Mach-O has 64-bit magic, false otherwise.

Returns:

  • (Boolean)

    true if the Mach-O has 64-bit magic, false otherwise



98
99
100
101
# File 'lib/macho/macho_file.rb', line 98

def_delegators :header, :magic, :ncmds, :sizeofcmds, :flags, :object?,
:executable?, :fvmlib?, :core?, :preload?, :dylib?,
:dylinker?, :bundle?, :dsym?, :kext?, :magic32?, :magic64?,
:alignment

#magic_stringString

Returns a string representation of the file's magic number.

Returns:

  • (String)

    a string representation of the file's magic number



104
105
106
# File 'lib/macho/macho_file.rb', line 104

def magic_string
  Headers::MH_MAGICS[magic]
end

#ncmdsInteger

Returns the number of load commands in the Mach-O.

Returns:

  • (Integer)

    the number of load commands in the Mach-O



98
99
100
101
# File 'lib/macho/macho_file.rb', line 98

def_delegators :header, :magic, :ncmds, :sizeofcmds, :flags, :object?,
:executable?, :fvmlib?, :core?, :preload?, :dylib?,
:dylinker?, :bundle?, :dsym?, :kext?, :magic32?, :magic64?,
:alignment

#object?Boolean

Returns whether or not the file is of type MH_OBJECT.

Returns:

  • (Boolean)

    whether or not the file is of type MH_OBJECT



98
99
100
101
# File 'lib/macho/macho_file.rb', line 98

def_delegators :header, :magic, :ncmds, :sizeofcmds, :flags, :object?,
:executable?, :fvmlib?, :core?, :preload?, :dylib?,
:dylinker?, :bundle?, :dsym?, :kext?, :magic32?, :magic64?,
:alignment

#populate_fieldsvoid

Note:

This method is public, but should (almost) never need to be called. The exception to this rule is when methods like #add_command and #delete_command have been called with repopulate = false.

This method returns an undefined value.

Populate the instance's fields with the raw Mach-O data.



233
234
235
236
# File 'lib/macho/macho_file.rb', line 233

def populate_fields
  @header = populate_mach_header
  @load_commands = populate_load_commands
end

#preload?Boolean

Returns whether or not the file is of type MH_PRELOAD.

Returns:

  • (Boolean)

    whether or not the file is of type MH_PRELOAD



98
99
100
101
# File 'lib/macho/macho_file.rb', line 98

def_delegators :header, :magic, :ncmds, :sizeofcmds, :flags, :object?,
:executable?, :fvmlib?, :core?, :preload?, :dylib?,
:dylinker?, :bundle?, :dsym?, :kext?, :magic32?, :magic64?,
:alignment

#replace_command(old_lc, new_lc) ⇒ void

Note:

This is public, but methods like #dylib_id= should be preferred.

This method returns an undefined value.

Replace a load command with another command in the Mach-O, preserving location.

Parameters:

Raises:

  • (HeaderPadError)

    if the new command exceeds the header pad buffer

See Also:



177
178
179
180
181
182
183
184
185
186
187
# File 'lib/macho/macho_file.rb', line 177

def replace_command(old_lc, new_lc)
  context = LoadCommands::LoadCommand::SerializationContext.context_for(self)
  cmd_raw = new_lc.serialize(context)
  new_sizeofcmds = sizeofcmds + cmd_raw.bytesize - old_lc.cmdsize
  if header.class.bytesize + new_sizeofcmds > low_fileoff
    raise HeaderPadError, @filename
  end

  delete_command(old_lc)
  insert_command(old_lc.view.offset, new_lc)
end

#rpathsArray<String>

All runtime paths searched by the dynamic linker for the Mach-O.

Returns:

  • (Array<String>)

    an array of all runtime paths



329
330
331
# File 'lib/macho/macho_file.rb', line 329

def rpaths
  command(:LC_RPATH).map(&:path).map(&:to_s)
end

#segmentsArray<LoadCommands::SegmentCommand>, Array<LoadCommands::SegmentCommand64>

All segment load commands in the Mach-O.

Returns:



247
248
249
250
251
252
253
# File 'lib/macho/macho_file.rb', line 247

def segments
  if magic32?
    command(:LC_SEGMENT)
  else
    command(:LC_SEGMENT_64)
  end
end

#serializeString

The file's raw Mach-O data.

Returns:

  • (String)

    the raw Mach-O data



60
61
62
# File 'lib/macho/macho_file.rb', line 60

def serialize
  @raw_data
end

#sizeofcmdsInteger

Returns the size of all load commands, in bytes, in the Mach-O.

Returns:

  • (Integer)

    the size of all load commands, in bytes, in the Mach-O



98
99
100
101
# File 'lib/macho/macho_file.rb', line 98

def_delegators :header, :magic, :ncmds, :sizeofcmds, :flags, :object?,
:executable?, :fvmlib?, :core?, :preload?, :dylib?,
:dylinker?, :bundle?, :dsym?, :kext?, :magic32?, :magic64?,
:alignment

#write(filename) ⇒ void

This method returns an undefined value.

Write all Mach-O data to the given filename.

Parameters:

  • filename (String)

    the file to write to



398
399
400
# File 'lib/macho/macho_file.rb', line 398

def write(filename)
  File.open(filename, "wb") { |f| f.write(@raw_data) }
end

#write!void

Note:

Overwrites all data in the file!

This method returns an undefined value.

Write all Mach-O data to the file used to initialize the instance.

Raises:

  • (MachOError)

    if the instance was initialized without a file



406
407
408
409
# File 'lib/macho/macho_file.rb', line 406

def write!
  raise MachOError, "no initial file to write to" if @filename.nil?
  File.open(@filename, "wb") { |f| f.write(@raw_data) }
end