ruby-macho 2.5.1 → 4.0.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: 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