ruby-macho 1.3.1 → 2.3.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: 1e5bd35359f5f750b1c57549af9b0f108be8ac30928a7e68f27ec8760a4efb2c
4
- data.tar.gz: ab5dd0b3e3b6256c97fb575e44f92cc9b3db8cab960d0176b29516ea8b7d8e74
3
+ metadata.gz: 73c21b87da2cf6cdac1c2123d846fa6fd8eb2863c4fe87d0170fa6162d110329
4
+ data.tar.gz: 0a59a2d38d59665eee474aad3f394aa54f1f2da5926c01b65824a75992da3e61
5
5
  SHA512:
6
- metadata.gz: 76e4b13240145463217dc2bc7f120476b792a3945216b9b466b354c13c278c652b96b3625b5fb1473d0c52ce518c8440b353cb9c594eed2e6488ba6332417bd2
7
- data.tar.gz: 0713510f129a8ad8d8962030f05a78277b8707c02b614cef1087a7723e4111ac4fedcbebf14cf974a5b030fb2a0210378eba89f70a535517427c896c0c55112a
6
+ metadata.gz: ac41936e243431a8346059b40e32fa6ed33c7f9f1f9dbbb9efcd7d1391d40931a6bdc33bf8fb5f676fb87af305cb92d18df9fe47151724cfb141996db47a2bd5
7
+ data.tar.gz: 4feeb4b940e38cb0fe641b28ae08dff26a17d213e6c8602442912ce59d7de756618adbd36feac62846ee1b8f0aa0bb2f0a765f5b74fe462b371954184eaa65bc
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,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "English"
1
4
  require_relative "macho/structure"
2
5
  require_relative "macho/view"
3
6
  require_relative "macho/headers"
@@ -12,7 +15,7 @@ require_relative "macho/tools"
12
15
  # The primary namespace for ruby-macho.
13
16
  module MachO
14
17
  # release version
15
- VERSION = "1.3.1".freeze
18
+ VERSION = "2.3.0"
16
19
 
17
20
  # Opens the given filename as a MachOFile or FatFile, depending on its magic.
18
21
  # @param filename [String] the file being opened
@@ -25,7 +28,7 @@ module MachO
25
28
  raise ArgumentError, "#{filename}: no such file" unless File.file?(filename)
26
29
  raise TruncatedFileError unless File.stat(filename).size >= 4
27
30
 
28
- magic = File.open(filename, "rb") { |f| f.read(4) }.unpack("N").first
31
+ magic = File.open(filename, "rb") { |f| f.read(4) }.unpack1("N")
29
32
 
30
33
  if Utils.fat_magic?(magic)
31
34
  file = FatFile.new(filename)
@@ -37,4 +40,22 @@ module MachO
37
40
 
38
41
  file
39
42
  end
43
+
44
+ # Signs the dylib using an ad-hoc identity.
45
+ # Necessary after making any changes to a dylib, since otherwise
46
+ # changing a signed file invalidates its signature.
47
+ # @param filename [String] the file being opened
48
+ # @return [void]
49
+ # @raise [ModificationError] if the operation fails
50
+ def self.codesign!(filename)
51
+ # codesign binary is not available on Linux
52
+ return if RUBY_PLATFORM !~ /darwin/
53
+ raise ArgumentError, "#{filename}: no such file" unless File.file?(filename)
54
+
55
+ system("codesign", "--sign", "-", "--force",
56
+ "--preserve-metadata=entitlements,requirements,flags,runtime",
57
+ filename)
58
+
59
+ raise ModificationError, "#{filename}: signing failed!" unless $CHILD_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
@@ -41,8 +43,8 @@ module MachO
41
43
  # Raised when a file's magic bytes are not valid Mach-O magic.
42
44
  class MagicError < NotAMachOError
43
45
  # @param num [Integer] the unknown number
44
- def initialize(num)
45
- super "Unrecognized Mach-O magic: 0x#{"%02x" % num}"
46
+ def initialize(magic)
47
+ super "Unrecognized Mach-O magic: 0x%02<magic>x" % { :magic => magic }
46
48
  end
47
49
  end
48
50
 
@@ -71,7 +73,7 @@ module MachO
71
73
  class CPUTypeError < MachOError
72
74
  # @param cputype [Integer] the unknown CPU type
73
75
  def initialize(cputype)
74
- super "Unrecognized CPU type: 0x#{"%08x" % cputype}"
76
+ super "Unrecognized CPU type: 0x%08<cputype>x" % { :cputype => cputype }
75
77
  end
76
78
  end
77
79
 
@@ -80,8 +82,8 @@ module MachO
80
82
  # @param cputype [Integer] the CPU type of the unknown pair
81
83
  # @param cpusubtype [Integer] the CPU sub-type of the unknown pair
82
84
  def initialize(cputype, cpusubtype)
83
- super "Unrecognized CPU sub-type: 0x#{"%08x" % cpusubtype}" \
84
- " (for CPU type: 0x#{"%08x" % cputype})"
85
+ super "Unrecognized CPU sub-type: 0x%08<cpusubtype>x" \
86
+ " (for CPU type: 0x%08<cputype>x" % { :cputype => cputype, :cpusubtype => cpusubtype }
85
87
  end
86
88
  end
87
89
 
@@ -89,7 +91,7 @@ module MachO
89
91
  class FiletypeError < MachOError
90
92
  # @param num [Integer] the unknown number
91
93
  def initialize(num)
92
- super "Unrecognized Mach-O filetype code: 0x#{"%02x" % num}"
94
+ super "Unrecognized Mach-O filetype code: 0x%02<num>x" % { :num => num }
93
95
  end
94
96
  end
95
97
 
@@ -97,7 +99,7 @@ module MachO
97
99
  class LoadCommandError < MachOError
98
100
  # @param num [Integer] the unknown number
99
101
  def initialize(num)
100
- super "Unrecognized Mach-O load command: 0x#{"%02x" % num}"
102
+ super "Unrecognized Mach-O load command: 0x%02<num>x" % { :num => num }
101
103
  end
102
104
  end
103
105
 
@@ -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
 
@@ -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
@@ -394,13 +400,13 @@ module MachO
394
400
  machos.each_with_index do |macho, index|
395
401
  begin
396
402
  yield macho
397
- rescue RecoverableModificationError => error
398
- error.macho_slice = index
403
+ rescue RecoverableModificationError => e
404
+ e.macho_slice = index
399
405
 
400
406
  # Strict mode: Immediately re-raise. Otherwise: Retain, check later.
401
- raise error if strict
407
+ raise e if strict
402
408
 
403
- errors << error
409
+ errors << e
404
410
  end
405
411
  end
406
412
 
@@ -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
@@ -536,7 +543,7 @@ module MachO
536
543
  # @note Always big endian.
537
544
  # @see MachOStructure::FORMAT
538
545
  # @api private
539
- FORMAT = "L>5".freeze
546
+ FORMAT = "L>5"
540
547
 
541
548
  # @see MachOStructure::SIZEOF
542
549
  # @api private
@@ -582,7 +589,7 @@ module MachO
582
589
  # @note Always big endian.
583
590
  # @see MachOStructure::FORMAT
584
591
  # @api private
585
- FORMAT = "L>2Q>2L>2".freeze
592
+ FORMAT = "L>2Q>2L>2"
586
593
 
587
594
  # @see MachOStructure::SIZEOF
588
595
  # @api private
@@ -632,7 +639,7 @@ module MachO
632
639
 
633
640
  # @see MachOStructure::FORMAT
634
641
  # @api private
635
- FORMAT = "L=7".freeze
642
+ FORMAT = "L=7"
636
643
 
637
644
  # @see MachOStructure::SIZEOF
638
645
  # @api private
@@ -755,7 +762,7 @@ module MachO
755
762
 
756
763
  # @see MachOStructure::FORMAT
757
764
  # @api private
758
- FORMAT = "L=8".freeze
765
+ FORMAT = "L=8"
759
766
 
760
767
  # @see MachOStructure::SIZEOF
761
768
  # @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
@@ -365,7 +371,7 @@ module MachO
365
371
 
366
372
  # @see MachOStructure::FORMAT
367
373
  # @api private
368
- FORMAT = "L=2a16".freeze
374
+ FORMAT = "L=2a16"
369
375
 
370
376
  # @see MachOStructure::SIZEOF
371
377
  # @api private
@@ -379,7 +385,7 @@ module MachO
379
385
 
380
386
  # @return [String] a string representation of the UUID
381
387
  def uuid_string
382
- hexes = uuid.map { |e| "%02x" % e }
388
+ hexes = uuid.map { |elem| "%02<elem>x" % { :elem => elem } }
383
389
  segs = [
384
390
  hexes[0..3].join, hexes[4..5].join, hexes[6..7].join,
385
391
  hexes[8..9].join, hexes[10..15].join
@@ -429,7 +435,7 @@ module MachO
429
435
 
430
436
  # @see MachOStructure::FORMAT
431
437
  # @api private
432
- FORMAT = "L=2Z16L=4l=2L=2".freeze
438
+ FORMAT = "L=2Z16L=4l=2L=2"
433
439
 
434
440
  # @see MachOStructure::SIZEOF
435
441
  # @api private
@@ -524,7 +530,7 @@ module MachO
524
530
  class SegmentCommand64 < SegmentCommand
525
531
  # @see MachOStructure::FORMAT
526
532
  # @api private
527
- FORMAT = "L=2Z16Q=4l=2L=2".freeze
533
+ FORMAT = "L=2Z16Q=4l=2L=2"
528
534
 
529
535
  # @see MachOStructure::SIZEOF
530
536
  # @api private
@@ -550,7 +556,7 @@ module MachO
550
556
 
551
557
  # @see MachOStructure::FORMAT
552
558
  # @api private
553
- FORMAT = "L=6".freeze
559
+ FORMAT = "L=6"
554
560
 
555
561
  # @see MachOStructure::SIZEOF
556
562
  # @api private
@@ -601,7 +607,7 @@ module MachO
601
607
 
602
608
  # @see MachOStructure::FORMAT
603
609
  # @api private
604
- FORMAT = "L=3".freeze
610
+ FORMAT = "L=3"
605
611
 
606
612
  # @see MachOStructure::SIZEOF
607
613
  # @api private
@@ -649,7 +655,7 @@ module MachO
649
655
 
650
656
  # @see MachOStructure::FORMAT
651
657
  # @api private
652
- FORMAT = "L=5".freeze
658
+ FORMAT = "L=5"
653
659
 
654
660
  # @see MachOStructure::SIZEOF
655
661
  # @api private
@@ -679,7 +685,7 @@ module MachO
679
685
  class ThreadCommand < LoadCommand
680
686
  # @see MachOStructure::FORMAT
681
687
  # @api private
682
- FORMAT = "L=2".freeze
688
+ FORMAT = "L=2"
683
689
 
684
690
  # @see MachOStructure::SIZEOF
685
691
  # @api private
@@ -717,7 +723,7 @@ module MachO
717
723
 
718
724
  # @see MachOStructure::FORMAT
719
725
  # @api private
720
- FORMAT = "L=10".freeze
726
+ FORMAT = "L=10"
721
727
 
722
728
  # @see MachOStructure::SIZEOF
723
729
  # @api private
@@ -758,7 +764,7 @@ module MachO
758
764
  class RoutinesCommand64 < RoutinesCommand
759
765
  # @see MachOStructure::FORMAT
760
766
  # @api private
761
- FORMAT = "L=2Q=8".freeze
767
+ FORMAT = "L=2Q=8"
762
768
 
763
769
  # @see MachOStructure::SIZEOF
764
770
  # @api private
@@ -773,7 +779,7 @@ module MachO
773
779
 
774
780
  # @see MachOStructure::FORMAT
775
781
  # @api private
776
- FORMAT = "L=3".freeze
782
+ FORMAT = "L=3"
777
783
 
778
784
  # @see MachOStructure::SIZEOF
779
785
  # @api private
@@ -801,7 +807,7 @@ module MachO
801
807
 
802
808
  # @see MachOStructure::FORMAT
803
809
  # @api private
804
- FORMAT = "L=3".freeze
810
+ FORMAT = "L=3"
805
811
 
806
812
  # @see MachOStructure::SIZEOF
807
813
  # @api private
@@ -829,7 +835,7 @@ module MachO
829
835
 
830
836
  # @see MachOStructure::FORMAT
831
837
  # @api private
832
- FORMAT = "L=3".freeze
838
+ FORMAT = "L=3"
833
839
 
834
840
  # @see MachOStructure::SIZEOF
835
841
  # @api private
@@ -857,7 +863,7 @@ module MachO
857
863
 
858
864
  # @see MachOStructure::FORMAT
859
865
  # @api private
860
- FORMAT = "L=3".freeze
866
+ FORMAT = "L=3"
861
867
 
862
868
  # @see MachOStructure::SIZEOF
863
869
  # @api private
@@ -894,7 +900,7 @@ module MachO
894
900
 
895
901
  # @see MachOStructure::FORMAT
896
902
  # @api private
897
- FORMAT = "L=6".freeze
903
+ FORMAT = "L=6"
898
904
 
899
905
  # @see MachOStructure::SIZEOF
900
906
  # @api private
@@ -979,7 +985,7 @@ module MachO
979
985
 
980
986
  # @see MachOStructure::FORMAT
981
987
  # @api private
982
- FORMAT = "L=20".freeze
988
+ FORMAT = "L=20"
983
989
 
984
990
  # @see MachOStructure::SIZEOF
985
991
  # @api private
@@ -1052,7 +1058,7 @@ module MachO
1052
1058
 
1053
1059
  # @see MachOStructure::FORMAT
1054
1060
  # @api private
1055
- FORMAT = "L=4".freeze
1061
+ FORMAT = "L=4"
1056
1062
 
1057
1063
  # @see MachOStructure::SIZEOF
1058
1064
  # @api private
@@ -1127,7 +1133,7 @@ module MachO
1127
1133
 
1128
1134
  # @see MachOStructure::FORMAT
1129
1135
  # @api private
1130
- FORMAT = "L=3".freeze
1136
+ FORMAT = "L=3"
1131
1137
 
1132
1138
  # @see MachOStructure::SIZEOF
1133
1139
  # @api private
@@ -1156,7 +1162,7 @@ module MachO
1156
1162
 
1157
1163
  # @see MachOStructure::FORMAT
1158
1164
  # @api private
1159
- FORMAT = "L=3".freeze
1165
+ FORMAT = "L=3"
1160
1166
 
1161
1167
  # @see MachOStructure::SIZEOF
1162
1168
  # @api private
@@ -1191,7 +1197,8 @@ module MachO
1191
1197
  # A load command representing the offsets and sizes of a blob of data in
1192
1198
  # the __LINKEDIT segment. Corresponds to LC_CODE_SIGNATURE,
1193
1199
  # LC_SEGMENT_SPLIT_INFO, LC_FUNCTION_STARTS, LC_DATA_IN_CODE,
1194
- # LC_DYLIB_CODE_SIGN_DRS, and LC_LINKER_OPTIMIZATION_HINT.
1200
+ # LC_DYLIB_CODE_SIGN_DRS, LC_LINKER_OPTIMIZATION_HINT, LC_DYLD_EXPORTS_TRIE,
1201
+ # or LC_DYLD_CHAINED_FIXUPS.
1195
1202
  class LinkeditDataCommand < LoadCommand
1196
1203
  # @return [Integer] offset to the data in the __LINKEDIT segment
1197
1204
  attr_reader :dataoff
@@ -1201,7 +1208,7 @@ module MachO
1201
1208
 
1202
1209
  # @see MachOStructure::FORMAT
1203
1210
  # @api private
1204
- FORMAT = "L=4".freeze
1211
+ FORMAT = "L=4"
1205
1212
 
1206
1213
  # @see MachOStructure::SIZEOF
1207
1214
  # @api private
@@ -1237,7 +1244,7 @@ module MachO
1237
1244
 
1238
1245
  # @see MachOStructure::FORMAT
1239
1246
  # @api private
1240
- FORMAT = "L=5".freeze
1247
+ FORMAT = "L=5"
1241
1248
 
1242
1249
  # @see MachOStructure::SIZEOF
1243
1250
  # @api private
@@ -1269,7 +1276,7 @@ module MachO
1269
1276
 
1270
1277
  # @see MachOStructure::FORMAT
1271
1278
  # @api private
1272
- FORMAT = "L=6".freeze
1279
+ FORMAT = "L=6"
1273
1280
 
1274
1281
  # @see MachOStructure::SIZEOF
1275
1282
  # @api private
@@ -1301,7 +1308,7 @@ module MachO
1301
1308
 
1302
1309
  # @see MachOStructure::FORMAT
1303
1310
  # @api private
1304
- FORMAT = "L=4".freeze
1311
+ FORMAT = "L=4"
1305
1312
 
1306
1313
  # @see MachOStructure::SIZEOF
1307
1314
  # @api private
@@ -1317,7 +1324,7 @@ module MachO
1317
1324
  # A string representation of the binary's minimum OS version.
1318
1325
  # @return [String] a string representing the minimum OS version.
1319
1326
  def version_string
1320
- binary = "%032b" % version
1327
+ binary = "%032<version>b" % { :version => version }
1321
1328
  segs = [
1322
1329
  binary[0..15], binary[16..23], binary[24..31]
1323
1330
  ].map { |s| s.to_i(2) }
@@ -1328,7 +1335,7 @@ module MachO
1328
1335
  # A string representation of the binary's SDK version.
1329
1336
  # @return [String] a string representing the SDK version.
1330
1337
  def sdk_string
1331
- binary = "%032b" % sdk
1338
+ binary = "%032<sdk>b" % { :sdk => sdk }
1332
1339
  segs = [
1333
1340
  binary[0..15], binary[16..23], binary[24..31]
1334
1341
  ].map { |s| s.to_i(2) }
@@ -1365,7 +1372,7 @@ module MachO
1365
1372
 
1366
1373
  # @see MachOStructure::FORMAT
1367
1374
  # @api private
1368
- FORMAT = "L=6".freeze
1375
+ FORMAT = "L=6"
1369
1376
 
1370
1377
  # @see MachOStructure::SIZEOF
1371
1378
  # @api private
@@ -1383,7 +1390,7 @@ module MachO
1383
1390
  # A string representation of the binary's minimum OS version.
1384
1391
  # @return [String] a string representing the minimum OS version.
1385
1392
  def minos_string
1386
- binary = "%032b" % minos
1393
+ binary = "%032<minos>b" % { :minos => minos }
1387
1394
  segs = [
1388
1395
  binary[0..15], binary[16..23], binary[24..31]
1389
1396
  ].map { |s| s.to_i(2) }
@@ -1394,7 +1401,7 @@ module MachO
1394
1401
  # A string representation of the binary's SDK version.
1395
1402
  # @return [String] a string representing the SDK version.
1396
1403
  def sdk_string
1397
- binary = "%032b" % sdk
1404
+ binary = "%032<sdk>b" % { :sdk => sdk }
1398
1405
  segs = [
1399
1406
  binary[0..15], binary[16..23], binary[24..31]
1400
1407
  ].map { |s| s.to_i(2) }
@@ -1494,7 +1501,7 @@ module MachO
1494
1501
 
1495
1502
  # @see MachOStructure::FORMAT
1496
1503
  # @api private
1497
- FORMAT = "L=12".freeze
1504
+ FORMAT = "L=12"
1498
1505
 
1499
1506
  # @see MachOStructure::SIZEOF
1500
1507
  # @api private
@@ -1542,7 +1549,7 @@ module MachO
1542
1549
 
1543
1550
  # @see MachOStructure::FORMAT
1544
1551
  # @api private
1545
- FORMAT = "L=3".freeze
1552
+ FORMAT = "L=3"
1546
1553
 
1547
1554
  # @see MachOStructure::SIZEOF
1548
1555
  # @api private
@@ -1572,7 +1579,7 @@ module MachO
1572
1579
 
1573
1580
  # @see MachOStructure::FORMAT
1574
1581
  # @api private
1575
- FORMAT = "L=2Q=2".freeze
1582
+ FORMAT = "L=2Q=2"
1576
1583
 
1577
1584
  # @see MachOStructure::SIZEOF
1578
1585
  # @api private
@@ -1602,7 +1609,7 @@ module MachO
1602
1609
 
1603
1610
  # @see MachOStructure::FORMAT
1604
1611
  # @api private
1605
- FORMAT = "L=2Q=1".freeze
1612
+ FORMAT = "L=2Q=1"
1606
1613
 
1607
1614
  # @see MachOStructure::SIZEOF
1608
1615
  # @api private
@@ -1617,7 +1624,7 @@ module MachO
1617
1624
  # A string representation of the sources used to build the binary.
1618
1625
  # @return [String] a string representation of the version
1619
1626
  def version_string
1620
- binary = "%064b" % version
1627
+ binary = "%064<version>b" % { :version => version }
1621
1628
  segs = [
1622
1629
  binary[0..23], binary[24..33], binary[34..43], binary[44..53],
1623
1630
  binary[54..63]
@@ -1646,7 +1653,7 @@ module MachO
1646
1653
 
1647
1654
  # @see MachOStructure::FORMAT
1648
1655
  # @api private
1649
- FORMAT = "L=4".freeze
1656
+ FORMAT = "L=4"
1650
1657
 
1651
1658
  # @see MachOStructure::SIZEOF
1652
1659
  # @api private
@@ -1674,7 +1681,7 @@ module MachO
1674
1681
  class IdentCommand < LoadCommand
1675
1682
  # @see MachOStructure::FORMAT
1676
1683
  # @api private
1677
- FORMAT = "L=2".freeze
1684
+ FORMAT = "L=2"
1678
1685
 
1679
1686
  # @see MachOStructure::SIZEOF
1680
1687
  # @api private
@@ -1692,7 +1699,7 @@ module MachO
1692
1699
 
1693
1700
  # @see MachOStructure::FORMAT
1694
1701
  # @api private
1695
- FORMAT = "L=4".freeze
1702
+ FORMAT = "L=4"
1696
1703
 
1697
1704
  # @see MachOStructure::SIZEOF
1698
1705
  # @api private
@@ -1727,7 +1734,7 @@ module MachO
1727
1734
 
1728
1735
  # @see MachOStructure::FORMAT
1729
1736
  # @api private
1730
- FORMAT = "L=5".freeze
1737
+ FORMAT = "L=5"
1731
1738
 
1732
1739
  # @see MachOStructure::SIZEOF
1733
1740
  # @api private
@@ -1764,7 +1771,7 @@ module MachO
1764
1771
 
1765
1772
  # @see MachOStructure::FORMAT
1766
1773
  # @api private
1767
- FORMAT = "L=2Z16Q=2".freeze
1774
+ FORMAT = "L=2Z16Q=2"
1768
1775
 
1769
1776
  # @see MachOStructure::SIZEOF
1770
1777
  # @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
@@ -180,7 +182,7 @@ module MachO
180
182
  attr_reader :reserved3
181
183
 
182
184
  # @see MachOStructure::FORMAT
183
- FORMAT = "Z16Z16Q=2L=8".freeze
185
+ FORMAT = "Z16Z16Q=2L=8"
184
186
 
185
187
  # @see MachOStructure::SIZEOF
186
188
  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.3.1
4
+ version: 2.3.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: 2018-09-30 00:00:00.000000000 Z
11
+ date: 2020-10-12 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.0'
45
+ version: '2.3'
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.1.2
54
53
  signing_key:
55
54
  specification_version: 4
56
55
  summary: ruby-macho - Mach-O file analyzer.