ruby-macho 3.0.0 → 4.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +15 -2
- data/lib/macho/exceptions.rb +30 -8
- data/lib/macho/fat_file.rb +12 -0
- data/lib/macho/headers.rb +33 -138
- data/lib/macho/load_commands.rb +181 -647
- data/lib/macho/macho_file.rb +22 -9
- data/lib/macho/sections.rb +17 -48
- data/lib/macho/structure.rb +264 -22
- data/lib/macho/view.rb +10 -1
- data/lib/macho.rb +2 -2
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9880662397dbd1b2db647be1945cf0ab8bdcf08014b165258810287979f44ddf
|
4
|
+
data.tar.gz: a6f379de4b7c17344b01f4b44e178255165c6c2983f756d5d15b4bd62dfb8847
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 50fb7106b9d6f8fcee1de541ee570db1fb7e4316896f4b4aecfbe50e922380c3b86241c8c96f43079880baa7ef88373f89b1360d92d4a81645a2dc4a7eb607ce
|
7
|
+
data.tar.gz: 148edd4572c6e1655086ef828bf5c6ef90df03b326eeedbe07a08b8f47462e4f979dc69b9956c58992898398129cdf2c30e744904fb171b4226fe68dfaa4d2a3
|
data/README.md
CHANGED
@@ -2,7 +2,7 @@ ruby-macho
|
|
2
2
|
================
|
3
3
|
|
4
4
|
[![Gem Version](https://badge.fury.io/rb/ruby-macho.svg)](http://badge.fury.io/rb/ruby-macho)
|
5
|
-
[![
|
5
|
+
[![CI](https://github.com/Homebrew/ruby-macho/actions/workflows/tests.yml/badge.svg)](https://github.com/Homebrew/ruby-macho/actions/workflows/tests.yml)
|
6
6
|
[![Coverage Status](https://codecov.io/gh/Homebrew/ruby-macho/branch/master/graph/badge.svg)](https://codecov.io/gh/Homebrew/ruby-macho)
|
7
7
|
|
8
8
|
A Ruby library for examining and modifying Mach-O files.
|
@@ -56,11 +56,24 @@ puts lc_vers.version_string # => "10.10.0"
|
|
56
56
|
|
57
57
|
* Unit and performance testing.
|
58
58
|
|
59
|
-
|
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
|
|
data/lib/macho/exceptions.rb
CHANGED
@@ -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
|
-
"
|
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
|
-
"
|
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
|
-
"
|
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
|
-
"
|
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
|
|
data/lib/macho/fat_file.rb
CHANGED
@@ -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
|
-
|
508
|
+
field :magic, :uint32, :endian => :big
|
509
509
|
|
510
510
|
# @return [Integer] the number of fat architecture structures following the header
|
511
|
-
|
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(
|
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
|
-
|
535
|
+
field :cputype, :uint32, :endian => :big
|
552
536
|
|
553
537
|
# @return [Integer] the CPU subtype of the Mach-O
|
554
|
-
|
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
|
-
|
541
|
+
field :offset, :uint32, :endian => :big
|
558
542
|
|
559
543
|
# @return [Integer] the size, in bytes, of the Mach-O data
|
560
|
-
|
544
|
+
field :size, :uint32, :endian => :big
|
561
545
|
|
562
546
|
# @return [Integer] the alignment, as a power of 2
|
563
|
-
|
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(
|
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 [
|
610
|
-
|
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
|
-
# @
|
618
|
-
|
619
|
-
SIZEOF = 32
|
577
|
+
# @return [Integer] the size, in bytes, of the Mach-O data
|
578
|
+
field :size, :uint64, :endian => :big
|
620
579
|
|
621
|
-
# @
|
622
|
-
|
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(
|
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
|
-
|
599
|
+
field :magic, :uint32
|
644
600
|
|
645
601
|
# @return [Integer] the CPU type of the Mach-O
|
646
|
-
|
602
|
+
field :cputype, :uint32
|
647
603
|
|
648
604
|
# @return [Integer] the CPU subtype of the Mach-O
|
649
|
-
|
605
|
+
field :cpusubtype, :uint32, :mask => CPU_SUBTYPE_MASK
|
650
606
|
|
651
607
|
# @return [Integer] the file type of the Mach-O
|
652
|
-
|
608
|
+
field :filetype, :uint32
|
653
609
|
|
654
610
|
# @return [Integer] the number of load commands in the Mach-O
|
655
|
-
|
611
|
+
field :ncmds, :uint32
|
656
612
|
|
657
613
|
# @return [Integer] the size of all load commands, in bytes, in the Mach-O
|
658
|
-
|
614
|
+
field :sizeofcmds, :uint32
|
659
615
|
|
660
616
|
# @return [Integer] the header flags associated with the Mach-O
|
661
|
-
|
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
|
-
|
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
|
-
|
736
|
+
field :signature, :uint32, :endian => :big
|
819
737
|
|
820
738
|
# @return [Integer] the type of compression used
|
821
|
-
|
739
|
+
field :compress_type, :uint32, :endian => :big
|
822
740
|
|
823
741
|
# @return [Integer] a checksum for the uncompressed data
|
824
|
-
|
742
|
+
field :adler32, :uint32, :endian => :big
|
825
743
|
|
826
744
|
# @return [Integer] the size of the uncompressed data, in bytes
|
827
|
-
|
745
|
+
field :uncompressed_size, :uint32, :endian => :big
|
828
746
|
|
829
747
|
# @return [Integer] the size of the compressed data, in bytes
|
830
|
-
|
748
|
+
field :compressed_size, :uint32, :endian => :big
|
831
749
|
|
832
750
|
# @return [Integer] the version of the prelink format
|
833
|
-
|
751
|
+
field :prelink_version, :uint32, :endian => :big
|
834
752
|
|
835
753
|
# @return [void]
|
836
|
-
|
754
|
+
field :reserved, :string, :size => 40, :unpack => "L>10"
|
837
755
|
|
838
756
|
# @return [void]
|
839
|
-
|
757
|
+
field :platform_name, :string, :size => 64
|
840
758
|
|
841
759
|
# @return [void]
|
842
|
-
|
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?
|