ruby-macho 3.0.0 → 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: ba2fc7b81345cb788c6e1de15bace8d35f7ac1f9a17b6b6a12289a4e51b978de
4
- data.tar.gz: 395dd63235823f33f11463d395b790b1cee2003ab430bb54326c03ca3de402b8
3
+ metadata.gz: 669b2133517dc564c92df8ebdc0b2f6fdc91869939cd6a642cfbc2b1c6e20a09
4
+ data.tar.gz: 0077afabc9c4be7d891e5c7e5a8b4ec173223b00f73d550fc96fb7cf4f103c1d
5
5
  SHA512:
6
- metadata.gz: c048978f3e0b1becaa3b050db8730f2f914cc8a8d662e1e7d24ca0a24c03c043747485e7e6033bad5789069795d8c04daad053d817f26c54dd809082975a01fe
7
- data.tar.gz: 53a576e666842a464af2f961825becfa51da0b26f3dd9ec656ce9859a6c0e9c017fc6622ec330bb0fc04e63089053a6ecd3740f505eb0e5aefecfb5728672d72
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
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
 
@@ -203,8 +225,8 @@ 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
210
232
 
@@ -327,6 +327,8 @@ module MachO
327
327
  # @raise [MagicError] if the magic is not valid Mach-O magic
328
328
  # @raise [MachOBinaryError] if the magic is for a non-fat Mach-O file
329
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
330
332
  # @api private
331
333
  def populate_fat_header
332
334
  # the smallest fat Mach-O header is 8 bytes
@@ -346,6 +348,9 @@ module MachO
346
348
  # formats.
347
349
  raise JavaClassFileError if fh.nfat_arch > 30
348
350
 
351
+ # Rationale: return an error if the file has no internal slices.
352
+ raise ZeroArchitectureError if fh.nfat_arch.zero?
353
+
349
354
  fh
350
355
  end
351
356
 
@@ -374,6 +379,13 @@ module MachO
374
379
 
375
380
  fat_archs.each do |arch|
376
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)
377
389
  end
378
390
 
379
391
  machos
data/lib/macho/headers.rb CHANGED
@@ -505,30 +505,14 @@ module MachO
505
505
  # @see MachO::FatArch
506
506
  class FatHeader < MachOStructure
507
507
  # @return [Integer] the magic number of the header (and file)
508
- attr_reader :magic
508
+ field :magic, :uint32, :endian => :big
509
509
 
510
510
  # @return [Integer] the number of fat architecture structures following the header
511
- attr_reader :nfat_arch
512
-
513
- # always big-endian
514
- # @see MachOStructure::FORMAT
515
- # @api private
516
- FORMAT = "N2"
517
-
518
- # @see MachOStructure::SIZEOF
519
- # @api private
520
- SIZEOF = 8
521
-
522
- # @api private
523
- def initialize(magic, nfat_arch)
524
- super()
525
- @magic = magic
526
- @nfat_arch = nfat_arch
527
- end
511
+ field :nfat_arch, :uint32, :endian => :big
528
512
 
529
513
  # @return [String] the serialized fields of the fat header
530
514
  def serialize
531
- [magic, nfat_arch].pack(FORMAT)
515
+ [magic, nfat_arch].pack(self.class.format)
532
516
  end
533
517
 
534
518
  # @return [Hash] a hash representation of this {FatHeader}
@@ -548,42 +532,23 @@ module MachO
548
532
  # @see MachO::Headers::FatHeader
549
533
  class FatArch < MachOStructure
550
534
  # @return [Integer] the CPU type of the Mach-O
551
- attr_reader :cputype
535
+ field :cputype, :uint32, :endian => :big
552
536
 
553
537
  # @return [Integer] the CPU subtype of the Mach-O
554
- attr_reader :cpusubtype
538
+ field :cpusubtype, :uint32, :endian => :big, :mask => CPU_SUBTYPE_MASK
555
539
 
556
540
  # @return [Integer] the file offset to the beginning of the Mach-O data
557
- attr_reader :offset
541
+ field :offset, :uint32, :endian => :big
558
542
 
559
543
  # @return [Integer] the size, in bytes, of the Mach-O data
560
- attr_reader :size
544
+ field :size, :uint32, :endian => :big
561
545
 
562
546
  # @return [Integer] the alignment, as a power of 2
563
- attr_reader :align
564
-
565
- # @note Always big endian.
566
- # @see MachOStructure::FORMAT
567
- # @api private
568
- FORMAT = "L>5"
569
-
570
- # @see MachOStructure::SIZEOF
571
- # @api private
572
- SIZEOF = 20
573
-
574
- # @api private
575
- def initialize(cputype, cpusubtype, offset, size, align)
576
- super()
577
- @cputype = cputype
578
- @cpusubtype = cpusubtype & ~CPU_SUBTYPE_MASK
579
- @offset = offset
580
- @size = size
581
- @align = align
582
- end
547
+ field :align, :uint32, :endian => :big
583
548
 
584
549
  # @return [String] the serialized fields of the fat arch
585
550
  def serialize
586
- [cputype, cpusubtype, offset, size, align].pack(FORMAT)
551
+ [cputype, cpusubtype, offset, size, align].pack(self.class.format)
587
552
  end
588
553
 
589
554
  # @return [Hash] a hash representation of this {FatArch}
@@ -606,27 +571,18 @@ module MachO
606
571
  # Mach-Os that it points to necessarily *are* 64-bit.
607
572
  # @see MachO::Headers::FatHeader
608
573
  class FatArch64 < FatArch
609
- # @return [void]
610
- attr_reader :reserved
611
-
612
- # @note Always big endian.
613
- # @see MachOStructure::FORMAT
614
- # @api private
615
- 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
616
576
 
617
- # @see MachOStructure::SIZEOF
618
- # @api private
619
- SIZEOF = 32
577
+ # @return [Integer] the size, in bytes, of the Mach-O data
578
+ field :size, :uint64, :endian => :big
620
579
 
621
- # @api private
622
- def initialize(cputype, cpusubtype, offset, size, align, reserved = 0)
623
- super(cputype, cpusubtype, offset, size, align)
624
- @reserved = reserved
625
- end
580
+ # @return [void]
581
+ field :reserved, :uint32, :endian => :big, :default => 0
626
582
 
627
583
  # @return [String] the serialized fields of the fat arch
628
584
  def serialize
629
- [cputype, cpusubtype, offset, size, align, reserved].pack(FORMAT)
585
+ [cputype, cpusubtype, offset, size, align, reserved].pack(self.class.format)
630
586
  end
631
587
 
632
588
  # @return [Hash] a hash representation of this {FatArch64}
@@ -640,48 +596,25 @@ module MachO
640
596
  # 32-bit Mach-O file header structure
641
597
  class MachHeader < MachOStructure
642
598
  # @return [Integer] the magic number
643
- attr_reader :magic
599
+ field :magic, :uint32
644
600
 
645
601
  # @return [Integer] the CPU type of the Mach-O
646
- attr_reader :cputype
602
+ field :cputype, :uint32
647
603
 
648
604
  # @return [Integer] the CPU subtype of the Mach-O
649
- attr_reader :cpusubtype
605
+ field :cpusubtype, :uint32, :mask => CPU_SUBTYPE_MASK
650
606
 
651
607
  # @return [Integer] the file type of the Mach-O
652
- attr_reader :filetype
608
+ field :filetype, :uint32
653
609
 
654
610
  # @return [Integer] the number of load commands in the Mach-O
655
- attr_reader :ncmds
611
+ field :ncmds, :uint32
656
612
 
657
613
  # @return [Integer] the size of all load commands, in bytes, in the Mach-O
658
- attr_reader :sizeofcmds
614
+ field :sizeofcmds, :uint32
659
615
 
660
616
  # @return [Integer] the header flags associated with the Mach-O
661
- attr_reader :flags
662
-
663
- # @see MachOStructure::FORMAT
664
- # @api private
665
- FORMAT = "L=7"
666
-
667
- # @see MachOStructure::SIZEOF
668
- # @api private
669
- SIZEOF = 28
670
-
671
- # @api private
672
- def initialize(magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds,
673
- flags)
674
- super()
675
- @magic = magic
676
- @cputype = cputype
677
- # For now we're not interested in additional capability bits also to be
678
- # found in the `cpusubtype` field. We only care about the CPU sub-type.
679
- @cpusubtype = cpusubtype & ~CPU_SUBTYPE_MASK
680
- @filetype = filetype
681
- @ncmds = ncmds
682
- @sizeofcmds = sizeofcmds
683
- @flags = flags
684
- end
617
+ field :flags, :uint32
685
618
 
686
619
  # @example
687
620
  # puts "this mach-o has position-independent execution" if header.flag?(:MH_PIE)
@@ -787,22 +720,7 @@ module MachO
787
720
  # 64-bit Mach-O file header structure
788
721
  class MachHeader64 < MachHeader
789
722
  # @return [void]
790
- attr_reader :reserved
791
-
792
- # @see MachOStructure::FORMAT
793
- # @api private
794
- FORMAT = "L=8"
795
-
796
- # @see MachOStructure::SIZEOF
797
- # @api private
798
- SIZEOF = 32
799
-
800
- # @api private
801
- def initialize(magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds,
802
- flags, reserved)
803
- super(magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags)
804
- @reserved = reserved
805
- end
723
+ field :reserved, :uint32
806
724
 
807
725
  # @return [Hash] a hash representation of this {MachHeader64}
808
726
  def to_h
@@ -815,54 +733,31 @@ module MachO
815
733
  # Prelinked kernel/"kernelcache" header structure
816
734
  class PrelinkedKernelHeader < MachOStructure
817
735
  # @return [Integer] the magic number for a compressed header ({COMPRESSED_MAGIC})
818
- attr_reader :signature
736
+ field :signature, :uint32, :endian => :big
819
737
 
820
738
  # @return [Integer] the type of compression used
821
- attr_reader :compress_type
739
+ field :compress_type, :uint32, :endian => :big
822
740
 
823
741
  # @return [Integer] a checksum for the uncompressed data
824
- attr_reader :adler32
742
+ field :adler32, :uint32, :endian => :big
825
743
 
826
744
  # @return [Integer] the size of the uncompressed data, in bytes
827
- attr_reader :uncompressed_size
745
+ field :uncompressed_size, :uint32, :endian => :big
828
746
 
829
747
  # @return [Integer] the size of the compressed data, in bytes
830
- attr_reader :compressed_size
748
+ field :compressed_size, :uint32, :endian => :big
831
749
 
832
750
  # @return [Integer] the version of the prelink format
833
- attr_reader :prelink_version
751
+ field :prelink_version, :uint32, :endian => :big
834
752
 
835
753
  # @return [void]
836
- attr_reader :reserved
754
+ field :reserved, :string, :size => 40, :unpack => "L>10"
837
755
 
838
756
  # @return [void]
839
- attr_reader :platform_name
757
+ field :platform_name, :string, :size => 64
840
758
 
841
759
  # @return [void]
842
- attr_reader :root_path
843
-
844
- # @see MachOStructure::FORMAT
845
- # @api private
846
- FORMAT = "L>6a40a64a256"
847
-
848
- # @see MachOStructure::SIZEOF
849
- # @api private
850
- SIZEOF = 384
851
-
852
- # @api private
853
- def initialize(signature, compress_type, adler32, uncompressed_size, compressed_size, prelink_version, reserved, platform_name, root_path)
854
- super()
855
-
856
- @signature = signature
857
- @compress_type = compress_type
858
- @adler32 = adler32
859
- @uncompressed_size = uncompressed_size
860
- @compressed_size = compressed_size
861
- @prelink_version = prelink_version
862
- @reserved = reserved.unpack("L>10")
863
- @platform_name = platform_name
864
- @root_path = root_path
865
- end
760
+ field :root_path, :string, :size => 256
866
761
 
867
762
  # @return [Boolean] whether this prelinked kernel supports KASLR
868
763
  def kaslr?