ruby-macho 2.0.0 → 2.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|