ruby-macho 0.2.2 → 0.2.3
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/LICENSE +2 -2
- data/README.md +4 -9
- data/lib/macho.rb +2 -2
- data/lib/macho/exceptions.rb +104 -73
- data/lib/macho/fat_file.rb +273 -228
- data/lib/macho/headers.rb +342 -265
- data/lib/macho/load_commands.rb +1009 -999
- data/lib/macho/macho_file.rb +509 -529
- data/lib/macho/open.rb +23 -14
- data/lib/macho/sections.rb +157 -157
- data/lib/macho/structure.rb +33 -17
- data/lib/macho/tools.rb +55 -55
- data/lib/macho/utils.rb +42 -30
- metadata +3 -4
data/lib/macho/macho_file.rb
CHANGED
@@ -1,531 +1,511 @@
|
|
1
1
|
module MachO
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
# delete the old str
|
512
|
-
@raw_data.slice!(cmd.offset + lc_str.to_i...cmd.offset + cmd.class.bytesize + old_str.size)
|
513
|
-
|
514
|
-
# insert the new str
|
515
|
-
@raw_data.insert(cmd.offset + lc_str.to_i, new_str)
|
516
|
-
|
517
|
-
# pad/unpad after new_sizeofcmds until offsets are corrected
|
518
|
-
null_pad = old_str.size - new_str.size
|
519
|
-
|
520
|
-
if null_pad < 0
|
521
|
-
@raw_data.slice!(new_sizeofcmds + header.class.bytesize, null_pad.abs)
|
522
|
-
else
|
523
|
-
@raw_data.insert(new_sizeofcmds + header.class.bytesize, "\x00" * null_pad)
|
524
|
-
end
|
525
|
-
|
526
|
-
# synchronize fields with the raw data
|
527
|
-
@header = get_mach_header
|
528
|
-
@load_commands = get_load_commands
|
529
|
-
end
|
530
|
-
end
|
2
|
+
# Represents a Mach-O file, which contains a header and load commands
|
3
|
+
# as well as binary executable instructions. Mach-O binaries are
|
4
|
+
# architecture specific.
|
5
|
+
# @see https://en.wikipedia.org/wiki/Mach-O
|
6
|
+
# @see MachO::FatFile
|
7
|
+
class MachOFile
|
8
|
+
# @return [String] the filename loaded from, or nil if loaded from a binary string
|
9
|
+
attr_accessor :filename
|
10
|
+
|
11
|
+
# @return [Symbol] the endianness of the file, :big or :little
|
12
|
+
attr_reader :endianness
|
13
|
+
|
14
|
+
# @return [MachO::MachHeader] if the Mach-O is 32-bit
|
15
|
+
# @return [MachO::MachHeader64] if the Mach-O is 64-bit
|
16
|
+
attr_reader :header
|
17
|
+
|
18
|
+
# @return [Array<MachO::LoadCommand>] an array of the file's load commands
|
19
|
+
attr_reader :load_commands
|
20
|
+
|
21
|
+
# Creates a new MachOFile instance from a binary string.
|
22
|
+
# @param bin [String] a binary string containing raw Mach-O data
|
23
|
+
# @return [MachO::MachOFile] a new MachOFile
|
24
|
+
def self.new_from_bin(bin)
|
25
|
+
instance = allocate
|
26
|
+
instance.initialize_from_bin(bin)
|
27
|
+
|
28
|
+
instance
|
29
|
+
end
|
30
|
+
|
31
|
+
# Creates a new FatFile from the given filename.
|
32
|
+
# @param filename [String] the Mach-O file to load from
|
33
|
+
# @raise [ArgumentError] if the given file does not exist
|
34
|
+
def initialize(filename)
|
35
|
+
raise ArgumentError.new("#{filename}: no such file") unless File.file?(filename)
|
36
|
+
|
37
|
+
@filename = filename
|
38
|
+
@raw_data = File.open(@filename, "rb") { |f| f.read }
|
39
|
+
@header = get_mach_header
|
40
|
+
@load_commands = get_load_commands
|
41
|
+
end
|
42
|
+
|
43
|
+
# @api private
|
44
|
+
def initialize_from_bin(bin)
|
45
|
+
@filename = nil
|
46
|
+
@raw_data = bin
|
47
|
+
@header = get_mach_header
|
48
|
+
@load_commands = get_load_commands
|
49
|
+
end
|
50
|
+
|
51
|
+
# The file's raw Mach-O data.
|
52
|
+
# @return [String] the raw Mach-O data
|
53
|
+
def serialize
|
54
|
+
@raw_data
|
55
|
+
end
|
56
|
+
|
57
|
+
# @return [Boolean] true if the Mach-O has 32-bit magic, false otherwise
|
58
|
+
def magic32?
|
59
|
+
MachO.magic32?(header.magic)
|
60
|
+
end
|
61
|
+
|
62
|
+
# @return [Boolean] true if the Mach-O has 64-bit magic, false otherwise
|
63
|
+
def magic64?
|
64
|
+
MachO.magic64?(header.magic)
|
65
|
+
end
|
66
|
+
|
67
|
+
# @return [Boolean] true if the file is of type `MH_OBJECT`, false otherwise
|
68
|
+
def object?
|
69
|
+
header.filetype == MH_OBJECT
|
70
|
+
end
|
71
|
+
|
72
|
+
# @return [Boolean] true if the file is of type `MH_EXECUTE`, false otherwise
|
73
|
+
def executable?
|
74
|
+
header.filetype == MH_EXECUTE
|
75
|
+
end
|
76
|
+
|
77
|
+
# @return [Boolean] true if the file is of type `MH_FVMLIB`, false otherwise
|
78
|
+
def fvmlib?
|
79
|
+
header.filetype == MH_FVMLIB
|
80
|
+
end
|
81
|
+
|
82
|
+
# @return [Boolean] true if the file is of type `MH_CORE`, false otherwise
|
83
|
+
def core?
|
84
|
+
header.filetype == MH_CORE
|
85
|
+
end
|
86
|
+
|
87
|
+
# @return [Boolean] true if the file is of type `MH_PRELOAD`, false otherwise
|
88
|
+
def preload?
|
89
|
+
header.filetype == MH_PRELOAD
|
90
|
+
end
|
91
|
+
|
92
|
+
# @return [Boolean] true if the file is of type `MH_DYLIB`, false otherwise
|
93
|
+
def dylib?
|
94
|
+
header.filetype == MH_DYLIB
|
95
|
+
end
|
96
|
+
|
97
|
+
# @return [Boolean] true if the file is of type `MH_DYLINKER`, false otherwise
|
98
|
+
def dylinker?
|
99
|
+
header.filetype == MH_DYLINKER
|
100
|
+
end
|
101
|
+
|
102
|
+
# @return [Boolean] true if the file is of type `MH_BUNDLE`, false otherwise
|
103
|
+
def bundle?
|
104
|
+
header.filetype == MH_BUNDLE
|
105
|
+
end
|
106
|
+
|
107
|
+
# @return [Boolean] true if the file is of type `MH_DSYM`, false otherwise
|
108
|
+
def dsym?
|
109
|
+
header.filetype == MH_DSYM
|
110
|
+
end
|
111
|
+
|
112
|
+
# @return [Boolean] true if the file is of type `MH_KEXT_BUNDLE`, false otherwise
|
113
|
+
def kext?
|
114
|
+
header.filetype == MH_KEXT_BUNDLE
|
115
|
+
end
|
116
|
+
|
117
|
+
# @return [Fixnum] the file's magic number
|
118
|
+
def magic
|
119
|
+
header.magic
|
120
|
+
end
|
121
|
+
|
122
|
+
# @return [String] a string representation of the file's magic number
|
123
|
+
def magic_string
|
124
|
+
MH_MAGICS[magic]
|
125
|
+
end
|
126
|
+
|
127
|
+
# @return [String] a string representation of the Mach-O's filetype
|
128
|
+
def filetype
|
129
|
+
MH_FILETYPES[header.filetype]
|
130
|
+
end
|
131
|
+
|
132
|
+
# @return [Symbol] a symbol representation of the Mach-O's CPU type
|
133
|
+
def cputype
|
134
|
+
CPU_TYPES[header.cputype]
|
135
|
+
end
|
136
|
+
|
137
|
+
# @return [Symbol] a symbol representation of the Mach-O's CPU subtype
|
138
|
+
def cpusubtype
|
139
|
+
CPU_SUBTYPES[header.cputype][header.cpusubtype]
|
140
|
+
end
|
141
|
+
|
142
|
+
# @return [Fixnum] the number of load commands in the Mach-O's header
|
143
|
+
def ncmds
|
144
|
+
header.ncmds
|
145
|
+
end
|
146
|
+
|
147
|
+
# @return [Fixnum] the size of all load commands, in bytes
|
148
|
+
def sizeofcmds
|
149
|
+
header.sizeofcmds
|
150
|
+
end
|
151
|
+
|
152
|
+
# @return [Fixnum] execution flags set by the linker
|
153
|
+
def flags
|
154
|
+
header.flags
|
155
|
+
end
|
156
|
+
|
157
|
+
# All load commands of a given name.
|
158
|
+
# @example
|
159
|
+
# file.command("LC_LOAD_DYLIB")
|
160
|
+
# file[:LC_LOAD_DYLIB]
|
161
|
+
# @param [String, Symbol] name the load command ID
|
162
|
+
# @return [Array<MachO::LoadCommand>] an array of LoadCommands corresponding to `name`
|
163
|
+
def command(name)
|
164
|
+
load_commands.select { |lc| lc.type == name.to_sym }
|
165
|
+
end
|
166
|
+
|
167
|
+
alias :[] :command
|
168
|
+
|
169
|
+
# All load commands responsible for loading dylibs.
|
170
|
+
# @return [Array<MachO::DylibCommand>] an array of DylibCommands
|
171
|
+
def dylib_load_commands
|
172
|
+
load_commands.select { |lc| DYLIB_LOAD_COMMANDS.include?(lc.type) }
|
173
|
+
end
|
174
|
+
|
175
|
+
# All segment load commands in the Mach-O.
|
176
|
+
# @return [Array<MachO::SegmentCommand>] if the Mach-O is 32-bit
|
177
|
+
# @return [Array<MachO::SegmentCommand64>] if the Mach-O is 64-bit
|
178
|
+
def segments
|
179
|
+
if magic32?
|
180
|
+
command(:LC_SEGMENT)
|
181
|
+
else
|
182
|
+
command(:LC_SEGMENT_64)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
# The Mach-O's dylib ID, or `nil` if not a dylib.
|
187
|
+
# @example
|
188
|
+
# file.dylib_id # => 'libBar.dylib'
|
189
|
+
# @return [String, nil] the Mach-O's dylib ID
|
190
|
+
def dylib_id
|
191
|
+
if !dylib?
|
192
|
+
return nil
|
193
|
+
end
|
194
|
+
|
195
|
+
dylib_id_cmd = command(:LC_ID_DYLIB).first
|
196
|
+
|
197
|
+
dylib_id_cmd.name.to_s
|
198
|
+
end
|
199
|
+
|
200
|
+
# Changes the Mach-O's dylib ID to `new_id`. Does nothing if not a dylib.
|
201
|
+
# @example
|
202
|
+
# file.dylib_id = "libFoo.dylib"
|
203
|
+
# @param new_id [String] the dylib's new ID
|
204
|
+
# @return [void]
|
205
|
+
# @raise [ArgumentError] if `new_id` is not a String
|
206
|
+
def dylib_id=(new_id)
|
207
|
+
if !new_id.is_a?(String)
|
208
|
+
raise ArgumentError.new("argument must be a String")
|
209
|
+
end
|
210
|
+
|
211
|
+
if !dylib?
|
212
|
+
return nil
|
213
|
+
end
|
214
|
+
|
215
|
+
dylib_cmd = command(:LC_ID_DYLIB).first
|
216
|
+
old_id = dylib_id
|
217
|
+
|
218
|
+
set_name_in_dylib(dylib_cmd, old_id, new_id)
|
219
|
+
end
|
220
|
+
|
221
|
+
# All shared libraries linked to the Mach-O.
|
222
|
+
# @return [Array<String>] an array of all shared libraries
|
223
|
+
def linked_dylibs
|
224
|
+
# Some linkers produce multiple `LC_LOAD_DYLIB` load commands for the same
|
225
|
+
# library, but at this point we're really only interested in a list of
|
226
|
+
# unique libraries this Mach-O file links to, thus: `uniq`. (This is also
|
227
|
+
# for consistency with `FatFile` that merges this list across all archs.)
|
228
|
+
dylib_load_commands.map(&:name).map(&:to_s).uniq
|
229
|
+
end
|
230
|
+
|
231
|
+
# Changes the shared library `old_name` to `new_name`
|
232
|
+
# @example
|
233
|
+
# file.change_install_name("/usr/lib/libWhatever.dylib", "/usr/local/lib/libWhatever2.dylib")
|
234
|
+
# @param old_name [String] the shared library's old name
|
235
|
+
# @param new_name [String] the shared library's new name
|
236
|
+
# @return [void]
|
237
|
+
# @raise [MachO::DylibUnknownError] if no shared library has the old name
|
238
|
+
def change_install_name(old_name, new_name)
|
239
|
+
dylib_cmd = dylib_load_commands.find { |d| d.name.to_s == old_name }
|
240
|
+
raise DylibUnknownError.new(old_name) if dylib_cmd.nil?
|
241
|
+
|
242
|
+
set_name_in_dylib(dylib_cmd, old_name, new_name)
|
243
|
+
end
|
244
|
+
|
245
|
+
alias :change_dylib :change_install_name
|
246
|
+
|
247
|
+
# All runtime paths searched by the dynamic linker for the Mach-O.
|
248
|
+
# @return [Array<String>] an array of all runtime paths
|
249
|
+
def rpaths
|
250
|
+
command(:LC_RPATH).map(&:path).map(&:to_s)
|
251
|
+
end
|
252
|
+
|
253
|
+
# Changes the runtime path `old_path` to `new_path`
|
254
|
+
# @example
|
255
|
+
# file.change_rpath("/usr/lib", "/usr/local/lib")
|
256
|
+
# @param old_path [String] the old runtime path
|
257
|
+
# @param new_path [String] the new runtime path
|
258
|
+
# @return [void]
|
259
|
+
# @raise [MachO::RpathUnknownError] if no such old runtime path exists
|
260
|
+
# @api private
|
261
|
+
def change_rpath(old_path, new_path)
|
262
|
+
rpath_cmd = command(:LC_RPATH).find { |r| r.path.to_s == old_path }
|
263
|
+
raise RpathUnknownError.new(old_path) if rpath_cmd.nil?
|
264
|
+
|
265
|
+
set_path_in_rpath(rpath_cmd, old_path, new_path)
|
266
|
+
end
|
267
|
+
|
268
|
+
# All sections of the segment `segment`.
|
269
|
+
# @param segment [MachO::SegmentCommand, MachO::SegmentCommand64] the segment being inspected
|
270
|
+
# @return [Array<MachO::Section>] if the Mach-O is 32-bit
|
271
|
+
# @return [Array<MachO::Section64>] if the Mach-O is 64-bit
|
272
|
+
def sections(segment)
|
273
|
+
sections = []
|
274
|
+
|
275
|
+
if !segment.is_a?(SegmentCommand) && !segment.is_a?(SegmentCommand64)
|
276
|
+
raise ArgumentError.new("not a valid segment")
|
277
|
+
end
|
278
|
+
|
279
|
+
if segment.nsects.zero?
|
280
|
+
return sections
|
281
|
+
end
|
282
|
+
|
283
|
+
offset = segment.offset + segment.class.bytesize
|
284
|
+
|
285
|
+
segment.nsects.times do
|
286
|
+
if segment.is_a? SegmentCommand
|
287
|
+
sections << Section.new_from_bin(endianness, @raw_data.slice(offset, Section.bytesize))
|
288
|
+
offset += Section.bytesize
|
289
|
+
else
|
290
|
+
sections << Section64.new_from_bin(endianness, @raw_data.slice(offset, Section64.bytesize))
|
291
|
+
offset += Section64.bytesize
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
sections
|
296
|
+
end
|
297
|
+
|
298
|
+
# Write all Mach-O data to the given filename.
|
299
|
+
# @param filename [String] the file to write to
|
300
|
+
# @return [void]
|
301
|
+
def write(filename)
|
302
|
+
File.open(filename, "wb") { |f| f.write(@raw_data) }
|
303
|
+
end
|
304
|
+
|
305
|
+
# Write all Mach-O data to the file used to initialize the instance.
|
306
|
+
# @return [void]
|
307
|
+
# @raise [MachO::MachOError] if the instance was initialized without a file
|
308
|
+
# @note Overwrites all data in the file!
|
309
|
+
def write!
|
310
|
+
if @filename.nil?
|
311
|
+
raise MachOError.new("cannot write to a default file when initialized from a binary string")
|
312
|
+
else
|
313
|
+
File.open(@filename, "wb") { |f| f.write(@raw_data) }
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
private
|
318
|
+
|
319
|
+
# The file's Mach-O header structure.
|
320
|
+
# @return [MachO::MachHeader] if the Mach-O is 32-bit
|
321
|
+
# @return [MachO::MachHeader64] if the Mach-O is 64-bit
|
322
|
+
# @raise [MachO::TruncatedFileError] if the file is too small to have a valid header
|
323
|
+
# @private
|
324
|
+
def get_mach_header
|
325
|
+
# the smallest Mach-O header is 28 bytes
|
326
|
+
raise TruncatedFileError.new if @raw_data.size < 28
|
327
|
+
|
328
|
+
magic = get_and_check_magic
|
329
|
+
mh_klass = MachO.magic32?(magic) ? MachHeader : MachHeader64
|
330
|
+
mh = mh_klass.new_from_bin(endianness, @raw_data[0, mh_klass.bytesize])
|
331
|
+
|
332
|
+
check_cputype(mh.cputype)
|
333
|
+
check_cpusubtype(mh.cputype, mh.cpusubtype)
|
334
|
+
check_filetype(mh.filetype)
|
335
|
+
|
336
|
+
mh
|
337
|
+
end
|
338
|
+
|
339
|
+
# Read just the file's magic number and check its validity.
|
340
|
+
# @return [Fixnum] the magic
|
341
|
+
# @raise [MachO::MagicError] if the magic is not valid Mach-O magic
|
342
|
+
# @raise [MachO::FatBinaryError] if the magic is for a Fat file
|
343
|
+
# @private
|
344
|
+
def get_and_check_magic
|
345
|
+
magic = @raw_data[0..3].unpack("N").first
|
346
|
+
|
347
|
+
raise MagicError.new(magic) unless MachO.magic?(magic)
|
348
|
+
raise FatBinaryError.new if MachO.fat_magic?(magic)
|
349
|
+
|
350
|
+
@endianness = MachO.little_magic?(magic) ? :little : :big
|
351
|
+
|
352
|
+
magic
|
353
|
+
end
|
354
|
+
|
355
|
+
# Check the file's CPU type.
|
356
|
+
# @param cputype [Fixnum] the CPU type
|
357
|
+
# @raise [MachO::CPUTypeError] if the CPU type is unknown
|
358
|
+
# @private
|
359
|
+
def check_cputype(cputype)
|
360
|
+
raise CPUTypeError.new(cputype) unless CPU_TYPES.key?(cputype)
|
361
|
+
end
|
362
|
+
|
363
|
+
# Check the file's CPU type/subtype pair.
|
364
|
+
# @param cpusubtype [Fixnum] the CPU subtype
|
365
|
+
# @raise [MachO::CPUSubtypeError] if the CPU sub-type is unknown
|
366
|
+
# @private
|
367
|
+
def check_cpusubtype(cputype, cpusubtype)
|
368
|
+
# Only check sub-type w/o capability bits (see `get_mach_header`).
|
369
|
+
raise CPUSubtypeError.new(cputype, cpusubtype) unless CPU_SUBTYPES[cputype].key?(cpusubtype)
|
370
|
+
end
|
371
|
+
|
372
|
+
# Check the file's type.
|
373
|
+
# @param filetype [Fixnum] the file type
|
374
|
+
# @raise [MachO::FiletypeError] if the file type is unknown
|
375
|
+
# @private
|
376
|
+
def check_filetype(filetype)
|
377
|
+
raise FiletypeError.new(filetype) unless MH_FILETYPES.key?(filetype)
|
378
|
+
end
|
379
|
+
|
380
|
+
# All load commands in the file.
|
381
|
+
# @return [Array<MachO::LoadCommand>] an array of load commands
|
382
|
+
# @raise [MachO::LoadCommandError] if an unknown load command is encountered
|
383
|
+
# @private
|
384
|
+
def get_load_commands
|
385
|
+
offset = header.class.bytesize
|
386
|
+
load_commands = []
|
387
|
+
|
388
|
+
header.ncmds.times do
|
389
|
+
fmt = (endianness == :little) ? "L<" : "L>"
|
390
|
+
cmd = @raw_data.slice(offset, 4).unpack(fmt).first
|
391
|
+
cmd_sym = LOAD_COMMANDS[cmd]
|
392
|
+
|
393
|
+
raise LoadCommandError.new(cmd) if cmd_sym.nil?
|
394
|
+
|
395
|
+
# why do I do this? i don't like declaring constants below
|
396
|
+
# classes, and i need them to resolve...
|
397
|
+
klass = MachO.const_get "#{LC_STRUCTURES[cmd_sym]}"
|
398
|
+
command = klass.new_from_bin(@raw_data, endianness, offset, @raw_data.slice(offset, klass.bytesize))
|
399
|
+
|
400
|
+
load_commands << command
|
401
|
+
offset += command.cmdsize
|
402
|
+
end
|
403
|
+
|
404
|
+
load_commands
|
405
|
+
end
|
406
|
+
|
407
|
+
# Updates the size of all load commands in the raw data.
|
408
|
+
# @param size [Fixnum] the new size, in bytes
|
409
|
+
# @return [void]
|
410
|
+
# @private
|
411
|
+
def set_sizeofcmds(size)
|
412
|
+
fmt = (endianness == :little) ? "L<" : "L>"
|
413
|
+
new_size = [size].pack(fmt)
|
414
|
+
@raw_data[20..23] = new_size
|
415
|
+
end
|
416
|
+
|
417
|
+
# Updates the `name` field in a DylibCommand.
|
418
|
+
# @param dylib_cmd [MachO::DylibCommand] the dylib command
|
419
|
+
# @param old_name [String] the old dylib name
|
420
|
+
# @param new_name [String] the new dylib name
|
421
|
+
# @return [void]
|
422
|
+
# @private
|
423
|
+
def set_name_in_dylib(dylib_cmd, old_name, new_name)
|
424
|
+
set_lc_str_in_cmd(dylib_cmd, dylib_cmd.name, old_name, new_name)
|
425
|
+
end
|
426
|
+
|
427
|
+
# Updates the `path` field in an RpathCommand.
|
428
|
+
# @param rpath_cmd [MachO::RpathCommand] the rpath command
|
429
|
+
# @param old_path [String] the old runtime name
|
430
|
+
# @param new_path [String] the new runtime name
|
431
|
+
# @return [void]
|
432
|
+
# @private
|
433
|
+
def set_path_in_rpath(rpath_cmd, old_path, new_path)
|
434
|
+
set_lc_str_in_cmd(rpath_cmd, rpath_cmd.path, old_path, new_path)
|
435
|
+
end
|
436
|
+
|
437
|
+
# Updates a generic LCStr field in any LoadCommand.
|
438
|
+
# @param cmd [MachO::LoadCommand] the load command
|
439
|
+
# @param lc_str [MachO::LoadCommand::LCStr] the load command string
|
440
|
+
# @param old_str [String] the old string
|
441
|
+
# @param new_str [String] the new string
|
442
|
+
# @raise [MachO::HeaderPadError] if the new name exceeds the header pad buffer
|
443
|
+
# @private
|
444
|
+
def set_lc_str_in_cmd(cmd, lc_str, old_str, new_str)
|
445
|
+
if magic32?
|
446
|
+
cmd_round = 4
|
447
|
+
else
|
448
|
+
cmd_round = 8
|
449
|
+
end
|
450
|
+
|
451
|
+
new_sizeofcmds = header.sizeofcmds
|
452
|
+
old_str = old_str.dup
|
453
|
+
new_str = new_str.dup
|
454
|
+
|
455
|
+
old_pad = MachO.round(old_str.size + 1, cmd_round) - old_str.size
|
456
|
+
new_pad = MachO.round(new_str.size + 1, cmd_round) - new_str.size
|
457
|
+
|
458
|
+
# pad the old and new IDs with null bytes to meet command bounds
|
459
|
+
old_str << "\x00" * old_pad
|
460
|
+
new_str << "\x00" * new_pad
|
461
|
+
|
462
|
+
# calculate the new size of the cmd and sizeofcmds in MH
|
463
|
+
new_size = cmd.class.bytesize + new_str.size
|
464
|
+
new_sizeofcmds += new_size - cmd.cmdsize
|
465
|
+
|
466
|
+
low_fileoff = @raw_data.size
|
467
|
+
|
468
|
+
# calculate the low file offset (offset to first section data)
|
469
|
+
segments.each do |seg|
|
470
|
+
sections(seg).each do |sect|
|
471
|
+
next if sect.size == 0
|
472
|
+
next if sect.flag?(:S_ZEROFILL)
|
473
|
+
next if sect.flag?(:S_THREAD_LOCAL_ZEROFILL)
|
474
|
+
next unless sect.offset < low_fileoff
|
475
|
+
|
476
|
+
low_fileoff = sect.offset
|
477
|
+
end
|
478
|
+
end
|
479
|
+
|
480
|
+
if new_sizeofcmds + header.class.bytesize > low_fileoff
|
481
|
+
raise HeaderPadError.new(@filename)
|
482
|
+
end
|
483
|
+
|
484
|
+
# update sizeofcmds in mach_header
|
485
|
+
set_sizeofcmds(new_sizeofcmds)
|
486
|
+
|
487
|
+
# update cmdsize in the cmd
|
488
|
+
fmt = (endianness == :little) ? "L<" : "L>"
|
489
|
+
@raw_data[cmd.offset + 4, 4] = [new_size].pack(fmt)
|
490
|
+
|
491
|
+
# delete the old str
|
492
|
+
@raw_data.slice!(cmd.offset + lc_str.to_i...cmd.offset + cmd.class.bytesize + old_str.size)
|
493
|
+
|
494
|
+
# insert the new str
|
495
|
+
@raw_data.insert(cmd.offset + lc_str.to_i, new_str)
|
496
|
+
|
497
|
+
# pad/unpad after new_sizeofcmds until offsets are corrected
|
498
|
+
null_pad = old_str.size - new_str.size
|
499
|
+
|
500
|
+
if null_pad < 0
|
501
|
+
@raw_data.slice!(new_sizeofcmds + header.class.bytesize, null_pad.abs)
|
502
|
+
else
|
503
|
+
@raw_data.insert(new_sizeofcmds + header.class.bytesize, "\x00" * null_pad)
|
504
|
+
end
|
505
|
+
|
506
|
+
# synchronize fields with the raw data
|
507
|
+
@header = get_mach_header
|
508
|
+
@load_commands = get_load_commands
|
509
|
+
end
|
510
|
+
end
|
531
511
|
end
|