ruby-macho 1.4.0 → 2.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 50aeda7d9f7ac3c91c6e34a9d150ffbf79d89389cb591d06494fb0eb4e48023b
4
- data.tar.gz: e7f56ff39f7159c20572e66bd07b506e826152e8b1da3d78afe22f4e8516b4a9
3
+ metadata.gz: 8b98417732314a92f3493ffd08e9221edbd342941bee24245b88b07fb292c2f8
4
+ data.tar.gz: cc17a19152fac25565b9a2759f7aa9aa2674318623e6bf620527e32e1796acda
5
5
  SHA512:
6
- metadata.gz: 2bb5bd7d605eb023b8cdf95446776507ddcfb08a7d30bc45acf3fff24d3b4edc5666b8c5f4801e226524ba60dad043a3c251819ff8b0cdd77e0e5b491a4a2e81
7
- data.tar.gz: a2d553f64c0e73882dcbce972bf8cdab7554c98f1c6daf2d504588e11d05d194ac417127d61a32f93263fa3487ab1655cf16867994018464c8d5a9f4df9b6073
6
+ metadata.gz: 7135b6f32de153de839197779a1770e4a18c071a063ec8fa0b88791171fc6d45e208dd8df7a4338ca1f663d74cc25b2b78c791c61873598177f314f01c4ed108
7
+ data.tar.gz: 16132d807fd316cddd68638cdf9877e638134b438c5fddeb7901619cd8f52aba244ca6d2e7613981bd7d645612bce66434dc2f17400bea8fcd750e7444e782d1
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:
@@ -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 = "1.4.0".freeze
19
+ VERSION = "2.4.0"
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,22 @@ 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
+ # codesign binary is not available on Linux
53
+ return if RUBY_PLATFORM !~ /darwin/
54
+ raise ArgumentError, "#{filename}: no such file" unless File.file?(filename)
55
+
56
+ _, _, status = Open3.capture3("codesign", "--sign", "-", "--force",
57
+ "--preserve-metadata=entitlements,requirements,flags,runtime",
58
+ filename)
59
+
60
+ raise CodeSigningError, "#{filename}: signing failed!" unless status.success?
61
+ end
40
62
  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
 
@@ -39,11 +45,7 @@ module MachO
39
45
  # put the smaller alignments further forwards in fat macho, so that we do less padding
40
46
  machos = machos.sort_by(&:segment_alignment)
41
47
 
42
- bin = if String.instance_methods.include? :+@
43
- +""
44
- else
45
- ""
46
- end
48
+ bin = +""
47
49
 
48
50
  bin << Headers::FatHeader.new(magic, machos.size).serialize
49
51
  offset = Headers::FatHeader.bytesize + (machos.size * fa_klass.bytesize)
@@ -53,9 +55,7 @@ module MachO
53
55
  machos.each do |macho|
54
56
  macho_offset = Utils.round(offset, 2**macho.segment_alignment)
55
57
 
56
- if !fat64 && macho_offset > (2**32 - 1)
57
- raise FatArchOffsetOverflowError, macho_offset
58
- end
58
+ raise FatArchOffsetOverflowError, macho_offset if !fat64 && macho_offset > (2**32 - 1)
59
59
 
60
60
  macho_pads[macho] = Utils.padding_for(offset, 2**macho.segment_alignment)
61
61
 
@@ -66,7 +66,7 @@ module MachO
66
66
  offset += (macho.serialize.bytesize + macho_pads[macho])
67
67
  end
68
68
 
69
- machos.each do |macho|
69
+ machos.each do |macho| # rubocop:disable Style/CombinableLoops
70
70
  bin << Utils.nullpad(macho_pads[macho])
71
71
  bin << macho.serialize
72
72
  end
@@ -76,30 +76,36 @@ module MachO
76
76
 
77
77
  # Creates a new FatFile instance from a binary string.
78
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
79
81
  # @return [FatFile] a new FatFile
80
- def self.new_from_bin(bin)
82
+ def self.new_from_bin(bin, **opts)
81
83
  instance = allocate
82
- instance.initialize_from_bin(bin)
84
+ instance.initialize_from_bin(bin, opts)
83
85
 
84
86
  instance
85
87
  end
86
88
 
87
89
  # Creates a new FatFile from the given filename.
88
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
89
93
  # @raise [ArgumentError] if the given file does not exist
90
- def initialize(filename)
94
+ def initialize(filename, **opts)
91
95
  raise ArgumentError, "#{filename}: no such file" unless File.file?(filename)
92
96
 
93
97
  @filename = filename
98
+ @options = opts
94
99
  @raw_data = File.open(@filename, "rb", &:read)
95
100
  populate_fields
96
101
  end
97
102
 
98
- # Initializes a new FatFile instance from a binary string.
103
+ # Initializes a new FatFile instance from a binary string with the given options.
99
104
  # @see new_from_bin
100
105
  # @api private
101
- def initialize_from_bin(bin)
106
+ def initialize_from_bin(bin, opts)
102
107
  @filename = nil
108
+ @options = opts
103
109
  @raw_data = bin
104
110
  populate_fields
105
111
  end
@@ -362,7 +368,7 @@ module MachO
362
368
  machos = []
363
369
 
364
370
  fat_archs.each do |arch|
365
- machos << MachOFile.new_from_bin(@raw_data[arch.offset, arch.size])
371
+ machos << MachOFile.new_from_bin(@raw_data[arch.offset, arch.size], **options)
366
372
  end
367
373
 
368
374
  machos
@@ -392,16 +398,14 @@ module MachO
392
398
  errors = []
393
399
 
394
400
  machos.each_with_index do |macho, index|
395
- begin
396
- yield macho
397
- rescue RecoverableModificationError => error
398
- error.macho_slice = index
401
+ yield macho
402
+ rescue RecoverableModificationError => e
403
+ e.macho_slice = index
399
404
 
400
- # Strict mode: Immediately re-raise. Otherwise: Retain, check later.
401
- raise error if strict
405
+ # Strict mode: Immediately re-raise. Otherwise: Retain, check later.
406
+ raise e if strict
402
407
 
403
- errors << error
404
- end
408
+ errors << e
405
409
  end
406
410
 
407
411
  # Non-strict mode: Raise first error if *all* Mach-O slices failed.
@@ -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
@@ -490,7 +492,7 @@ module MachO
490
492
  # always big-endian
491
493
  # @see MachOStructure::FORMAT
492
494
  # @api private
493
- FORMAT = "N2".freeze
495
+ FORMAT = "N2"
494
496
 
495
497
  # @see MachOStructure::SIZEOF
496
498
  # @api private
@@ -498,6 +500,7 @@ module MachO
498
500
 
499
501
  # @api private
500
502
  def initialize(magic, nfat_arch)
503
+ super()
501
504
  @magic = magic
502
505
  @nfat_arch = nfat_arch
503
506
  end
@@ -541,7 +544,7 @@ module MachO
541
544
  # @note Always big endian.
542
545
  # @see MachOStructure::FORMAT
543
546
  # @api private
544
- FORMAT = "L>5".freeze
547
+ FORMAT = "L>5"
545
548
 
546
549
  # @see MachOStructure::SIZEOF
547
550
  # @api private
@@ -549,6 +552,7 @@ module MachO
549
552
 
550
553
  # @api private
551
554
  def initialize(cputype, cpusubtype, offset, size, align)
555
+ super()
552
556
  @cputype = cputype
553
557
  @cpusubtype = cpusubtype & ~CPU_SUBTYPE_MASK
554
558
  @offset = offset
@@ -587,7 +591,7 @@ module MachO
587
591
  # @note Always big endian.
588
592
  # @see MachOStructure::FORMAT
589
593
  # @api private
590
- FORMAT = "L>2Q>2L>2".freeze
594
+ FORMAT = "L>2Q>2L>2"
591
595
 
592
596
  # @see MachOStructure::SIZEOF
593
597
  # @api private
@@ -637,7 +641,7 @@ module MachO
637
641
 
638
642
  # @see MachOStructure::FORMAT
639
643
  # @api private
640
- FORMAT = "L=7".freeze
644
+ FORMAT = "L=7"
641
645
 
642
646
  # @see MachOStructure::SIZEOF
643
647
  # @api private
@@ -646,6 +650,7 @@ module MachO
646
650
  # @api private
647
651
  def initialize(magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds,
648
652
  flags)
653
+ super()
649
654
  @magic = magic
650
655
  @cputype = cputype
651
656
  # For now we're not interested in additional capability bits also to be
@@ -760,7 +765,7 @@ module MachO
760
765
 
761
766
  # @see MachOStructure::FORMAT
762
767
  # @api private
763
- FORMAT = "L=8".freeze
768
+ FORMAT = "L=8"
764
769
 
765
770
  # @see MachOStructure::SIZEOF
766
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)
@@ -471,7 +478,7 @@ module MachO
471
478
  # @raise [FatBinaryError] if the magic is for a Fat file
472
479
  # @api private
473
480
  def populate_and_check_magic
474
- magic = @raw_data[0..3].unpack("N").first
481
+ magic = @raw_data[0..3].unpack1("N")
475
482
 
476
483
  raise MagicError, magic unless Utils.magic?(magic)
477
484
  raise FatBinaryError if Utils.fat_magic?(magic)
@@ -511,19 +518,25 @@ module MachO
511
518
  # @raise [LoadCommandError] if an unknown load command is encountered
512
519
  # @api private
513
520
  def populate_load_commands
521
+ permissive = options.fetch(:permissive, false)
514
522
  offset = header.class.bytesize
515
523
  load_commands = []
516
524
 
517
525
  header.ncmds.times do
518
526
  fmt = Utils.specialize_format("L=", endianness)
519
- cmd = @raw_data.slice(offset, 4).unpack(fmt).first
527
+ cmd = @raw_data.slice(offset, 4).unpack1(fmt)
520
528
  cmd_sym = LoadCommands::LOAD_COMMANDS[cmd]
521
529
 
522
- raise LoadCommandError, cmd if cmd_sym.nil?
530
+ raise LoadCommandError, cmd unless cmd_sym || permissive
531
+
532
+ # If we're here, then either cmd_sym represents a valid load
533
+ # command *or* we're in permissive mode.
534
+ klass = if (klass_str = LoadCommands::LC_STRUCTURES[cmd_sym])
535
+ LoadCommands.const_get klass_str
536
+ else
537
+ LoadCommands::LoadCommand
538
+ end
523
539
 
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
540
  view = MachOView.new(@raw_data, endianness, offset)
528
541
  command = klass.new_from_bin(view)
529
542
 
@@ -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
@@ -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.
@@ -23,6 +25,8 @@ module MachO
23
25
 
24
26
  file.change_dylib_id(new_id, options)
25
27
  file.write!
28
+
29
+ MachO.codesign!(filename)
26
30
  end
27
31
 
28
32
  # Changes a shared library install name in a Mach-O or Fat binary,
@@ -39,6 +43,8 @@ module MachO
39
43
 
40
44
  file.change_install_name(old_name, new_name, options)
41
45
  file.write!
46
+
47
+ MachO.codesign!(filename)
42
48
  end
43
49
 
44
50
  # Changes a runtime path in a Mach-O or Fat binary, overwriting the source
@@ -55,6 +61,8 @@ module MachO
55
61
 
56
62
  file.change_rpath(old_path, new_path, options)
57
63
  file.write!
64
+
65
+ MachO.codesign!(filename)
58
66
  end
59
67
 
60
68
  # Add a runtime path to a Mach-O or Fat binary, overwriting the source file.
@@ -69,6 +77,8 @@ module MachO
69
77
 
70
78
  file.add_rpath(new_path, options)
71
79
  file.write!
80
+
81
+ MachO.codesign!(filename)
72
82
  end
73
83
 
74
84
  # Delete a runtime path from a Mach-O or Fat binary, overwriting the source
@@ -84,6 +94,8 @@ module MachO
84
94
 
85
95
  file.delete_rpath(old_path, options)
86
96
  file.write!
97
+
98
+ MachO.codesign!(filename)
87
99
  end
88
100
 
89
101
  # Merge multiple Mach-Os into one universal (Fat) binary.
@@ -104,6 +116,8 @@ module MachO
104
116
 
105
117
  fat_macho = MachO::FatFile.new_from_machos(*machos, :fat64 => fat64)
106
118
  fat_macho.write(filename)
119
+
120
+ MachO.codesign!(filename)
107
121
  end
108
122
  end
109
123
  end
@@ -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
@@ -27,7 +29,7 @@ module MachO
27
29
  # @return [String] the null string (or empty string, for `size = 0`)
28
30
  # @raise [ArgumentError] if a non-positive nullpad is requested
29
31
  def self.nullpad(size)
30
- raise ArgumentError, "size < 0: #{size}" if size < 0
32
+ raise ArgumentError, "size < 0: #{size}" if size.negative?
31
33
 
32
34
  "\x00" * size
33
35
  end
@@ -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.
@@ -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: 1.4.0
4
+ version: 2.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - William Woodruff
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-02-21 00:00:00.000000000 Z
11
+ date: 2020-11-06 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,14 +42,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
42
42
  requirements:
43
43
  - - ">="
44
44
  - !ruby/object:Gem::Version
45
- version: '2.0'
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
- rubygems_version: 3.0.1
52
+ rubygems_version: 3.1.2
53
53
  signing_key:
54
54
  specification_version: 4
55
55
  summary: ruby-macho - Mach-O file analyzer.