ruby-macho 2.5.1 → 4.0.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: cfe1d04c6c841958f4ac428e7207d89e02371fa9ca9ac3cb48e3e2a34a65dc24
4
- data.tar.gz: 21dc14db70485e37ff5e430d5f8a99af6cb1c0ac14f02ae1943121fa2c482629
3
+ metadata.gz: 669b2133517dc564c92df8ebdc0b2f6fdc91869939cd6a642cfbc2b1c6e20a09
4
+ data.tar.gz: 0077afabc9c4be7d891e5c7e5a8b4ec173223b00f73d550fc96fb7cf4f103c1d
5
5
  SHA512:
6
- metadata.gz: d668c48f840f4423ae4bbc89d30c53e26a709491d2f8ca66bc98c637563e596d107638779bd517bf1fe89458d5f05beadf76ea60d80a44578aa0b409be61fc43
7
- data.tar.gz: 84009cdfbe5c61bc84ffd8838c7fe033171ebc05af18b7748c53ced99a0e92fd7f45f1a7c0f50001c05eb253e00564b3f8dbe7735fe04df087b3fef72d2be7e6
6
+ metadata.gz: 4e8bd64595d93d50c0046c5bd292010a51a9d4154cbcda3a0a0a962882475e61d631d55da106e4655ba31a32011f583222d5b147129230e08ad11d12a34ee8e6
7
+ data.tar.gz: ccc53798eb56f378f5688ad7141636ecca274e552e8ad62f4fab5600b6a6fddcac0e5ffc75ed4924e62538a6b3ac7b291667870b6cd02bf0edf6286da1a4e62a
data/README.md CHANGED
@@ -56,11 +56,24 @@ puts lc_vers.version_string # => "10.10.0"
56
56
 
57
57
  * Unit and performance testing.
58
58
 
59
- Attribution:
59
+ ### Contributing, setting up `overcommit` and the linters
60
+
61
+ In order to keep the repo, docs and data tidy, we use a tool called [`overcommit`](https://github.com/sds/overcommit)
62
+ to connect up the git hooks to a set of quality checks. The fastest way to get setup is to run the following to make
63
+ sure you have all the tools:
64
+
65
+ ```shell
66
+ gem install overcommit bundler
67
+ bundle install
68
+ overcommit --install
69
+ ```
70
+
71
+ ### Attribution
60
72
 
61
73
  * Constants were taken from Apple, Inc's
62
- [`loader.h` in `cctools/include/mach-o`](https://www.opensource.apple.com/source/cctools/cctools-870/include/mach-o/loader.h).
74
+ [`loader.h` in `cctools/include/mach-o`](https://opensource.apple.com/source/cctools/cctools-973.0.1/include/mach-o/loader.h.auto.html).
63
75
  (Apple Public Source License 2.0).
76
+ * Binary files used for testing were taken from The LLVM Project. ([Apache License v2.0 with LLVM Exceptions](test/bin/llvm/LICENSE.txt)).
64
77
 
65
78
  ### License
66
79
 
@@ -56,6 +56,28 @@ module MachO
56
56
  end
57
57
  end
58
58
 
59
+ # Raised when a a fat Mach-O file has zero architectures
60
+ class ZeroArchitectureError < NotAMachOError
61
+ def initialize
62
+ super "Fat file has zero internal architectures"
63
+ end
64
+ end
65
+
66
+ # Raised when there is a mismatch between the fat arch
67
+ # and internal slice cputype or cpusubtype.
68
+ class CPUTypeMismatchError < NotAMachOError
69
+ def initialize(fat_cputype, fat_cpusubtype, macho_cputype, macho_cpusubtype)
70
+ # @param cputype_fat [Integer] the CPU type in the fat header
71
+ # @param cpusubtype_fat [Integer] the CPU subtype in the fat header
72
+ # @param cputype_macho [Integer] the CPU type in the macho header
73
+ # @param cpusubtype_macho [Integer] the CPU subtype in the macho header
74
+ super ("Mismatch between cputypes >> 0x%08<fat_cputype>x and 0x%08<macho_cputype>x\n" \
75
+ "and/or cpusubtypes >> 0x%08<fat_cpusubtype>x and 0x%08<macho_cpusubtype>x" %
76
+ { :fat_cputype => fat_cputype, :macho_cputype => macho_cputype,
77
+ :fat_cpusubtype => fat_cpusubtype, :macho_cpusubtype => macho_cpusubtype })
78
+ end
79
+ end
80
+
59
81
  # Raised when a fat binary is loaded with MachOFile.
60
82
  class FatBinaryError < MachOError
61
83
  def initialize
@@ -83,8 +105,8 @@ module MachO
83
105
  # @param cputype [Integer] the CPU type of the unknown pair
84
106
  # @param cpusubtype [Integer] the CPU sub-type of the unknown pair
85
107
  def initialize(cputype, cpusubtype)
86
- super "Unrecognized CPU sub-type: 0x%08<cpusubtype>x" \
87
- " (for CPU type: 0x%08<cputype>x" % { :cputype => cputype, :cpusubtype => cpusubtype }
108
+ super "Unrecognized CPU sub-type: 0x%08<cpusubtype>x " \
109
+ "(for CPU type: 0x%08<cputype>x" % { :cputype => cputype, :cpusubtype => cpusubtype }
88
110
  end
89
111
  end
90
112
 
@@ -119,8 +141,8 @@ module MachO
119
141
  # @param expected_arity [Integer] the number of arguments expected
120
142
  # @param actual_arity [Integer] the number of arguments received
121
143
  def initialize(cmd_sym, expected_arity, actual_arity)
122
- super "Expected #{expected_arity} arguments for #{cmd_sym} creation," \
123
- " got #{actual_arity}"
144
+ super "Expected #{expected_arity} arguments for #{cmd_sym} creation, " \
145
+ "got #{actual_arity}"
124
146
  end
125
147
  end
126
148
 
@@ -136,8 +158,8 @@ module MachO
136
158
  class LCStrMalformedError < MachOError
137
159
  # @param lc [MachO::LoadCommand] the load command containing the string
138
160
  def initialize(lc)
139
- super "Load command #{lc.type} at offset #{lc.view.offset} contains a" \
140
- " malformed string"
161
+ super "Load command #{lc.type} at offset #{lc.view.offset} contains a " \
162
+ "malformed string"
141
163
  end
142
164
  end
143
165
 
@@ -154,8 +176,8 @@ module MachO
154
176
  # @param filename [String] the filename
155
177
  def initialize(filename)
156
178
  super "Updated load commands do not fit in the header of " \
157
- "#{filename}. #{filename} needs to be relinked, possibly with " \
158
- "-headerpad or -headerpad_max_install_names"
179
+ "#{filename}. #{filename} needs to be relinked, possibly with " \
180
+ "-headerpad or -headerpad_max_install_names"
159
181
  end
160
182
  end
161
183
 
@@ -203,8 +225,18 @@ module MachO
203
225
  class FatArchOffsetOverflowError < MachOError
204
226
  # @param offset [Integer] the offending offset
205
227
  def initialize(offset)
206
- super "Offset #{offset} exceeds the 32-bit width of a fat_arch offset." \
207
- " Consider merging with `fat64: true`"
228
+ super "Offset #{offset} exceeds the 32-bit width of a fat_arch offset. " \
229
+ "Consider merging with `fat64: true`"
208
230
  end
209
231
  end
232
+
233
+ # Raised when attempting to parse a compressed Mach-O without explicitly
234
+ # requesting decompression.
235
+ class CompressedMachOError < MachOError
236
+ end
237
+
238
+ # Raised when attempting to decompress a compressed Mach-O without adequate
239
+ # dependencies, or on other decompression errors.
240
+ class DecompressionError < MachOError
241
+ end
210
242
  end
@@ -55,7 +55,7 @@ module MachO
55
55
  machos.each do |macho|
56
56
  macho_offset = Utils.round(offset, 2**macho.segment_alignment)
57
57
 
58
- raise FatArchOffsetOverflowError, macho_offset if !fat64 && macho_offset > (2**32 - 1)
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
 
@@ -96,7 +96,7 @@ module MachO
96
96
 
97
97
  @filename = filename
98
98
  @options = opts
99
- @raw_data = File.open(@filename, "rb", &:read)
99
+ @raw_data = File.binread(@filename)
100
100
  populate_fields
101
101
  end
102
102
 
@@ -238,6 +238,8 @@ module MachO
238
238
  # @param options [Hash]
239
239
  # @option options [Boolean] :strict (true) if true, fail if one slice fails.
240
240
  # if false, fail only if all slices fail.
241
+ # @option options [Boolean] :uniq (false) for each slice: if true, change
242
+ # each rpath simultaneously.
241
243
  # @return [void]
242
244
  # @see MachOFile#change_rpath
243
245
  def change_rpath(old_path, new_path, options = {})
@@ -268,6 +270,9 @@ module MachO
268
270
  # @param options [Hash]
269
271
  # @option options [Boolean] :strict (true) if true, fail if one slice fails.
270
272
  # if false, fail only if all slices fail.
273
+ # @option options [Boolean] :uniq (false) for each slice: if true, delete
274
+ # only the first runtime path that matches. if false, delete all duplicate
275
+ # paths that match.
271
276
  # @return void
272
277
  # @see MachOFile#delete_rpath
273
278
  def delete_rpath(path, options = {})
@@ -291,7 +296,7 @@ module MachO
291
296
  # @param filename [String] the file to write to
292
297
  # @return [void]
293
298
  def write(filename)
294
- File.open(filename, "wb") { |f| f.write(@raw_data) }
299
+ File.binwrite(filename, @raw_data)
295
300
  end
296
301
 
297
302
  # Write all (fat) data to the file used to initialize the instance.
@@ -301,7 +306,7 @@ module MachO
301
306
  def write!
302
307
  raise MachOError, "no initial file to write to" if filename.nil?
303
308
 
304
- File.open(@filename, "wb") { |f| f.write(@raw_data) }
309
+ File.binwrite(@filename, @raw_data)
305
310
  end
306
311
 
307
312
  # @return [Hash] a hash representation of this {FatFile}
@@ -322,6 +327,8 @@ module MachO
322
327
  # @raise [MagicError] if the magic is not valid Mach-O magic
323
328
  # @raise [MachOBinaryError] if the magic is for a non-fat Mach-O file
324
329
  # @raise [JavaClassFileError] if the file is a Java classfile
330
+ # @raise [ZeroArchitectureError] if the file has no internal slices
331
+ # (i.e., nfat_arch == 0) and the permissive option is not set
325
332
  # @api private
326
333
  def populate_fat_header
327
334
  # the smallest fat Mach-O header is 8 bytes
@@ -341,6 +348,9 @@ module MachO
341
348
  # formats.
342
349
  raise JavaClassFileError if fh.nfat_arch > 30
343
350
 
351
+ # Rationale: return an error if the file has no internal slices.
352
+ raise ZeroArchitectureError if fh.nfat_arch.zero?
353
+
344
354
  fh
345
355
  end
346
356
 
@@ -369,6 +379,13 @@ module MachO
369
379
 
370
380
  fat_archs.each do |arch|
371
381
  machos << MachOFile.new_from_bin(@raw_data[arch.offset, arch.size], **options)
382
+
383
+ # Make sure that each fat_arch and internal slice.
384
+ # contain matching cputypes and cpusubtypes
385
+ next if machos.last.header.cputype == arch.cputype &&
386
+ machos.last.header.cpusubtype == arch.cpusubtype
387
+
388
+ raise CPUTypeMismatchError.new(arch.cputype, arch.cpusubtype, machos.last.header.cputype, machos.last.header.cpusubtype)
372
389
  end
373
390
 
374
391
  machos
data/lib/macho/headers.rb CHANGED
@@ -37,6 +37,18 @@ module MachO
37
37
  # @api private
38
38
  MH_CIGAM_64 = 0xcffaedfe
39
39
 
40
+ # compressed mach-o magic
41
+ # @api private
42
+ COMPRESSED_MAGIC = 0x636f6d70 # "comp"
43
+
44
+ # a compressed mach-o slice, using LZSS for compression
45
+ # @api private
46
+ COMP_TYPE_LZSS = 0x6c7a7373 # "lzss"
47
+
48
+ # a compressed mach-o slice, using LZVN ("FastLib") for compression
49
+ # @api private
50
+ COMP_TYPE_FASTLIB = 0x6c7a766e # "lzvn"
51
+
40
52
  # association of magic numbers to string representations
41
53
  # @api private
42
54
  MH_MAGICS = {
@@ -433,6 +445,11 @@ module MachO
433
445
  # @api private
434
446
  MH_KEXT_BUNDLE = 0xb
435
447
 
448
+ # a set of Mach-Os, running in the same userspace, sharing a linkedit. The kext collection files are an example
449
+ # of this object type
450
+ # @api private
451
+ MH_FILESET = 0xc
452
+
436
453
  # association of filetypes to Symbol representations
437
454
  # @api private
438
455
  MH_FILETYPES = {
@@ -447,6 +464,7 @@ module MachO
447
464
  MH_DYLIB_STUB => :dylib_stub,
448
465
  MH_DSYM => :dsym,
449
466
  MH_KEXT_BUNDLE => :kext_bundle,
467
+ MH_FILESET => :fileset,
450
468
  }.freeze
451
469
 
452
470
  # association of mach header flag symbols to values
@@ -478,36 +496,23 @@ module MachO
478
496
  :MH_HAS_TLV_DESCRIPTORS => 0x800000,
479
497
  :MH_NO_HEAP_EXECUTION => 0x1000000,
480
498
  :MH_APP_EXTENSION_SAFE => 0x02000000,
499
+ :MH_NLIST_OUTOFSYNC_WITH_DYLDINFO => 0x04000000,
500
+ :MH_SIM_SUPPORT => 0x08000000,
501
+ :MH_DYLIB_IN_CACHE => 0x80000000,
481
502
  }.freeze
482
503
 
483
504
  # Fat binary header structure
484
505
  # @see MachO::FatArch
485
506
  class FatHeader < MachOStructure
486
507
  # @return [Integer] the magic number of the header (and file)
487
- attr_reader :magic
508
+ field :magic, :uint32, :endian => :big
488
509
 
489
510
  # @return [Integer] the number of fat architecture structures following the header
490
- attr_reader :nfat_arch
491
-
492
- # always big-endian
493
- # @see MachOStructure::FORMAT
494
- # @api private
495
- FORMAT = "N2"
496
-
497
- # @see MachOStructure::SIZEOF
498
- # @api private
499
- SIZEOF = 8
500
-
501
- # @api private
502
- def initialize(magic, nfat_arch)
503
- super()
504
- @magic = magic
505
- @nfat_arch = nfat_arch
506
- end
511
+ field :nfat_arch, :uint32, :endian => :big
507
512
 
508
513
  # @return [String] the serialized fields of the fat header
509
514
  def serialize
510
- [magic, nfat_arch].pack(FORMAT)
515
+ [magic, nfat_arch].pack(self.class.format)
511
516
  end
512
517
 
513
518
  # @return [Hash] a hash representation of this {FatHeader}
@@ -527,42 +532,23 @@ module MachO
527
532
  # @see MachO::Headers::FatHeader
528
533
  class FatArch < MachOStructure
529
534
  # @return [Integer] the CPU type of the Mach-O
530
- attr_reader :cputype
535
+ field :cputype, :uint32, :endian => :big
531
536
 
532
537
  # @return [Integer] the CPU subtype of the Mach-O
533
- attr_reader :cpusubtype
538
+ field :cpusubtype, :uint32, :endian => :big, :mask => CPU_SUBTYPE_MASK
534
539
 
535
540
  # @return [Integer] the file offset to the beginning of the Mach-O data
536
- attr_reader :offset
541
+ field :offset, :uint32, :endian => :big
537
542
 
538
543
  # @return [Integer] the size, in bytes, of the Mach-O data
539
- attr_reader :size
544
+ field :size, :uint32, :endian => :big
540
545
 
541
546
  # @return [Integer] the alignment, as a power of 2
542
- attr_reader :align
543
-
544
- # @note Always big endian.
545
- # @see MachOStructure::FORMAT
546
- # @api private
547
- FORMAT = "L>5"
548
-
549
- # @see MachOStructure::SIZEOF
550
- # @api private
551
- SIZEOF = 20
552
-
553
- # @api private
554
- def initialize(cputype, cpusubtype, offset, size, align)
555
- super()
556
- @cputype = cputype
557
- @cpusubtype = cpusubtype & ~CPU_SUBTYPE_MASK
558
- @offset = offset
559
- @size = size
560
- @align = align
561
- end
547
+ field :align, :uint32, :endian => :big
562
548
 
563
549
  # @return [String] the serialized fields of the fat arch
564
550
  def serialize
565
- [cputype, cpusubtype, offset, size, align].pack(FORMAT)
551
+ [cputype, cpusubtype, offset, size, align].pack(self.class.format)
566
552
  end
567
553
 
568
554
  # @return [Hash] a hash representation of this {FatArch}
@@ -585,27 +571,18 @@ module MachO
585
571
  # Mach-Os that it points to necessarily *are* 64-bit.
586
572
  # @see MachO::Headers::FatHeader
587
573
  class FatArch64 < FatArch
588
- # @return [void]
589
- attr_reader :reserved
590
-
591
- # @note Always big endian.
592
- # @see MachOStructure::FORMAT
593
- # @api private
594
- FORMAT = "L>2Q>2L>2"
574
+ # @return [Integer] the file offset to the beginning of the Mach-O data
575
+ field :offset, :uint64, :endian => :big
595
576
 
596
- # @see MachOStructure::SIZEOF
597
- # @api private
598
- SIZEOF = 32
577
+ # @return [Integer] the size, in bytes, of the Mach-O data
578
+ field :size, :uint64, :endian => :big
599
579
 
600
- # @api private
601
- def initialize(cputype, cpusubtype, offset, size, align, reserved = 0)
602
- super(cputype, cpusubtype, offset, size, align)
603
- @reserved = reserved
604
- end
580
+ # @return [void]
581
+ field :reserved, :uint32, :endian => :big, :default => 0
605
582
 
606
583
  # @return [String] the serialized fields of the fat arch
607
584
  def serialize
608
- [cputype, cpusubtype, offset, size, align, reserved].pack(FORMAT)
585
+ [cputype, cpusubtype, offset, size, align, reserved].pack(self.class.format)
609
586
  end
610
587
 
611
588
  # @return [Hash] a hash representation of this {FatArch64}
@@ -619,48 +596,25 @@ module MachO
619
596
  # 32-bit Mach-O file header structure
620
597
  class MachHeader < MachOStructure
621
598
  # @return [Integer] the magic number
622
- attr_reader :magic
599
+ field :magic, :uint32
623
600
 
624
601
  # @return [Integer] the CPU type of the Mach-O
625
- attr_reader :cputype
602
+ field :cputype, :uint32
626
603
 
627
604
  # @return [Integer] the CPU subtype of the Mach-O
628
- attr_reader :cpusubtype
605
+ field :cpusubtype, :uint32, :mask => CPU_SUBTYPE_MASK
629
606
 
630
607
  # @return [Integer] the file type of the Mach-O
631
- attr_reader :filetype
608
+ field :filetype, :uint32
632
609
 
633
610
  # @return [Integer] the number of load commands in the Mach-O
634
- attr_reader :ncmds
611
+ field :ncmds, :uint32
635
612
 
636
613
  # @return [Integer] the size of all load commands, in bytes, in the Mach-O
637
- attr_reader :sizeofcmds
614
+ field :sizeofcmds, :uint32
638
615
 
639
616
  # @return [Integer] the header flags associated with the Mach-O
640
- attr_reader :flags
641
-
642
- # @see MachOStructure::FORMAT
643
- # @api private
644
- FORMAT = "L=7"
645
-
646
- # @see MachOStructure::SIZEOF
647
- # @api private
648
- SIZEOF = 28
649
-
650
- # @api private
651
- def initialize(magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds,
652
- flags)
653
- super()
654
- @magic = magic
655
- @cputype = cputype
656
- # For now we're not interested in additional capability bits also to be
657
- # found in the `cpusubtype` field. We only care about the CPU sub-type.
658
- @cpusubtype = cpusubtype & ~CPU_SUBTYPE_MASK
659
- @filetype = filetype
660
- @ncmds = ncmds
661
- @sizeofcmds = sizeofcmds
662
- @flags = flags
663
- end
617
+ field :flags, :uint32
664
618
 
665
619
  # @example
666
620
  # puts "this mach-o has position-independent execution" if header.flag?(:MH_PIE)
@@ -724,6 +678,11 @@ module MachO
724
678
  filetype == Headers::MH_KEXT_BUNDLE
725
679
  end
726
680
 
681
+ # @return [Boolean] whether or not the file is of type `MH_FILESET`
682
+ def fileset?
683
+ filetype == Headers::MH_FILESET
684
+ end
685
+
727
686
  # @return [Boolean] true if the Mach-O has 32-bit magic, false otherwise
728
687
  def magic32?
729
688
  Utils.magic32?(magic)
@@ -761,27 +720,72 @@ module MachO
761
720
  # 64-bit Mach-O file header structure
762
721
  class MachHeader64 < MachHeader
763
722
  # @return [void]
764
- attr_reader :reserved
723
+ field :reserved, :uint32
724
+
725
+ # @return [Hash] a hash representation of this {MachHeader64}
726
+ def to_h
727
+ {
728
+ "reserved" => reserved,
729
+ }.merge super
730
+ end
731
+ end
732
+
733
+ # Prelinked kernel/"kernelcache" header structure
734
+ class PrelinkedKernelHeader < MachOStructure
735
+ # @return [Integer] the magic number for a compressed header ({COMPRESSED_MAGIC})
736
+ field :signature, :uint32, :endian => :big
737
+
738
+ # @return [Integer] the type of compression used
739
+ field :compress_type, :uint32, :endian => :big
740
+
741
+ # @return [Integer] a checksum for the uncompressed data
742
+ field :adler32, :uint32, :endian => :big
743
+
744
+ # @return [Integer] the size of the uncompressed data, in bytes
745
+ field :uncompressed_size, :uint32, :endian => :big
746
+
747
+ # @return [Integer] the size of the compressed data, in bytes
748
+ field :compressed_size, :uint32, :endian => :big
749
+
750
+ # @return [Integer] the version of the prelink format
751
+ field :prelink_version, :uint32, :endian => :big
752
+
753
+ # @return [void]
754
+ field :reserved, :string, :size => 40, :unpack => "L>10"
755
+
756
+ # @return [void]
757
+ field :platform_name, :string, :size => 64
765
758
 
766
- # @see MachOStructure::FORMAT
767
- # @api private
768
- FORMAT = "L=8"
759
+ # @return [void]
760
+ field :root_path, :string, :size => 256
769
761
 
770
- # @see MachOStructure::SIZEOF
771
- # @api private
772
- SIZEOF = 32
762
+ # @return [Boolean] whether this prelinked kernel supports KASLR
763
+ def kaslr?
764
+ prelink_version >= 1
765
+ end
773
766
 
774
- # @api private
775
- def initialize(magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds,
776
- flags, reserved)
777
- super(magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags)
778
- @reserved = reserved
767
+ # @return [Boolean] whether this prelinked kernel is compressed with LZSS
768
+ def lzss?
769
+ compress_type == COMP_TYPE_LZSS
779
770
  end
780
771
 
781
- # @return [Hash] a hash representation of this {MachHeader64}
772
+ # @return [Boolean] whether this prelinked kernel is compressed with LZVN
773
+ def lzvn?
774
+ compress_type == COMP_TYPE_FASTLIB
775
+ end
776
+
777
+ # @return [Hash] a hash representation of this {PrelinkedKernelHeader}
782
778
  def to_h
783
779
  {
780
+ "signature" => signature,
781
+ "compress_type" => compress_type,
782
+ "adler32" => adler32,
783
+ "uncompressed_size" => uncompressed_size,
784
+ "compressed_size" => compressed_size,
785
+ "prelink_version" => prelink_version,
784
786
  "reserved" => reserved,
787
+ "platform_name" => platform_name,
788
+ "root_path" => root_path,
785
789
  }.merge super
786
790
  end
787
791
  end