ruby-macho 1.3.1 → 2.3.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: 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.