ruby-macho 2.1.0 → 2.5.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5c4fdf0c3007545c3c9a84dacc832b803fc83edfe4aef0513eba857f14fcd2d1
4
- data.tar.gz: 0dd36193221a6d9f25084b5d8dea8f17eb8165a1f304e2d0006433ade11d61b8
3
+ metadata.gz: cfe1d04c6c841958f4ac428e7207d89e02371fa9ca9ac3cb48e3e2a34a65dc24
4
+ data.tar.gz: 21dc14db70485e37ff5e430d5f8a99af6cb1c0ac14f02ae1943121fa2c482629
5
5
  SHA512:
6
- metadata.gz: a07521accccc3e50119811ca36392c9ecc579dd0d8a238e8dcec5473f75325a2017a367d58212bc8fd1287e78f0b043f19546cf37eaa95c5c7f65623e7d3e015
7
- data.tar.gz: 16f243d60a944e725795bb77b6c2a301e5be7939df83c70c9929f23346792f7854c9b5ad626ea2bdf301155f882f43b934e12064efdc709723a6321fc13a83d5
6
+ metadata.gz: d668c48f840f4423ae4bbc89d30c53e26a709491d2f8ca66bc98c637563e596d107638779bd517bf1fe89458d5f05beadf76ea60d80a44578aa0b409be61fc43
7
+ data.tar.gz: 84009cdfbe5c61bc84ffd8838c7fe033171ebc05af18b7748c53ced99a0e92fd7f45f1a7c0f50001c05eb253e00564b3f8dbe7735fe04df087b3fef72d2be7e6
data/README.md CHANGED
@@ -13,6 +13,14 @@ The [Mach-O file format](https://en.wikipedia.org/wiki/Mach-O) is used by macOS
13
13
  and iOS (among others) as a general purpose binary format for object files,
14
14
  executables, dynamic libraries, and so forth.
15
15
 
16
+ ### Installation
17
+
18
+ ruby-macho can be installed via RubyGems:
19
+
20
+ ```bash
21
+ $ gem install ruby-macho
22
+ ```
23
+
16
24
  ### Documentation
17
25
 
18
26
  Full documentation is available on [RubyDoc](http://www.rubydoc.info/gems/ruby-macho/).
@@ -46,7 +54,6 @@ puts lc_vers.version_string # => "10.10.0"
46
54
 
47
55
  ### What needs to be done?
48
56
 
49
- * Documentation.
50
57
  * Unit and performance testing.
51
58
 
52
59
  Attribution:
data/lib/macho.rb CHANGED
@@ -1,3 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "open3"
4
+
1
5
  require_relative "macho/structure"
2
6
  require_relative "macho/view"
3
7
  require_relative "macho/headers"
@@ -12,7 +16,7 @@ require_relative "macho/tools"
12
16
  # The primary namespace for ruby-macho.
13
17
  module MachO
14
18
  # release version
15
- VERSION = "2.1.0".freeze
19
+ VERSION = "2.5.1"
16
20
 
17
21
  # Opens the given filename as a MachOFile or FatFile, depending on its magic.
18
22
  # @param filename [String] the file being opened
@@ -25,7 +29,7 @@ module MachO
25
29
  raise ArgumentError, "#{filename}: no such file" unless File.file?(filename)
26
30
  raise TruncatedFileError unless File.stat(filename).size >= 4
27
31
 
28
- magic = File.open(filename, "rb") { |f| f.read(4) }.unpack("N").first
32
+ magic = File.open(filename, "rb") { |f| f.read(4) }.unpack1("N")
29
33
 
30
34
  if Utils.fat_magic?(magic)
31
35
  file = FatFile.new(filename)
@@ -37,4 +41,21 @@ module MachO
37
41
 
38
42
  file
39
43
  end
44
+
45
+ # Signs the dylib using an ad-hoc identity.
46
+ # Necessary after making any changes to a dylib, since otherwise
47
+ # changing a signed file invalidates its signature.
48
+ # @param filename [String] the file being opened
49
+ # @return [void]
50
+ # @raise [ModificationError] if the operation fails
51
+ def self.codesign!(filename)
52
+ raise ArgumentError, "codesign binary is not available on Linux" if RUBY_PLATFORM !~ /darwin/
53
+ raise ArgumentError, "#{filename}: no such file" unless File.file?(filename)
54
+
55
+ _, _, status = Open3.capture3("codesign", "--sign", "-", "--force",
56
+ "--preserve-metadata=entitlements,requirements,flags,runtime",
57
+ filename)
58
+
59
+ raise CodeSigningError, "#{filename}: signing failed!" unless status.success?
60
+ end
40
61
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MachO
2
4
  # A generic Mach-O error in execution.
3
5
  class MachOError < RuntimeError
@@ -7,6 +9,11 @@ module MachO
7
9
  class ModificationError < MachOError
8
10
  end
9
11
 
12
+ # Raised when codesigning fails. Certain environments
13
+ # may want to rescue this to treat it as non-fatal.
14
+ class CodeSigningError < MachOError
15
+ end
16
+
10
17
  # Raised when a Mach-O file modification fails but can be recovered when
11
18
  # operating on multiple Mach-O slices of a fat binary in non-strict mode.
12
19
  class RecoverableModificationError < ModificationError
@@ -25,10 +32,6 @@ module MachO
25
32
 
26
33
  # Raised when a file is not a Mach-O.
27
34
  class NotAMachOError < MachOError
28
- # @param error [String] the error in question
29
- def initialize(error)
30
- super error
31
- end
32
35
  end
33
36
 
34
37
  # Raised when a file is too short to be a valid Mach-O file.
@@ -41,8 +44,8 @@ module MachO
41
44
  # Raised when a file's magic bytes are not valid Mach-O magic.
42
45
  class MagicError < NotAMachOError
43
46
  # @param num [Integer] the unknown number
44
- def initialize(num)
45
- super "Unrecognized Mach-O magic: 0x#{"%02x" % num}"
47
+ def initialize(magic)
48
+ super "Unrecognized Mach-O magic: 0x%02<magic>x" % { :magic => magic }
46
49
  end
47
50
  end
48
51
 
@@ -71,7 +74,7 @@ module MachO
71
74
  class CPUTypeError < MachOError
72
75
  # @param cputype [Integer] the unknown CPU type
73
76
  def initialize(cputype)
74
- super "Unrecognized CPU type: 0x#{"%08x" % cputype}"
77
+ super "Unrecognized CPU type: 0x%08<cputype>x" % { :cputype => cputype }
75
78
  end
76
79
  end
77
80
 
@@ -80,8 +83,8 @@ module MachO
80
83
  # @param cputype [Integer] the CPU type of the unknown pair
81
84
  # @param cpusubtype [Integer] the CPU sub-type of the unknown pair
82
85
  def initialize(cputype, cpusubtype)
83
- super "Unrecognized CPU sub-type: 0x#{"%08x" % cpusubtype}" \
84
- " (for CPU type: 0x#{"%08x" % cputype})"
86
+ super "Unrecognized CPU sub-type: 0x%08<cpusubtype>x" \
87
+ " (for CPU type: 0x%08<cputype>x" % { :cputype => cputype, :cpusubtype => cpusubtype }
85
88
  end
86
89
  end
87
90
 
@@ -89,7 +92,7 @@ module MachO
89
92
  class FiletypeError < MachOError
90
93
  # @param num [Integer] the unknown number
91
94
  def initialize(num)
92
- super "Unrecognized Mach-O filetype code: 0x#{"%02x" % num}"
95
+ super "Unrecognized Mach-O filetype code: 0x%02<num>x" % { :num => num }
93
96
  end
94
97
  end
95
98
 
@@ -97,7 +100,7 @@ module MachO
97
100
  class LoadCommandError < MachOError
98
101
  # @param num [Integer] the unknown number
99
102
  def initialize(num)
100
- super "Unrecognized Mach-O load command: 0x#{"%02x" % num}"
103
+ super "Unrecognized Mach-O load command: 0x%02<num>x" % { :num => num }
101
104
  end
102
105
  end
103
106
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "forwardable"
2
4
 
3
5
  module MachO
@@ -11,6 +13,10 @@ module MachO
11
13
  # @return [String] the filename loaded from, or nil if loaded from a binary string
12
14
  attr_accessor :filename
13
15
 
16
+ # @return [Hash] any parser options that the instance was created with
17
+ # @note Options specified in a {FatFile} trickle down into the internal {MachOFile}s.
18
+ attr_reader :options
19
+
14
20
  # @return [Headers::FatHeader] the file's header
15
21
  attr_reader :header
16
22
 
@@ -49,9 +55,7 @@ module MachO
49
55
  machos.each do |macho|
50
56
  macho_offset = Utils.round(offset, 2**macho.segment_alignment)
51
57
 
52
- if !fat64 && macho_offset > (2**32 - 1)
53
- raise FatArchOffsetOverflowError, macho_offset
54
- end
58
+ raise FatArchOffsetOverflowError, macho_offset if !fat64 && macho_offset > (2**32 - 1)
55
59
 
56
60
  macho_pads[macho] = Utils.padding_for(offset, 2**macho.segment_alignment)
57
61
 
@@ -62,7 +66,7 @@ module MachO
62
66
  offset += (macho.serialize.bytesize + macho_pads[macho])
63
67
  end
64
68
 
65
- machos.each do |macho|
69
+ machos.each do |macho| # rubocop:disable Style/CombinableLoops
66
70
  bin << Utils.nullpad(macho_pads[macho])
67
71
  bin << macho.serialize
68
72
  end
@@ -72,30 +76,36 @@ module MachO
72
76
 
73
77
  # Creates a new FatFile instance from a binary string.
74
78
  # @param bin [String] a binary string containing raw Mach-O data
79
+ # @param opts [Hash] options to control the parser with
80
+ # @note see {MachOFile#initialize} for currently valid options
75
81
  # @return [FatFile] a new FatFile
76
- def self.new_from_bin(bin)
82
+ def self.new_from_bin(bin, **opts)
77
83
  instance = allocate
78
- instance.initialize_from_bin(bin)
84
+ instance.initialize_from_bin(bin, opts)
79
85
 
80
86
  instance
81
87
  end
82
88
 
83
89
  # Creates a new FatFile from the given filename.
84
90
  # @param filename [String] the fat file to load from
91
+ # @param opts [Hash] options to control the parser with
92
+ # @note see {MachOFile#initialize} for currently valid options
85
93
  # @raise [ArgumentError] if the given file does not exist
86
- def initialize(filename)
94
+ def initialize(filename, **opts)
87
95
  raise ArgumentError, "#{filename}: no such file" unless File.file?(filename)
88
96
 
89
97
  @filename = filename
98
+ @options = opts
90
99
  @raw_data = File.open(@filename, "rb", &:read)
91
100
  populate_fields
92
101
  end
93
102
 
94
- # Initializes a new FatFile instance from a binary string.
103
+ # Initializes a new FatFile instance from a binary string with the given options.
95
104
  # @see new_from_bin
96
105
  # @api private
97
- def initialize_from_bin(bin)
106
+ def initialize_from_bin(bin, opts)
98
107
  @filename = nil
108
+ @options = opts
99
109
  @raw_data = bin
100
110
  populate_fields
101
111
  end
@@ -358,7 +368,7 @@ module MachO
358
368
  machos = []
359
369
 
360
370
  fat_archs.each do |arch|
361
- machos << MachOFile.new_from_bin(@raw_data[arch.offset, arch.size])
371
+ machos << MachOFile.new_from_bin(@raw_data[arch.offset, arch.size], **options)
362
372
  end
363
373
 
364
374
  machos
@@ -388,16 +398,14 @@ module MachO
388
398
  errors = []
389
399
 
390
400
  machos.each_with_index do |macho, index|
391
- begin
392
- yield macho
393
- rescue RecoverableModificationError => error
394
- error.macho_slice = index
401
+ yield macho
402
+ rescue RecoverableModificationError => e
403
+ e.macho_slice = index
395
404
 
396
- # Strict mode: Immediately re-raise. Otherwise: Retain, check later.
397
- raise error if strict
405
+ # Strict mode: Immediately re-raise. Otherwise: Retain, check later.
406
+ raise e if strict
398
407
 
399
- errors << error
400
- end
408
+ errors << e
401
409
  end
402
410
 
403
411
  # Non-strict mode: Raise first error if *all* Mach-O slices failed.
data/lib/macho/headers.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MachO
2
4
  # Classes and constants for parsing the headers of Mach-O binaries.
3
5
  module Headers
@@ -241,6 +243,10 @@ module MachO
241
243
  # @api private
242
244
  CPU_SUBTYPE_ARM64_32_V8 = 1
243
245
 
246
+ # the e (A12) sub-type for `CPU_TYPE_ARM64`
247
+ # @api private
248
+ CPU_SUBTYPE_ARM64E = 2
249
+
244
250
  # the lowest common sub-type for `CPU_TYPE_MC88000`
245
251
  # @api private
246
252
  CPU_SUBTYPE_MC88000_ALL = 0
@@ -350,6 +356,7 @@ module MachO
350
356
  CPU_TYPE_ARM64 => {
351
357
  CPU_SUBTYPE_ARM64_ALL => :arm64,
352
358
  CPU_SUBTYPE_ARM64_V8 => :arm64v8,
359
+ CPU_SUBTYPE_ARM64E => :arm64e,
353
360
  }.freeze,
354
361
  CPU_TYPE_ARM64_32 => {
355
362
  CPU_SUBTYPE_ARM64_32_V8 => :arm64_32v8,
@@ -485,7 +492,7 @@ module MachO
485
492
  # always big-endian
486
493
  # @see MachOStructure::FORMAT
487
494
  # @api private
488
- FORMAT = "N2".freeze
495
+ FORMAT = "N2"
489
496
 
490
497
  # @see MachOStructure::SIZEOF
491
498
  # @api private
@@ -493,6 +500,7 @@ module MachO
493
500
 
494
501
  # @api private
495
502
  def initialize(magic, nfat_arch)
503
+ super()
496
504
  @magic = magic
497
505
  @nfat_arch = nfat_arch
498
506
  end
@@ -536,7 +544,7 @@ module MachO
536
544
  # @note Always big endian.
537
545
  # @see MachOStructure::FORMAT
538
546
  # @api private
539
- FORMAT = "L>5".freeze
547
+ FORMAT = "L>5"
540
548
 
541
549
  # @see MachOStructure::SIZEOF
542
550
  # @api private
@@ -544,6 +552,7 @@ module MachO
544
552
 
545
553
  # @api private
546
554
  def initialize(cputype, cpusubtype, offset, size, align)
555
+ super()
547
556
  @cputype = cputype
548
557
  @cpusubtype = cpusubtype & ~CPU_SUBTYPE_MASK
549
558
  @offset = offset
@@ -582,7 +591,7 @@ module MachO
582
591
  # @note Always big endian.
583
592
  # @see MachOStructure::FORMAT
584
593
  # @api private
585
- FORMAT = "L>2Q>2L>2".freeze
594
+ FORMAT = "L>2Q>2L>2"
586
595
 
587
596
  # @see MachOStructure::SIZEOF
588
597
  # @api private
@@ -632,7 +641,7 @@ module MachO
632
641
 
633
642
  # @see MachOStructure::FORMAT
634
643
  # @api private
635
- FORMAT = "L=7".freeze
644
+ FORMAT = "L=7"
636
645
 
637
646
  # @see MachOStructure::SIZEOF
638
647
  # @api private
@@ -641,6 +650,7 @@ module MachO
641
650
  # @api private
642
651
  def initialize(magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds,
643
652
  flags)
653
+ super()
644
654
  @magic = magic
645
655
  @cputype = cputype
646
656
  # For now we're not interested in additional capability bits also to be
@@ -755,7 +765,7 @@ module MachO
755
765
 
756
766
  # @see MachOStructure::FORMAT
757
767
  # @api private
758
- FORMAT = "L=8".freeze
768
+ FORMAT = "L=8"
759
769
 
760
770
  # @see MachOStructure::SIZEOF
761
771
  # @api private
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MachO
2
4
  # Classes and constants for parsing load commands in Mach-O binaries.
3
5
  module LoadCommands
@@ -60,6 +62,8 @@ module MachO
60
62
  0x30 => :LC_VERSION_MIN_WATCHOS,
61
63
  0x31 => :LC_NOTE,
62
64
  0x32 => :LC_BUILD_VERSION,
65
+ (0x33 | LC_REQ_DYLD) => :LC_DYLD_EXPORTS_TRIE,
66
+ (0x34 | LC_REQ_DYLD) => :LD_DYLD_CHAINED_FIXUPS,
63
67
  }.freeze
64
68
 
65
69
  # association of symbol representations to load command constants
@@ -145,6 +149,8 @@ module MachO
145
149
  :LC_VERSION_MIN_WATCHOS => "VersionMinCommand",
146
150
  :LC_NOTE => "NoteCommand",
147
151
  :LC_BUILD_VERSION => "BuildVersionCommand",
152
+ :LC_DYLD_EXPORTS_TRIE => "LinkeditDataCommand",
153
+ :LD_DYLD_CHAINED_FIXUPS => "LinkeditDataCommand",
148
154
  }.freeze
149
155
 
150
156
  # association of segment name symbols to names
@@ -186,7 +192,7 @@ module MachO
186
192
 
187
193
  # @see MachOStructure::FORMAT
188
194
  # @api private
189
- FORMAT = "L=2".freeze
195
+ FORMAT = "L=2"
190
196
 
191
197
  # @see MachOStructure::SIZEOF
192
198
  # @api private
@@ -225,6 +231,7 @@ module MachO
225
231
  # @param cmdsize [Integer] the size of the load command in bytes
226
232
  # @api private
227
233
  def initialize(view, cmd, cmdsize)
234
+ super()
228
235
  @view = view
229
236
  @cmd = cmd
230
237
  @cmdsize = cmdsize
@@ -365,7 +372,7 @@ module MachO
365
372
 
366
373
  # @see MachOStructure::FORMAT
367
374
  # @api private
368
- FORMAT = "L=2a16".freeze
375
+ FORMAT = "L=2a16"
369
376
 
370
377
  # @see MachOStructure::SIZEOF
371
378
  # @api private
@@ -379,7 +386,7 @@ module MachO
379
386
 
380
387
  # @return [String] a string representation of the UUID
381
388
  def uuid_string
382
- hexes = uuid.map { |e| "%02x" % e }
389
+ hexes = uuid.map { |elem| "%02<elem>x" % { :elem => elem } }
383
390
  segs = [
384
391
  hexes[0..3].join, hexes[4..5].join, hexes[6..7].join,
385
392
  hexes[8..9].join, hexes[10..15].join
@@ -429,7 +436,7 @@ module MachO
429
436
 
430
437
  # @see MachOStructure::FORMAT
431
438
  # @api private
432
- FORMAT = "L=2Z16L=4l=2L=2".freeze
439
+ FORMAT = "L=2Z16L=4l=2L=2"
433
440
 
434
441
  # @see MachOStructure::SIZEOF
435
442
  # @api private
@@ -524,7 +531,7 @@ module MachO
524
531
  class SegmentCommand64 < SegmentCommand
525
532
  # @see MachOStructure::FORMAT
526
533
  # @api private
527
- FORMAT = "L=2Z16Q=4l=2L=2".freeze
534
+ FORMAT = "L=2Z16Q=4l=2L=2"
528
535
 
529
536
  # @see MachOStructure::SIZEOF
530
537
  # @api private
@@ -550,7 +557,7 @@ module MachO
550
557
 
551
558
  # @see MachOStructure::FORMAT
552
559
  # @api private
553
- FORMAT = "L=6".freeze
560
+ FORMAT = "L=6"
554
561
 
555
562
  # @see MachOStructure::SIZEOF
556
563
  # @api private
@@ -601,7 +608,7 @@ module MachO
601
608
 
602
609
  # @see MachOStructure::FORMAT
603
610
  # @api private
604
- FORMAT = "L=3".freeze
611
+ FORMAT = "L=3"
605
612
 
606
613
  # @see MachOStructure::SIZEOF
607
614
  # @api private
@@ -649,7 +656,7 @@ module MachO
649
656
 
650
657
  # @see MachOStructure::FORMAT
651
658
  # @api private
652
- FORMAT = "L=5".freeze
659
+ FORMAT = "L=5"
653
660
 
654
661
  # @see MachOStructure::SIZEOF
655
662
  # @api private
@@ -679,7 +686,7 @@ module MachO
679
686
  class ThreadCommand < LoadCommand
680
687
  # @see MachOStructure::FORMAT
681
688
  # @api private
682
- FORMAT = "L=2".freeze
689
+ FORMAT = "L=2"
683
690
 
684
691
  # @see MachOStructure::SIZEOF
685
692
  # @api private
@@ -717,7 +724,7 @@ module MachO
717
724
 
718
725
  # @see MachOStructure::FORMAT
719
726
  # @api private
720
- FORMAT = "L=10".freeze
727
+ FORMAT = "L=10"
721
728
 
722
729
  # @see MachOStructure::SIZEOF
723
730
  # @api private
@@ -758,7 +765,7 @@ module MachO
758
765
  class RoutinesCommand64 < RoutinesCommand
759
766
  # @see MachOStructure::FORMAT
760
767
  # @api private
761
- FORMAT = "L=2Q=8".freeze
768
+ FORMAT = "L=2Q=8"
762
769
 
763
770
  # @see MachOStructure::SIZEOF
764
771
  # @api private
@@ -773,7 +780,7 @@ module MachO
773
780
 
774
781
  # @see MachOStructure::FORMAT
775
782
  # @api private
776
- FORMAT = "L=3".freeze
783
+ FORMAT = "L=3"
777
784
 
778
785
  # @see MachOStructure::SIZEOF
779
786
  # @api private
@@ -801,7 +808,7 @@ module MachO
801
808
 
802
809
  # @see MachOStructure::FORMAT
803
810
  # @api private
804
- FORMAT = "L=3".freeze
811
+ FORMAT = "L=3"
805
812
 
806
813
  # @see MachOStructure::SIZEOF
807
814
  # @api private
@@ -829,7 +836,7 @@ module MachO
829
836
 
830
837
  # @see MachOStructure::FORMAT
831
838
  # @api private
832
- FORMAT = "L=3".freeze
839
+ FORMAT = "L=3"
833
840
 
834
841
  # @see MachOStructure::SIZEOF
835
842
  # @api private
@@ -857,7 +864,7 @@ module MachO
857
864
 
858
865
  # @see MachOStructure::FORMAT
859
866
  # @api private
860
- FORMAT = "L=3".freeze
867
+ FORMAT = "L=3"
861
868
 
862
869
  # @see MachOStructure::SIZEOF
863
870
  # @api private
@@ -894,7 +901,7 @@ module MachO
894
901
 
895
902
  # @see MachOStructure::FORMAT
896
903
  # @api private
897
- FORMAT = "L=6".freeze
904
+ FORMAT = "L=6"
898
905
 
899
906
  # @see MachOStructure::SIZEOF
900
907
  # @api private
@@ -979,7 +986,7 @@ module MachO
979
986
 
980
987
  # @see MachOStructure::FORMAT
981
988
  # @api private
982
- FORMAT = "L=20".freeze
989
+ FORMAT = "L=20"
983
990
 
984
991
  # @see MachOStructure::SIZEOF
985
992
  # @api private
@@ -1052,7 +1059,7 @@ module MachO
1052
1059
 
1053
1060
  # @see MachOStructure::FORMAT
1054
1061
  # @api private
1055
- FORMAT = "L=4".freeze
1062
+ FORMAT = "L=4"
1056
1063
 
1057
1064
  # @see MachOStructure::SIZEOF
1058
1065
  # @api private
@@ -1127,7 +1134,7 @@ module MachO
1127
1134
 
1128
1135
  # @see MachOStructure::FORMAT
1129
1136
  # @api private
1130
- FORMAT = "L=3".freeze
1137
+ FORMAT = "L=3"
1131
1138
 
1132
1139
  # @see MachOStructure::SIZEOF
1133
1140
  # @api private
@@ -1156,7 +1163,7 @@ module MachO
1156
1163
 
1157
1164
  # @see MachOStructure::FORMAT
1158
1165
  # @api private
1159
- FORMAT = "L=3".freeze
1166
+ FORMAT = "L=3"
1160
1167
 
1161
1168
  # @see MachOStructure::SIZEOF
1162
1169
  # @api private
@@ -1191,7 +1198,8 @@ module MachO
1191
1198
  # A load command representing the offsets and sizes of a blob of data in
1192
1199
  # the __LINKEDIT segment. Corresponds to LC_CODE_SIGNATURE,
1193
1200
  # LC_SEGMENT_SPLIT_INFO, LC_FUNCTION_STARTS, LC_DATA_IN_CODE,
1194
- # LC_DYLIB_CODE_SIGN_DRS, and LC_LINKER_OPTIMIZATION_HINT.
1201
+ # LC_DYLIB_CODE_SIGN_DRS, LC_LINKER_OPTIMIZATION_HINT, LC_DYLD_EXPORTS_TRIE,
1202
+ # or LC_DYLD_CHAINED_FIXUPS.
1195
1203
  class LinkeditDataCommand < LoadCommand
1196
1204
  # @return [Integer] offset to the data in the __LINKEDIT segment
1197
1205
  attr_reader :dataoff
@@ -1201,7 +1209,7 @@ module MachO
1201
1209
 
1202
1210
  # @see MachOStructure::FORMAT
1203
1211
  # @api private
1204
- FORMAT = "L=4".freeze
1212
+ FORMAT = "L=4"
1205
1213
 
1206
1214
  # @see MachOStructure::SIZEOF
1207
1215
  # @api private
@@ -1237,7 +1245,7 @@ module MachO
1237
1245
 
1238
1246
  # @see MachOStructure::FORMAT
1239
1247
  # @api private
1240
- FORMAT = "L=5".freeze
1248
+ FORMAT = "L=5"
1241
1249
 
1242
1250
  # @see MachOStructure::SIZEOF
1243
1251
  # @api private
@@ -1269,7 +1277,7 @@ module MachO
1269
1277
 
1270
1278
  # @see MachOStructure::FORMAT
1271
1279
  # @api private
1272
- FORMAT = "L=6".freeze
1280
+ FORMAT = "L=6"
1273
1281
 
1274
1282
  # @see MachOStructure::SIZEOF
1275
1283
  # @api private
@@ -1301,7 +1309,7 @@ module MachO
1301
1309
 
1302
1310
  # @see MachOStructure::FORMAT
1303
1311
  # @api private
1304
- FORMAT = "L=4".freeze
1312
+ FORMAT = "L=4"
1305
1313
 
1306
1314
  # @see MachOStructure::SIZEOF
1307
1315
  # @api private
@@ -1317,7 +1325,7 @@ module MachO
1317
1325
  # A string representation of the binary's minimum OS version.
1318
1326
  # @return [String] a string representing the minimum OS version.
1319
1327
  def version_string
1320
- binary = "%032b" % version
1328
+ binary = "%032<version>b" % { :version => version }
1321
1329
  segs = [
1322
1330
  binary[0..15], binary[16..23], binary[24..31]
1323
1331
  ].map { |s| s.to_i(2) }
@@ -1328,7 +1336,7 @@ module MachO
1328
1336
  # A string representation of the binary's SDK version.
1329
1337
  # @return [String] a string representing the SDK version.
1330
1338
  def sdk_string
1331
- binary = "%032b" % sdk
1339
+ binary = "%032<sdk>b" % { :sdk => sdk }
1332
1340
  segs = [
1333
1341
  binary[0..15], binary[16..23], binary[24..31]
1334
1342
  ].map { |s| s.to_i(2) }
@@ -1365,7 +1373,7 @@ module MachO
1365
1373
 
1366
1374
  # @see MachOStructure::FORMAT
1367
1375
  # @api private
1368
- FORMAT = "L=6".freeze
1376
+ FORMAT = "L=6"
1369
1377
 
1370
1378
  # @see MachOStructure::SIZEOF
1371
1379
  # @api private
@@ -1383,7 +1391,7 @@ module MachO
1383
1391
  # A string representation of the binary's minimum OS version.
1384
1392
  # @return [String] a string representing the minimum OS version.
1385
1393
  def minos_string
1386
- binary = "%032b" % minos
1394
+ binary = "%032<minos>b" % { :minos => minos }
1387
1395
  segs = [
1388
1396
  binary[0..15], binary[16..23], binary[24..31]
1389
1397
  ].map { |s| s.to_i(2) }
@@ -1394,7 +1402,7 @@ module MachO
1394
1402
  # A string representation of the binary's SDK version.
1395
1403
  # @return [String] a string representing the SDK version.
1396
1404
  def sdk_string
1397
- binary = "%032b" % sdk
1405
+ binary = "%032<sdk>b" % { :sdk => sdk }
1398
1406
  segs = [
1399
1407
  binary[0..15], binary[16..23], binary[24..31]
1400
1408
  ].map { |s| s.to_i(2) }
@@ -1494,7 +1502,7 @@ module MachO
1494
1502
 
1495
1503
  # @see MachOStructure::FORMAT
1496
1504
  # @api private
1497
- FORMAT = "L=12".freeze
1505
+ FORMAT = "L=12"
1498
1506
 
1499
1507
  # @see MachOStructure::SIZEOF
1500
1508
  # @api private
@@ -1542,7 +1550,7 @@ module MachO
1542
1550
 
1543
1551
  # @see MachOStructure::FORMAT
1544
1552
  # @api private
1545
- FORMAT = "L=3".freeze
1553
+ FORMAT = "L=3"
1546
1554
 
1547
1555
  # @see MachOStructure::SIZEOF
1548
1556
  # @api private
@@ -1572,7 +1580,7 @@ module MachO
1572
1580
 
1573
1581
  # @see MachOStructure::FORMAT
1574
1582
  # @api private
1575
- FORMAT = "L=2Q=2".freeze
1583
+ FORMAT = "L=2Q=2"
1576
1584
 
1577
1585
  # @see MachOStructure::SIZEOF
1578
1586
  # @api private
@@ -1602,7 +1610,7 @@ module MachO
1602
1610
 
1603
1611
  # @see MachOStructure::FORMAT
1604
1612
  # @api private
1605
- FORMAT = "L=2Q=1".freeze
1613
+ FORMAT = "L=2Q=1"
1606
1614
 
1607
1615
  # @see MachOStructure::SIZEOF
1608
1616
  # @api private
@@ -1617,7 +1625,7 @@ module MachO
1617
1625
  # A string representation of the sources used to build the binary.
1618
1626
  # @return [String] a string representation of the version
1619
1627
  def version_string
1620
- binary = "%064b" % version
1628
+ binary = "%064<version>b" % { :version => version }
1621
1629
  segs = [
1622
1630
  binary[0..23], binary[24..33], binary[34..43], binary[44..53],
1623
1631
  binary[54..63]
@@ -1646,7 +1654,7 @@ module MachO
1646
1654
 
1647
1655
  # @see MachOStructure::FORMAT
1648
1656
  # @api private
1649
- FORMAT = "L=4".freeze
1657
+ FORMAT = "L=4"
1650
1658
 
1651
1659
  # @see MachOStructure::SIZEOF
1652
1660
  # @api private
@@ -1674,7 +1682,7 @@ module MachO
1674
1682
  class IdentCommand < LoadCommand
1675
1683
  # @see MachOStructure::FORMAT
1676
1684
  # @api private
1677
- FORMAT = "L=2".freeze
1685
+ FORMAT = "L=2"
1678
1686
 
1679
1687
  # @see MachOStructure::SIZEOF
1680
1688
  # @api private
@@ -1692,7 +1700,7 @@ module MachO
1692
1700
 
1693
1701
  # @see MachOStructure::FORMAT
1694
1702
  # @api private
1695
- FORMAT = "L=4".freeze
1703
+ FORMAT = "L=4"
1696
1704
 
1697
1705
  # @see MachOStructure::SIZEOF
1698
1706
  # @api private
@@ -1727,7 +1735,7 @@ module MachO
1727
1735
 
1728
1736
  # @see MachOStructure::FORMAT
1729
1737
  # @api private
1730
- FORMAT = "L=5".freeze
1738
+ FORMAT = "L=5"
1731
1739
 
1732
1740
  # @see MachOStructure::SIZEOF
1733
1741
  # @api private
@@ -1764,7 +1772,7 @@ module MachO
1764
1772
 
1765
1773
  # @see MachOStructure::FORMAT
1766
1774
  # @api private
1767
- FORMAT = "L=2Z16Q=2".freeze
1775
+ FORMAT = "L=2Z16Q=2"
1768
1776
 
1769
1777
  # @see MachOStructure::SIZEOF
1770
1778
  # @api private
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "forwardable"
2
4
 
3
5
  module MachO
@@ -9,10 +11,13 @@ module MachO
9
11
  class MachOFile
10
12
  extend Forwardable
11
13
 
12
- # @return [String] the filename loaded from, or nil if loaded from a binary
14
+ # @return [String, nil] the filename loaded from, or nil if loaded from a binary
13
15
  # string
14
16
  attr_accessor :filename
15
17
 
18
+ # @return [Hash] any parser options that the instance was created with
19
+ attr_reader :options
20
+
16
21
  # @return [Symbol] the endianness of the file, :big or :little
17
22
  attr_reader :endianness
18
23
 
@@ -27,30 +32,36 @@ module MachO
27
32
 
28
33
  # Creates a new instance from a binary string.
29
34
  # @param bin [String] a binary string containing raw Mach-O data
35
+ # @param opts [Hash] options to control the parser with
36
+ # @option opts [Boolean] :permissive whether to ignore unknown load commands
30
37
  # @return [MachOFile] a new MachOFile
31
- def self.new_from_bin(bin)
38
+ def self.new_from_bin(bin, **opts)
32
39
  instance = allocate
33
- instance.initialize_from_bin(bin)
40
+ instance.initialize_from_bin(bin, opts)
34
41
 
35
42
  instance
36
43
  end
37
44
 
38
45
  # Creates a new instance from data read from the given filename.
39
46
  # @param filename [String] the Mach-O file to load from
47
+ # @param opts [Hash] options to control the parser with
48
+ # @option opts [Boolean] :permissive whether to ignore unknown load commands
40
49
  # @raise [ArgumentError] if the given file does not exist
41
- def initialize(filename)
50
+ def initialize(filename, **opts)
42
51
  raise ArgumentError, "#{filename}: no such file" unless File.file?(filename)
43
52
 
44
53
  @filename = filename
54
+ @options = opts
45
55
  @raw_data = File.open(@filename, "rb", &:read)
46
56
  populate_fields
47
57
  end
48
58
 
49
- # Initializes a new MachOFile instance from a binary string.
59
+ # Initializes a new MachOFile instance from a binary string with the given options.
50
60
  # @see MachO::MachOFile.new_from_bin
51
61
  # @api private
52
- def initialize_from_bin(bin)
62
+ def initialize_from_bin(bin, opts)
53
63
  @filename = nil
64
+ @options = opts
54
65
  @raw_data = bin
55
66
  populate_fields
56
67
  end
@@ -146,16 +157,13 @@ module MachO
146
157
  def insert_command(offset, lc, options = {})
147
158
  context = LoadCommands::LoadCommand::SerializationContext.context_for(self)
148
159
  cmd_raw = lc.serialize(context)
160
+ fileoff = offset + cmd_raw.bytesize
149
161
 
150
- if offset < header.class.bytesize || offset + cmd_raw.bytesize > low_fileoff
151
- raise OffsetInsertionError, offset
152
- end
162
+ raise OffsetInsertionError, offset if offset < header.class.bytesize || fileoff > low_fileoff
153
163
 
154
164
  new_sizeofcmds = sizeofcmds + cmd_raw.bytesize
155
165
 
156
- if header.class.bytesize + new_sizeofcmds > low_fileoff
157
- raise HeaderPadError, @filename
158
- end
166
+ raise HeaderPadError, @filename if header.class.bytesize + new_sizeofcmds > low_fileoff
159
167
 
160
168
  # update Mach-O header fields to account for inserted load command
161
169
  update_ncmds(ncmds + 1)
@@ -178,9 +186,8 @@ module MachO
178
186
  context = LoadCommands::LoadCommand::SerializationContext.context_for(self)
179
187
  cmd_raw = new_lc.serialize(context)
180
188
  new_sizeofcmds = sizeofcmds + cmd_raw.bytesize - old_lc.cmdsize
181
- if header.class.bytesize + new_sizeofcmds > low_fileoff
182
- raise HeaderPadError, @filename
183
- end
189
+
190
+ raise HeaderPadError, @filename if header.class.bytesize + new_sizeofcmds > low_fileoff
184
191
 
185
192
  delete_command(old_lc)
186
193
  insert_command(old_lc.view.offset, new_lc)
@@ -411,11 +418,8 @@ module MachO
411
418
  rpath_cmds = command(:LC_RPATH).select { |r| r.path.to_s == path }
412
419
  raise RpathUnknownError, path if rpath_cmds.empty?
413
420
 
414
- # delete the commands in reverse order, offset descending. this
415
- # allows us to defer (expensive) field population until the very end
416
- rpath_cmds.reverse_each { |cmd| delete_command(cmd, :repopulate => false) }
417
-
418
- populate_fields
421
+ # delete the commands in reverse order, offset descending.
422
+ rpath_cmds.reverse_each { |cmd| delete_command(cmd) }
419
423
  end
420
424
 
421
425
  # Write all Mach-O data to the given filename.
@@ -471,7 +475,7 @@ module MachO
471
475
  # @raise [FatBinaryError] if the magic is for a Fat file
472
476
  # @api private
473
477
  def populate_and_check_magic
474
- magic = @raw_data[0..3].unpack("N").first
478
+ magic = @raw_data[0..3].unpack1("N")
475
479
 
476
480
  raise MagicError, magic unless Utils.magic?(magic)
477
481
  raise FatBinaryError if Utils.fat_magic?(magic)
@@ -511,19 +515,25 @@ module MachO
511
515
  # @raise [LoadCommandError] if an unknown load command is encountered
512
516
  # @api private
513
517
  def populate_load_commands
518
+ permissive = options.fetch(:permissive, false)
514
519
  offset = header.class.bytesize
515
520
  load_commands = []
516
521
 
517
522
  header.ncmds.times do
518
523
  fmt = Utils.specialize_format("L=", endianness)
519
- cmd = @raw_data.slice(offset, 4).unpack(fmt).first
524
+ cmd = @raw_data.slice(offset, 4).unpack1(fmt)
520
525
  cmd_sym = LoadCommands::LOAD_COMMANDS[cmd]
521
526
 
522
- raise LoadCommandError, cmd if cmd_sym.nil?
527
+ raise LoadCommandError, cmd unless cmd_sym || permissive
528
+
529
+ # If we're here, then either cmd_sym represents a valid load
530
+ # command *or* we're in permissive mode.
531
+ klass = if (klass_str = LoadCommands::LC_STRUCTURES[cmd_sym])
532
+ LoadCommands.const_get klass_str
533
+ else
534
+ LoadCommands::LoadCommand
535
+ end
523
536
 
524
- # why do I do this? i don't like declaring constants below
525
- # classes, and i need them to resolve...
526
- klass = LoadCommands.const_get LoadCommands::LC_STRUCTURES[cmd_sym]
527
537
  view = MachOView.new(@raw_data, endianness, offset)
528
538
  command = klass.new_from_bin(view)
529
539
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MachO
2
4
  # Classes and constants for parsing sections in Mach-O binaries.
3
5
  module Sections
@@ -108,7 +110,7 @@ module MachO
108
110
  attr_reader :reserved2
109
111
 
110
112
  # @see MachOStructure::FORMAT
111
- FORMAT = "Z16Z16L=9".freeze
113
+ FORMAT = "Z16Z16L=9"
112
114
 
113
115
  # @see MachOStructure::SIZEOF
114
116
  SIZEOF = 68
@@ -116,6 +118,7 @@ module MachO
116
118
  # @api private
117
119
  def initialize(sectname, segname, addr, size, offset, align, reloff,
118
120
  nreloc, flags, reserved1, reserved2)
121
+ super()
119
122
  @sectname = sectname
120
123
  @segname = segname
121
124
  @addr = addr
@@ -180,7 +183,7 @@ module MachO
180
183
  attr_reader :reserved3
181
184
 
182
185
  # @see MachOStructure::FORMAT
183
- FORMAT = "Z16Z16Q=2L=8".freeze
186
+ FORMAT = "Z16Z16Q=2L=8"
184
187
 
185
188
  # @see MachOStructure::SIZEOF
186
189
  SIZEOF = 80
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MachO
2
4
  # A general purpose pseudo-structure.
3
5
  # @abstract
@@ -5,7 +7,7 @@ module MachO
5
7
  # The String#unpack format of the data structure.
6
8
  # @return [String] the unpacking format
7
9
  # @api private
8
- FORMAT = "".freeze
10
+ FORMAT = ""
9
11
 
10
12
  # The size of the data structure, in bytes.
11
13
  # @return [Integer] the size, in bytes
data/lib/macho/tools.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MachO
2
4
  # A collection of convenient methods for common operations on Mach-O and Fat
3
5
  # binaries.
data/lib/macho/utils.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MachO
2
4
  # A collection of utility functions used throughout ruby-macho.
3
5
  module Utils
@@ -51,7 +53,7 @@ module MachO
51
53
  def self.pack_strings(fixed_offset, alignment, strings = {})
52
54
  offsets = {}
53
55
  next_offset = fixed_offset
54
- payload = ""
56
+ payload = +""
55
57
 
56
58
  strings.each do |key, string|
57
59
  offsets[key] = next_offset
@@ -61,7 +63,7 @@ module MachO
61
63
  end
62
64
 
63
65
  payload << Utils.nullpad(padding_for(fixed_offset + payload.bytesize, alignment))
64
- [payload, offsets]
66
+ [payload.freeze, offsets]
65
67
  end
66
68
 
67
69
  # Compares the given number to valid Mach-O magic numbers.
data/lib/macho/view.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MachO
2
4
  # A representation of some unspecified Mach-O data.
3
5
  class MachOView
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-macho
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.0
4
+ version: 2.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - William Woodruff
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-09-26 00:00:00.000000000 Z
11
+ date: 2021-05-15 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: A library for viewing and manipulating Mach-O files in Ruby.
14
14
  email: william@yossarian.net
@@ -42,15 +42,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
42
42
  requirements:
43
43
  - - ">="
44
44
  - !ruby/object:Gem::Version
45
- version: '2.2'
45
+ version: '2.5'
46
46
  required_rubygems_version: !ruby/object:Gem::Requirement
47
47
  requirements:
48
48
  - - ">="
49
49
  - !ruby/object:Gem::Version
50
50
  version: '0'
51
51
  requirements: []
52
- rubyforge_project:
53
- rubygems_version: 2.7.6
52
+ rubygems_version: 3.0.3
54
53
  signing_key:
55
54
  specification_version: 4
56
55
  summary: ruby-macho - Mach-O file analyzer.