ruby-macho 2.0.0 → 2.5.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 +8 -1
- data/lib/macho.rb +23 -2
- data/lib/macho/exceptions.rb +24 -11
- data/lib/macho/fat_file.rb +55 -31
- data/lib/macho/headers.rb +86 -9
- data/lib/macho/load_commands.rb +53 -40
- data/lib/macho/macho_file.rb +35 -21
- data/lib/macho/sections.rb +7 -2
- data/lib/macho/structure.rb +3 -1
- data/lib/macho/tools.rb +5 -2
- data/lib/macho/utils.rb +22 -6
- data/lib/macho/view.rb +2 -0
- metadata +7 -8
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cd8907fa4f63922522d0e84d6b5cdcc2a0c36f423db72bd0f947d328935c63de
|
|
4
|
+
data.tar.gz: b7f2f638a051121f3f5e3f5507c8e69be56ec25239df2a435a9cfebf08834619
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 06bec5c6b0c5336d87298f85ffa7e9bcf09ef092f18fba36b3ee5b6c6469b2e0ed9cd02732808eef9b8cae9095b348ed2eb9240a531200055fed615ca3cdf149
|
|
7
|
+
data.tar.gz: 51b382c46e95a60f416cfe304bbd7a95169b5f6dbc0709a6ef3ce7bc6ede8d29d3ea30b2032ffb73357bbb2a7df0f21a23f553f70c0fbaf9a37c538d7c98158b
|
data/README.md
CHANGED
|
@@ -13,6 +13,14 @@ The [Mach-O file format](https://en.wikipedia.org/wiki/Mach-O) is used by macOS
|
|
|
13
13
|
and iOS (among others) as a general purpose binary format for object files,
|
|
14
14
|
executables, dynamic libraries, and so forth.
|
|
15
15
|
|
|
16
|
+
### Installation
|
|
17
|
+
|
|
18
|
+
ruby-macho can be installed via RubyGems:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
$ gem install ruby-macho
|
|
22
|
+
```
|
|
23
|
+
|
|
16
24
|
### Documentation
|
|
17
25
|
|
|
18
26
|
Full documentation is available on [RubyDoc](http://www.rubydoc.info/gems/ruby-macho/).
|
|
@@ -46,7 +54,6 @@ puts lc_vers.version_string # => "10.10.0"
|
|
|
46
54
|
|
|
47
55
|
### What needs to be done?
|
|
48
56
|
|
|
49
|
-
* Documentation.
|
|
50
57
|
* Unit and performance testing.
|
|
51
58
|
|
|
52
59
|
Attribution:
|
data/lib/macho.rb
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "open3"
|
|
4
|
+
|
|
1
5
|
require_relative "macho/structure"
|
|
2
6
|
require_relative "macho/view"
|
|
3
7
|
require_relative "macho/headers"
|
|
@@ -12,7 +16,7 @@ require_relative "macho/tools"
|
|
|
12
16
|
# The primary namespace for ruby-macho.
|
|
13
17
|
module MachO
|
|
14
18
|
# release version
|
|
15
|
-
VERSION = "2.
|
|
19
|
+
VERSION = "2.5.0"
|
|
16
20
|
|
|
17
21
|
# Opens the given filename as a MachOFile or FatFile, depending on its magic.
|
|
18
22
|
# @param filename [String] the file being opened
|
|
@@ -25,7 +29,7 @@ module MachO
|
|
|
25
29
|
raise ArgumentError, "#{filename}: no such file" unless File.file?(filename)
|
|
26
30
|
raise TruncatedFileError unless File.stat(filename).size >= 4
|
|
27
31
|
|
|
28
|
-
magic = File.open(filename, "rb") { |f| f.read(4) }.
|
|
32
|
+
magic = File.open(filename, "rb") { |f| f.read(4) }.unpack1("N")
|
|
29
33
|
|
|
30
34
|
if Utils.fat_magic?(magic)
|
|
31
35
|
file = FatFile.new(filename)
|
|
@@ -37,4 +41,21 @@ module MachO
|
|
|
37
41
|
|
|
38
42
|
file
|
|
39
43
|
end
|
|
44
|
+
|
|
45
|
+
# Signs the dylib using an ad-hoc identity.
|
|
46
|
+
# Necessary after making any changes to a dylib, since otherwise
|
|
47
|
+
# changing a signed file invalidates its signature.
|
|
48
|
+
# @param filename [String] the file being opened
|
|
49
|
+
# @return [void]
|
|
50
|
+
# @raise [ModificationError] if the operation fails
|
|
51
|
+
def self.codesign!(filename)
|
|
52
|
+
raise ArgumentError, "codesign binary is not available on Linux" if RUBY_PLATFORM !~ /darwin/
|
|
53
|
+
raise ArgumentError, "#{filename}: no such file" unless File.file?(filename)
|
|
54
|
+
|
|
55
|
+
_, _, status = Open3.capture3("codesign", "--sign", "-", "--force",
|
|
56
|
+
"--preserve-metadata=entitlements,requirements,flags,runtime",
|
|
57
|
+
filename)
|
|
58
|
+
|
|
59
|
+
raise CodeSigningError, "#{filename}: signing failed!" unless status.success?
|
|
60
|
+
end
|
|
40
61
|
end
|
data/lib/macho/exceptions.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module MachO
|
|
2
4
|
# A generic Mach-O error in execution.
|
|
3
5
|
class MachOError < RuntimeError
|
|
@@ -7,6 +9,11 @@ module MachO
|
|
|
7
9
|
class ModificationError < MachOError
|
|
8
10
|
end
|
|
9
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
|
+
|
|
10
17
|
# Raised when a Mach-O file modification fails but can be recovered when
|
|
11
18
|
# operating on multiple Mach-O slices of a fat binary in non-strict mode.
|
|
12
19
|
class RecoverableModificationError < ModificationError
|
|
@@ -25,10 +32,6 @@ module MachO
|
|
|
25
32
|
|
|
26
33
|
# Raised when a file is not a Mach-O.
|
|
27
34
|
class NotAMachOError < MachOError
|
|
28
|
-
# @param error [String] the error in question
|
|
29
|
-
def initialize(error)
|
|
30
|
-
super error
|
|
31
|
-
end
|
|
32
35
|
end
|
|
33
36
|
|
|
34
37
|
# Raised when a file is too short to be a valid Mach-O file.
|
|
@@ -41,8 +44,8 @@ module MachO
|
|
|
41
44
|
# Raised when a file's magic bytes are not valid Mach-O magic.
|
|
42
45
|
class MagicError < NotAMachOError
|
|
43
46
|
# @param num [Integer] the unknown number
|
|
44
|
-
def initialize(
|
|
45
|
-
super "Unrecognized Mach-O magic: 0x
|
|
47
|
+
def initialize(magic)
|
|
48
|
+
super "Unrecognized Mach-O magic: 0x%02<magic>x" % { :magic => magic }
|
|
46
49
|
end
|
|
47
50
|
end
|
|
48
51
|
|
|
@@ -71,7 +74,7 @@ module MachO
|
|
|
71
74
|
class CPUTypeError < MachOError
|
|
72
75
|
# @param cputype [Integer] the unknown CPU type
|
|
73
76
|
def initialize(cputype)
|
|
74
|
-
super "Unrecognized CPU type: 0x
|
|
77
|
+
super "Unrecognized CPU type: 0x%08<cputype>x" % { :cputype => cputype }
|
|
75
78
|
end
|
|
76
79
|
end
|
|
77
80
|
|
|
@@ -80,8 +83,8 @@ module MachO
|
|
|
80
83
|
# @param cputype [Integer] the CPU type of the unknown pair
|
|
81
84
|
# @param cpusubtype [Integer] the CPU sub-type of the unknown pair
|
|
82
85
|
def initialize(cputype, cpusubtype)
|
|
83
|
-
super "Unrecognized CPU sub-type: 0x
|
|
84
|
-
" (for CPU type: 0x
|
|
86
|
+
super "Unrecognized CPU sub-type: 0x%08<cpusubtype>x" \
|
|
87
|
+
" (for CPU type: 0x%08<cputype>x" % { :cputype => cputype, :cpusubtype => cpusubtype }
|
|
85
88
|
end
|
|
86
89
|
end
|
|
87
90
|
|
|
@@ -89,7 +92,7 @@ module MachO
|
|
|
89
92
|
class FiletypeError < MachOError
|
|
90
93
|
# @param num [Integer] the unknown number
|
|
91
94
|
def initialize(num)
|
|
92
|
-
super "Unrecognized Mach-O filetype code: 0x
|
|
95
|
+
super "Unrecognized Mach-O filetype code: 0x%02<num>x" % { :num => num }
|
|
93
96
|
end
|
|
94
97
|
end
|
|
95
98
|
|
|
@@ -97,7 +100,7 @@ module MachO
|
|
|
97
100
|
class LoadCommandError < MachOError
|
|
98
101
|
# @param num [Integer] the unknown number
|
|
99
102
|
def initialize(num)
|
|
100
|
-
super "Unrecognized Mach-O load command: 0x
|
|
103
|
+
super "Unrecognized Mach-O load command: 0x%02<num>x" % { :num => num }
|
|
101
104
|
end
|
|
102
105
|
end
|
|
103
106
|
|
|
@@ -194,4 +197,14 @@ module MachO
|
|
|
194
197
|
super "Unimplemented: #{thing}"
|
|
195
198
|
end
|
|
196
199
|
end
|
|
200
|
+
|
|
201
|
+
# Raised when attempting to create a {FatFile} from one or more {MachOFile}s
|
|
202
|
+
# whose offsets will not fit within the resulting 32-bit {Headers::FatArch#offset} fields.
|
|
203
|
+
class FatArchOffsetOverflowError < MachOError
|
|
204
|
+
# @param offset [Integer] the offending offset
|
|
205
|
+
def initialize(offset)
|
|
206
|
+
super "Offset #{offset} exceeds the 32-bit width of a fat_arch offset." \
|
|
207
|
+
" Consider merging with `fat64: true`"
|
|
208
|
+
end
|
|
209
|
+
end
|
|
197
210
|
end
|
data/lib/macho/fat_file.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require "forwardable"
|
|
2
4
|
|
|
3
5
|
module MachO
|
|
@@ -11,10 +13,14 @@ module MachO
|
|
|
11
13
|
# @return [String] the filename loaded from, or nil if loaded from a binary string
|
|
12
14
|
attr_accessor :filename
|
|
13
15
|
|
|
16
|
+
# @return [Hash] any parser options that the instance was created with
|
|
17
|
+
# @note Options specified in a {FatFile} trickle down into the internal {MachOFile}s.
|
|
18
|
+
attr_reader :options
|
|
19
|
+
|
|
14
20
|
# @return [Headers::FatHeader] the file's header
|
|
15
21
|
attr_reader :header
|
|
16
22
|
|
|
17
|
-
# @return [Array<Headers::FatArch
|
|
23
|
+
# @return [Array<Headers::FatArch>, Array<Headers::FatArch64] an array of fat architectures
|
|
18
24
|
attr_reader :fat_archs
|
|
19
25
|
|
|
20
26
|
# @return [Array<MachOFile>] an array of Mach-O binaries
|
|
@@ -22,37 +28,47 @@ module MachO
|
|
|
22
28
|
|
|
23
29
|
# Creates a new FatFile from the given (single-arch) Mach-Os
|
|
24
30
|
# @param machos [Array<MachOFile>] the machos to combine
|
|
31
|
+
# @param fat64 [Boolean] whether to use {Headers::FatArch64}s to represent each slice
|
|
25
32
|
# @return [FatFile] a new FatFile containing the give machos
|
|
26
33
|
# @raise [ArgumentError] if less than one Mach-O is given
|
|
27
|
-
|
|
34
|
+
# @raise [FatArchOffsetOverflowError] if the Mach-Os are too big to be represented
|
|
35
|
+
# in a 32-bit {Headers::FatArch} and `fat64` is `false`.
|
|
36
|
+
def self.new_from_machos(*machos, fat64: false)
|
|
28
37
|
raise ArgumentError, "expected at least one Mach-O" if machos.empty?
|
|
29
38
|
|
|
39
|
+
fa_klass, magic = if fat64
|
|
40
|
+
[Headers::FatArch64, Headers::FAT_MAGIC_64]
|
|
41
|
+
else
|
|
42
|
+
[Headers::FatArch, Headers::FAT_MAGIC]
|
|
43
|
+
end
|
|
44
|
+
|
|
30
45
|
# put the smaller alignments further forwards in fat macho, so that we do less padding
|
|
31
46
|
machos = machos.sort_by(&:segment_alignment)
|
|
32
47
|
|
|
33
48
|
bin = +""
|
|
34
49
|
|
|
35
|
-
bin << Headers::FatHeader.new(
|
|
36
|
-
offset = Headers::FatHeader.bytesize + (machos.size *
|
|
50
|
+
bin << Headers::FatHeader.new(magic, machos.size).serialize
|
|
51
|
+
offset = Headers::FatHeader.bytesize + (machos.size * fa_klass.bytesize)
|
|
37
52
|
|
|
38
53
|
macho_pads = {}
|
|
39
|
-
macho_bins = {}
|
|
40
54
|
|
|
41
55
|
machos.each do |macho|
|
|
42
|
-
macho_offset
|
|
56
|
+
macho_offset = Utils.round(offset, 2**macho.segment_alignment)
|
|
57
|
+
|
|
58
|
+
raise FatArchOffsetOverflowError, macho_offset if !fat64 && macho_offset > (2**32 - 1)
|
|
59
|
+
|
|
43
60
|
macho_pads[macho] = Utils.padding_for(offset, 2**macho.segment_alignment)
|
|
44
|
-
macho_bins[macho] = macho.serialize
|
|
45
61
|
|
|
46
|
-
bin <<
|
|
47
|
-
|
|
48
|
-
|
|
62
|
+
bin << fa_klass.new(macho.header.cputype, macho.header.cpusubtype,
|
|
63
|
+
macho_offset, macho.serialize.bytesize,
|
|
64
|
+
macho.segment_alignment).serialize
|
|
49
65
|
|
|
50
|
-
offset += (
|
|
66
|
+
offset += (macho.serialize.bytesize + macho_pads[macho])
|
|
51
67
|
end
|
|
52
68
|
|
|
53
|
-
machos.each do |macho|
|
|
69
|
+
machos.each do |macho| # rubocop:disable Style/CombinableLoops
|
|
54
70
|
bin << Utils.nullpad(macho_pads[macho])
|
|
55
|
-
bin <<
|
|
71
|
+
bin << macho.serialize
|
|
56
72
|
end
|
|
57
73
|
|
|
58
74
|
new_from_bin(bin)
|
|
@@ -60,30 +76,36 @@ module MachO
|
|
|
60
76
|
|
|
61
77
|
# Creates a new FatFile instance from a binary string.
|
|
62
78
|
# @param bin [String] a binary string containing raw Mach-O data
|
|
79
|
+
# @param opts [Hash] options to control the parser with
|
|
80
|
+
# @note see {MachOFile#initialize} for currently valid options
|
|
63
81
|
# @return [FatFile] a new FatFile
|
|
64
|
-
def self.new_from_bin(bin)
|
|
82
|
+
def self.new_from_bin(bin, **opts)
|
|
65
83
|
instance = allocate
|
|
66
|
-
instance.initialize_from_bin(bin)
|
|
84
|
+
instance.initialize_from_bin(bin, opts)
|
|
67
85
|
|
|
68
86
|
instance
|
|
69
87
|
end
|
|
70
88
|
|
|
71
89
|
# Creates a new FatFile from the given filename.
|
|
72
90
|
# @param filename [String] the fat file to load from
|
|
91
|
+
# @param opts [Hash] options to control the parser with
|
|
92
|
+
# @note see {MachOFile#initialize} for currently valid options
|
|
73
93
|
# @raise [ArgumentError] if the given file does not exist
|
|
74
|
-
def initialize(filename)
|
|
94
|
+
def initialize(filename, **opts)
|
|
75
95
|
raise ArgumentError, "#{filename}: no such file" unless File.file?(filename)
|
|
76
96
|
|
|
77
97
|
@filename = filename
|
|
98
|
+
@options = opts
|
|
78
99
|
@raw_data = File.open(@filename, "rb", &:read)
|
|
79
100
|
populate_fields
|
|
80
101
|
end
|
|
81
102
|
|
|
82
|
-
# Initializes a new FatFile instance from a binary string.
|
|
103
|
+
# Initializes a new FatFile instance from a binary string with the given options.
|
|
83
104
|
# @see new_from_bin
|
|
84
105
|
# @api private
|
|
85
|
-
def initialize_from_bin(bin)
|
|
106
|
+
def initialize_from_bin(bin, opts)
|
|
86
107
|
@filename = nil
|
|
108
|
+
@options = opts
|
|
87
109
|
@raw_data = bin
|
|
88
110
|
populate_fields
|
|
89
111
|
end
|
|
@@ -278,6 +300,7 @@ module MachO
|
|
|
278
300
|
# @note Overwrites all data in the file!
|
|
279
301
|
def write!
|
|
280
302
|
raise MachOError, "no initial file to write to" if filename.nil?
|
|
303
|
+
|
|
281
304
|
File.open(@filename, "wb") { |f| f.write(@raw_data) }
|
|
282
305
|
end
|
|
283
306
|
|
|
@@ -327,10 +350,12 @@ module MachO
|
|
|
327
350
|
def populate_fat_archs
|
|
328
351
|
archs = []
|
|
329
352
|
|
|
330
|
-
|
|
331
|
-
|
|
353
|
+
fa_klass = Utils.fat_magic32?(header.magic) ? Headers::FatArch : Headers::FatArch64
|
|
354
|
+
fa_off = Headers::FatHeader.bytesize
|
|
355
|
+
fa_len = fa_klass.bytesize
|
|
356
|
+
|
|
332
357
|
header.nfat_arch.times do |i|
|
|
333
|
-
archs <<
|
|
358
|
+
archs << fa_klass.new_from_bin(:big, @raw_data[fa_off + (fa_len * i), fa_len])
|
|
334
359
|
end
|
|
335
360
|
|
|
336
361
|
archs
|
|
@@ -343,7 +368,7 @@ module MachO
|
|
|
343
368
|
machos = []
|
|
344
369
|
|
|
345
370
|
fat_archs.each do |arch|
|
|
346
|
-
machos << MachOFile.new_from_bin(@raw_data[arch.offset, arch.size])
|
|
371
|
+
machos << MachOFile.new_from_bin(@raw_data[arch.offset, arch.size], **options)
|
|
347
372
|
end
|
|
348
373
|
|
|
349
374
|
machos
|
|
@@ -373,15 +398,14 @@ module MachO
|
|
|
373
398
|
errors = []
|
|
374
399
|
|
|
375
400
|
machos.each_with_index do |macho, index|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
end
|
|
401
|
+
yield macho
|
|
402
|
+
rescue RecoverableModificationError => e
|
|
403
|
+
e.macho_slice = index
|
|
404
|
+
|
|
405
|
+
# Strict mode: Immediately re-raise. Otherwise: Retain, check later.
|
|
406
|
+
raise e if strict
|
|
407
|
+
|
|
408
|
+
errors << e
|
|
385
409
|
end
|
|
386
410
|
|
|
387
411
|
# Non-strict mode: Raise first error if *all* Mach-O slices failed.
|
data/lib/macho/headers.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module MachO
|
|
2
4
|
# Classes and constants for parsing the headers of Mach-O binaries.
|
|
3
5
|
module Headers
|
|
@@ -6,11 +8,19 @@ module MachO
|
|
|
6
8
|
FAT_MAGIC = 0xcafebabe
|
|
7
9
|
|
|
8
10
|
# little-endian fat magic
|
|
9
|
-
#
|
|
10
|
-
# fat headers are always big-endian
|
|
11
|
+
# @note This is defined for completeness, but should never appear in ruby-macho code,
|
|
12
|
+
# since fat headers are always big-endian.
|
|
11
13
|
# @api private
|
|
12
14
|
FAT_CIGAM = 0xbebafeca
|
|
13
15
|
|
|
16
|
+
# 64-bit big-endian fat magic
|
|
17
|
+
FAT_MAGIC_64 = 0xcafebabf
|
|
18
|
+
|
|
19
|
+
# 64-bit little-endian fat magic
|
|
20
|
+
# @note This is defined for completeness, but should never appear in ruby-macho code,
|
|
21
|
+
# since fat headers are always big-endian.
|
|
22
|
+
FAT_CIGAM_64 = 0xbfbafeca
|
|
23
|
+
|
|
14
24
|
# 32-bit big-endian magic
|
|
15
25
|
# @api private
|
|
16
26
|
MH_MAGIC = 0xfeedface
|
|
@@ -31,6 +41,7 @@ module MachO
|
|
|
31
41
|
# @api private
|
|
32
42
|
MH_MAGICS = {
|
|
33
43
|
FAT_MAGIC => "FAT_MAGIC",
|
|
44
|
+
FAT_MAGIC_64 => "FAT_MAGIC_64",
|
|
34
45
|
MH_MAGIC => "MH_MAGIC",
|
|
35
46
|
MH_CIGAM => "MH_CIGAM",
|
|
36
47
|
MH_MAGIC_64 => "MH_MAGIC_64",
|
|
@@ -41,6 +52,11 @@ module MachO
|
|
|
41
52
|
# @api private
|
|
42
53
|
CPU_ARCH_ABI64 = 0x01000000
|
|
43
54
|
|
|
55
|
+
# mask for CPUs with 64-bit architectures (when running a 32-bit ABI?)
|
|
56
|
+
# @see https://github.com/Homebrew/ruby-macho/issues/113
|
|
57
|
+
# @api private
|
|
58
|
+
CPU_ARCH_ABI32 = 0x02000000
|
|
59
|
+
|
|
44
60
|
# any CPU (unused?)
|
|
45
61
|
# @api private
|
|
46
62
|
CPU_TYPE_ANY = -1
|
|
@@ -69,6 +85,10 @@ module MachO
|
|
|
69
85
|
# @api private
|
|
70
86
|
CPU_TYPE_ARM64 = (CPU_TYPE_ARM | CPU_ARCH_ABI64)
|
|
71
87
|
|
|
88
|
+
# 64-bit ARM compatible CPUs (running in 32-bit mode?)
|
|
89
|
+
# @see https://github.com/Homebrew/ruby-macho/issues/113
|
|
90
|
+
CPU_TYPE_ARM64_32 = (CPU_TYPE_ARM | CPU_ARCH_ABI32)
|
|
91
|
+
|
|
72
92
|
# PowerPC compatible CPUs
|
|
73
93
|
# @api private
|
|
74
94
|
CPU_TYPE_POWERPC = 0x12
|
|
@@ -85,6 +105,7 @@ module MachO
|
|
|
85
105
|
CPU_TYPE_X86_64 => :x86_64,
|
|
86
106
|
CPU_TYPE_ARM => :arm,
|
|
87
107
|
CPU_TYPE_ARM64 => :arm64,
|
|
108
|
+
CPU_TYPE_ARM64_32 => :arm64_32,
|
|
88
109
|
CPU_TYPE_POWERPC => :ppc,
|
|
89
110
|
CPU_TYPE_POWERPC64 => :ppc64,
|
|
90
111
|
}.freeze
|
|
@@ -218,6 +239,14 @@ module MachO
|
|
|
218
239
|
# @api private
|
|
219
240
|
CPU_SUBTYPE_ARM64_V8 = 1
|
|
220
241
|
|
|
242
|
+
# the v8 sub-type for `CPU_TYPE_ARM64_32`
|
|
243
|
+
# @api private
|
|
244
|
+
CPU_SUBTYPE_ARM64_32_V8 = 1
|
|
245
|
+
|
|
246
|
+
# the e (A12) sub-type for `CPU_TYPE_ARM64`
|
|
247
|
+
# @api private
|
|
248
|
+
CPU_SUBTYPE_ARM64E = 2
|
|
249
|
+
|
|
221
250
|
# the lowest common sub-type for `CPU_TYPE_MC88000`
|
|
222
251
|
# @api private
|
|
223
252
|
CPU_SUBTYPE_MC88000_ALL = 0
|
|
@@ -327,6 +356,10 @@ module MachO
|
|
|
327
356
|
CPU_TYPE_ARM64 => {
|
|
328
357
|
CPU_SUBTYPE_ARM64_ALL => :arm64,
|
|
329
358
|
CPU_SUBTYPE_ARM64_V8 => :arm64v8,
|
|
359
|
+
CPU_SUBTYPE_ARM64E => :arm64e,
|
|
360
|
+
}.freeze,
|
|
361
|
+
CPU_TYPE_ARM64_32 => {
|
|
362
|
+
CPU_SUBTYPE_ARM64_32_V8 => :arm64_32v8,
|
|
330
363
|
}.freeze,
|
|
331
364
|
CPU_TYPE_POWERPC => {
|
|
332
365
|
CPU_SUBTYPE_POWERPC_ALL => :ppc,
|
|
@@ -459,7 +492,7 @@ module MachO
|
|
|
459
492
|
# always big-endian
|
|
460
493
|
# @see MachOStructure::FORMAT
|
|
461
494
|
# @api private
|
|
462
|
-
FORMAT = "N2"
|
|
495
|
+
FORMAT = "N2"
|
|
463
496
|
|
|
464
497
|
# @see MachOStructure::SIZEOF
|
|
465
498
|
# @api private
|
|
@@ -467,6 +500,7 @@ module MachO
|
|
|
467
500
|
|
|
468
501
|
# @api private
|
|
469
502
|
def initialize(magic, nfat_arch)
|
|
503
|
+
super()
|
|
470
504
|
@magic = magic
|
|
471
505
|
@nfat_arch = nfat_arch
|
|
472
506
|
end
|
|
@@ -486,8 +520,10 @@ module MachO
|
|
|
486
520
|
end
|
|
487
521
|
end
|
|
488
522
|
|
|
489
|
-
#
|
|
490
|
-
#
|
|
523
|
+
# 32-bit fat binary header architecture structure. A 32-bit fat Mach-O has one or more of
|
|
524
|
+
# these, indicating one or more internal Mach-O blobs.
|
|
525
|
+
# @note "32-bit" indicates the fact that this structure stores 32-bit offsets, not that the
|
|
526
|
+
# Mach-Os that it points to necessarily *are* 32-bit.
|
|
491
527
|
# @see MachO::Headers::FatHeader
|
|
492
528
|
class FatArch < MachOStructure
|
|
493
529
|
# @return [Integer] the CPU type of the Mach-O
|
|
@@ -505,10 +541,10 @@ module MachO
|
|
|
505
541
|
# @return [Integer] the alignment, as a power of 2
|
|
506
542
|
attr_reader :align
|
|
507
543
|
|
|
508
|
-
#
|
|
544
|
+
# @note Always big endian.
|
|
509
545
|
# @see MachOStructure::FORMAT
|
|
510
546
|
# @api private
|
|
511
|
-
FORMAT = "
|
|
547
|
+
FORMAT = "L>5"
|
|
512
548
|
|
|
513
549
|
# @see MachOStructure::SIZEOF
|
|
514
550
|
# @api private
|
|
@@ -516,6 +552,7 @@ module MachO
|
|
|
516
552
|
|
|
517
553
|
# @api private
|
|
518
554
|
def initialize(cputype, cpusubtype, offset, size, align)
|
|
555
|
+
super()
|
|
519
556
|
@cputype = cputype
|
|
520
557
|
@cpusubtype = cpusubtype & ~CPU_SUBTYPE_MASK
|
|
521
558
|
@offset = offset
|
|
@@ -542,6 +579,43 @@ module MachO
|
|
|
542
579
|
end
|
|
543
580
|
end
|
|
544
581
|
|
|
582
|
+
# 64-bit fat binary header architecture structure. A 64-bit fat Mach-O has one or more of
|
|
583
|
+
# these, indicating one or more internal Mach-O blobs.
|
|
584
|
+
# @note "64-bit" indicates the fact that this structure stores 64-bit offsets, not that the
|
|
585
|
+
# Mach-Os that it points to necessarily *are* 64-bit.
|
|
586
|
+
# @see MachO::Headers::FatHeader
|
|
587
|
+
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"
|
|
595
|
+
|
|
596
|
+
# @see MachOStructure::SIZEOF
|
|
597
|
+
# @api private
|
|
598
|
+
SIZEOF = 32
|
|
599
|
+
|
|
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
|
|
605
|
+
|
|
606
|
+
# @return [String] the serialized fields of the fat arch
|
|
607
|
+
def serialize
|
|
608
|
+
[cputype, cpusubtype, offset, size, align, reserved].pack(FORMAT)
|
|
609
|
+
end
|
|
610
|
+
|
|
611
|
+
# @return [Hash] a hash representation of this {FatArch64}
|
|
612
|
+
def to_h
|
|
613
|
+
{
|
|
614
|
+
"reserved" => reserved,
|
|
615
|
+
}.merge super
|
|
616
|
+
end
|
|
617
|
+
end
|
|
618
|
+
|
|
545
619
|
# 32-bit Mach-O file header structure
|
|
546
620
|
class MachHeader < MachOStructure
|
|
547
621
|
# @return [Integer] the magic number
|
|
@@ -567,7 +641,7 @@ module MachO
|
|
|
567
641
|
|
|
568
642
|
# @see MachOStructure::FORMAT
|
|
569
643
|
# @api private
|
|
570
|
-
FORMAT = "L=7"
|
|
644
|
+
FORMAT = "L=7"
|
|
571
645
|
|
|
572
646
|
# @see MachOStructure::SIZEOF
|
|
573
647
|
# @api private
|
|
@@ -576,6 +650,7 @@ module MachO
|
|
|
576
650
|
# @api private
|
|
577
651
|
def initialize(magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds,
|
|
578
652
|
flags)
|
|
653
|
+
super()
|
|
579
654
|
@magic = magic
|
|
580
655
|
@cputype = cputype
|
|
581
656
|
# For now we're not interested in additional capability bits also to be
|
|
@@ -593,7 +668,9 @@ module MachO
|
|
|
593
668
|
# @return [Boolean] true if `flag` is present in the header's flag section
|
|
594
669
|
def flag?(flag)
|
|
595
670
|
flag = MH_FLAGS[flag]
|
|
671
|
+
|
|
596
672
|
return false if flag.nil?
|
|
673
|
+
|
|
597
674
|
flags & flag == flag
|
|
598
675
|
end
|
|
599
676
|
|
|
@@ -688,7 +765,7 @@ module MachO
|
|
|
688
765
|
|
|
689
766
|
# @see MachOStructure::FORMAT
|
|
690
767
|
# @api private
|
|
691
|
-
FORMAT = "L=8"
|
|
768
|
+
FORMAT = "L=8"
|
|
692
769
|
|
|
693
770
|
# @see MachOStructure::SIZEOF
|
|
694
771
|
# @api private
|
data/lib/macho/load_commands.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module MachO
|
|
2
4
|
# Classes and constants for parsing load commands in Mach-O binaries.
|
|
3
5
|
module LoadCommands
|
|
@@ -60,6 +62,8 @@ module MachO
|
|
|
60
62
|
0x30 => :LC_VERSION_MIN_WATCHOS,
|
|
61
63
|
0x31 => :LC_NOTE,
|
|
62
64
|
0x32 => :LC_BUILD_VERSION,
|
|
65
|
+
(0x33 | LC_REQ_DYLD) => :LC_DYLD_EXPORTS_TRIE,
|
|
66
|
+
(0x34 | LC_REQ_DYLD) => :LD_DYLD_CHAINED_FIXUPS,
|
|
63
67
|
}.freeze
|
|
64
68
|
|
|
65
69
|
# association of symbol representations to load command constants
|
|
@@ -145,6 +149,8 @@ module MachO
|
|
|
145
149
|
:LC_VERSION_MIN_WATCHOS => "VersionMinCommand",
|
|
146
150
|
:LC_NOTE => "NoteCommand",
|
|
147
151
|
:LC_BUILD_VERSION => "BuildVersionCommand",
|
|
152
|
+
:LC_DYLD_EXPORTS_TRIE => "LinkeditDataCommand",
|
|
153
|
+
:LD_DYLD_CHAINED_FIXUPS => "LinkeditDataCommand",
|
|
148
154
|
}.freeze
|
|
149
155
|
|
|
150
156
|
# association of segment name symbols to names
|
|
@@ -186,7 +192,7 @@ module MachO
|
|
|
186
192
|
|
|
187
193
|
# @see MachOStructure::FORMAT
|
|
188
194
|
# @api private
|
|
189
|
-
FORMAT = "L=2"
|
|
195
|
+
FORMAT = "L=2"
|
|
190
196
|
|
|
191
197
|
# @see MachOStructure::SIZEOF
|
|
192
198
|
# @api private
|
|
@@ -225,6 +231,7 @@ module MachO
|
|
|
225
231
|
# @param cmdsize [Integer] the size of the load command in bytes
|
|
226
232
|
# @api private
|
|
227
233
|
def initialize(view, cmd, cmdsize)
|
|
234
|
+
super()
|
|
228
235
|
@view = view
|
|
229
236
|
@cmd = cmd
|
|
230
237
|
@cmdsize = cmdsize
|
|
@@ -242,6 +249,7 @@ module MachO
|
|
|
242
249
|
# @api private
|
|
243
250
|
def serialize(context)
|
|
244
251
|
raise LoadCommandNotSerializableError, LOAD_COMMANDS[cmd] unless serializable?
|
|
252
|
+
|
|
245
253
|
format = Utils.specialize_format(FORMAT, context.endianness)
|
|
246
254
|
[cmd, SIZEOF].pack(format)
|
|
247
255
|
end
|
|
@@ -298,7 +306,9 @@ module MachO
|
|
|
298
306
|
lc_end = view.offset + lc.cmdsize - 1
|
|
299
307
|
raw_string = view.raw_data.slice(lc_str_abs..lc_end)
|
|
300
308
|
@string, null_byte, _padding = raw_string.partition("\x00")
|
|
309
|
+
|
|
301
310
|
raise LCStrMalformedError, lc if null_byte.empty?
|
|
311
|
+
|
|
302
312
|
@string_offset = lc_str
|
|
303
313
|
else
|
|
304
314
|
@string = lc_str
|
|
@@ -362,7 +372,7 @@ module MachO
|
|
|
362
372
|
|
|
363
373
|
# @see MachOStructure::FORMAT
|
|
364
374
|
# @api private
|
|
365
|
-
FORMAT = "L=2a16"
|
|
375
|
+
FORMAT = "L=2a16"
|
|
366
376
|
|
|
367
377
|
# @see MachOStructure::SIZEOF
|
|
368
378
|
# @api private
|
|
@@ -376,7 +386,7 @@ module MachO
|
|
|
376
386
|
|
|
377
387
|
# @return [String] a string representation of the UUID
|
|
378
388
|
def uuid_string
|
|
379
|
-
hexes = uuid.map { |
|
|
389
|
+
hexes = uuid.map { |elem| "%02<elem>x" % { :elem => elem } }
|
|
380
390
|
segs = [
|
|
381
391
|
hexes[0..3].join, hexes[4..5].join, hexes[6..7].join,
|
|
382
392
|
hexes[8..9].join, hexes[10..15].join
|
|
@@ -426,7 +436,7 @@ module MachO
|
|
|
426
436
|
|
|
427
437
|
# @see MachOStructure::FORMAT
|
|
428
438
|
# @api private
|
|
429
|
-
FORMAT = "L=2Z16L=4l=2L=2"
|
|
439
|
+
FORMAT = "L=2Z16L=4l=2L=2"
|
|
430
440
|
|
|
431
441
|
# @see MachOStructure::SIZEOF
|
|
432
442
|
# @api private
|
|
@@ -473,7 +483,9 @@ module MachO
|
|
|
473
483
|
# @return [Boolean] true if `flag` is present in the segment's flag field
|
|
474
484
|
def flag?(flag)
|
|
475
485
|
flag = SEGMENT_FLAGS[flag]
|
|
486
|
+
|
|
476
487
|
return false if flag.nil?
|
|
488
|
+
|
|
477
489
|
flags & flag == flag
|
|
478
490
|
end
|
|
479
491
|
|
|
@@ -519,7 +531,7 @@ module MachO
|
|
|
519
531
|
class SegmentCommand64 < SegmentCommand
|
|
520
532
|
# @see MachOStructure::FORMAT
|
|
521
533
|
# @api private
|
|
522
|
-
FORMAT = "L=2Z16Q=4l=2L=2"
|
|
534
|
+
FORMAT = "L=2Z16Q=4l=2L=2"
|
|
523
535
|
|
|
524
536
|
# @see MachOStructure::SIZEOF
|
|
525
537
|
# @api private
|
|
@@ -545,7 +557,7 @@ module MachO
|
|
|
545
557
|
|
|
546
558
|
# @see MachOStructure::FORMAT
|
|
547
559
|
# @api private
|
|
548
|
-
FORMAT = "L=6"
|
|
560
|
+
FORMAT = "L=6"
|
|
549
561
|
|
|
550
562
|
# @see MachOStructure::SIZEOF
|
|
551
563
|
# @api private
|
|
@@ -596,7 +608,7 @@ module MachO
|
|
|
596
608
|
|
|
597
609
|
# @see MachOStructure::FORMAT
|
|
598
610
|
# @api private
|
|
599
|
-
FORMAT = "L=3"
|
|
611
|
+
FORMAT = "L=3"
|
|
600
612
|
|
|
601
613
|
# @see MachOStructure::SIZEOF
|
|
602
614
|
# @api private
|
|
@@ -644,7 +656,7 @@ module MachO
|
|
|
644
656
|
|
|
645
657
|
# @see MachOStructure::FORMAT
|
|
646
658
|
# @api private
|
|
647
|
-
FORMAT = "L=5"
|
|
659
|
+
FORMAT = "L=5"
|
|
648
660
|
|
|
649
661
|
# @see MachOStructure::SIZEOF
|
|
650
662
|
# @api private
|
|
@@ -674,7 +686,7 @@ module MachO
|
|
|
674
686
|
class ThreadCommand < LoadCommand
|
|
675
687
|
# @see MachOStructure::FORMAT
|
|
676
688
|
# @api private
|
|
677
|
-
FORMAT = "L=2"
|
|
689
|
+
FORMAT = "L=2"
|
|
678
690
|
|
|
679
691
|
# @see MachOStructure::SIZEOF
|
|
680
692
|
# @api private
|
|
@@ -712,7 +724,7 @@ module MachO
|
|
|
712
724
|
|
|
713
725
|
# @see MachOStructure::FORMAT
|
|
714
726
|
# @api private
|
|
715
|
-
FORMAT = "L=10"
|
|
727
|
+
FORMAT = "L=10"
|
|
716
728
|
|
|
717
729
|
# @see MachOStructure::SIZEOF
|
|
718
730
|
# @api private
|
|
@@ -753,7 +765,7 @@ module MachO
|
|
|
753
765
|
class RoutinesCommand64 < RoutinesCommand
|
|
754
766
|
# @see MachOStructure::FORMAT
|
|
755
767
|
# @api private
|
|
756
|
-
FORMAT = "L=2Q=8"
|
|
768
|
+
FORMAT = "L=2Q=8"
|
|
757
769
|
|
|
758
770
|
# @see MachOStructure::SIZEOF
|
|
759
771
|
# @api private
|
|
@@ -768,7 +780,7 @@ module MachO
|
|
|
768
780
|
|
|
769
781
|
# @see MachOStructure::FORMAT
|
|
770
782
|
# @api private
|
|
771
|
-
FORMAT = "L=3"
|
|
783
|
+
FORMAT = "L=3"
|
|
772
784
|
|
|
773
785
|
# @see MachOStructure::SIZEOF
|
|
774
786
|
# @api private
|
|
@@ -796,7 +808,7 @@ module MachO
|
|
|
796
808
|
|
|
797
809
|
# @see MachOStructure::FORMAT
|
|
798
810
|
# @api private
|
|
799
|
-
FORMAT = "L=3"
|
|
811
|
+
FORMAT = "L=3"
|
|
800
812
|
|
|
801
813
|
# @see MachOStructure::SIZEOF
|
|
802
814
|
# @api private
|
|
@@ -824,7 +836,7 @@ module MachO
|
|
|
824
836
|
|
|
825
837
|
# @see MachOStructure::FORMAT
|
|
826
838
|
# @api private
|
|
827
|
-
FORMAT = "L=3"
|
|
839
|
+
FORMAT = "L=3"
|
|
828
840
|
|
|
829
841
|
# @see MachOStructure::SIZEOF
|
|
830
842
|
# @api private
|
|
@@ -852,7 +864,7 @@ module MachO
|
|
|
852
864
|
|
|
853
865
|
# @see MachOStructure::FORMAT
|
|
854
866
|
# @api private
|
|
855
|
-
FORMAT = "L=3"
|
|
867
|
+
FORMAT = "L=3"
|
|
856
868
|
|
|
857
869
|
# @see MachOStructure::SIZEOF
|
|
858
870
|
# @api private
|
|
@@ -889,7 +901,7 @@ module MachO
|
|
|
889
901
|
|
|
890
902
|
# @see MachOStructure::FORMAT
|
|
891
903
|
# @api private
|
|
892
|
-
FORMAT = "L=6"
|
|
904
|
+
FORMAT = "L=6"
|
|
893
905
|
|
|
894
906
|
# @see MachOStructure::SIZEOF
|
|
895
907
|
# @api private
|
|
@@ -974,7 +986,7 @@ module MachO
|
|
|
974
986
|
|
|
975
987
|
# @see MachOStructure::FORMAT
|
|
976
988
|
# @api private
|
|
977
|
-
FORMAT = "L=20"
|
|
989
|
+
FORMAT = "L=20"
|
|
978
990
|
|
|
979
991
|
# @see MachOStructure::SIZEOF
|
|
980
992
|
# @api private
|
|
@@ -1047,7 +1059,7 @@ module MachO
|
|
|
1047
1059
|
|
|
1048
1060
|
# @see MachOStructure::FORMAT
|
|
1049
1061
|
# @api private
|
|
1050
|
-
FORMAT = "L=4"
|
|
1062
|
+
FORMAT = "L=4"
|
|
1051
1063
|
|
|
1052
1064
|
# @see MachOStructure::SIZEOF
|
|
1053
1065
|
# @api private
|
|
@@ -1122,7 +1134,7 @@ module MachO
|
|
|
1122
1134
|
|
|
1123
1135
|
# @see MachOStructure::FORMAT
|
|
1124
1136
|
# @api private
|
|
1125
|
-
FORMAT = "L=3"
|
|
1137
|
+
FORMAT = "L=3"
|
|
1126
1138
|
|
|
1127
1139
|
# @see MachOStructure::SIZEOF
|
|
1128
1140
|
# @api private
|
|
@@ -1151,7 +1163,7 @@ module MachO
|
|
|
1151
1163
|
|
|
1152
1164
|
# @see MachOStructure::FORMAT
|
|
1153
1165
|
# @api private
|
|
1154
|
-
FORMAT = "L=3"
|
|
1166
|
+
FORMAT = "L=3"
|
|
1155
1167
|
|
|
1156
1168
|
# @see MachOStructure::SIZEOF
|
|
1157
1169
|
# @api private
|
|
@@ -1186,7 +1198,8 @@ module MachO
|
|
|
1186
1198
|
# A load command representing the offsets and sizes of a blob of data in
|
|
1187
1199
|
# the __LINKEDIT segment. Corresponds to LC_CODE_SIGNATURE,
|
|
1188
1200
|
# LC_SEGMENT_SPLIT_INFO, LC_FUNCTION_STARTS, LC_DATA_IN_CODE,
|
|
1189
|
-
# LC_DYLIB_CODE_SIGN_DRS,
|
|
1201
|
+
# LC_DYLIB_CODE_SIGN_DRS, LC_LINKER_OPTIMIZATION_HINT, LC_DYLD_EXPORTS_TRIE,
|
|
1202
|
+
# or LC_DYLD_CHAINED_FIXUPS.
|
|
1190
1203
|
class LinkeditDataCommand < LoadCommand
|
|
1191
1204
|
# @return [Integer] offset to the data in the __LINKEDIT segment
|
|
1192
1205
|
attr_reader :dataoff
|
|
@@ -1196,7 +1209,7 @@ module MachO
|
|
|
1196
1209
|
|
|
1197
1210
|
# @see MachOStructure::FORMAT
|
|
1198
1211
|
# @api private
|
|
1199
|
-
FORMAT = "L=4"
|
|
1212
|
+
FORMAT = "L=4"
|
|
1200
1213
|
|
|
1201
1214
|
# @see MachOStructure::SIZEOF
|
|
1202
1215
|
# @api private
|
|
@@ -1232,7 +1245,7 @@ module MachO
|
|
|
1232
1245
|
|
|
1233
1246
|
# @see MachOStructure::FORMAT
|
|
1234
1247
|
# @api private
|
|
1235
|
-
FORMAT = "L=5"
|
|
1248
|
+
FORMAT = "L=5"
|
|
1236
1249
|
|
|
1237
1250
|
# @see MachOStructure::SIZEOF
|
|
1238
1251
|
# @api private
|
|
@@ -1264,7 +1277,7 @@ module MachO
|
|
|
1264
1277
|
|
|
1265
1278
|
# @see MachOStructure::FORMAT
|
|
1266
1279
|
# @api private
|
|
1267
|
-
FORMAT = "L=6"
|
|
1280
|
+
FORMAT = "L=6"
|
|
1268
1281
|
|
|
1269
1282
|
# @see MachOStructure::SIZEOF
|
|
1270
1283
|
# @api private
|
|
@@ -1296,7 +1309,7 @@ module MachO
|
|
|
1296
1309
|
|
|
1297
1310
|
# @see MachOStructure::FORMAT
|
|
1298
1311
|
# @api private
|
|
1299
|
-
FORMAT = "L=4"
|
|
1312
|
+
FORMAT = "L=4"
|
|
1300
1313
|
|
|
1301
1314
|
# @see MachOStructure::SIZEOF
|
|
1302
1315
|
# @api private
|
|
@@ -1312,7 +1325,7 @@ module MachO
|
|
|
1312
1325
|
# A string representation of the binary's minimum OS version.
|
|
1313
1326
|
# @return [String] a string representing the minimum OS version.
|
|
1314
1327
|
def version_string
|
|
1315
|
-
binary = "%
|
|
1328
|
+
binary = "%032<version>b" % { :version => version }
|
|
1316
1329
|
segs = [
|
|
1317
1330
|
binary[0..15], binary[16..23], binary[24..31]
|
|
1318
1331
|
].map { |s| s.to_i(2) }
|
|
@@ -1323,7 +1336,7 @@ module MachO
|
|
|
1323
1336
|
# A string representation of the binary's SDK version.
|
|
1324
1337
|
# @return [String] a string representing the SDK version.
|
|
1325
1338
|
def sdk_string
|
|
1326
|
-
binary = "%
|
|
1339
|
+
binary = "%032<sdk>b" % { :sdk => sdk }
|
|
1327
1340
|
segs = [
|
|
1328
1341
|
binary[0..15], binary[16..23], binary[24..31]
|
|
1329
1342
|
].map { |s| s.to_i(2) }
|
|
@@ -1360,7 +1373,7 @@ module MachO
|
|
|
1360
1373
|
|
|
1361
1374
|
# @see MachOStructure::FORMAT
|
|
1362
1375
|
# @api private
|
|
1363
|
-
FORMAT = "L=6"
|
|
1376
|
+
FORMAT = "L=6"
|
|
1364
1377
|
|
|
1365
1378
|
# @see MachOStructure::SIZEOF
|
|
1366
1379
|
# @api private
|
|
@@ -1378,7 +1391,7 @@ module MachO
|
|
|
1378
1391
|
# A string representation of the binary's minimum OS version.
|
|
1379
1392
|
# @return [String] a string representing the minimum OS version.
|
|
1380
1393
|
def minos_string
|
|
1381
|
-
binary = "%
|
|
1394
|
+
binary = "%032<minos>b" % { :minos => minos }
|
|
1382
1395
|
segs = [
|
|
1383
1396
|
binary[0..15], binary[16..23], binary[24..31]
|
|
1384
1397
|
].map { |s| s.to_i(2) }
|
|
@@ -1389,7 +1402,7 @@ module MachO
|
|
|
1389
1402
|
# A string representation of the binary's SDK version.
|
|
1390
1403
|
# @return [String] a string representing the SDK version.
|
|
1391
1404
|
def sdk_string
|
|
1392
|
-
binary = "%
|
|
1405
|
+
binary = "%032<sdk>b" % { :sdk => sdk }
|
|
1393
1406
|
segs = [
|
|
1394
1407
|
binary[0..15], binary[16..23], binary[24..31]
|
|
1395
1408
|
].map { |s| s.to_i(2) }
|
|
@@ -1489,7 +1502,7 @@ module MachO
|
|
|
1489
1502
|
|
|
1490
1503
|
# @see MachOStructure::FORMAT
|
|
1491
1504
|
# @api private
|
|
1492
|
-
FORMAT = "L=12"
|
|
1505
|
+
FORMAT = "L=12"
|
|
1493
1506
|
|
|
1494
1507
|
# @see MachOStructure::SIZEOF
|
|
1495
1508
|
# @api private
|
|
@@ -1537,7 +1550,7 @@ module MachO
|
|
|
1537
1550
|
|
|
1538
1551
|
# @see MachOStructure::FORMAT
|
|
1539
1552
|
# @api private
|
|
1540
|
-
FORMAT = "L=3"
|
|
1553
|
+
FORMAT = "L=3"
|
|
1541
1554
|
|
|
1542
1555
|
# @see MachOStructure::SIZEOF
|
|
1543
1556
|
# @api private
|
|
@@ -1567,7 +1580,7 @@ module MachO
|
|
|
1567
1580
|
|
|
1568
1581
|
# @see MachOStructure::FORMAT
|
|
1569
1582
|
# @api private
|
|
1570
|
-
FORMAT = "L=2Q=2"
|
|
1583
|
+
FORMAT = "L=2Q=2"
|
|
1571
1584
|
|
|
1572
1585
|
# @see MachOStructure::SIZEOF
|
|
1573
1586
|
# @api private
|
|
@@ -1597,7 +1610,7 @@ module MachO
|
|
|
1597
1610
|
|
|
1598
1611
|
# @see MachOStructure::FORMAT
|
|
1599
1612
|
# @api private
|
|
1600
|
-
FORMAT = "L=2Q=1"
|
|
1613
|
+
FORMAT = "L=2Q=1"
|
|
1601
1614
|
|
|
1602
1615
|
# @see MachOStructure::SIZEOF
|
|
1603
1616
|
# @api private
|
|
@@ -1612,7 +1625,7 @@ module MachO
|
|
|
1612
1625
|
# A string representation of the sources used to build the binary.
|
|
1613
1626
|
# @return [String] a string representation of the version
|
|
1614
1627
|
def version_string
|
|
1615
|
-
binary = "%
|
|
1628
|
+
binary = "%064<version>b" % { :version => version }
|
|
1616
1629
|
segs = [
|
|
1617
1630
|
binary[0..23], binary[24..33], binary[34..43], binary[44..53],
|
|
1618
1631
|
binary[54..63]
|
|
@@ -1641,7 +1654,7 @@ module MachO
|
|
|
1641
1654
|
|
|
1642
1655
|
# @see MachOStructure::FORMAT
|
|
1643
1656
|
# @api private
|
|
1644
|
-
FORMAT = "L=4"
|
|
1657
|
+
FORMAT = "L=4"
|
|
1645
1658
|
|
|
1646
1659
|
# @see MachOStructure::SIZEOF
|
|
1647
1660
|
# @api private
|
|
@@ -1669,7 +1682,7 @@ module MachO
|
|
|
1669
1682
|
class IdentCommand < LoadCommand
|
|
1670
1683
|
# @see MachOStructure::FORMAT
|
|
1671
1684
|
# @api private
|
|
1672
|
-
FORMAT = "L=2"
|
|
1685
|
+
FORMAT = "L=2"
|
|
1673
1686
|
|
|
1674
1687
|
# @see MachOStructure::SIZEOF
|
|
1675
1688
|
# @api private
|
|
@@ -1687,7 +1700,7 @@ module MachO
|
|
|
1687
1700
|
|
|
1688
1701
|
# @see MachOStructure::FORMAT
|
|
1689
1702
|
# @api private
|
|
1690
|
-
FORMAT = "L=4"
|
|
1703
|
+
FORMAT = "L=4"
|
|
1691
1704
|
|
|
1692
1705
|
# @see MachOStructure::SIZEOF
|
|
1693
1706
|
# @api private
|
|
@@ -1722,7 +1735,7 @@ module MachO
|
|
|
1722
1735
|
|
|
1723
1736
|
# @see MachOStructure::FORMAT
|
|
1724
1737
|
# @api private
|
|
1725
|
-
FORMAT = "L=5"
|
|
1738
|
+
FORMAT = "L=5"
|
|
1726
1739
|
|
|
1727
1740
|
# @see MachOStructure::SIZEOF
|
|
1728
1741
|
# @api private
|
|
@@ -1759,7 +1772,7 @@ module MachO
|
|
|
1759
1772
|
|
|
1760
1773
|
# @see MachOStructure::FORMAT
|
|
1761
1774
|
# @api private
|
|
1762
|
-
FORMAT = "L=2Z16Q=2"
|
|
1775
|
+
FORMAT = "L=2Z16Q=2"
|
|
1763
1776
|
|
|
1764
1777
|
# @see MachOStructure::SIZEOF
|
|
1765
1778
|
# @api private
|
data/lib/macho/macho_file.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require "forwardable"
|
|
2
4
|
|
|
3
5
|
module MachO
|
|
@@ -9,10 +11,13 @@ module MachO
|
|
|
9
11
|
class MachOFile
|
|
10
12
|
extend Forwardable
|
|
11
13
|
|
|
12
|
-
# @return [String] the filename loaded from, or nil if loaded from a binary
|
|
14
|
+
# @return [String, nil] the filename loaded from, or nil if loaded from a binary
|
|
13
15
|
# string
|
|
14
16
|
attr_accessor :filename
|
|
15
17
|
|
|
18
|
+
# @return [Hash] any parser options that the instance was created with
|
|
19
|
+
attr_reader :options
|
|
20
|
+
|
|
16
21
|
# @return [Symbol] the endianness of the file, :big or :little
|
|
17
22
|
attr_reader :endianness
|
|
18
23
|
|
|
@@ -27,30 +32,36 @@ module MachO
|
|
|
27
32
|
|
|
28
33
|
# Creates a new instance from a binary string.
|
|
29
34
|
# @param bin [String] a binary string containing raw Mach-O data
|
|
35
|
+
# @param opts [Hash] options to control the parser with
|
|
36
|
+
# @option opts [Boolean] :permissive whether to ignore unknown load commands
|
|
30
37
|
# @return [MachOFile] a new MachOFile
|
|
31
|
-
def self.new_from_bin(bin)
|
|
38
|
+
def self.new_from_bin(bin, **opts)
|
|
32
39
|
instance = allocate
|
|
33
|
-
instance.initialize_from_bin(bin)
|
|
40
|
+
instance.initialize_from_bin(bin, opts)
|
|
34
41
|
|
|
35
42
|
instance
|
|
36
43
|
end
|
|
37
44
|
|
|
38
45
|
# Creates a new instance from data read from the given filename.
|
|
39
46
|
# @param filename [String] the Mach-O file to load from
|
|
47
|
+
# @param opts [Hash] options to control the parser with
|
|
48
|
+
# @option opts [Boolean] :permissive whether to ignore unknown load commands
|
|
40
49
|
# @raise [ArgumentError] if the given file does not exist
|
|
41
|
-
def initialize(filename)
|
|
50
|
+
def initialize(filename, **opts)
|
|
42
51
|
raise ArgumentError, "#{filename}: no such file" unless File.file?(filename)
|
|
43
52
|
|
|
44
53
|
@filename = filename
|
|
54
|
+
@options = opts
|
|
45
55
|
@raw_data = File.open(@filename, "rb", &:read)
|
|
46
56
|
populate_fields
|
|
47
57
|
end
|
|
48
58
|
|
|
49
|
-
# Initializes a new MachOFile instance from a binary string.
|
|
59
|
+
# Initializes a new MachOFile instance from a binary string with the given options.
|
|
50
60
|
# @see MachO::MachOFile.new_from_bin
|
|
51
61
|
# @api private
|
|
52
|
-
def initialize_from_bin(bin)
|
|
62
|
+
def initialize_from_bin(bin, opts)
|
|
53
63
|
@filename = nil
|
|
64
|
+
@options = opts
|
|
54
65
|
@raw_data = bin
|
|
55
66
|
populate_fields
|
|
56
67
|
end
|
|
@@ -146,16 +157,13 @@ module MachO
|
|
|
146
157
|
def insert_command(offset, lc, options = {})
|
|
147
158
|
context = LoadCommands::LoadCommand::SerializationContext.context_for(self)
|
|
148
159
|
cmd_raw = lc.serialize(context)
|
|
160
|
+
fileoff = offset + cmd_raw.bytesize
|
|
149
161
|
|
|
150
|
-
if offset < header.class.bytesize ||
|
|
151
|
-
raise OffsetInsertionError, offset
|
|
152
|
-
end
|
|
162
|
+
raise OffsetInsertionError, offset if offset < header.class.bytesize || fileoff > low_fileoff
|
|
153
163
|
|
|
154
164
|
new_sizeofcmds = sizeofcmds + cmd_raw.bytesize
|
|
155
165
|
|
|
156
|
-
if header.class.bytesize + new_sizeofcmds > low_fileoff
|
|
157
|
-
raise HeaderPadError, @filename
|
|
158
|
-
end
|
|
166
|
+
raise HeaderPadError, @filename if header.class.bytesize + new_sizeofcmds > low_fileoff
|
|
159
167
|
|
|
160
168
|
# update Mach-O header fields to account for inserted load command
|
|
161
169
|
update_ncmds(ncmds + 1)
|
|
@@ -178,9 +186,8 @@ module MachO
|
|
|
178
186
|
context = LoadCommands::LoadCommand::SerializationContext.context_for(self)
|
|
179
187
|
cmd_raw = new_lc.serialize(context)
|
|
180
188
|
new_sizeofcmds = sizeofcmds + cmd_raw.bytesize - old_lc.cmdsize
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
end
|
|
189
|
+
|
|
190
|
+
raise HeaderPadError, @filename if header.class.bytesize + new_sizeofcmds > low_fileoff
|
|
184
191
|
|
|
185
192
|
delete_command(old_lc)
|
|
186
193
|
insert_command(old_lc.view.offset, new_lc)
|
|
@@ -431,6 +438,7 @@ module MachO
|
|
|
431
438
|
# @note Overwrites all data in the file!
|
|
432
439
|
def write!
|
|
433
440
|
raise MachOError, "no initial file to write to" if @filename.nil?
|
|
441
|
+
|
|
434
442
|
File.open(@filename, "wb") { |f| f.write(@raw_data) }
|
|
435
443
|
end
|
|
436
444
|
|
|
@@ -470,7 +478,7 @@ module MachO
|
|
|
470
478
|
# @raise [FatBinaryError] if the magic is for a Fat file
|
|
471
479
|
# @api private
|
|
472
480
|
def populate_and_check_magic
|
|
473
|
-
magic = @raw_data[0..3].
|
|
481
|
+
magic = @raw_data[0..3].unpack1("N")
|
|
474
482
|
|
|
475
483
|
raise MagicError, magic unless Utils.magic?(magic)
|
|
476
484
|
raise FatBinaryError if Utils.fat_magic?(magic)
|
|
@@ -510,19 +518,25 @@ module MachO
|
|
|
510
518
|
# @raise [LoadCommandError] if an unknown load command is encountered
|
|
511
519
|
# @api private
|
|
512
520
|
def populate_load_commands
|
|
521
|
+
permissive = options.fetch(:permissive, false)
|
|
513
522
|
offset = header.class.bytesize
|
|
514
523
|
load_commands = []
|
|
515
524
|
|
|
516
525
|
header.ncmds.times do
|
|
517
526
|
fmt = Utils.specialize_format("L=", endianness)
|
|
518
|
-
cmd = @raw_data.slice(offset, 4).
|
|
527
|
+
cmd = @raw_data.slice(offset, 4).unpack1(fmt)
|
|
519
528
|
cmd_sym = LoadCommands::LOAD_COMMANDS[cmd]
|
|
520
529
|
|
|
521
|
-
raise LoadCommandError, cmd
|
|
530
|
+
raise LoadCommandError, cmd unless cmd_sym || permissive
|
|
531
|
+
|
|
532
|
+
# If we're here, then either cmd_sym represents a valid load
|
|
533
|
+
# command *or* we're in permissive mode.
|
|
534
|
+
klass = if (klass_str = LoadCommands::LC_STRUCTURES[cmd_sym])
|
|
535
|
+
LoadCommands.const_get klass_str
|
|
536
|
+
else
|
|
537
|
+
LoadCommands::LoadCommand
|
|
538
|
+
end
|
|
522
539
|
|
|
523
|
-
# why do I do this? i don't like declaring constants below
|
|
524
|
-
# classes, and i need them to resolve...
|
|
525
|
-
klass = LoadCommands.const_get LoadCommands::LC_STRUCTURES[cmd_sym]
|
|
526
540
|
view = MachOView.new(@raw_data, endianness, offset)
|
|
527
541
|
command = klass.new_from_bin(view)
|
|
528
542
|
|
data/lib/macho/sections.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module MachO
|
|
2
4
|
# Classes and constants for parsing sections in Mach-O binaries.
|
|
3
5
|
module Sections
|
|
@@ -108,7 +110,7 @@ module MachO
|
|
|
108
110
|
attr_reader :reserved2
|
|
109
111
|
|
|
110
112
|
# @see MachOStructure::FORMAT
|
|
111
|
-
FORMAT = "Z16Z16L=9"
|
|
113
|
+
FORMAT = "Z16Z16L=9"
|
|
112
114
|
|
|
113
115
|
# @see MachOStructure::SIZEOF
|
|
114
116
|
SIZEOF = 68
|
|
@@ -116,6 +118,7 @@ module MachO
|
|
|
116
118
|
# @api private
|
|
117
119
|
def initialize(sectname, segname, addr, size, offset, align, reloff,
|
|
118
120
|
nreloc, flags, reserved1, reserved2)
|
|
121
|
+
super()
|
|
119
122
|
@sectname = sectname
|
|
120
123
|
@segname = segname
|
|
121
124
|
@addr = addr
|
|
@@ -150,7 +153,9 @@ module MachO
|
|
|
150
153
|
# @return [Boolean] whether the flag is present in the section's {flags}
|
|
151
154
|
def flag?(flag)
|
|
152
155
|
flag = SECTION_FLAGS[flag]
|
|
156
|
+
|
|
153
157
|
return false if flag.nil?
|
|
158
|
+
|
|
154
159
|
flags & flag == flag
|
|
155
160
|
end
|
|
156
161
|
|
|
@@ -178,7 +183,7 @@ module MachO
|
|
|
178
183
|
attr_reader :reserved3
|
|
179
184
|
|
|
180
185
|
# @see MachOStructure::FORMAT
|
|
181
|
-
FORMAT = "Z16Z16Q=2L=8"
|
|
186
|
+
FORMAT = "Z16Z16Q=2L=8"
|
|
182
187
|
|
|
183
188
|
# @see MachOStructure::SIZEOF
|
|
184
189
|
SIZEOF = 80
|
data/lib/macho/structure.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module MachO
|
|
2
4
|
# A general purpose pseudo-structure.
|
|
3
5
|
# @abstract
|
|
@@ -5,7 +7,7 @@ module MachO
|
|
|
5
7
|
# The String#unpack format of the data structure.
|
|
6
8
|
# @return [String] the unpacking format
|
|
7
9
|
# @api private
|
|
8
|
-
FORMAT = ""
|
|
10
|
+
FORMAT = ""
|
|
9
11
|
|
|
10
12
|
# The size of the data structure, in bytes.
|
|
11
13
|
# @return [Integer] the size, in bytes
|
data/lib/macho/tools.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module MachO
|
|
2
4
|
# A collection of convenient methods for common operations on Mach-O and Fat
|
|
3
5
|
# binaries.
|
|
@@ -89,8 +91,9 @@ module MachO
|
|
|
89
91
|
# Merge multiple Mach-Os into one universal (Fat) binary.
|
|
90
92
|
# @param filename [String] the fat binary to create
|
|
91
93
|
# @param files [Array<String>] the files to merge
|
|
94
|
+
# @param fat64 [Boolean] whether to use {Headers::FatArch64}s to represent each slice
|
|
92
95
|
# @return [void]
|
|
93
|
-
def self.merge_machos(filename, *files)
|
|
96
|
+
def self.merge_machos(filename, *files, fat64: false)
|
|
94
97
|
machos = files.map do |file|
|
|
95
98
|
macho = MachO.open(file)
|
|
96
99
|
case macho
|
|
@@ -101,7 +104,7 @@ module MachO
|
|
|
101
104
|
end
|
|
102
105
|
end.flatten
|
|
103
106
|
|
|
104
|
-
fat_macho = MachO::FatFile.new_from_machos(*machos)
|
|
107
|
+
fat_macho = MachO::FatFile.new_from_machos(*machos, :fat64 => fat64)
|
|
105
108
|
fat_macho.write(filename)
|
|
106
109
|
end
|
|
107
110
|
end
|
data/lib/macho/utils.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module MachO
|
|
2
4
|
# A collection of utility functions used throughout ruby-macho.
|
|
3
5
|
module Utils
|
|
@@ -51,7 +53,7 @@ module MachO
|
|
|
51
53
|
def self.pack_strings(fixed_offset, alignment, strings = {})
|
|
52
54
|
offsets = {}
|
|
53
55
|
next_offset = fixed_offset
|
|
54
|
-
payload = ""
|
|
56
|
+
payload = +""
|
|
55
57
|
|
|
56
58
|
strings.each do |key, string|
|
|
57
59
|
offsets[key] = next_offset
|
|
@@ -61,7 +63,7 @@ module MachO
|
|
|
61
63
|
end
|
|
62
64
|
|
|
63
65
|
payload << Utils.nullpad(padding_for(fixed_offset + payload.bytesize, alignment))
|
|
64
|
-
[payload, offsets]
|
|
66
|
+
[payload.freeze, offsets]
|
|
65
67
|
end
|
|
66
68
|
|
|
67
69
|
# Compares the given number to valid Mach-O magic numbers.
|
|
@@ -75,35 +77,49 @@ module MachO
|
|
|
75
77
|
# @param num [Integer] the number being checked
|
|
76
78
|
# @return [Boolean] whether `num` is a valid Fat magic number
|
|
77
79
|
def self.fat_magic?(num)
|
|
80
|
+
[Headers::FAT_MAGIC, Headers::FAT_MAGIC_64].include? num
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Compares the given number to valid 32-bit Fat magic numbers.
|
|
84
|
+
# @param num [Integer] the number being checked
|
|
85
|
+
# @return [Boolean] whether `num` is a valid 32-bit fat magic number
|
|
86
|
+
def self.fat_magic32?(num)
|
|
78
87
|
num == Headers::FAT_MAGIC
|
|
79
88
|
end
|
|
80
89
|
|
|
90
|
+
# Compares the given number to valid 64-bit Fat magic numbers.
|
|
91
|
+
# @param num [Integer] the number being checked
|
|
92
|
+
# @return [Boolean] whether `num` is a valid 64-bit fat magic number
|
|
93
|
+
def self.fat_magic64?(num)
|
|
94
|
+
num == Headers::FAT_MAGIC_64
|
|
95
|
+
end
|
|
96
|
+
|
|
81
97
|
# Compares the given number to valid 32-bit Mach-O magic numbers.
|
|
82
98
|
# @param num [Integer] the number being checked
|
|
83
99
|
# @return [Boolean] whether `num` is a valid 32-bit magic number
|
|
84
100
|
def self.magic32?(num)
|
|
85
|
-
|
|
101
|
+
[Headers::MH_MAGIC, Headers::MH_CIGAM].include? num
|
|
86
102
|
end
|
|
87
103
|
|
|
88
104
|
# Compares the given number to valid 64-bit Mach-O magic numbers.
|
|
89
105
|
# @param num [Integer] the number being checked
|
|
90
106
|
# @return [Boolean] whether `num` is a valid 64-bit magic number
|
|
91
107
|
def self.magic64?(num)
|
|
92
|
-
|
|
108
|
+
[Headers::MH_MAGIC_64, Headers::MH_CIGAM_64].include? num
|
|
93
109
|
end
|
|
94
110
|
|
|
95
111
|
# Compares the given number to valid little-endian magic numbers.
|
|
96
112
|
# @param num [Integer] the number being checked
|
|
97
113
|
# @return [Boolean] whether `num` is a valid little-endian magic number
|
|
98
114
|
def self.little_magic?(num)
|
|
99
|
-
|
|
115
|
+
[Headers::MH_CIGAM, Headers::MH_CIGAM_64].include? num
|
|
100
116
|
end
|
|
101
117
|
|
|
102
118
|
# Compares the given number to valid big-endian magic numbers.
|
|
103
119
|
# @param num [Integer] the number being checked
|
|
104
120
|
# @return [Boolean] whether `num` is a valid big-endian magic number
|
|
105
121
|
def self.big_magic?(num)
|
|
106
|
-
|
|
122
|
+
[Headers::MH_MAGIC, Headers::MH_MAGIC_64].include? num
|
|
107
123
|
end
|
|
108
124
|
end
|
|
109
125
|
end
|
data/lib/macho/view.rb
CHANGED
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: 2.
|
|
4
|
+
version: 2.5.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: 2020-11-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,16 +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.5'
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
signing_key:
|
|
52
|
+
rubygems_version: 3.1.2
|
|
53
|
+
signing_key:
|
|
55
54
|
specification_version: 4
|
|
56
55
|
summary: ruby-macho - Mach-O file analyzer.
|
|
57
56
|
test_files: []
|