ruby-macho 0.0.3 → 0.0.5
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/lib/macho.rb +3 -3
- data/lib/macho/exceptions.rb +8 -3
- data/lib/macho/fat_file.rb +114 -0
- data/lib/macho/headers.rb +64 -1
- data/lib/macho/load_commands.rb +11 -0
- data/lib/macho/{file.rb → macho_file.rb} +31 -12
- data/lib/macho/sections.rb +15 -0
- data/lib/macho/utils.rb +1 -2
- metadata +3 -4
- data/lib/int_helpers.rb +0 -17
- data/lib/otool_helpers.rb +0 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e6bf6b45bcf73b710246fe124075f4ca77c73070
|
4
|
+
data.tar.gz: 5713089fd72d423c6a25b90839e29a317072169b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a2aaa85277768159ee35ff7e44817afd1bad136884a2ced66dd471a39657e3827d4adbb7987fbca38c36aae25ef2ec3f8bfa5b9296d06013443f8f2e77db85fc
|
7
|
+
data.tar.gz: 20d89669d43b4fa964f878ade17d5130c58743a9bd742978e36cb485a32f4575d1d81957eb68719647340a9fc83120da611570e70da54a27ede7a4fe5adb02a2
|
data/lib/macho.rb
CHANGED
@@ -1,13 +1,13 @@
|
|
1
|
-
$:.unshift File.dirname(__FILE__)
|
2
|
-
|
3
1
|
require "cstruct"
|
4
2
|
require "macho/headers"
|
5
3
|
require "macho/structure"
|
6
4
|
require "macho/load_commands"
|
7
5
|
require "macho/sections"
|
8
|
-
require "macho/
|
6
|
+
require "macho/macho_file"
|
7
|
+
require "macho/fat_file"
|
9
8
|
require "macho/exceptions"
|
10
9
|
require "macho/utils"
|
10
|
+
require "macho/tools"
|
11
11
|
|
12
12
|
module MachO
|
13
13
|
# nothing to see here.
|
data/lib/macho/exceptions.rb
CHANGED
@@ -10,10 +10,15 @@ module MachO
|
|
10
10
|
end
|
11
11
|
end
|
12
12
|
|
13
|
-
# raised when a file's magic bytes are those of a fat binary
|
14
13
|
class FatBinaryError < MachOError
|
15
|
-
def initialize
|
16
|
-
super "
|
14
|
+
def initialize
|
15
|
+
super "Fat binaries must be loaded with MachO::FatFile"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class MachOBinaryError < MachOError
|
20
|
+
def initialize
|
21
|
+
super "Normal binaries must be loaded with MachO::MachOFile"
|
17
22
|
end
|
18
23
|
end
|
19
24
|
|
@@ -0,0 +1,114 @@
|
|
1
|
+
module MachO
|
2
|
+
class FatFile
|
3
|
+
attr_reader :header, :fat_archs, :machos
|
4
|
+
|
5
|
+
def initialize(filename)
|
6
|
+
raise ArgumentError.new("filename must be a String") unless filename.is_a? String
|
7
|
+
|
8
|
+
@filename = filename
|
9
|
+
@raw_data = open(@filename, "rb") { |f| f.read }
|
10
|
+
@header = get_fat_header
|
11
|
+
@fat_archs = get_fat_archs
|
12
|
+
@machos = get_machos
|
13
|
+
end
|
14
|
+
|
15
|
+
def serialize
|
16
|
+
@raw_data
|
17
|
+
end
|
18
|
+
|
19
|
+
def dylib_id
|
20
|
+
if !machos.all?(&:dylib?)
|
21
|
+
return nil
|
22
|
+
end
|
23
|
+
|
24
|
+
ids = machos.map(&:dylib_id)
|
25
|
+
|
26
|
+
# this should never be the case, but let's be defensive
|
27
|
+
if !ids.uniq!.size == 1
|
28
|
+
return nil
|
29
|
+
end
|
30
|
+
|
31
|
+
ids.first
|
32
|
+
end
|
33
|
+
|
34
|
+
def dylib_id=(new_id)
|
35
|
+
if !new_id.is_a?(String)
|
36
|
+
raise ArgumentError.new("argument must be a String")
|
37
|
+
end
|
38
|
+
|
39
|
+
if !machos.all?(&:dylib?)
|
40
|
+
return nil
|
41
|
+
end
|
42
|
+
|
43
|
+
machos.each do |macho|
|
44
|
+
macho.dylib_id = new_id
|
45
|
+
end
|
46
|
+
|
47
|
+
synchronize_raw_data
|
48
|
+
end
|
49
|
+
|
50
|
+
def linked_dylibs
|
51
|
+
dylibs = machos.map(&:linked_dylibs)
|
52
|
+
|
53
|
+
# can machos inside fat binaries have different dylibs?
|
54
|
+
dylibs.uniq!
|
55
|
+
end
|
56
|
+
|
57
|
+
def write(filename)
|
58
|
+
File.open(filename, "wb") { |f| f.write(@raw_data) }
|
59
|
+
end
|
60
|
+
|
61
|
+
def write!
|
62
|
+
File.open(@filename, "wb") { |f| f.write(@raw_data) }
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def get_fat_header
|
68
|
+
magic, nfat_arch = @raw_data[0..7].unpack("N2")
|
69
|
+
|
70
|
+
if !Utils.magic?(magic)
|
71
|
+
raise MagicError.new(magic)
|
72
|
+
end
|
73
|
+
|
74
|
+
if !Utils.fat_magic?(magic)
|
75
|
+
raise MachOBinaryError.new
|
76
|
+
end
|
77
|
+
|
78
|
+
FatHeader.new(magic, nfat_arch)
|
79
|
+
end
|
80
|
+
|
81
|
+
def get_fat_archs
|
82
|
+
archs = []
|
83
|
+
|
84
|
+
header[:nfat_arch].times do |i|
|
85
|
+
fields = @raw_data[8 + (FatArch.bytesize * i), FatArch.bytesize].unpack("N5")
|
86
|
+
archs << FatArch.new(*fields)
|
87
|
+
end
|
88
|
+
|
89
|
+
archs
|
90
|
+
end
|
91
|
+
|
92
|
+
def get_machos
|
93
|
+
machos = []
|
94
|
+
|
95
|
+
fat_archs.each do |arch|
|
96
|
+
machos << MachOFile.new_from_bin(@raw_data[arch[:offset], arch[:size]])
|
97
|
+
end
|
98
|
+
|
99
|
+
machos
|
100
|
+
end
|
101
|
+
|
102
|
+
# when we create machos within FatFile, we initialize them with slices
|
103
|
+
# from @raw_data. this means creating new arrays that don't affect
|
104
|
+
# @raw_data directly, so we need to synchronize it after changing
|
105
|
+
# anything within the machos.
|
106
|
+
def synchronize_raw_data
|
107
|
+
machos.each_with_index do |macho, i|
|
108
|
+
arch = fat_archs[i]
|
109
|
+
|
110
|
+
@raw_data[arch[:offset], arch[:size]] = macho.serialize
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
data/lib/macho/headers.rb
CHANGED
@@ -79,7 +79,62 @@ module MachO
|
|
79
79
|
MH_KEXT_BUNDLE => "MH_KEXT_BUNDLE"
|
80
80
|
}
|
81
81
|
|
82
|
-
#
|
82
|
+
# values for flags field in MachHeader/MachHeader64
|
83
|
+
MH_NOUNDEFS = 0x1
|
84
|
+
MH_INCRLINK = 0x2
|
85
|
+
MH_DYLDLINK = 0x4
|
86
|
+
MH_BINDATLOAD = 0x8
|
87
|
+
MH_PREBOUND = 0x10
|
88
|
+
MH_SPLIT_SEGS = 0x20
|
89
|
+
MH_LAZY_INIT = 0x40
|
90
|
+
MH_TWOLEVEL = 0x80
|
91
|
+
MH_FORCE_FLAT = 0x100
|
92
|
+
MH_NOMULTIDEFS = 0x200
|
93
|
+
MH_NOPREFIXBINDING = 0x400
|
94
|
+
MH_PREBINDABLE = 0x800
|
95
|
+
MH_ALLMODSBOUND = 0x1000
|
96
|
+
MH_SUBSECTIONS_VIA_SYMBOLS = 0x2000
|
97
|
+
MH_CANONICAL = 0x4000
|
98
|
+
MH_WEAK_DEFINES = 0x8000
|
99
|
+
MH_BINDS_TO_WEAK = 0x10000
|
100
|
+
MH_ALLOW_STACK_EXECUTION = 0x20000
|
101
|
+
MH_ROOT_SAFE = 0x40000
|
102
|
+
MH_SETUID_SAFE = 0x80000
|
103
|
+
MH_NO_REEXPORTED_DYLIBS = 0x100000
|
104
|
+
MH_PIE = 0x200000
|
105
|
+
MH_DEAD_STRIPPABLE_DYLIB = 0x400000
|
106
|
+
MH_HAS_TLV_DESCRIPTORS = 0x800000
|
107
|
+
MH_NO_HEAP_EXECUTION = 0x1000000
|
108
|
+
MH_APP_EXTENSION_SAFE = 0x02000000
|
109
|
+
|
110
|
+
MH_FLAGS = {
|
111
|
+
MH_NOUNDEFS => "MH_NOUNDEFS",
|
112
|
+
MH_INCRLINK => "MH_INCRLINK",
|
113
|
+
MH_DYLDLINK => "MH_DYLDLINK",
|
114
|
+
MH_BINDATLOAD => "MH_BINDATLOAD",
|
115
|
+
MH_PREBOUND => "MH_PREBOUND",
|
116
|
+
MH_SPLIT_SEGS => "MH_SPLIT_SEGS",
|
117
|
+
MH_LAZY_INIT => "MH_LAZY_INIT",
|
118
|
+
MH_TWOLEVEL => "MH_TWOLEVEL",
|
119
|
+
MH_FORCE_FLAT => "MH_FORCE_FLAT",
|
120
|
+
MH_NOMULTIDEFS => "MH_NOMULTIDEFS",
|
121
|
+
MH_NOPREFIXBINDING => "MH_NOPREFIXBINDING",
|
122
|
+
MH_PREBINDABLE => "MH_PREBINDABLE",
|
123
|
+
MH_ALLMODSBOUND => "MH_ALLMODSBOUND",
|
124
|
+
MH_SUBSECTIONS_VIA_SYMBOLS => "MH_SUBSECTIONS_VIA_SYMBOLS",
|
125
|
+
MH_CANONICAL => "MH_CANONICAL",
|
126
|
+
MH_WEAK_DEFINES => "MH_WEAK_DEFINES",
|
127
|
+
MH_BINDS_TO_WEAK => "MH_BINDS_TO_WEAK",
|
128
|
+
MH_ALLOW_STACK_EXECUTION => "MH_ALLOW_STACK_EXECUTION",
|
129
|
+
MH_ROOT_SAFE => "MH_ROOT_SAFE",
|
130
|
+
MH_SETUID_SAFE => "MH_SETUID_SAFE",
|
131
|
+
MH_NO_REEXPORTED_DYLIBS => "MH_NO_REEXPORTED_DYLIBS",
|
132
|
+
MH_PIE => "MH_PIE",
|
133
|
+
MH_DEAD_STRIPPABLE_DYLIB => "MH_DEAD_STRIPPABLE_DYLIB",
|
134
|
+
MH_HAS_TLV_DESCRIPTORS => "MH_HAS_TLV_DESCRIPTORS",
|
135
|
+
MH_NO_HEAP_EXECUTION => "MH_NO_HEAP_EXECUTION",
|
136
|
+
MH_APP_EXTENSION_SAFE => "MH_APP_EXTENSION_SAFE"
|
137
|
+
}
|
83
138
|
|
84
139
|
# 'Fat' binaries envelop Mach-O binaries so include them for completeness,
|
85
140
|
# Fat binary header structure
|
@@ -106,6 +161,10 @@ module MachO
|
|
106
161
|
uint32 :ncmds
|
107
162
|
uint32 :sizeofcmds
|
108
163
|
uint32 :flags
|
164
|
+
|
165
|
+
def flag?(flag)
|
166
|
+
flags & flag == flag
|
167
|
+
end
|
109
168
|
end
|
110
169
|
|
111
170
|
# 64-bit Mach-O file header structure
|
@@ -118,5 +177,9 @@ module MachO
|
|
118
177
|
uint32 :sizeofcmds
|
119
178
|
uint32 :flags
|
120
179
|
uint32 :reserved
|
180
|
+
|
181
|
+
def flag?(flag)
|
182
|
+
flags & flag == flag
|
183
|
+
end
|
121
184
|
end
|
122
185
|
end
|
data/lib/macho/load_commands.rb
CHANGED
@@ -152,6 +152,17 @@ module MachO
|
|
152
152
|
LC_LINKER_OPTIMIZATION_HINT => "LinkeditDataCommand"
|
153
153
|
}
|
154
154
|
|
155
|
+
# currently known segment names
|
156
|
+
# we don't use these anywhere right now, but they're good to have
|
157
|
+
SEG_PAGEZERO = "__PAGEZERO"
|
158
|
+
SEG_TEXT = "__TEXT"
|
159
|
+
SEG_DATA = "__DATA"
|
160
|
+
SEG_OBJC = "__OBJC"
|
161
|
+
SEG_ICON = "__ICON"
|
162
|
+
SEG_LINKEDIT = "__LINKEDIT"
|
163
|
+
SEG_UNIXSTACK = "__UNIXSTACK"
|
164
|
+
SEG_IMPORT = "__IMPORT"
|
165
|
+
|
155
166
|
# Mach-O load command structure
|
156
167
|
# this is the most generic load command - only cmd ID and size are
|
157
168
|
# represented, and no actual data. used when a more specific class
|
@@ -2,6 +2,13 @@ module MachO
|
|
2
2
|
class MachOFile
|
3
3
|
attr_reader :header, :load_commands
|
4
4
|
|
5
|
+
def self.new_from_bin(bin)
|
6
|
+
instance = allocate
|
7
|
+
instance.initialize_from_bin(bin)
|
8
|
+
|
9
|
+
instance
|
10
|
+
end
|
11
|
+
|
5
12
|
def initialize(filename)
|
6
13
|
raise ArgumentError.new("filename must be a String") unless filename.is_a? String
|
7
14
|
|
@@ -11,6 +18,17 @@ module MachO
|
|
11
18
|
@load_commands = get_load_commands
|
12
19
|
end
|
13
20
|
|
21
|
+
def initialize_from_bin(bin)
|
22
|
+
@filename = nil
|
23
|
+
@raw_data = bin
|
24
|
+
@header = get_mach_header
|
25
|
+
@load_commands = get_load_commands
|
26
|
+
end
|
27
|
+
|
28
|
+
def serialize
|
29
|
+
@raw_data
|
30
|
+
end
|
31
|
+
|
14
32
|
def magic32?
|
15
33
|
Utils.magic32?(header[:magic])
|
16
34
|
end
|
@@ -231,10 +249,13 @@ module MachO
|
|
231
249
|
end
|
232
250
|
|
233
251
|
def write!
|
234
|
-
|
252
|
+
if @filename.nil?
|
253
|
+
raise MachOError.new("cannot write to a default file when initialized from a binary string")
|
254
|
+
else
|
255
|
+
File.open(@filename, "wb") { |f| f.write(@raw_data) }
|
256
|
+
end
|
235
257
|
end
|
236
258
|
|
237
|
-
#######
|
238
259
|
private
|
239
260
|
|
240
261
|
def get_mach_header
|
@@ -247,10 +268,10 @@ module MachO
|
|
247
268
|
flags = get_flags
|
248
269
|
|
249
270
|
if Utils.magic32?(magic)
|
250
|
-
|
271
|
+
MachHeader.new(magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags)
|
251
272
|
else
|
252
|
-
# the reserved field is
|
253
|
-
|
273
|
+
# the reserved field is...reserved, so just fill it with 0
|
274
|
+
MachHeader64.new(magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags, 0)
|
254
275
|
end
|
255
276
|
end
|
256
277
|
|
@@ -261,9 +282,8 @@ module MachO
|
|
261
282
|
raise MagicError.new(magic)
|
262
283
|
end
|
263
284
|
|
264
|
-
# TODO: support fat (universal) binaries
|
265
285
|
if Utils.fat_magic?(magic)
|
266
|
-
raise FatBinaryError.new
|
286
|
+
raise FatBinaryError.new
|
267
287
|
end
|
268
288
|
|
269
289
|
magic
|
@@ -272,7 +292,7 @@ module MachO
|
|
272
292
|
def get_cputype
|
273
293
|
cputype = @raw_data[4..7].unpack("V").first
|
274
294
|
|
275
|
-
if !CPU_TYPES.
|
295
|
+
if !CPU_TYPES.has_key?(cputype)
|
276
296
|
raise CPUTypeError.new(cputype)
|
277
297
|
end
|
278
298
|
|
@@ -285,7 +305,7 @@ module MachO
|
|
285
305
|
# this mask isn't documented!
|
286
306
|
cpusubtype &= ~CPU_SUBTYPE_LIB64
|
287
307
|
|
288
|
-
if !CPU_SUBTYPES.
|
308
|
+
if !CPU_SUBTYPES.has_key?(cpusubtype)
|
289
309
|
raise CPUSubtypeError.new(cpusubtype)
|
290
310
|
end
|
291
311
|
|
@@ -295,7 +315,7 @@ module MachO
|
|
295
315
|
def get_filetype
|
296
316
|
filetype = @raw_data[12..15].unpack("V").first
|
297
317
|
|
298
|
-
if !MH_FILETYPES.
|
318
|
+
if !MH_FILETYPES.has_key?(filetype)
|
299
319
|
raise FiletypeError.new(filetype)
|
300
320
|
end
|
301
321
|
|
@@ -314,7 +334,6 @@ module MachO
|
|
314
334
|
sizeofcmds
|
315
335
|
end
|
316
336
|
|
317
|
-
# TODO: parse flags, maybe?
|
318
337
|
def get_flags
|
319
338
|
flags = @raw_data[24..27].unpack("V").first
|
320
339
|
|
@@ -334,7 +353,7 @@ module MachO
|
|
334
353
|
|
335
354
|
# why do I do this? i don't like declaring constants below
|
336
355
|
# classes, and i need them to resolve...
|
337
|
-
klass =
|
356
|
+
klass = MachO.const_get "#{LC_STRUCTURES[cmd]}"
|
338
357
|
command = klass.new_from_bin(offset, @raw_data.slice(offset, klass.bytesize))
|
339
358
|
|
340
359
|
load_commands << command
|
data/lib/macho/sections.rb
CHANGED
@@ -41,6 +41,21 @@ module MachO
|
|
41
41
|
S_ATTR_EXT_RELOC = 0x00000200
|
42
42
|
S_ATTR_LOC_RELOC = 0x00000100
|
43
43
|
|
44
|
+
# currently known section names
|
45
|
+
# we don't use these anywhere right now, but they're good to have
|
46
|
+
SECT_TEXT = "__text"
|
47
|
+
SECT_FVMLIB_INIT0 = "__fvmlib_init0"
|
48
|
+
SECT_FVMLIB_INIT1 = "__fvmlib_init1"
|
49
|
+
SECT_DATA = "__data"
|
50
|
+
SECT_BSS = "__bss"
|
51
|
+
SECT_COMMON = "__common"
|
52
|
+
SECT_OBJC_SYMBOLS = "__symbol_table"
|
53
|
+
SECT_OBJC_MODULES = "__module_info"
|
54
|
+
SECT_OBJC_STRINGS = "__selector_strs"
|
55
|
+
SECT_OBJC_REFS = "__selector_refs"
|
56
|
+
SECT_ICON_HEADER = "__header"
|
57
|
+
SECT_ICON_TIFF = "__tiff"
|
58
|
+
|
44
59
|
class Section < MachOStructure
|
45
60
|
attr_reader :sectname, :segname, :addr, :size, :offset, :align, :reloff
|
46
61
|
attr_reader :nreloc, :flags, :reserved1, :reserved2
|
data/lib/macho/utils.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-macho
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- William Woodruff
|
@@ -17,16 +17,15 @@ extensions: []
|
|
17
17
|
extra_rdoc_files: []
|
18
18
|
files:
|
19
19
|
- lib/cstruct.rb
|
20
|
-
- lib/int_helpers.rb
|
21
20
|
- lib/macho.rb
|
22
21
|
- lib/macho/exceptions.rb
|
23
|
-
- lib/macho/
|
22
|
+
- lib/macho/fat_file.rb
|
24
23
|
- lib/macho/headers.rb
|
25
24
|
- lib/macho/load_commands.rb
|
25
|
+
- lib/macho/macho_file.rb
|
26
26
|
- lib/macho/sections.rb
|
27
27
|
- lib/macho/structure.rb
|
28
28
|
- lib/macho/utils.rb
|
29
|
-
- lib/otool_helpers.rb
|
30
29
|
homepage: https://github.com/woodruffw/ruby-macho
|
31
30
|
licenses:
|
32
31
|
- MIT
|
data/lib/int_helpers.rb
DELETED
@@ -1,17 +0,0 @@
|
|
1
|
-
module INTHelpers
|
2
|
-
Change = Struct.new("Changes", :old, :new)
|
3
|
-
|
4
|
-
Rpath = Struct.new("Rpaths", :old, :new, :found) do
|
5
|
-
def found?
|
6
|
-
found
|
7
|
-
end
|
8
|
-
end
|
9
|
-
|
10
|
-
AddRpath = Struct.new("AddRpaths", :new)
|
11
|
-
|
12
|
-
DeleteRpath = Struct.new("DeleteRpaths", :old, :found) do
|
13
|
-
def found?
|
14
|
-
found
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
data/lib/otool_helpers.rb
DELETED