ruby-macho 0.2.4 → 0.2.5
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 -9
- data/lib/macho.rb +2 -1
- data/lib/macho/exceptions.rb +83 -6
- data/lib/macho/fat_file.rb +130 -39
- data/lib/macho/headers.rb +131 -25
- data/lib/macho/load_commands.rb +518 -177
- data/lib/macho/macho_file.rb +254 -172
- data/lib/macho/open.rb +5 -5
- data/lib/macho/sections.rb +20 -9
- data/lib/macho/structure.rb +8 -16
- data/lib/macho/tools.rb +34 -15
- data/lib/macho/utils.rb +87 -39
- data/lib/macho/view.rb +23 -0
- metadata +4 -2
data/lib/macho/open.rb
CHANGED
@@ -7,17 +7,17 @@ module MachO
|
|
7
7
|
# @raise [MachO::TruncatedFileError] if the file is too small to have a valid header
|
8
8
|
# @raise [MachO::MagicError] if the file's magic is not valid Mach-O magic
|
9
9
|
def self.open(filename)
|
10
|
-
raise ArgumentError
|
11
|
-
raise TruncatedFileError
|
10
|
+
raise ArgumentError, "#{filename}: no such file" unless File.file?(filename)
|
11
|
+
raise TruncatedFileError unless File.stat(filename).size >= 4
|
12
12
|
|
13
13
|
magic = File.open(filename, "rb") { |f| f.read(4) }.unpack("N").first
|
14
14
|
|
15
|
-
if
|
15
|
+
if Utils.fat_magic?(magic)
|
16
16
|
file = FatFile.new(filename)
|
17
|
-
elsif
|
17
|
+
elsif Utils.magic?(magic)
|
18
18
|
file = MachOFile.new(filename)
|
19
19
|
else
|
20
|
-
raise MagicError
|
20
|
+
raise MagicError, magic
|
21
21
|
end
|
22
22
|
|
23
23
|
file
|
data/lib/macho/sections.rb
CHANGED
@@ -45,7 +45,7 @@ module MachO
|
|
45
45
|
:S_ATTR_DEBUG => 0x02000000,
|
46
46
|
:S_ATTR_SOME_INSTRUCTIONS => 0x00000400,
|
47
47
|
:S_ATTR_EXT_RELOC => 0x00000200,
|
48
|
-
:S_ATTR_LOC_RELOC => 0x00000100
|
48
|
+
:S_ATTR_LOC_RELOC => 0x00000100,
|
49
49
|
}.freeze
|
50
50
|
|
51
51
|
# association of section name symbols to names
|
@@ -62,7 +62,7 @@ module MachO
|
|
62
62
|
:SECT_OBJC_STRINGS => "__selector_strs",
|
63
63
|
:SECT_OBJC_REFS => "__selector_refs",
|
64
64
|
:SECT_ICON_HEADER => "__header",
|
65
|
-
:SECT_ICON_TIFF => "__tiff"
|
65
|
+
:SECT_ICON_TIFF => "__tiff",
|
66
66
|
}.freeze
|
67
67
|
|
68
68
|
# Represents a section of a segment for 32-bit architectures.
|
@@ -91,7 +91,7 @@ module MachO
|
|
91
91
|
# @return [Fixnum] the number of relocation entries
|
92
92
|
attr_reader :nreloc
|
93
93
|
|
94
|
-
# @return [Fixnum] flags for type and
|
94
|
+
# @return [Fixnum] flags for type and attributes of the section
|
95
95
|
attr_reader :flags
|
96
96
|
|
97
97
|
# @return [void] reserved (for offset or index)
|
@@ -100,12 +100,15 @@ module MachO
|
|
100
100
|
# @return [void] reserved (for count or sizeof)
|
101
101
|
attr_reader :reserved2
|
102
102
|
|
103
|
-
|
103
|
+
# @see MachOStructure::FORMAT
|
104
|
+
FORMAT = "a16a16L=9".freeze
|
105
|
+
|
106
|
+
# @see MachOStructure::SIZEOF
|
104
107
|
SIZEOF = 68
|
105
108
|
|
106
109
|
# @api private
|
107
110
|
def initialize(sectname, segname, addr, size, offset, align, reloff,
|
108
|
-
|
111
|
+
nreloc, flags, reserved1, reserved2)
|
109
112
|
@sectname = sectname
|
110
113
|
@segname = segname
|
111
114
|
@addr = addr
|
@@ -121,12 +124,17 @@ module MachO
|
|
121
124
|
|
122
125
|
# @return [String] the section's name, with any trailing NULL characters removed
|
123
126
|
def section_name
|
124
|
-
|
127
|
+
sectname.delete("\x00")
|
125
128
|
end
|
126
129
|
|
127
130
|
# @return [String] the parent segment's name, with any trailing NULL characters removed
|
128
131
|
def segment_name
|
129
|
-
|
132
|
+
segname.delete("\x00")
|
133
|
+
end
|
134
|
+
|
135
|
+
# @return [Boolean] true if the section has no contents (i.e, `size` is 0)
|
136
|
+
def empty?
|
137
|
+
size.zero?
|
130
138
|
end
|
131
139
|
|
132
140
|
# @example
|
@@ -145,12 +153,15 @@ module MachO
|
|
145
153
|
# @return [void] reserved
|
146
154
|
attr_reader :reserved3
|
147
155
|
|
148
|
-
|
156
|
+
# @see MachOStructure::FORMAT
|
157
|
+
FORMAT = "a16a16Q=2L=8".freeze
|
158
|
+
|
159
|
+
# @see MachOStructure::SIZEOF
|
149
160
|
SIZEOF = 80
|
150
161
|
|
151
162
|
# @api private
|
152
163
|
def initialize(sectname, segname, addr, size, offset, align, reloff,
|
153
|
-
|
164
|
+
nreloc, flags, reserved1, reserved2, reserved3)
|
154
165
|
super(sectname, segname, addr, size, offset, align, reloff,
|
155
166
|
nreloc, flags, reserved1, reserved2)
|
156
167
|
@reserved3 = reserved3
|
data/lib/macho/structure.rb
CHANGED
@@ -3,9 +3,13 @@ module MachO
|
|
3
3
|
# @abstract
|
4
4
|
class MachOStructure
|
5
5
|
# The String#unpack format of the data structure.
|
6
|
-
|
6
|
+
# @return [String] the unpacking format
|
7
|
+
# @api private
|
8
|
+
FORMAT = "".freeze
|
7
9
|
|
8
10
|
# The size of the data structure, in bytes.
|
11
|
+
# @return [Fixnum] the size, in bytes
|
12
|
+
# @api private
|
9
13
|
SIZEOF = 0
|
10
14
|
|
11
15
|
# @return [Fixnum] the size, in bytes, of the represented structure.
|
@@ -13,26 +17,14 @@ module MachO
|
|
13
17
|
self::SIZEOF
|
14
18
|
end
|
15
19
|
|
16
|
-
# @param endianness [Symbol] either
|
20
|
+
# @param endianness [Symbol] either `:big` or `:little`
|
17
21
|
# @param bin [String] the string to be unpacked into the new structure
|
18
22
|
# @return [MachO::MachOStructure] a new MachOStructure initialized with `bin`
|
19
23
|
# @api private
|
20
24
|
def self.new_from_bin(endianness, bin)
|
21
|
-
format = specialize_format(self::FORMAT, endianness)
|
22
|
-
|
23
|
-
self.new(*bin.unpack(format))
|
24
|
-
end
|
25
|
-
|
26
|
-
private
|
25
|
+
format = Utils.specialize_format(self::FORMAT, endianness)
|
27
26
|
|
28
|
-
|
29
|
-
# @param format [String] the format string being converted
|
30
|
-
# @param endianness [Symbol] either :big or :little
|
31
|
-
# @return [String] the converted string
|
32
|
-
# @api private
|
33
|
-
def self.specialize_format(format, endianness)
|
34
|
-
modifier = (endianness == :big) ? ">" : "<"
|
35
|
-
format.tr("=", modifier)
|
27
|
+
new(*bin.unpack(format))
|
36
28
|
end
|
37
29
|
end
|
38
30
|
end
|
data/lib/macho/tools.rb
CHANGED
@@ -12,12 +12,14 @@ module MachO
|
|
12
12
|
# Changes the dylib ID of a Mach-O or Fat binary, overwriting the source file.
|
13
13
|
# @param filename [String] the Mach-O or Fat binary being modified
|
14
14
|
# @param new_id [String] the new dylib ID for the binary
|
15
|
+
# @param options [Hash]
|
16
|
+
# @option options [Boolean] :strict (true) whether or not to fail loudly
|
17
|
+
# with an exception if the change cannot be performed
|
15
18
|
# @return [void]
|
16
|
-
|
17
|
-
def self.change_dylib_id(filename, new_id)
|
19
|
+
def self.change_dylib_id(filename, new_id, options = {})
|
18
20
|
file = MachO.open(filename)
|
19
21
|
|
20
|
-
file.
|
22
|
+
file.change_dylib_id(new_id, options)
|
21
23
|
file.write!
|
22
24
|
end
|
23
25
|
|
@@ -25,12 +27,14 @@ module MachO
|
|
25
27
|
# @param filename [String] the Mach-O or Fat binary being modified
|
26
28
|
# @param old_name [String] the old shared library name
|
27
29
|
# @param new_name [String] the new shared library name
|
30
|
+
# @param options [Hash]
|
31
|
+
# @option options [Boolean] :strict (true) whether or not to fail loudly
|
32
|
+
# with an exception if the change cannot be performed
|
28
33
|
# @return [void]
|
29
|
-
|
30
|
-
def self.change_install_name(filename, old_name, new_name)
|
34
|
+
def self.change_install_name(filename, old_name, new_name, options = {})
|
31
35
|
file = MachO.open(filename)
|
32
36
|
|
33
|
-
file.change_install_name(old_name, new_name)
|
37
|
+
file.change_install_name(old_name, new_name, options)
|
34
38
|
file.write!
|
35
39
|
end
|
36
40
|
|
@@ -38,28 +42,43 @@ module MachO
|
|
38
42
|
# @param filename [String] the Mach-O or Fat binary being modified
|
39
43
|
# @param old_path [String] the old runtime path
|
40
44
|
# @param new_path [String] the new runtime path
|
45
|
+
# @param options [Hash]
|
46
|
+
# @option options [Boolean] :strict (true) whether or not to fail loudly
|
47
|
+
# with an exception if the change cannot be performed
|
41
48
|
# @return [void]
|
42
|
-
|
43
|
-
|
44
|
-
|
49
|
+
def self.change_rpath(filename, old_path, new_path, options = {})
|
50
|
+
file = MachO.open(filename)
|
51
|
+
|
52
|
+
file.change_rpath(old_path, new_path, options)
|
53
|
+
file.write!
|
45
54
|
end
|
46
55
|
|
47
56
|
# Add a runtime path to a Mach-O or Fat binary, overwriting the source file.
|
48
57
|
# @param filename [String] the Mach-O or Fat binary being modified
|
49
58
|
# @param new_path [String] the new runtime path
|
59
|
+
# @param options [Hash]
|
60
|
+
# @option options [Boolean] :strict (true) whether or not to fail loudly
|
61
|
+
# with an exception if the change cannot be performed
|
50
62
|
# @return [void]
|
51
|
-
|
52
|
-
|
53
|
-
|
63
|
+
def self.add_rpath(filename, new_path, options = {})
|
64
|
+
file = MachO.open(filename)
|
65
|
+
|
66
|
+
file.add_rpath(new_path, options)
|
67
|
+
file.write!
|
54
68
|
end
|
55
69
|
|
56
70
|
# Delete a runtime path from a Mach-O or Fat binary, overwriting the source file.
|
57
71
|
# @param filename [String] the Mach-O or Fat binary being modified
|
58
72
|
# @param old_path [String] the old runtime path
|
73
|
+
# @param options [Hash]
|
74
|
+
# @option options [Boolean] :strict (true) whether or not to fail loudly
|
75
|
+
# with an exception if the change cannot be performed
|
59
76
|
# @return [void]
|
60
|
-
|
61
|
-
|
62
|
-
|
77
|
+
def self.delete_rpath(filename, old_path, options = {})
|
78
|
+
file = MachO.open(filename)
|
79
|
+
|
80
|
+
file.delete_rpath(old_path, options)
|
81
|
+
file.write!
|
63
82
|
end
|
64
83
|
end
|
65
84
|
end
|
data/lib/macho/utils.rb
CHANGED
@@ -1,48 +1,96 @@
|
|
1
1
|
module MachO
|
2
|
-
#
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
value
|
10
|
-
|
11
|
-
|
2
|
+
# A collection of utility functions used throughout ruby-macho.
|
3
|
+
module Utils
|
4
|
+
# Rounds a value to the next multiple of the given round.
|
5
|
+
# @param value [Fixnum] the number being rounded
|
6
|
+
# @param round [Fixnum] the number being rounded with
|
7
|
+
# @return [Fixnum] the rounded value
|
8
|
+
# @see http://www.opensource.apple.com/source/cctools/cctools-870/libstuff/rnd.c
|
9
|
+
def self.round(value, round)
|
10
|
+
round -= 1
|
11
|
+
value += round
|
12
|
+
value &= ~round
|
13
|
+
value
|
14
|
+
end
|
12
15
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
16
|
+
# Returns the number of bytes needed to pad the given size to the given alignment.
|
17
|
+
# @param size [Fixnum] the unpadded size
|
18
|
+
# @param alignment [Fixnum] the number to alignment the size with
|
19
|
+
# @return [Fixnum] the number of pad bytes required
|
20
|
+
def self.padding_for(size, alignment)
|
21
|
+
round(size, alignment) - size
|
22
|
+
end
|
18
23
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
+
# Converts an abstract (native-endian) String#unpack format to big or little.
|
25
|
+
# @param format [String] the format string being converted
|
26
|
+
# @param endianness [Symbol] either `:big` or `:little`
|
27
|
+
# @return [String] the converted string
|
28
|
+
def self.specialize_format(format, endianness)
|
29
|
+
modifier = endianness == :big ? ">" : "<"
|
30
|
+
format.tr("=", modifier)
|
31
|
+
end
|
24
32
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
33
|
+
# Packs tagged strings into an aligned payload.
|
34
|
+
# @param fixed_offset [Fixnum] the baseline offset for the first packed string
|
35
|
+
# @param alignment [Fixnum] the alignment value to use for packing
|
36
|
+
# @param strings [Hash] the labeled strings to pack
|
37
|
+
# @return [Array<String, Hash>] the packed string and labeled offsets
|
38
|
+
def self.pack_strings(fixed_offset, alignment, strings = {})
|
39
|
+
offsets = {}
|
40
|
+
next_offset = fixed_offset
|
41
|
+
payload = ""
|
30
42
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
43
|
+
strings.each do |key, string|
|
44
|
+
offsets[key] = next_offset
|
45
|
+
payload << string
|
46
|
+
payload << "\x00"
|
47
|
+
next_offset += string.bytesize + 1
|
48
|
+
end
|
36
49
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
50
|
+
payload << "\x00" * padding_for(fixed_offset + payload.bytesize, alignment)
|
51
|
+
[payload, offsets]
|
52
|
+
end
|
53
|
+
|
54
|
+
# Compares the given number to valid Mach-O magic numbers.
|
55
|
+
# @param num [Fixnum] the number being checked
|
56
|
+
# @return [Boolean] true if `num` is a valid Mach-O magic number, false otherwise
|
57
|
+
def self.magic?(num)
|
58
|
+
MH_MAGICS.key?(num)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Compares the given number to valid Fat magic numbers.
|
62
|
+
# @param num [Fixnum] the number being checked
|
63
|
+
# @return [Boolean] true if `num` is a valid Fat magic number, false otherwise
|
64
|
+
def self.fat_magic?(num)
|
65
|
+
num == FAT_MAGIC
|
66
|
+
end
|
67
|
+
|
68
|
+
# Compares the given number to valid 32-bit Mach-O magic numbers.
|
69
|
+
# @param num [Fixnum] the number being checked
|
70
|
+
# @return [Boolean] true if `num` is a valid 32-bit magic number, false otherwise
|
71
|
+
def self.magic32?(num)
|
72
|
+
num == MH_MAGIC || num == MH_CIGAM
|
73
|
+
end
|
74
|
+
|
75
|
+
# Compares the given number to valid 64-bit Mach-O magic numbers.
|
76
|
+
# @param num [Fixnum] the number being checked
|
77
|
+
# @return [Boolean] true if `num` is a valid 64-bit magic number, false otherwise
|
78
|
+
def self.magic64?(num)
|
79
|
+
num == MH_MAGIC_64 || num == MH_CIGAM_64
|
80
|
+
end
|
81
|
+
|
82
|
+
# Compares the given number to valid little-endian magic numbers.
|
83
|
+
# @param num [Fixnum] the number being checked
|
84
|
+
# @return [Boolean] true if `num` is a valid little-endian magic number, false otherwise
|
85
|
+
def self.little_magic?(num)
|
86
|
+
num == MH_CIGAM || num == MH_CIGAM_64
|
87
|
+
end
|
42
88
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
89
|
+
# Compares the given number to valid big-endian magic numbers.
|
90
|
+
# @param num [Fixnum] the number being checked
|
91
|
+
# @return [Boolean] true if `num` is a valid big-endian magic number, false otherwise
|
92
|
+
def self.big_magic?(num)
|
93
|
+
num == MH_CIGAM || num == MH_CIGAM_64
|
94
|
+
end
|
47
95
|
end
|
48
96
|
end
|
data/lib/macho/view.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
module MachO
|
2
|
+
# A representation of some unspecified Mach-O data.
|
3
|
+
class MachOView
|
4
|
+
# @return [String] the raw Mach-O data
|
5
|
+
attr_reader :raw_data
|
6
|
+
|
7
|
+
# @return [Symbol] the endianness of the data (`:big` or `:little`)
|
8
|
+
attr_reader :endianness
|
9
|
+
|
10
|
+
# @return [Fixnum] the offset of the relevant data (in {#raw_data})
|
11
|
+
attr_reader :offset
|
12
|
+
|
13
|
+
# Creates a new MachOView.
|
14
|
+
# @param raw_data [String] the raw Mach-O data
|
15
|
+
# @param endianness [Symbol] the endianness of the data
|
16
|
+
# @param offset [Fixnum] the offset of the relevant data
|
17
|
+
def initialize(raw_data, endianness, offset)
|
18
|
+
@raw_data = raw_data
|
19
|
+
@endianness = endianness
|
20
|
+
@offset = offset
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-macho
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- William Woodruff
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-08-07 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@tuffbizz.com
|
@@ -30,6 +30,7 @@ files:
|
|
30
30
|
- lib/macho/structure.rb
|
31
31
|
- lib/macho/tools.rb
|
32
32
|
- lib/macho/utils.rb
|
33
|
+
- lib/macho/view.rb
|
33
34
|
homepage: https://github.com/Homebrew/ruby-macho
|
34
35
|
licenses:
|
35
36
|
- MIT
|
@@ -55,3 +56,4 @@ signing_key:
|
|
55
56
|
specification_version: 4
|
56
57
|
summary: ruby-macho - Mach-O file analyzer.
|
57
58
|
test_files: []
|
59
|
+
has_rdoc:
|