ruby-macho 2.3.0 → 3.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 +4 -4
- data/README.md +1 -1
- data/lib/macho/exceptions.rb +20 -9
- data/lib/macho/fat_file.rb +16 -13
- data/lib/macho/headers.rb +112 -0
- data/lib/macho/load_commands.rb +49 -2
- data/lib/macho/macho_file.rb +78 -23
- data/lib/macho/sections.rb +47 -6
- data/lib/macho/tools.rb +4 -12
- data/lib/macho/utils.rb +7 -0
- data/lib/macho.rb +8 -8
- metadata +7 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ba2fc7b81345cb788c6e1de15bace8d35f7ac1f9a17b6b6a12289a4e51b978de
|
|
4
|
+
data.tar.gz: 395dd63235823f33f11463d395b790b1cee2003ab430bb54326c03ca3de402b8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c048978f3e0b1becaa3b050db8730f2f914cc8a8d662e1e7d24ca0a24c03c043747485e7e6033bad5789069795d8c04daad053d817f26c54dd809082975a01fe
|
|
7
|
+
data.tar.gz: 53a576e666842a464af2f961825becfa51da0b26f3dd9ec656ce9859a6c0e9c017fc6622ec330bb0fc04e63089053a6ecd3740f505eb0e5aefecfb5728672d72
|
data/README.md
CHANGED
|
@@ -59,7 +59,7 @@ puts lc_vers.version_string # => "10.10.0"
|
|
|
59
59
|
Attribution:
|
|
60
60
|
|
|
61
61
|
* Constants were taken from Apple, Inc's
|
|
62
|
-
[`loader.h` in `cctools/include/mach-o`](https://
|
|
62
|
+
[`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
63
|
(Apple Public Source License 2.0).
|
|
64
64
|
|
|
65
65
|
### License
|
data/lib/macho/exceptions.rb
CHANGED
|
@@ -9,6 +9,11 @@ module MachO
|
|
|
9
9
|
class ModificationError < MachOError
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
+
# Raised when codesigning fails. Certain environments
|
|
13
|
+
# may want to rescue this to treat it as non-fatal.
|
|
14
|
+
class CodeSigningError < MachOError
|
|
15
|
+
end
|
|
16
|
+
|
|
12
17
|
# Raised when a Mach-O file modification fails but can be recovered when
|
|
13
18
|
# operating on multiple Mach-O slices of a fat binary in non-strict mode.
|
|
14
19
|
class RecoverableModificationError < ModificationError
|
|
@@ -27,10 +32,6 @@ module MachO
|
|
|
27
32
|
|
|
28
33
|
# Raised when a file is not a Mach-O.
|
|
29
34
|
class NotAMachOError < MachOError
|
|
30
|
-
# @param error [String] the error in question
|
|
31
|
-
def initialize(error)
|
|
32
|
-
super error
|
|
33
|
-
end
|
|
34
35
|
end
|
|
35
36
|
|
|
36
37
|
# Raised when a file is too short to be a valid Mach-O file.
|
|
@@ -83,7 +84,7 @@ module MachO
|
|
|
83
84
|
# @param cpusubtype [Integer] the CPU sub-type of the unknown pair
|
|
84
85
|
def initialize(cputype, cpusubtype)
|
|
85
86
|
super "Unrecognized CPU sub-type: 0x%08<cpusubtype>x" \
|
|
86
|
-
|
|
87
|
+
" (for CPU type: 0x%08<cputype>x" % { :cputype => cputype, :cpusubtype => cpusubtype }
|
|
87
88
|
end
|
|
88
89
|
end
|
|
89
90
|
|
|
@@ -119,7 +120,7 @@ module MachO
|
|
|
119
120
|
# @param actual_arity [Integer] the number of arguments received
|
|
120
121
|
def initialize(cmd_sym, expected_arity, actual_arity)
|
|
121
122
|
super "Expected #{expected_arity} arguments for #{cmd_sym} creation," \
|
|
122
|
-
|
|
123
|
+
" got #{actual_arity}"
|
|
123
124
|
end
|
|
124
125
|
end
|
|
125
126
|
|
|
@@ -136,7 +137,7 @@ module MachO
|
|
|
136
137
|
# @param lc [MachO::LoadCommand] the load command containing the string
|
|
137
138
|
def initialize(lc)
|
|
138
139
|
super "Load command #{lc.type} at offset #{lc.view.offset} contains a" \
|
|
139
|
-
|
|
140
|
+
" malformed string"
|
|
140
141
|
end
|
|
141
142
|
end
|
|
142
143
|
|
|
@@ -153,8 +154,8 @@ module MachO
|
|
|
153
154
|
# @param filename [String] the filename
|
|
154
155
|
def initialize(filename)
|
|
155
156
|
super "Updated load commands do not fit in the header of " \
|
|
156
|
-
|
|
157
|
-
|
|
157
|
+
"#{filename}. #{filename} needs to be relinked, possibly with " \
|
|
158
|
+
"-headerpad or -headerpad_max_install_names"
|
|
158
159
|
end
|
|
159
160
|
end
|
|
160
161
|
|
|
@@ -206,4 +207,14 @@ module MachO
|
|
|
206
207
|
" Consider merging with `fat64: true`"
|
|
207
208
|
end
|
|
208
209
|
end
|
|
210
|
+
|
|
211
|
+
# Raised when attempting to parse a compressed Mach-O without explicitly
|
|
212
|
+
# requesting decompression.
|
|
213
|
+
class CompressedMachOError < MachOError
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# Raised when attempting to decompress a compressed Mach-O without adequate
|
|
217
|
+
# dependencies, or on other decompression errors.
|
|
218
|
+
class DecompressionError < MachOError
|
|
219
|
+
end
|
|
209
220
|
end
|
data/lib/macho/fat_file.rb
CHANGED
|
@@ -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
|
|
|
@@ -66,7 +66,7 @@ module MachO
|
|
|
66
66
|
offset += (macho.serialize.bytesize + macho_pads[macho])
|
|
67
67
|
end
|
|
68
68
|
|
|
69
|
-
machos.each do |macho|
|
|
69
|
+
machos.each do |macho| # rubocop:disable Style/CombinableLoops
|
|
70
70
|
bin << Utils.nullpad(macho_pads[macho])
|
|
71
71
|
bin << macho.serialize
|
|
72
72
|
end
|
|
@@ -96,7 +96,7 @@ module MachO
|
|
|
96
96
|
|
|
97
97
|
@filename = filename
|
|
98
98
|
@options = opts
|
|
99
|
-
@raw_data = File.
|
|
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.
|
|
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.
|
|
309
|
+
File.binwrite(@filename, @raw_data)
|
|
305
310
|
end
|
|
306
311
|
|
|
307
312
|
# @return [Hash] a hash representation of this {FatFile}
|
|
@@ -398,16 +403,14 @@ module MachO
|
|
|
398
403
|
errors = []
|
|
399
404
|
|
|
400
405
|
machos.each_with_index do |macho, index|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
e.macho_slice = index
|
|
406
|
+
yield macho
|
|
407
|
+
rescue RecoverableModificationError => e
|
|
408
|
+
e.macho_slice = index
|
|
405
409
|
|
|
406
|
-
|
|
407
|
-
|
|
410
|
+
# Strict mode: Immediately re-raise. Otherwise: Retain, check later.
|
|
411
|
+
raise e if strict
|
|
408
412
|
|
|
409
|
-
|
|
410
|
-
end
|
|
413
|
+
errors << e
|
|
411
414
|
end
|
|
412
415
|
|
|
413
416
|
# Non-strict mode: Raise first error if *all* Mach-O slices failed.
|
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,6 +496,9 @@ 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
|
|
@@ -500,6 +521,7 @@ module MachO
|
|
|
500
521
|
|
|
501
522
|
# @api private
|
|
502
523
|
def initialize(magic, nfat_arch)
|
|
524
|
+
super()
|
|
503
525
|
@magic = magic
|
|
504
526
|
@nfat_arch = nfat_arch
|
|
505
527
|
end
|
|
@@ -551,6 +573,7 @@ module MachO
|
|
|
551
573
|
|
|
552
574
|
# @api private
|
|
553
575
|
def initialize(cputype, cpusubtype, offset, size, align)
|
|
576
|
+
super()
|
|
554
577
|
@cputype = cputype
|
|
555
578
|
@cpusubtype = cpusubtype & ~CPU_SUBTYPE_MASK
|
|
556
579
|
@offset = offset
|
|
@@ -648,6 +671,7 @@ module MachO
|
|
|
648
671
|
# @api private
|
|
649
672
|
def initialize(magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds,
|
|
650
673
|
flags)
|
|
674
|
+
super()
|
|
651
675
|
@magic = magic
|
|
652
676
|
@cputype = cputype
|
|
653
677
|
# For now we're not interested in additional capability bits also to be
|
|
@@ -721,6 +745,11 @@ module MachO
|
|
|
721
745
|
filetype == Headers::MH_KEXT_BUNDLE
|
|
722
746
|
end
|
|
723
747
|
|
|
748
|
+
# @return [Boolean] whether or not the file is of type `MH_FILESET`
|
|
749
|
+
def fileset?
|
|
750
|
+
filetype == Headers::MH_FILESET
|
|
751
|
+
end
|
|
752
|
+
|
|
724
753
|
# @return [Boolean] true if the Mach-O has 32-bit magic, false otherwise
|
|
725
754
|
def magic32?
|
|
726
755
|
Utils.magic32?(magic)
|
|
@@ -782,5 +811,88 @@ module MachO
|
|
|
782
811
|
}.merge super
|
|
783
812
|
end
|
|
784
813
|
end
|
|
814
|
+
|
|
815
|
+
# Prelinked kernel/"kernelcache" header structure
|
|
816
|
+
class PrelinkedKernelHeader < MachOStructure
|
|
817
|
+
# @return [Integer] the magic number for a compressed header ({COMPRESSED_MAGIC})
|
|
818
|
+
attr_reader :signature
|
|
819
|
+
|
|
820
|
+
# @return [Integer] the type of compression used
|
|
821
|
+
attr_reader :compress_type
|
|
822
|
+
|
|
823
|
+
# @return [Integer] a checksum for the uncompressed data
|
|
824
|
+
attr_reader :adler32
|
|
825
|
+
|
|
826
|
+
# @return [Integer] the size of the uncompressed data, in bytes
|
|
827
|
+
attr_reader :uncompressed_size
|
|
828
|
+
|
|
829
|
+
# @return [Integer] the size of the compressed data, in bytes
|
|
830
|
+
attr_reader :compressed_size
|
|
831
|
+
|
|
832
|
+
# @return [Integer] the version of the prelink format
|
|
833
|
+
attr_reader :prelink_version
|
|
834
|
+
|
|
835
|
+
# @return [void]
|
|
836
|
+
attr_reader :reserved
|
|
837
|
+
|
|
838
|
+
# @return [void]
|
|
839
|
+
attr_reader :platform_name
|
|
840
|
+
|
|
841
|
+
# @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
|
|
866
|
+
|
|
867
|
+
# @return [Boolean] whether this prelinked kernel supports KASLR
|
|
868
|
+
def kaslr?
|
|
869
|
+
prelink_version >= 1
|
|
870
|
+
end
|
|
871
|
+
|
|
872
|
+
# @return [Boolean] whether this prelinked kernel is compressed with LZSS
|
|
873
|
+
def lzss?
|
|
874
|
+
compress_type == COMP_TYPE_LZSS
|
|
875
|
+
end
|
|
876
|
+
|
|
877
|
+
# @return [Boolean] whether this prelinked kernel is compressed with LZVN
|
|
878
|
+
def lzvn?
|
|
879
|
+
compress_type == COMP_TYPE_FASTLIB
|
|
880
|
+
end
|
|
881
|
+
|
|
882
|
+
# @return [Hash] a hash representation of this {PrelinkedKernelHeader}
|
|
883
|
+
def to_h
|
|
884
|
+
{
|
|
885
|
+
"signature" => signature,
|
|
886
|
+
"compress_type" => compress_type,
|
|
887
|
+
"adler32" => adler32,
|
|
888
|
+
"uncompressed_size" => uncompressed_size,
|
|
889
|
+
"compressed_size" => compressed_size,
|
|
890
|
+
"prelink_version" => prelink_version,
|
|
891
|
+
"reserved" => reserved,
|
|
892
|
+
"platform_name" => platform_name,
|
|
893
|
+
"root_path" => root_path,
|
|
894
|
+
}.merge super
|
|
895
|
+
end
|
|
896
|
+
end
|
|
785
897
|
end
|
|
786
898
|
end
|
data/lib/macho/load_commands.rb
CHANGED
|
@@ -63,7 +63,8 @@ module MachO
|
|
|
63
63
|
0x31 => :LC_NOTE,
|
|
64
64
|
0x32 => :LC_BUILD_VERSION,
|
|
65
65
|
(0x33 | LC_REQ_DYLD) => :LC_DYLD_EXPORTS_TRIE,
|
|
66
|
-
(0x34 | LC_REQ_DYLD) => :
|
|
66
|
+
(0x34 | LC_REQ_DYLD) => :LC_DYLD_CHAINED_FIXUPS,
|
|
67
|
+
(0x35 | LC_REQ_DYLD) => :LC_FILESET_ENTRY,
|
|
67
68
|
}.freeze
|
|
68
69
|
|
|
69
70
|
# association of symbol representations to load command constants
|
|
@@ -150,7 +151,8 @@ module MachO
|
|
|
150
151
|
:LC_NOTE => "NoteCommand",
|
|
151
152
|
:LC_BUILD_VERSION => "BuildVersionCommand",
|
|
152
153
|
:LC_DYLD_EXPORTS_TRIE => "LinkeditDataCommand",
|
|
153
|
-
:
|
|
154
|
+
:LC_DYLD_CHAINED_FIXUPS => "LinkeditDataCommand",
|
|
155
|
+
:LC_FILESET_ENTRY => "FilesetEntryCommand",
|
|
154
156
|
}.freeze
|
|
155
157
|
|
|
156
158
|
# association of segment name symbols to names
|
|
@@ -173,6 +175,7 @@ module MachO
|
|
|
173
175
|
:SG_FVMLIB => 0x2,
|
|
174
176
|
:SG_NORELOC => 0x4,
|
|
175
177
|
:SG_PROTECTED_VERSION_1 => 0x8,
|
|
178
|
+
:SG_READ_ONLY => 0x10,
|
|
176
179
|
}.freeze
|
|
177
180
|
|
|
178
181
|
# The top-level Mach-O load command structure.
|
|
@@ -231,6 +234,7 @@ module MachO
|
|
|
231
234
|
# @param cmdsize [Integer] the size of the load command in bytes
|
|
232
235
|
# @api private
|
|
233
236
|
def initialize(view, cmd, cmdsize)
|
|
237
|
+
super()
|
|
234
238
|
@view = view
|
|
235
239
|
@cmd = cmd
|
|
236
240
|
@cmdsize = cmdsize
|
|
@@ -1793,5 +1797,48 @@ module MachO
|
|
|
1793
1797
|
}.merge super
|
|
1794
1798
|
end
|
|
1795
1799
|
end
|
|
1800
|
+
|
|
1801
|
+
# A load command containing a description of a Mach-O that is a constituent of a fileset.
|
|
1802
|
+
# Each entry is further described by its own Mach header.
|
|
1803
|
+
# Corresponds to LC_FILESET_ENTRY.
|
|
1804
|
+
class FilesetEntryCommand < LoadCommand
|
|
1805
|
+
# @return [Integer] the virtual memory address of the entry
|
|
1806
|
+
attr_reader :vmaddr
|
|
1807
|
+
|
|
1808
|
+
# @return [Integer] the file offset of the entry
|
|
1809
|
+
attr_reader :fileoff
|
|
1810
|
+
|
|
1811
|
+
# @return [LCStr] the entry's ID
|
|
1812
|
+
attr_reader :entry_id
|
|
1813
|
+
|
|
1814
|
+
# @return [void]
|
|
1815
|
+
attr_reader :reserved
|
|
1816
|
+
|
|
1817
|
+
# @see MachOStructure::FORMAT
|
|
1818
|
+
# @api private
|
|
1819
|
+
FORMAT = "L=2Q=2L=2"
|
|
1820
|
+
|
|
1821
|
+
# @see MachOStructure::SIZEOF
|
|
1822
|
+
# @api private
|
|
1823
|
+
SIZEOF = 28
|
|
1824
|
+
|
|
1825
|
+
def initialize(view, cmd, cmdsize, vmaddr, fileoff, entry_id, reserved)
|
|
1826
|
+
super(view, cmd, cmdsize)
|
|
1827
|
+
@vmaddr = vmaddr
|
|
1828
|
+
@fileoff = fileoff
|
|
1829
|
+
@entry_id = LCStr.new(self, entry_id)
|
|
1830
|
+
@reserved = reserved
|
|
1831
|
+
end
|
|
1832
|
+
|
|
1833
|
+
# @return [Hash] a hash representation of this {FilesetEntryCommand}
|
|
1834
|
+
def to_h
|
|
1835
|
+
{
|
|
1836
|
+
"vmaddr" => vmaddr,
|
|
1837
|
+
"fileoff" => fileoff,
|
|
1838
|
+
"entry_id" => entry_id,
|
|
1839
|
+
"reserved" => reserved,
|
|
1840
|
+
}.merge super
|
|
1841
|
+
end
|
|
1842
|
+
end
|
|
1796
1843
|
end
|
|
1797
1844
|
end
|
data/lib/macho/macho_file.rb
CHANGED
|
@@ -34,7 +34,11 @@ module MachO
|
|
|
34
34
|
# @param bin [String] a binary string containing raw Mach-O data
|
|
35
35
|
# @param opts [Hash] options to control the parser with
|
|
36
36
|
# @option opts [Boolean] :permissive whether to ignore unknown load commands
|
|
37
|
+
# @option opts [Boolean] :decompress whether to decompress, if capable
|
|
37
38
|
# @return [MachOFile] a new MachOFile
|
|
39
|
+
# @note The `:decompress` option relies on non-default dependencies. Compression
|
|
40
|
+
# is only used in niche Mach-Os, so leaving this disabled is a reasonable default for
|
|
41
|
+
# virtually all normal uses.
|
|
38
42
|
def self.new_from_bin(bin, **opts)
|
|
39
43
|
instance = allocate
|
|
40
44
|
instance.initialize_from_bin(bin, opts)
|
|
@@ -46,13 +50,17 @@ module MachO
|
|
|
46
50
|
# @param filename [String] the Mach-O file to load from
|
|
47
51
|
# @param opts [Hash] options to control the parser with
|
|
48
52
|
# @option opts [Boolean] :permissive whether to ignore unknown load commands
|
|
53
|
+
# @option opts [Boolean] :decompress whether to decompress, if capable
|
|
49
54
|
# @raise [ArgumentError] if the given file does not exist
|
|
55
|
+
# @note The `:decompress` option relies on non-default dependencies. Compression
|
|
56
|
+
# is only used in niche Mach-Os, so leaving this disabled is a reasonable default for
|
|
57
|
+
# virtually all normal uses.
|
|
50
58
|
def initialize(filename, **opts)
|
|
51
59
|
raise ArgumentError, "#{filename}: no such file" unless File.file?(filename)
|
|
52
60
|
|
|
53
61
|
@filename = filename
|
|
54
62
|
@options = opts
|
|
55
|
-
@raw_data = File.
|
|
63
|
+
@raw_data = File.binread(@filename)
|
|
56
64
|
populate_fields
|
|
57
65
|
end
|
|
58
66
|
|
|
@@ -152,8 +160,8 @@ module MachO
|
|
|
152
160
|
# the instance fields
|
|
153
161
|
# @raise [OffsetInsertionError] if the offset is not in the load command region
|
|
154
162
|
# @raise [HeaderPadError] if the new command exceeds the header pad buffer
|
|
155
|
-
# @note Calling this method with an arbitrary offset in the load command
|
|
156
|
-
#
|
|
163
|
+
# @note Calling this method with an arbitrary offset in the load command region
|
|
164
|
+
# **will leave the object in an inconsistent state**.
|
|
157
165
|
def insert_command(offset, lc, options = {})
|
|
158
166
|
context = LoadCommands::LoadCommand::SerializationContext.context_for(self)
|
|
159
167
|
cmd_raw = lc.serialize(context)
|
|
@@ -196,7 +204,7 @@ module MachO
|
|
|
196
204
|
# Appends a new load command to the Mach-O.
|
|
197
205
|
# @param lc [LoadCommands::LoadCommand] the load command being added
|
|
198
206
|
# @param options [Hash]
|
|
199
|
-
# @option
|
|
207
|
+
# @option f [Boolean] :repopulate (true) whether or not to repopulate
|
|
200
208
|
# the instance fields
|
|
201
209
|
# @return [void]
|
|
202
210
|
# @see #insert_command
|
|
@@ -368,20 +376,20 @@ module MachO
|
|
|
368
376
|
# file.change_rpath("/usr/lib", "/usr/local/lib")
|
|
369
377
|
# @param old_path [String] the old runtime path
|
|
370
378
|
# @param new_path [String] the new runtime path
|
|
371
|
-
# @param
|
|
379
|
+
# @param options [Hash]
|
|
380
|
+
# @option options [Boolean] :uniq (false) if true, change duplicate
|
|
381
|
+
# rpaths simultaneously.
|
|
372
382
|
# @return [void]
|
|
373
383
|
# @raise [RpathUnknownError] if no such old runtime path exists
|
|
374
384
|
# @raise [RpathExistsError] if the new runtime path already exists
|
|
375
|
-
|
|
376
|
-
# compatibility with {MachO::FatFile#change_rpath}
|
|
377
|
-
def change_rpath(old_path, new_path, _options = {})
|
|
385
|
+
def change_rpath(old_path, new_path, options = {})
|
|
378
386
|
old_lc = command(:LC_RPATH).find { |r| r.path.to_s == old_path }
|
|
379
387
|
raise RpathUnknownError, old_path if old_lc.nil?
|
|
380
388
|
raise RpathExistsError, new_path if rpaths.include?(new_path)
|
|
381
389
|
|
|
382
390
|
new_lc = LoadCommands::LoadCommand.create(:LC_RPATH, new_path)
|
|
383
391
|
|
|
384
|
-
delete_rpath(old_path)
|
|
392
|
+
delete_rpath(old_path, options)
|
|
385
393
|
insert_command(old_lc.view.offset, new_lc)
|
|
386
394
|
end
|
|
387
395
|
|
|
@@ -409,27 +417,29 @@ module MachO
|
|
|
409
417
|
# file.delete_rpath("/lib")
|
|
410
418
|
# file.rpaths # => []
|
|
411
419
|
# @param path [String] the runtime path to delete
|
|
412
|
-
# @param
|
|
420
|
+
# @param options [Hash]
|
|
421
|
+
# @option options [Boolean] :uniq (false) if true, also delete
|
|
422
|
+
# duplicates of the requested path. If false, delete the first
|
|
423
|
+
# instance (by offset) of the requested path.
|
|
413
424
|
# @return void
|
|
414
425
|
# @raise [RpathUnknownError] if no such runtime path exists
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
rpath_cmds = command(:LC_RPATH).select { |r| r.path.to_s == path }
|
|
419
|
-
raise RpathUnknownError, path if rpath_cmds.empty?
|
|
426
|
+
def delete_rpath(path, options = {})
|
|
427
|
+
uniq = options.fetch(:uniq, false)
|
|
428
|
+
search_method = uniq ? :select : :find
|
|
420
429
|
|
|
421
|
-
#
|
|
422
|
-
|
|
423
|
-
|
|
430
|
+
# Cast rpath_cmds into an Array so we can handle the uniq and non-uniq cases the same way
|
|
431
|
+
rpath_cmds = Array(command(:LC_RPATH).method(search_method).call { |r| r.path.to_s == path })
|
|
432
|
+
raise RpathUnknownError, path if rpath_cmds.empty?
|
|
424
433
|
|
|
425
|
-
|
|
434
|
+
# delete the commands in reverse order, offset descending.
|
|
435
|
+
rpath_cmds.reverse_each { |cmd| delete_command(cmd) }
|
|
426
436
|
end
|
|
427
437
|
|
|
428
438
|
# Write all Mach-O data to the given filename.
|
|
429
439
|
# @param filename [String] the file to write to
|
|
430
440
|
# @return [void]
|
|
431
441
|
def write(filename)
|
|
432
|
-
File.
|
|
442
|
+
File.binwrite(filename, @raw_data)
|
|
433
443
|
end
|
|
434
444
|
|
|
435
445
|
# Write all Mach-O data to the file used to initialize the instance.
|
|
@@ -439,7 +449,7 @@ module MachO
|
|
|
439
449
|
def write!
|
|
440
450
|
raise MachOError, "no initial file to write to" if @filename.nil?
|
|
441
451
|
|
|
442
|
-
File.
|
|
452
|
+
File.binwrite(@filename, @raw_data)
|
|
443
453
|
end
|
|
444
454
|
|
|
445
455
|
# @return [Hash] a hash representation of this {MachOFile}
|
|
@@ -461,6 +471,9 @@ module MachO
|
|
|
461
471
|
# the smallest Mach-O header is 28 bytes
|
|
462
472
|
raise TruncatedFileError if @raw_data.size < 28
|
|
463
473
|
|
|
474
|
+
magic = @raw_data[0..3].unpack1("N")
|
|
475
|
+
populate_prelinked_kernel_header if Utils.compressed_magic?(magic)
|
|
476
|
+
|
|
464
477
|
magic = populate_and_check_magic
|
|
465
478
|
mh_klass = Utils.magic32?(magic) ? Headers::MachHeader : Headers::MachHeader64
|
|
466
479
|
mh = mh_klass.new_from_bin(endianness, @raw_data[0, mh_klass.bytesize])
|
|
@@ -472,6 +485,48 @@ module MachO
|
|
|
472
485
|
mh
|
|
473
486
|
end
|
|
474
487
|
|
|
488
|
+
# Read a compressed Mach-O header and check its validity, as well as whether we're able
|
|
489
|
+
# to parse it.
|
|
490
|
+
# @return [void]
|
|
491
|
+
# @raise [CompressedMachOError] if we weren't asked to perform decompression
|
|
492
|
+
# @raise [DecompressionError] if decompression is impossible or fails
|
|
493
|
+
# @api private
|
|
494
|
+
def populate_prelinked_kernel_header
|
|
495
|
+
raise CompressedMachOError unless options.fetch(:decompress, false)
|
|
496
|
+
|
|
497
|
+
@plh = Headers::PrelinkedKernelHeader.new_from_bin :big, @raw_data[0, Headers::PrelinkedKernelHeader.bytesize]
|
|
498
|
+
|
|
499
|
+
raise DecompressionError, "unsupported compression type: LZSS" if @plh.lzss?
|
|
500
|
+
raise DecompressionError, "unknown compression type: 0x#{plh.compress_type.to_s 16}" unless @plh.lzvn?
|
|
501
|
+
|
|
502
|
+
decompress_macho_lzvn
|
|
503
|
+
end
|
|
504
|
+
|
|
505
|
+
# Attempt to decompress a Mach-O file from the data specified in a prelinked kernel header.
|
|
506
|
+
# @return [void]
|
|
507
|
+
# @raise [DecompressionError] if decompression is impossible or fails
|
|
508
|
+
# @api private
|
|
509
|
+
# @note This method rewrites the internal state of {MachOFile} to pretend as if it was never
|
|
510
|
+
# compressed to begin with, allowing all other APIs to transparently act on compressed Mach-Os.
|
|
511
|
+
def decompress_macho_lzvn
|
|
512
|
+
begin
|
|
513
|
+
require "lzfse"
|
|
514
|
+
rescue LoadError
|
|
515
|
+
raise DecompressionError, "LZVN required but the optional 'lzfse' gem is not installed"
|
|
516
|
+
end
|
|
517
|
+
|
|
518
|
+
# From this point onwards, the internal buffer of this MachOFile refers to the decompressed
|
|
519
|
+
# contents specified by the prelinked kernel header.
|
|
520
|
+
begin
|
|
521
|
+
@raw_data = LZFSE.lzvn_decompress @raw_data.slice(Headers::PrelinkedKernelHeader.bytesize, @plh.compressed_size)
|
|
522
|
+
# Sanity checks.
|
|
523
|
+
raise DecompressionError if @raw_data.size != @plh.uncompressed_size
|
|
524
|
+
# TODO: check the adler32 CRC in @plh
|
|
525
|
+
rescue LZFSE::DecodeError
|
|
526
|
+
raise DecompressionError, "LZVN decompression failed"
|
|
527
|
+
end
|
|
528
|
+
end
|
|
529
|
+
|
|
475
530
|
# Read just the file's magic number and check its validity.
|
|
476
531
|
# @return [Integer] the magic
|
|
477
532
|
# @raise [MagicError] if the magic is not valid Mach-O magic
|
|
@@ -556,8 +611,8 @@ module MachO
|
|
|
556
611
|
segments.each do |seg|
|
|
557
612
|
seg.sections.each do |sect|
|
|
558
613
|
next if sect.empty?
|
|
559
|
-
next if sect.
|
|
560
|
-
next if sect.
|
|
614
|
+
next if sect.type?(:S_ZEROFILL)
|
|
615
|
+
next if sect.type?(:S_THREAD_LOCAL_ZEROFILL)
|
|
561
616
|
next unless sect.offset < offset
|
|
562
617
|
|
|
563
618
|
offset = sect.offset
|
data/lib/macho/sections.rb
CHANGED
|
@@ -4,24 +4,24 @@ module MachO
|
|
|
4
4
|
# Classes and constants for parsing sections in Mach-O binaries.
|
|
5
5
|
module Sections
|
|
6
6
|
# type mask
|
|
7
|
-
|
|
7
|
+
SECTION_TYPE_MASK = 0x000000ff
|
|
8
8
|
|
|
9
9
|
# attributes mask
|
|
10
|
-
|
|
10
|
+
SECTION_ATTRIBUTES_MASK = 0xffffff00
|
|
11
11
|
|
|
12
12
|
# user settable attributes mask
|
|
13
|
-
|
|
13
|
+
SECTION_ATTRIBUTES_USR_MASK = 0xff000000
|
|
14
14
|
|
|
15
15
|
# system settable attributes mask
|
|
16
|
-
|
|
16
|
+
SECTION_ATTRIBUTES_SYS_MASK = 0x00ffff00
|
|
17
17
|
|
|
18
18
|
# maximum specifiable section alignment, as a power of 2
|
|
19
19
|
# @note see `MAXSECTALIGN` macro in `cctools/misc/lipo.c`
|
|
20
20
|
MAX_SECT_ALIGN = 15
|
|
21
21
|
|
|
22
|
-
# association of section
|
|
22
|
+
# association of section type symbols to values
|
|
23
23
|
# @api private
|
|
24
|
-
|
|
24
|
+
SECTION_TYPES = {
|
|
25
25
|
:S_REGULAR => 0x0,
|
|
26
26
|
:S_ZEROFILL => 0x1,
|
|
27
27
|
:S_CSTRING_LITERALS => 0x2,
|
|
@@ -44,6 +44,12 @@ module MachO
|
|
|
44
44
|
:S_THREAD_LOCAL_VARIABLES => 0x13,
|
|
45
45
|
:S_THREAD_LOCAL_VARIABLE_POINTERS => 0x14,
|
|
46
46
|
:S_THREAD_LOCAL_INIT_FUNCTION_POINTERS => 0x15,
|
|
47
|
+
:S_INIT_FUNC_OFFSETS => 0x16,
|
|
48
|
+
}.freeze
|
|
49
|
+
|
|
50
|
+
# association of section attribute symbols to values
|
|
51
|
+
# @api private
|
|
52
|
+
SECTION_ATTRIBUTES = {
|
|
47
53
|
:S_ATTR_PURE_INSTRUCTIONS => 0x80000000,
|
|
48
54
|
:S_ATTR_NO_TOC => 0x40000000,
|
|
49
55
|
:S_ATTR_STRIP_STATIC_SYMS => 0x20000000,
|
|
@@ -56,6 +62,13 @@ module MachO
|
|
|
56
62
|
:S_ATTR_LOC_RELOC => 0x00000100,
|
|
57
63
|
}.freeze
|
|
58
64
|
|
|
65
|
+
# association of section flag symbols to values
|
|
66
|
+
# @api private
|
|
67
|
+
SECTION_FLAGS = {
|
|
68
|
+
**SECTION_TYPES,
|
|
69
|
+
**SECTION_ATTRIBUTES,
|
|
70
|
+
}.freeze
|
|
71
|
+
|
|
59
72
|
# association of section name symbols to names
|
|
60
73
|
# @api private
|
|
61
74
|
SECTION_NAMES = {
|
|
@@ -118,6 +131,7 @@ module MachO
|
|
|
118
131
|
# @api private
|
|
119
132
|
def initialize(sectname, segname, addr, size, offset, align, reloff,
|
|
120
133
|
nreloc, flags, reserved1, reserved2)
|
|
134
|
+
super()
|
|
121
135
|
@sectname = sectname
|
|
122
136
|
@segname = segname
|
|
123
137
|
@addr = addr
|
|
@@ -146,6 +160,33 @@ module MachO
|
|
|
146
160
|
size.zero?
|
|
147
161
|
end
|
|
148
162
|
|
|
163
|
+
# @return [Integer] the raw numeric type of this section
|
|
164
|
+
def type
|
|
165
|
+
flags & SECTION_TYPE_MASK
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# @example
|
|
169
|
+
# puts "this section is regular" if sect.type?(:S_REGULAR)
|
|
170
|
+
# @param type_sym [Symbol] a section type symbol
|
|
171
|
+
# @return [Boolean] whether this section is of the given type
|
|
172
|
+
def type?(type_sym)
|
|
173
|
+
type == SECTION_TYPES[type_sym]
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# @return [Integer] the raw numeric attributes of this section
|
|
177
|
+
def attributes
|
|
178
|
+
flags & SECTION_ATTRIBUTES_MASK
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# @example
|
|
182
|
+
# puts "pure instructions" if sect.attribute?(:S_ATTR_PURE_INSTRUCTIONS)
|
|
183
|
+
# @param attr_sym [Symbol] a section attribute symbol
|
|
184
|
+
# @return [Boolean] whether this section is of the given type
|
|
185
|
+
def attribute?(attr_sym)
|
|
186
|
+
!!(attributes & SECTION_ATTRIBUTES[attr_sym])
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# @deprecated Use {#type?} or {#attribute?} instead.
|
|
149
190
|
# @example
|
|
150
191
|
# puts "this section is regular" if sect.flag?(:S_REGULAR)
|
|
151
192
|
# @param flag [Symbol] a section flag symbol
|
data/lib/macho/tools.rb
CHANGED
|
@@ -25,8 +25,6 @@ module MachO
|
|
|
25
25
|
|
|
26
26
|
file.change_dylib_id(new_id, options)
|
|
27
27
|
file.write!
|
|
28
|
-
|
|
29
|
-
MachO.codesign!(filename)
|
|
30
28
|
end
|
|
31
29
|
|
|
32
30
|
# Changes a shared library install name in a Mach-O or Fat binary,
|
|
@@ -43,8 +41,6 @@ module MachO
|
|
|
43
41
|
|
|
44
42
|
file.change_install_name(old_name, new_name, options)
|
|
45
43
|
file.write!
|
|
46
|
-
|
|
47
|
-
MachO.codesign!(filename)
|
|
48
44
|
end
|
|
49
45
|
|
|
50
46
|
# Changes a runtime path in a Mach-O or Fat binary, overwriting the source
|
|
@@ -55,14 +51,14 @@ module MachO
|
|
|
55
51
|
# @param options [Hash]
|
|
56
52
|
# @option options [Boolean] :strict (true) whether or not to fail loudly
|
|
57
53
|
# with an exception if the change cannot be performed
|
|
54
|
+
# @option options [Boolean] :uniq (false) whether or not to change duplicate
|
|
55
|
+
# rpaths simultaneously
|
|
58
56
|
# @return [void]
|
|
59
57
|
def self.change_rpath(filename, old_path, new_path, options = {})
|
|
60
58
|
file = MachO.open(filename)
|
|
61
59
|
|
|
62
60
|
file.change_rpath(old_path, new_path, options)
|
|
63
61
|
file.write!
|
|
64
|
-
|
|
65
|
-
MachO.codesign!(filename)
|
|
66
62
|
end
|
|
67
63
|
|
|
68
64
|
# Add a runtime path to a Mach-O or Fat binary, overwriting the source file.
|
|
@@ -77,8 +73,6 @@ module MachO
|
|
|
77
73
|
|
|
78
74
|
file.add_rpath(new_path, options)
|
|
79
75
|
file.write!
|
|
80
|
-
|
|
81
|
-
MachO.codesign!(filename)
|
|
82
76
|
end
|
|
83
77
|
|
|
84
78
|
# Delete a runtime path from a Mach-O or Fat binary, overwriting the source
|
|
@@ -88,14 +82,14 @@ module MachO
|
|
|
88
82
|
# @param options [Hash]
|
|
89
83
|
# @option options [Boolean] :strict (true) whether or not to fail loudly
|
|
90
84
|
# with an exception if the change cannot be performed
|
|
85
|
+
# @option options [Boolean] :uniq (false) whether or not to delete duplicate
|
|
86
|
+
# rpaths simultaneously
|
|
91
87
|
# @return [void]
|
|
92
88
|
def self.delete_rpath(filename, old_path, options = {})
|
|
93
89
|
file = MachO.open(filename)
|
|
94
90
|
|
|
95
91
|
file.delete_rpath(old_path, options)
|
|
96
92
|
file.write!
|
|
97
|
-
|
|
98
|
-
MachO.codesign!(filename)
|
|
99
93
|
end
|
|
100
94
|
|
|
101
95
|
# Merge multiple Mach-Os into one universal (Fat) binary.
|
|
@@ -116,8 +110,6 @@ module MachO
|
|
|
116
110
|
|
|
117
111
|
fat_macho = MachO::FatFile.new_from_machos(*machos, :fat64 => fat64)
|
|
118
112
|
fat_macho.write(filename)
|
|
119
|
-
|
|
120
|
-
MachO.codesign!(filename)
|
|
121
113
|
end
|
|
122
114
|
end
|
|
123
115
|
end
|
data/lib/macho/utils.rb
CHANGED
|
@@ -121,5 +121,12 @@ module MachO
|
|
|
121
121
|
def self.big_magic?(num)
|
|
122
122
|
[Headers::MH_MAGIC, Headers::MH_MAGIC_64].include? num
|
|
123
123
|
end
|
|
124
|
+
|
|
125
|
+
# Compares the given number to the known magic number for a compressed Mach-O slice.
|
|
126
|
+
# @param num [Integer] the number being checked
|
|
127
|
+
# @return [Boolean] whether `num` is a valid compressed header magic number
|
|
128
|
+
def self.compressed_magic?(num)
|
|
129
|
+
num == Headers::COMPRESSED_MAGIC
|
|
130
|
+
end
|
|
124
131
|
end
|
|
125
132
|
end
|
data/lib/macho.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "
|
|
3
|
+
require "open3"
|
|
4
|
+
|
|
4
5
|
require_relative "macho/structure"
|
|
5
6
|
require_relative "macho/view"
|
|
6
7
|
require_relative "macho/headers"
|
|
@@ -15,7 +16,7 @@ require_relative "macho/tools"
|
|
|
15
16
|
# The primary namespace for ruby-macho.
|
|
16
17
|
module MachO
|
|
17
18
|
# release version
|
|
18
|
-
VERSION = "
|
|
19
|
+
VERSION = "3.0.0"
|
|
19
20
|
|
|
20
21
|
# Opens the given filename as a MachOFile or FatFile, depending on its magic.
|
|
21
22
|
# @param filename [String] the file being opened
|
|
@@ -48,14 +49,13 @@ module MachO
|
|
|
48
49
|
# @return [void]
|
|
49
50
|
# @raise [ModificationError] if the operation fails
|
|
50
51
|
def self.codesign!(filename)
|
|
51
|
-
|
|
52
|
-
return if RUBY_PLATFORM !~ /darwin/
|
|
52
|
+
raise ArgumentError, "codesign binary is not available on Linux" if RUBY_PLATFORM !~ /darwin/
|
|
53
53
|
raise ArgumentError, "#{filename}: no such file" unless File.file?(filename)
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
_, _, status = Open3.capture3("codesign", "--sign", "-", "--force",
|
|
56
|
+
"--preserve-metadata=entitlements,requirements,flags,runtime",
|
|
57
|
+
filename)
|
|
58
58
|
|
|
59
|
-
raise
|
|
59
|
+
raise CodeSigningError, "#{filename}: signing failed!" unless status.success?
|
|
60
60
|
end
|
|
61
61
|
end
|
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:
|
|
4
|
+
version: 3.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- William Woodruff
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2022-01-11 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
|
|
@@ -34,7 +34,7 @@ homepage: https://github.com/Homebrew/ruby-macho
|
|
|
34
34
|
licenses:
|
|
35
35
|
- MIT
|
|
36
36
|
metadata: {}
|
|
37
|
-
post_install_message:
|
|
37
|
+
post_install_message:
|
|
38
38
|
rdoc_options: []
|
|
39
39
|
require_paths:
|
|
40
40
|
- lib
|
|
@@ -42,15 +42,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
42
42
|
requirements:
|
|
43
43
|
- - ">="
|
|
44
44
|
- !ruby/object:Gem::Version
|
|
45
|
-
version: '2.
|
|
45
|
+
version: '2.6'
|
|
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
|
-
rubygems_version: 3.
|
|
53
|
-
signing_key:
|
|
52
|
+
rubygems_version: 3.2.32
|
|
53
|
+
signing_key:
|
|
54
54
|
specification_version: 4
|
|
55
55
|
summary: ruby-macho - Mach-O file analyzer.
|
|
56
56
|
test_files: []
|