ruby-macho 1.4.0 → 2.4.0

Sign up to get free protection for your applications and to get access to all the features.
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.