ruby-macho 4.1.0 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +17 -2
- data/lib/macho/code_signing.rb +583 -0
- data/lib/macho/exceptions.rb +39 -31
- data/lib/macho/fat_file.rb +50 -4
- data/lib/macho/headers.rb +21 -9
- data/lib/macho/load_commands.rb +48 -3
- data/lib/macho/macho_file.rb +76 -25
- data/lib/macho/structure.rb +1 -1
- data/lib/macho.rb +12 -13
- metadata +6 -7
data/lib/macho/macho_file.rb
CHANGED
|
@@ -60,7 +60,12 @@ module MachO
|
|
|
60
60
|
|
|
61
61
|
@filename = filename
|
|
62
62
|
@options = opts
|
|
63
|
-
|
|
63
|
+
File.open(@filename, "rb") do |file|
|
|
64
|
+
@raw_data = file.read(Headers::MachHeader.bytesize)
|
|
65
|
+
@raw_data ||= ""
|
|
66
|
+
populate_mach_header if !opts.fetch(:decompress, false) || !Utils.compressed_magic?(@raw_data.unpack1("N"))
|
|
67
|
+
@raw_data << file.read.to_s
|
|
68
|
+
end
|
|
64
69
|
populate_fields
|
|
65
70
|
end
|
|
66
71
|
|
|
@@ -147,7 +152,7 @@ module MachO
|
|
|
147
152
|
# @return [Array<LoadCommands::LoadCommand>] an array of load commands
|
|
148
153
|
# corresponding to `name`
|
|
149
154
|
def command(name)
|
|
150
|
-
|
|
155
|
+
@load_commands_by_type.fetch(name.to_sym, []).dup
|
|
151
156
|
end
|
|
152
157
|
|
|
153
158
|
alias [] command
|
|
@@ -245,6 +250,7 @@ module MachO
|
|
|
245
250
|
# The exception to this rule is when methods like {#add_command} and
|
|
246
251
|
# {#delete_command} have been called with `repopulate = false`.
|
|
247
252
|
def populate_fields
|
|
253
|
+
clear_memoization_cache
|
|
248
254
|
@header = populate_mach_header
|
|
249
255
|
@load_commands = populate_load_commands
|
|
250
256
|
end
|
|
@@ -252,7 +258,8 @@ module MachO
|
|
|
252
258
|
# All load commands responsible for loading dylibs.
|
|
253
259
|
# @return [Array<LoadCommands::DylibCommand>] an array of DylibCommands
|
|
254
260
|
def dylib_load_commands
|
|
255
|
-
load_commands.select { |lc| LoadCommands::DYLIB_LOAD_COMMANDS.include?(lc.type) }
|
|
261
|
+
@dylib_load_commands ||= load_commands.select { |lc| LoadCommands::DYLIB_LOAD_COMMANDS.include?(lc.type) }
|
|
262
|
+
@dylib_load_commands.dup
|
|
256
263
|
end
|
|
257
264
|
|
|
258
265
|
# All segment load commands in the Mach-O.
|
|
@@ -271,26 +278,7 @@ module MachO
|
|
|
271
278
|
# @note This is **not** the same as {#alignment}!
|
|
272
279
|
# @note See `get_align` and `get_align_64` in `cctools/misc/lipo.c`
|
|
273
280
|
def segment_alignment
|
|
274
|
-
|
|
275
|
-
return 12 if %i[i386 x86_64 ppc ppc64].include?(cputype)
|
|
276
|
-
return 14 if %i[arm arm64].include?(cputype)
|
|
277
|
-
|
|
278
|
-
cur_align = Sections::MAX_SECT_ALIGN
|
|
279
|
-
|
|
280
|
-
segments.each do |segment|
|
|
281
|
-
if filetype == :object
|
|
282
|
-
# start with the smallest alignment, and work our way up
|
|
283
|
-
align = magic32? ? 2 : 3
|
|
284
|
-
segment.sections.each do |section|
|
|
285
|
-
align = section.align unless section.align <= align
|
|
286
|
-
end
|
|
287
|
-
else
|
|
288
|
-
align = segment.guess_align
|
|
289
|
-
end
|
|
290
|
-
cur_align = align if align < cur_align
|
|
291
|
-
end
|
|
292
|
-
|
|
293
|
-
cur_align
|
|
281
|
+
@segment_alignment ||= calculate_segment_alignment
|
|
294
282
|
end
|
|
295
283
|
|
|
296
284
|
# The Mach-O's dylib ID, or `nil` if not a dylib.
|
|
@@ -338,7 +326,8 @@ module MachO
|
|
|
338
326
|
# library, but at this point we're really only interested in a list of
|
|
339
327
|
# unique libraries this Mach-O file links to, thus: `uniq`. (This is also
|
|
340
328
|
# for consistency with `FatFile` that merges this list across all archs.)
|
|
341
|
-
dylib_load_commands.map
|
|
329
|
+
@linked_dylibs ||= dylib_load_commands.map { |lc| lc.name.to_s }.uniq
|
|
330
|
+
@linked_dylibs.dup
|
|
342
331
|
end
|
|
343
332
|
|
|
344
333
|
# Changes the shared library `old_name` to `new_name`
|
|
@@ -368,7 +357,8 @@ module MachO
|
|
|
368
357
|
# All runtime paths searched by the dynamic linker for the Mach-O.
|
|
369
358
|
# @return [Array<String>] an array of all runtime paths
|
|
370
359
|
def rpaths
|
|
371
|
-
command(:LC_RPATH).map
|
|
360
|
+
@rpaths ||= command(:LC_RPATH).map { |lc| lc.path.to_s }
|
|
361
|
+
@rpaths.dup
|
|
372
362
|
end
|
|
373
363
|
|
|
374
364
|
# Changes the runtime path `old_path` to `new_path`
|
|
@@ -448,6 +438,13 @@ module MachO
|
|
|
448
438
|
rpath_cmds.reverse_each { |cmd| delete_command(cmd) }
|
|
449
439
|
end
|
|
450
440
|
|
|
441
|
+
# Replaces the embedded signature with a pure-Ruby ad-hoc signature.
|
|
442
|
+
# @param identifier [String, nil] the signing identifier
|
|
443
|
+
# @return [void]
|
|
444
|
+
def codesign!(identifier: nil)
|
|
445
|
+
CodeSigning::AdhocSigner.new(self, identifier || CodeSigning.identifier(self, filename)).sign!
|
|
446
|
+
end
|
|
447
|
+
|
|
451
448
|
# Write all Mach-O data to the given filename.
|
|
452
449
|
# @param filename [String] the file to write to
|
|
453
450
|
# @return [void]
|
|
@@ -475,6 +472,17 @@ module MachO
|
|
|
475
472
|
|
|
476
473
|
private
|
|
477
474
|
|
|
475
|
+
# Clears all memoized values. Called when the file is repopulated.
|
|
476
|
+
# @return [void]
|
|
477
|
+
# @api private
|
|
478
|
+
def clear_memoization_cache
|
|
479
|
+
@linked_dylibs = nil
|
|
480
|
+
@rpaths = nil
|
|
481
|
+
@dylib_load_commands = nil
|
|
482
|
+
@load_commands_by_type = nil
|
|
483
|
+
@segment_alignment = nil
|
|
484
|
+
end
|
|
485
|
+
|
|
478
486
|
# The file's Mach-O header structure.
|
|
479
487
|
# @return [Headers::MachHeader] if the Mach-O is 32-bit
|
|
480
488
|
# @return [Headers::MachHeader64] if the Mach-O is 64-bit
|
|
@@ -583,16 +591,27 @@ module MachO
|
|
|
583
591
|
|
|
584
592
|
# All load commands in the file.
|
|
585
593
|
# @return [Array<LoadCommands::LoadCommand>] an array of load commands
|
|
594
|
+
# @raise [TruncatedFileError] if the declared load command data is incomplete
|
|
586
595
|
# @raise [LoadCommandError] if an unknown load command is encountered
|
|
596
|
+
# @raise [LoadCommandSizeError] if a load command's size is invalid
|
|
587
597
|
# @api private
|
|
588
598
|
def populate_load_commands
|
|
589
599
|
permissive = options.fetch(:permissive, false)
|
|
590
600
|
offset = header.class.bytesize
|
|
601
|
+
load_commands_end = offset + sizeofcmds
|
|
602
|
+
raise TruncatedFileError if load_commands_end > @raw_data.bytesize
|
|
603
|
+
|
|
591
604
|
load_commands = []
|
|
605
|
+
@load_commands_by_type = Hash.new { |h, k| h[k] = [] }
|
|
592
606
|
|
|
593
607
|
header.ncmds.times do
|
|
608
|
+
raise TruncatedFileError if offset + LoadCommands::LoadCommand.bytesize > load_commands_end
|
|
609
|
+
|
|
594
610
|
fmt = Utils.specialize_format("L=", endianness)
|
|
595
611
|
cmd = @raw_data.slice(offset, 4).unpack1(fmt)
|
|
612
|
+
cmdsize = @raw_data.slice(offset + 4, 4).unpack1(fmt)
|
|
613
|
+
raise LoadCommandSizeError, cmdsize if cmdsize % 4 != 0 || offset + cmdsize > load_commands_end
|
|
614
|
+
|
|
596
615
|
cmd_sym = LoadCommands::LOAD_COMMANDS[cmd]
|
|
597
616
|
|
|
598
617
|
raise LoadCommandError, cmd unless cmd_sym || permissive
|
|
@@ -605,16 +624,45 @@ module MachO
|
|
|
605
624
|
LoadCommands::LoadCommand
|
|
606
625
|
end
|
|
607
626
|
|
|
627
|
+
raise LoadCommandSizeError, cmdsize if cmdsize < klass.bytesize
|
|
628
|
+
|
|
608
629
|
view = MachOView.new(self, @raw_data, endianness, offset)
|
|
609
630
|
command = klass.new_from_bin(view)
|
|
610
631
|
|
|
611
632
|
load_commands << command
|
|
633
|
+
@load_commands_by_type[command.type] << command
|
|
612
634
|
offset += command.cmdsize
|
|
613
635
|
end
|
|
614
636
|
|
|
615
637
|
load_commands
|
|
616
638
|
end
|
|
617
639
|
|
|
640
|
+
# Calculate the segment alignment for the Mach-O. Guesses conservatively.
|
|
641
|
+
# @return [Integer] the alignment, as a power of 2
|
|
642
|
+
# @api private
|
|
643
|
+
def calculate_segment_alignment
|
|
644
|
+
# special cases: 12 for x86/64/PPC/PP64, 14 for ARM/ARM64
|
|
645
|
+
return 12 if %i[i386 x86_64 ppc ppc64].include?(cputype)
|
|
646
|
+
return 14 if %i[arm arm64].include?(cputype)
|
|
647
|
+
|
|
648
|
+
cur_align = Sections::MAX_SECT_ALIGN
|
|
649
|
+
|
|
650
|
+
segments.each do |segment|
|
|
651
|
+
if filetype == :object
|
|
652
|
+
# start with the smallest alignment, and work our way up
|
|
653
|
+
align = magic32? ? 2 : 3
|
|
654
|
+
segment.sections.each do |section|
|
|
655
|
+
align = section.align unless section.align <= align
|
|
656
|
+
end
|
|
657
|
+
else
|
|
658
|
+
align = segment.guess_align
|
|
659
|
+
end
|
|
660
|
+
cur_align = align if align < cur_align
|
|
661
|
+
end
|
|
662
|
+
|
|
663
|
+
cur_align
|
|
664
|
+
end
|
|
665
|
+
|
|
618
666
|
# The low file offset (offset to first section data).
|
|
619
667
|
# @return [Integer] the offset
|
|
620
668
|
# @api private
|
|
@@ -622,6 +670,9 @@ module MachO
|
|
|
622
670
|
offset = @raw_data.size
|
|
623
671
|
|
|
624
672
|
segments.each do |seg|
|
|
673
|
+
offset = seg.fileoff if seg.nsects.zero? && seg.fileoff.positive? &&
|
|
674
|
+
seg.filesize.positive? && seg.fileoff < offset
|
|
675
|
+
|
|
625
676
|
seg.sections.each do |sect|
|
|
626
677
|
next if sect.empty?
|
|
627
678
|
next if sect.type?(:S_ZEROFILL)
|
data/lib/macho/structure.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module MachO
|
|
4
|
-
# A general purpose pseudo-structure. Described in detail in
|
|
4
|
+
# A general purpose pseudo-structure. Described in detail in machostructure-dsl-docs.md.
|
|
5
5
|
# @abstract
|
|
6
6
|
class MachOStructure
|
|
7
7
|
# Constants used for parsing MachOStructure fields
|
data/lib/macho.rb
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "open3"
|
|
4
|
-
|
|
5
3
|
require_relative "macho/utils"
|
|
6
4
|
require_relative "macho/structure"
|
|
7
5
|
require_relative "macho/view"
|
|
8
6
|
require_relative "macho/headers"
|
|
7
|
+
require_relative "macho/code_signing"
|
|
9
8
|
require_relative "macho/load_commands"
|
|
10
9
|
require_relative "macho/sections"
|
|
11
10
|
require_relative "macho/macho_file"
|
|
@@ -16,7 +15,7 @@ require_relative "macho/tools"
|
|
|
16
15
|
# The primary namespace for ruby-macho.
|
|
17
16
|
module MachO
|
|
18
17
|
# release version
|
|
19
|
-
VERSION = "
|
|
18
|
+
VERSION = "6.0.0"
|
|
20
19
|
|
|
21
20
|
# Opens the given filename as a MachOFile or FatFile, depending on its magic.
|
|
22
21
|
# @param filename [String] the file being opened
|
|
@@ -42,20 +41,20 @@ module MachO
|
|
|
42
41
|
file
|
|
43
42
|
end
|
|
44
43
|
|
|
45
|
-
# Signs
|
|
46
|
-
# Necessary after
|
|
47
|
-
#
|
|
44
|
+
# Signs a thin or fat Mach-O using an ad-hoc identity.
|
|
45
|
+
# Necessary after changing signed Mach-O data because the signature covers
|
|
46
|
+
# the header, load commands and all bytes preceding the signature.
|
|
48
47
|
# @param filename [String] the file being opened
|
|
49
48
|
# @return [void]
|
|
50
|
-
# @raise [
|
|
49
|
+
# @raise [CodeSigningError] if the operation fails
|
|
51
50
|
def self.codesign!(filename)
|
|
52
|
-
raise ArgumentError, "codesign binary is not available on Linux" if RUBY_PLATFORM !~ /darwin/
|
|
53
51
|
raise ArgumentError, "#{filename}: no such file" unless File.file?(filename)
|
|
54
52
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
53
|
+
file = MachO.open(filename)
|
|
54
|
+
file.codesign!
|
|
55
|
+
file.write!
|
|
56
|
+
nil
|
|
57
|
+
rescue MachOError => e
|
|
58
|
+
raise CodeSigningError, "#{filename}: signing failed: #{e.message}"
|
|
60
59
|
end
|
|
61
60
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ruby-macho
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 6.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- William Woodruff
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies: []
|
|
13
12
|
description: A library for viewing and manipulating Mach-O files in Ruby.
|
|
14
13
|
email: william@yossarian.net
|
|
@@ -20,6 +19,7 @@ files:
|
|
|
20
19
|
- LICENSE
|
|
21
20
|
- README.md
|
|
22
21
|
- lib/macho.rb
|
|
22
|
+
- lib/macho/code_signing.rb
|
|
23
23
|
- lib/macho/exceptions.rb
|
|
24
24
|
- lib/macho/fat_file.rb
|
|
25
25
|
- lib/macho/headers.rb
|
|
@@ -35,7 +35,7 @@ licenses:
|
|
|
35
35
|
- MIT
|
|
36
36
|
metadata:
|
|
37
37
|
rubygems_mfa_required: 'true'
|
|
38
|
-
|
|
38
|
+
funding_uri: https://github.com/sponsors/Homebrew
|
|
39
39
|
rdoc_options: []
|
|
40
40
|
require_paths:
|
|
41
41
|
- lib
|
|
@@ -43,15 +43,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
43
43
|
requirements:
|
|
44
44
|
- - ">="
|
|
45
45
|
- !ruby/object:Gem::Version
|
|
46
|
-
version: '2
|
|
46
|
+
version: '3.2'
|
|
47
47
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
48
48
|
requirements:
|
|
49
49
|
- - ">="
|
|
50
50
|
- !ruby/object:Gem::Version
|
|
51
51
|
version: '0'
|
|
52
52
|
requirements: []
|
|
53
|
-
rubygems_version:
|
|
54
|
-
signing_key:
|
|
53
|
+
rubygems_version: 4.0.10
|
|
55
54
|
specification_version: 4
|
|
56
55
|
summary: ruby-macho - Mach-O file analyzer.
|
|
57
56
|
test_files: []
|