ruby-macho 0.0.3 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- 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