kompiler 0.3.1 → 0.3.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.
@@ -0,0 +1,1274 @@
1
+ require 'kompiler/wrappers/packed_bytes'
2
+ require 'securerandom'
3
+ require 'digest'
4
+
5
+
6
+ module Kompiler
7
+
8
+ module Wrappers
9
+
10
+
11
+ module MachO
12
+
13
+ MH_MAGIC_32 = 0xfeedface
14
+ MH_MAGIC_64 = 0xfeedfacf
15
+
16
+ LC_REQ_DYLD = 0x80000000
17
+
18
+
19
+ # Encodes an unsigned integer into ULEB128 byte sequence
20
+ def self.encode_uleb value
21
+
22
+ result_bytes = ""
23
+
24
+ loop do
25
+ value_bits = value & 0x7f # 7 lower bits
26
+ value >>= 7 # shift value by 7 bits
27
+ continue_bit = (value == 0) ? 0 : 1 # "More bytes to come" bit
28
+ final_byte = value_bits | (continue_bit << 7)
29
+ result_bytes << final_byte.chr
30
+
31
+ break if value == 0 # If nothing else to encode, exit the loop
32
+ end
33
+
34
+ return result_bytes
35
+
36
+ end
37
+
38
+
39
+
40
+ def self.wrap_obj code, symbols, arch_type: 64, cputype: 0, cpusubtype: 0
41
+
42
+ segments = [
43
+ {
44
+ name: "__TEXT",
45
+ vaddr: 0,
46
+ vsize: code.bytesize,
47
+ maxprot: 7,
48
+ initprot: 7,
49
+ flags: 0,
50
+
51
+ sections: [
52
+ {
53
+ name: "__text",
54
+ vaddr: 0,
55
+ align: 4,
56
+ flags: 0x80000400, # section contains some machine instructions
57
+ content: code,
58
+ },
59
+ ]
60
+ }
61
+ ]
62
+
63
+
64
+
65
+ output = MachO.build(segments: segments, symbols: symbols, prebuilt_lcs: prebuilt_lcs, arch_type: arch_type, cputype: cputype, cpusubtype: cpusubtype, filetype: 1, align_section_contents: true)
66
+
67
+ return output
68
+
69
+ end
70
+
71
+
72
+ def self.wrap_exec_static code, symbols, arch_type: 64, cputype: 0, cpusubtype: 0, thread_state: nil, virtual_entry_address: 0x80000, codesign: false
73
+
74
+ raise "Thread state must be provided for building a static Mach-O executable" if thread_state == nil
75
+
76
+ segments = [
77
+ {
78
+ name: "__PAGEZERO",
79
+ vaddr: 0,
80
+ vsize: virtual_entry_address - 0x1000,
81
+ maxprot: 0,
82
+ initprot: 0,
83
+ flags: 0,
84
+ file_offset: 0,
85
+ filesize: 0,
86
+ sections: []
87
+ },
88
+ {
89
+ name: "__TEXT",
90
+ vaddr: virtual_entry_address - 0x1000,
91
+ maxprot: 7,
92
+ initprot: 7,
93
+ flags: 0,
94
+ file_offset: 0,
95
+ sections: [
96
+ {
97
+ name: "__text",
98
+ vaddr: virtual_entry_address,
99
+ align: 0x1000,
100
+ flags: 0x80000400, # section contains some machine instructions
101
+ content: code,
102
+ }
103
+ ]
104
+ }
105
+ ]
106
+
107
+ if codesign
108
+ segments << {
109
+ name: "__LINKEDIT",
110
+ vaddr: 0x200000000,
111
+ vsize: 0,
112
+ maxprot: 1,
113
+ initprot: 1,
114
+ flags: 0,
115
+ sections: []
116
+ }
117
+ end
118
+
119
+
120
+ uuid_lc = PackedBytes.new
121
+ uuid_lc.uint32 0x1b
122
+ uuid_lc.uint32 24
123
+ uuid_lc.bytes Random.urandom(16) # UUID
124
+
125
+
126
+ unix_thread_lc = PackedBytes.new
127
+ unix_thread_lc.uint32 0x5 # LC_UNIX_THREAD
128
+ unix_thread_lc.uint32 thread_state.bytesize + 8 # cmdsize
129
+ unix_thread_lc.add thread_state
130
+
131
+
132
+
133
+ prebuilt_lcs = [
134
+ {
135
+ bytes: uuid_lc.result,
136
+ },
137
+ {
138
+ bytes: unix_thread_lc.result,
139
+ },
140
+ ]
141
+
142
+ flags = 1
143
+
144
+ out = MachO.build segments: segments, symbols: symbols, prebuilt_lcs: prebuilt_lcs, codesign: codesign, arch_type: arch_type, cputype: cputype, cpusubtype: cpusubtype, filetype: 2, flags: flags, align_section_contents: true
145
+
146
+ return out
147
+ end
148
+
149
+
150
+
151
+
152
+
153
+
154
+
155
+ # Wrap executable with dynamic linker
156
+ # Wraps a binary program in Mach-O format using dynamic link commands (especially used on ARM MacOS)
157
+ #
158
+ def self.wrap_exec_dylink code, symbols, arch_type: 64, cputype: 0, cpusubtype: 0, virtual_entry_address: 0x80000, codesign: false
159
+
160
+ pagezero_size = 0x100000000
161
+
162
+ text_segment_vaddr = pagezero_size
163
+
164
+ text_section_vaddr = text_segment_vaddr + 0x1000
165
+
166
+ virtual_entry_address = text_section_vaddr
167
+
168
+ linkedit_vaddr = pagezero_size * 3
169
+
170
+
171
+
172
+ n_local_sym = symbols.size
173
+
174
+ symbols.map! do
175
+ _1[:value] += text_section_vaddr
176
+ _1
177
+ end
178
+
179
+
180
+ linkedit = PackedBytes.new
181
+
182
+
183
+ symtab, sym_strtab = MachO.build_symtab(symbols, arch_type: arch_type).values
184
+
185
+ symtab_offset = linkedit.result.bytesize
186
+ linkedit.add symtab
187
+ sym_strtab_offset = linkedit.result.bytesize
188
+ linkedit.add sym_strtab
189
+
190
+
191
+ segments = [
192
+ {
193
+ name: "__PAGEZERO",
194
+ file_offset: 0,
195
+ filesize: 0,
196
+ vaddr: 0,
197
+ vsize: pagezero_size,
198
+ flags: 0,
199
+ maxprot: 0,
200
+ initprot: 0,
201
+ sections: [],
202
+ },
203
+ {
204
+ name: "__TEXT",
205
+ vaddr: text_segment_vaddr,
206
+ # vsize: 0x4000,
207
+ maxprot: 7,
208
+ initprot: 7,
209
+ flags: 0,
210
+ file_offset: 0,
211
+ # filesize: 8192,
212
+
213
+ sections: [
214
+ {
215
+ name: "__text",
216
+ vaddr: text_section_vaddr,
217
+ align: 0x1000,
218
+ flags: 0x80000400, # section contains some machine instructions
219
+ content: code,
220
+ },
221
+ ]
222
+ },
223
+ {
224
+ name: "__LINKEDIT",
225
+ vaddr: linkedit_vaddr,
226
+ maxprot: 1,
227
+ initprot: 1,
228
+ flags: 0,
229
+
230
+ content: linkedit.result,
231
+ content_align: 0x4000,
232
+ }
233
+ ]
234
+
235
+ entry_point_lc = PackedBytes.new
236
+ entry_point_lc.uint32 0x80000028 # LC_MAIN
237
+ entry_point_lc.uint32 24 # cmdsize
238
+
239
+ entry_point_lc.uint64 0x80000 # file offset address
240
+ entry_point_lc.uint64 0 # stack size
241
+
242
+
243
+
244
+ uuid_lc = PackedBytes.new
245
+ uuid_lc.uint32 0x1b
246
+ uuid_lc.uint32 24
247
+ uuid_lc.bytes Random.urandom(16) # UUID
248
+
249
+
250
+
251
+ load_dylinker_lc = PackedBytes.new
252
+
253
+ load_dylinker_lc.uint32 0xe
254
+ load_dylinker_lc.uint32 32
255
+ load_dylinker_lc.bytes "\f\x00\x00\x00/usr/lib/dyld\x00\x00\x00\x00\x00\x00\x00"
256
+
257
+ symtab_lc = PackedBytes.new
258
+
259
+ symtab_lc.uint32 0x2
260
+ symtab_lc.uint32 24
261
+
262
+ symtab_lc.uint32 0
263
+ symtab_lc.uint32 symbols.size
264
+
265
+ symtab_lc.uint32 0
266
+ symtab_lc.uint32 sym_strtab.bytesize
267
+
268
+
269
+ dyld_info_lc = PackedBytes.new
270
+
271
+ dyld_info_lc.uint32 0x22
272
+ dyld_info_lc.uint32 12 * 4
273
+
274
+ dyld_info_lc.uint32 0 # rebase_off
275
+ dyld_info_lc.uint32 0 # size
276
+
277
+ dyld_info_lc.uint32 0 # bind_off
278
+ dyld_info_lc.uint32 0 # size
279
+
280
+ dyld_info_lc.uint32 0 # weak_bind_off
281
+ dyld_info_lc.uint32 0 # size
282
+
283
+ dyld_info_lc.uint32 0 # lazy_bind_off
284
+ dyld_info_lc.uint32 0 # size
285
+
286
+ dyld_info_lc.uint32 0 # export_off
287
+ dyld_info_lc.uint32 0 # size
288
+
289
+
290
+
291
+ code_signature_lc = PackedBytes.new
292
+
293
+ code_signature_lc.uint32 0x1d # LC_CODE_SIGNATURE
294
+ code_signature_lc.uint32 16 # cmdsize
295
+
296
+ code_signature_lc.uint32 0 # offset
297
+ code_signature_lc.uint32 0 # size
298
+
299
+
300
+
301
+ prebuilt_lcs = [
302
+ {
303
+ bytes: symtab_lc.result,
304
+ relocations: [
305
+ {
306
+ content_index: 1,
307
+ bytefield_offset: 8,
308
+ bytefield_size: 4,
309
+ addend: symtab_offset,
310
+ },
311
+ {
312
+ content_index: 1,
313
+ bytefield_offset: 16,
314
+ bytefield_size: 4,
315
+ addend: sym_strtab_offset,
316
+ }
317
+ ]
318
+ },
319
+ {
320
+ bytes: load_dylinker_lc.result,
321
+ },
322
+ {
323
+ bytes: uuid_lc.result
324
+ },
325
+ {
326
+ bytes: dyld_info_lc.result
327
+ },
328
+ {
329
+ bytes: entry_point_lc.result,
330
+ relocations: [
331
+ {
332
+ content_index: 0,
333
+ bytefield_offset: 8,
334
+ bytefield_size: 8
335
+ }
336
+ ]
337
+ },
338
+ ]
339
+
340
+
341
+ mh_flags = 0x200000 | 0x4 | 0x1 # MH_PIE | MH_DYLDLINK | MH_NOUNDEFS
342
+
343
+
344
+ output = MachO.build(segments: segments, symbols: nil, prebuilt_lcs: prebuilt_lcs, arch_type: arch_type, codesign: codesign, cputype: cputype, cpusubtype: cpusubtype, filetype: 2, align_section_contents: true, flags: mh_flags)
345
+
346
+ return output
347
+
348
+ end
349
+
350
+
351
+ def self.build_thread_state_x86_32(r: [0] * 7, esp: 0, ss: 0, eflags: 0, eip: 0, cs: 0, ds: 0, es: 0, fs: 0, gs: 0)
352
+ raise "r must be an array of 7 elements" if r.size != 7
353
+
354
+ thread_state = PackedBytes.new
355
+
356
+ thread_state.uint32 1 # flavor = x86_THREAD_STATE_32
357
+ thread_state.uint32 16 # count = 16 4-byte fields
358
+
359
+ r.each do |r_val|
360
+ thread_state.uint32 r_val
361
+ end
362
+
363
+ thread_state.uint32 esp
364
+ thread_state.uint32 ss
365
+ thread_state.uint32 eflags
366
+ thread_state.uint32 eip
367
+ thread_state.uint32 cs
368
+ thread_state.uint32 ds
369
+ thread_state.uint32 es
370
+ thread_state.uint32 fs
371
+ thread_state.uint32 gs
372
+
373
+ return thread_state.result
374
+ end
375
+
376
+
377
+ def self.build_thread_state_x86_64(r: [0] * 16, rip: 0, rflags: 0, cs: 0, fs: 0, gs: 0)
378
+
379
+ raise "r must be an array of 16 elements" if r.size != 16
380
+
381
+ thread_state = PackedBytes.new
382
+
383
+ thread_state.uint32 4 # flavor = x86_THREAD_STATE_64
384
+ thread_state.uint32 42 # count = 21 8-byte fields / sizeof(int) (4 bytes)
385
+
386
+
387
+ r.each do |r_val|
388
+ thread_state.uint64 r_val
389
+ end
390
+
391
+ thread_state.uint64 rip
392
+
393
+ thread_state.uint64 rflags
394
+ thread_state.uint64 cs
395
+ thread_state.uint64 fs
396
+ thread_state.uint64 gs
397
+
398
+
399
+ return thread_state.result
400
+ end
401
+
402
+
403
+
404
+ def self.build_thread_state_arm64(x: [0] * 29, fp: 0, lr: 0, sp: 0, pc: 0, cpsr: 0)
405
+
406
+ thread_state = PackedBytes.new
407
+
408
+ thread_state.uint32 6 # flavor = ARM_THREAD_STATE64
409
+ thread_state.uint32 33 * 2 + 2 # count = 33 8-byte fields + 2 4-byte fields / sizeof(int) (4 bytes)
410
+
411
+ raise "x must be an array of 29 values." if x.size != 29
412
+
413
+ x.each do |x_val|
414
+ thread_state.uint64 x_val
415
+ end
416
+
417
+ thread_state.uint64 fp
418
+ thread_state.uint64 lr
419
+ thread_state.uint64 sp
420
+ thread_state.uint64 pc
421
+
422
+ thread_state.uint32 cpsr
423
+
424
+ thread_state.uint32 0 # pad
425
+
426
+ return thread_state.result
427
+ end
428
+
429
+
430
+
431
+ def self.build_thread_state_arm32(r: [0] * 13, lr: 0, sp: 0, pc: 0, cpsr: 0)
432
+
433
+ thread_state = PackedBytes.new
434
+
435
+ thread_state.uint32 1 # flavor = ARM_THREAD_STATE
436
+ thread_state.uint32 17 # count = 17 4-byte fields / sizeof(int) (4 bytes)
437
+
438
+ raise "r must be an array of 13 values." if r.size != 13
439
+
440
+ r.each do |r_val|
441
+ thread_state.uint32 r_val
442
+ end
443
+
444
+ thread_state.uint32 lr
445
+ thread_state.uint32 sp
446
+ thread_state.uint32 pc
447
+
448
+ thread_state.uint32 cpsr
449
+
450
+ return thread_state.result
451
+ end
452
+
453
+
454
+
455
+ def self.build_thread_state arch: nil, entry_address: 0, stack_pointer: 0
456
+ raise "arch must be specified" if arch == nil
457
+
458
+ case arch
459
+ when "arm64"
460
+ return MachO.build_thread_state_arm64(pc: entry_address, sp: stack_pointer)
461
+ when "arm32"
462
+ return MachO.build_thread_state_arm32(pc: entry_address, sp: stack_pointer)
463
+ when "x86-64"
464
+ r = [0] * 16
465
+ r[7] = stack_pointer
466
+ return MachO.build_thread_state_x86_64(rip: entry_address, r: r)
467
+ when "x86-32"
468
+ r = [0] * 7
469
+ return MachO.build_thread_state_x86_32(r: r, eip: entry_address)
470
+ else
471
+ raise "Unkown architecture"
472
+ end
473
+ end
474
+
475
+
476
+ def self.build_macho_header arch_type: 64, cputype: 0, cpusubtype: 0, filetype: 0, ncmds: 0, sizeofcmds: 0, flags: 0
477
+
478
+ macho_header = PackedBytes.new
479
+
480
+ case arch_type
481
+ when 64
482
+ macho_header.uint32 MH_MAGIC_64
483
+ when 32
484
+ macho_header.uint32 MH_MAGIC_32
485
+ end
486
+
487
+ macho_header.uint32 cputype
488
+ macho_header.uint32 cpusubtype
489
+
490
+ macho_header.uint32 filetype
491
+
492
+ macho_header.uint32 ncmds
493
+
494
+ macho_header.uint32 sizeofcmds
495
+
496
+ macho_header.uint32 flags
497
+
498
+ if arch_type == 64
499
+ # Reserved field in 64-bit
500
+ macho_header.uint32 0
501
+ end
502
+
503
+ return macho_header
504
+ end
505
+
506
+
507
+ # Build a Mach-O file from the input segments, symbols and other pre-built load commands
508
+ #
509
+ # Arguments:
510
+ # segments - list of segments to include in the file (structure below)
511
+ # symbols - list of symbols to be included inside a single symtab (structure below)
512
+ # prebuilt_lcs - a list of other already built load commands (strings) to be included in the file
513
+ # arch_type - architecture type, either 32- or 64-bit (default is 64)
514
+ # codesign - specifies whether to create a code signature. If yes, a __LINKEDIT segment must be present. The code signature will be a code_directory struct with hashes of everything before it. A CODE_SIGNATURE load command will be added automatically.
515
+ # cputype - Mach-O header cputype field
516
+ # cpusubtype - Mach-O header cpusubtype field
517
+ # flags - Mach-O header flags field
518
+ # filetype - Mach-O file type
519
+ # align_section_contents - specifies whether to align the contents of each section to its alignment boundary (default is false)
520
+ #
521
+ # Segment structure:
522
+ # name - the segment's name
523
+ # vaddr - virtual load address of the segment
524
+ # vsize - virtual size in memory of the segment
525
+ # maxprot - Mach-O segment maxprot field
526
+ # initprot - Mach-O segment initprot field
527
+ # flags - Mach-O segment flags field
528
+ # sections - a list of segment's section (structure below)
529
+ #
530
+ # Segment section structure:
531
+ # name - the section's name
532
+ # vaddr - virtual load address of section
533
+ # vsize - virtual size in memory of the section
534
+ # align - section's byte alignment
535
+ # flags - section's flags
536
+ # content - section's content
537
+ #
538
+ # Symbol structure:
539
+ # name - symbol's name
540
+ # value - symbol's value
541
+ # type - symbol's type field value
542
+ # sect - symbol's sect field value
543
+ # desc - symbol's desc field value
544
+ #
545
+ # Prebuilt load command structure:
546
+ # bytes - the load command bytes
547
+ # contents - contents related to the load command
548
+ # contents_align - alignment boundary in the file for the contents
549
+ # relocations - a list of relocations to replace some load command's bytes based on the content's file offset
550
+ #
551
+ # Prebuilt load command relocation structure:
552
+ # content_index - index of the contents related to the relocation (contents of segments and symtab will appear last)
553
+ # bytefield_offset - offset of the to-be-replaced bytes in the load command
554
+ # bytefield_size - size of the bytefield to be replaced
555
+ # addend - value to add to the contents' file offset to replace the specified bytefield in the load command with
556
+ # Relocation logic:
557
+ # load_command[:bytes][lc_offset...(lc_offset + lc_size)] = int_to_bytes(contents[content_i][:file_offset] + addend, lc_size)
558
+ #
559
+ def self.build segments: [], symbols: [], prebuilt_lcs: [], arch_type: 64, cputype: 0, cpusubtype: 0, filetype: 0, align_section_contents: false, flags: 0, codesign: false
560
+
561
+
562
+ n_symtabs = 1
563
+ n_symtabs = 0 if symbols == nil
564
+
565
+ file_content = PackedBytes.new
566
+
567
+ case arch_type
568
+ when 32
569
+ flexval_size = 4
570
+ when 64
571
+ flexval_size = 8
572
+ end
573
+
574
+ segment_entry_size = 4 * 2 + 16 + flexval_size * 4 + 4 * 2 + 4 * 2
575
+
576
+ section_entry_size = 16 + 16 + flexval_size * 2 + 7 * 4
577
+
578
+ if arch_type == 64
579
+ section_entry_size += 4 # one more reserved field for 64-bit
580
+ end
581
+
582
+ symtab_entry_size = 4 * 6
583
+
584
+ code_signature_lc_size = 4 * 4
585
+
586
+ n_code_signature_lcs = codesign ? 1 : 0
587
+
588
+ n_sections = segments.map{(_1[:sections] || []).size}.sum
589
+
590
+ prebuilt_lcs_size = prebuilt_lcs.map{_1[:bytes].bytesize}.sum
591
+
592
+ cmds_size = segment_entry_size * segments.size + section_entry_size * n_sections + symtab_entry_size * n_symtabs + code_signature_lc_size * n_code_signature_lcs + prebuilt_lcs_size
593
+
594
+
595
+ n_cmds = segments.size + n_symtabs + n_code_signature_lcs + prebuilt_lcs.size # One segment (with possibly multiple sections), one symtab
596
+
597
+ macho_header = build_macho_header(
598
+ arch_type: arch_type,
599
+ cputype: cputype,
600
+ cpusubtype: cpusubtype,
601
+ filetype: filetype,
602
+ ncmds: n_cmds,
603
+ sizeofcmds: cmds_size,
604
+ flags: flags
605
+ )
606
+
607
+
608
+ file_content.add macho_header
609
+
610
+
611
+
612
+ # Calculate where all of the content can be placed
613
+ contents_offset = cmds_size + file_content.result.bytesize
614
+
615
+
616
+ contents = ""
617
+
618
+
619
+ prebuilt_lc_content_offsets = []
620
+
621
+ prebuilt_lcs.each do |lc|
622
+ if lc.keys.include? :contents
623
+
624
+ align = lc[:contents_align] || 1
625
+
626
+ file_offset = contents.bytesize + contents_offset
627
+
628
+ if file_offset % align != 0
629
+ pad = align - (file_offset % align)
630
+ contents << "\0" * pad
631
+ file_offset += pad
632
+ end
633
+
634
+ prebuilt_lc_content_offsets << file_offset
635
+
636
+ contents << lc[:contents]
637
+ else
638
+ prebuilt_lc_content_offsets << nil
639
+ end
640
+ end
641
+
642
+
643
+
644
+ contents_offsets = []
645
+
646
+ if codesign
647
+ # Ensure that the __LINKEDIT segment exists, and place it as the last segment to not interfere with other load commands and hash more of the file
648
+
649
+ linkedit_segments = segments.each_with_index.filter{|seg, idx| seg[:name] == "__LINKEDIT"}
650
+ if linkedit_segments.size == 0
651
+ raise "Segment __LINKEDIT must be present if codesign is true"
652
+ # segments << {name: "__LINKEDIT", vaddr}
653
+ else
654
+ linkedit_segment, linkedit_segment_idx = linkedit_segments[0]
655
+
656
+ # Make __LINKEDIT the last segment
657
+ segments[-1], segments[linkedit_segment_idx] = segments[linkedit_segment_idx], segments[-1]
658
+ end
659
+ end
660
+
661
+
662
+
663
+
664
+
665
+ segments.each do |segment|
666
+
667
+ raise "Segment name is larger than 16 characters" if segment[:name].bytesize > 16
668
+
669
+ raise "Segment can't contain both the :content and :sections key" if segment.keys.include?(:content) && segment.keys.include?(:sections) && segment[:sections].size > 0
670
+
671
+ if segment.keys.include? :content
672
+
673
+ segment[:sections] = []
674
+
675
+ alignment = segment[:content_align] || 1
676
+
677
+ file_offset = contents_offset + contents.bytesize
678
+ if file_offset % alignment != 0
679
+ pad = alignment - (file_offset % alignment)
680
+ contents << "\0" * pad
681
+ file_offset += pad
682
+ end
683
+
684
+ contents << segment[:content]
685
+
686
+ segment[:file_offset] ||= file_offset
687
+ segment[:filesize] ||= segment[:content].bytesize
688
+
689
+ segment[:vaddr] ||= 0
690
+ segment[:vsize] ||= segment[:filesize]
691
+
692
+ segment[:flags] ||= 0
693
+
694
+ segment[:maxprot] ||= 0
695
+ segment[:initprot] ||= 0
696
+
697
+ contents_offsets << segment[:file_offset]
698
+
699
+ next
700
+ end
701
+
702
+
703
+ start_offset = contents.bytesize + contents_offset
704
+
705
+ segment[:sections].each_with_index do |section, section_i|
706
+
707
+ raise "Section name \"#{section[:name]}\" is larger than 16 characters" if section[:name].bytesize > 16
708
+
709
+ section[:align] ||= 1
710
+
711
+ raise "Section alignment value cannot be 0" if section[:align] == 0
712
+
713
+ sec_offset = contents_offset + contents.bytesize
714
+
715
+ pad_amount = 0
716
+ if (sec_offset % section[:align] != 0) && align_section_contents
717
+ pad_amount = section[:align] - (sec_offset % section[:align])
718
+ end
719
+
720
+ sec_offset += pad_amount
721
+ contents << "\0" * pad_amount
722
+
723
+ section[:file_offset] = sec_offset
724
+
725
+ if section_i == 0
726
+ segment[:file_offset] ||= sec_offset
727
+ end
728
+
729
+ contents_offsets << section[:file_offset]
730
+
731
+ contents << section[:content]
732
+
733
+ section[:vaddr] ||= 0
734
+ section[:vsize] ||= section[:content].bytesize
735
+ end
736
+
737
+ end_offset = contents.bytesize + contents_offset
738
+
739
+ segment[:file_offset] ||= start_offset
740
+ segment[:filesize] ||= end_offset - segment[:file_offset]
741
+
742
+ segment[:vaddr] ||= 0
743
+ segment[:vsize] ||= segment[:filesize]
744
+
745
+ segment[:flags] ||= 0
746
+
747
+ segment[:maxprot] ||= 0
748
+ segment[:initprot] ||= 0
749
+ end
750
+
751
+
752
+ if codesign
753
+
754
+ # The __LINKEDIT segment is last (logic written previously)
755
+ linkedit_segment = segments.last
756
+
757
+
758
+ code_directory = PackedBytes.new endianess: :be
759
+
760
+ code_directory.uint32 0xfade0c02 # Magic
761
+
762
+ code_directory_length_offset = code_directory.result.bytesize
763
+ code_directory.uint32 0 # Length (will be replaced)
764
+
765
+ code_directory.uint32 0 # Version
766
+
767
+ code_directory.uint32 0 # Flags
768
+
769
+ code_directory_header_size = 96
770
+
771
+ hash_array_offset = code_directory_header_size
772
+
773
+ total_hashing_size = linkedit_segment[:file_offset] + linkedit_segment[:filesize]
774
+
775
+ hashing_pagesize = 0x1000
776
+
777
+ n_hashes = (total_hashing_size / hashing_pagesize.to_f).ceil
778
+
779
+ hash_size = 32
780
+ hash_type = 2 # SHA256
781
+
782
+ hash_array_size = hash_size * n_hashes
783
+
784
+
785
+ ident_offset = hash_array_offset + hash_array_size
786
+
787
+
788
+ code_directory.uint32 hash_array_offset # Hash offset
789
+
790
+ code_directory.uint32 ident_offset # Identifier string offset
791
+
792
+ code_directory.uint32 0 # nSpecialSlots
793
+
794
+ code_directory.uint32 n_hashes # nCodeSlots
795
+
796
+ code_directory.uint32 total_hashing_size # codeLimit
797
+
798
+
799
+ code_directory.uint8 hash_size
800
+ code_directory.uint8 hash_type
801
+
802
+ code_directory.uint8 0
803
+ hashing_pagesize_enc = Math.log2(hashing_pagesize)
804
+ raise "Code signing pagesize is not a power of two" if hashing_pagesize_enc.to_i != hashing_pagesize_enc
805
+ code_directory.uint8 hashing_pagesize_enc.to_i
806
+
807
+ code_directory.uint32 0 # spare2
808
+
809
+ code_directory.uint32 0 # scatterOffset (zero for absent)
810
+ code_directory.uint32 0 # teamIDOffset (zero for absent)
811
+
812
+ code_directory.uint32 0 # spare3
813
+
814
+ code_directory.uint64 total_hashing_size # codeLimit64
815
+
816
+ code_directory.uint64 0 # offset of executable segment
817
+ code_directory.uint64 0 # limit of executable segment
818
+ code_directory.uint64 0 # exec segment flags
819
+
820
+ code_directory.uint32 0 # runtime
821
+ code_directory.uint32 0xFFFF # pre-encrypt hash slots offset
822
+
823
+
824
+
825
+ hash_array_placeholder = "\0" * hash_array_size
826
+
827
+
828
+ code_directory.add hash_array_placeholder
829
+
830
+ ident_string = "program-#{SecureRandom.uuid.gsub('-', '')}\0"
831
+
832
+ code_directory.add ident_string
833
+
834
+
835
+ code_directory_bytes = code_directory.result
836
+ code_directory_bytes[code_directory_length_offset...(code_directory_length_offset + 4)] = [code_directory_bytes.bytesize].pack("L>")
837
+
838
+ blobs = [
839
+ {type: 0, content: code_directory.result}
840
+ ]
841
+
842
+ # Embedded signature blob (superblob with magic = 0xfade0cc0)
843
+ superblob = MachO.build_superblob(magic: 0xfade0cc0, blobs: blobs)
844
+
845
+
846
+ # Calculate the offset of the hash array that will be replaced later
847
+
848
+ code_directory_offset = MachO.calc_superblob_offset(blobs, 0)
849
+ superblob_offset = linkedit_segment[:file_offset] + linkedit_segment[:filesize]
850
+
851
+ codesign_hash_array_offset = superblob_offset + code_directory_offset + hash_array_offset
852
+
853
+ contents << superblob
854
+
855
+ # Change the linkedit segment to include the embedded signature blob
856
+ linkedit_segment[:filesize] += superblob.bytesize
857
+ linkedit_segment[:vsize] += superblob.bytesize
858
+
859
+ # Add a code signature load command
860
+ code_signature_lc = PackedBytes.new
861
+ code_signature_lc.uint32 0x1d # LC_CODE_SIGNATURE
862
+ code_signature_lc.uint32 4 * 4 # cmdsize
863
+
864
+ code_signature_lc.uint32 superblob_offset
865
+ code_signature_lc.uint32 superblob.bytesize
866
+
867
+
868
+ prebuilt_lcs << {
869
+ bytes: code_signature_lc.result
870
+ }
871
+
872
+ end
873
+
874
+
875
+
876
+ symtab_offset = nil
877
+
878
+
879
+ if symbols != nil
880
+
881
+ symtab_contents = PackedBytes.new
882
+
883
+ sym_strtab_contents = "\0"
884
+
885
+ symbols.each do |symbol|
886
+ if symbol.keys.include? :name
887
+ symbol[:name_offset] = sym_strtab_contents.bytesize
888
+ sym_strtab_contents << symbol[:name] + "\0"
889
+ else
890
+ symbol[:name_offset] = 0
891
+ end
892
+
893
+ symtab_contents.uint32 symbol[:name_offset]
894
+ symtab_contents.uint8 symbol[:type]
895
+ symtab_contents.uint8 symbol[:sect]
896
+ symtab_contents.uint16 symbol[:desc]
897
+ symtab_contents.bytes symbol[:value], flexval_size
898
+ end
899
+
900
+
901
+ symtab_offset = contents.bytesize + contents_offset
902
+ symtab_align = 360
903
+
904
+ if symtab_offset % symtab_align != 0
905
+ pad = (symtab_align - (symtab_offset % symtab_align))
906
+ contents << "\0" * pad
907
+ symtab_offset += pad
908
+ end
909
+
910
+ # contents_offsets << symtab_offset
911
+
912
+ contents << symtab_contents.result
913
+
914
+ sym_strtab_offset = contents.bytesize + contents_offset
915
+
916
+ contents << sym_strtab_contents
917
+
918
+ end
919
+
920
+
921
+
922
+ contents_offsets << symtab_offset if symtab_offset != nil
923
+
924
+ contents_offsets += prebuilt_lc_content_offsets
925
+
926
+
927
+
928
+ prebuilt_lcs.each do |lc|
929
+
930
+ if lc.keys.include? :relocations
931
+ lc[:relocations].each do |reloc|
932
+
933
+ addr = contents_offsets[reloc[:content_index]]
934
+
935
+ raise "Load command contents at index #{reloc[:content_index]} is empty and doesn't have an offset" if addr == nil
936
+
937
+ write_val = addr + (reloc[:addend] || 0)
938
+
939
+ bytesize = reloc[:bytefield_size]
940
+
941
+ bits = (0...(bytesize * 8)).map{|bit_i| write_val[bit_i] }
942
+ bytes = (0...bytesize).map{|byte_i| bits[(byte_i*8)...(byte_i*8 + 8)] }.map{|byte_bits| byte_bits.each_with_index.map{|bit, bit_i| 2 ** bit_i * bit }.sum }
943
+ str_bytes = bytes.map(&:chr).join
944
+
945
+ lc[:bytes][reloc[:bytefield_offset]...(reloc[:bytefield_offset] + bytesize)] = str_bytes
946
+ end
947
+ end
948
+
949
+ end
950
+
951
+
952
+ load_commands = PackedBytes.new
953
+
954
+
955
+ segments.each do |segment|
956
+
957
+ case arch_type
958
+ when 64
959
+ load_commands.uint32 0x19 # LC_SEGMENT_64
960
+ when 32
961
+ load_commands.uint32 0x1 # LC_SEGMENT
962
+ end
963
+
964
+ load_commands.uint32 segment_entry_size + section_entry_size * segment[:sections].size
965
+
966
+ load_commands.bytes segment[:name] + "\0" * (16 - segment[:name].bytesize)
967
+
968
+ load_commands.bytes segment[:vaddr], flexval_size
969
+ load_commands.bytes segment[:vsize], flexval_size
970
+
971
+ load_commands.bytes segment[:file_offset], flexval_size
972
+ load_commands.bytes segment[:filesize], flexval_size
973
+
974
+ load_commands.uint32 segment[:maxprot]
975
+ load_commands.uint32 segment[:initprot]
976
+
977
+ load_commands.uint32 segment[:sections].size
978
+
979
+ load_commands.uint32 segment[:flags]
980
+
981
+
982
+
983
+ segment[:sections].each do |section|
984
+
985
+ load_commands.bytes section[:name] + "\0" * (16 - section[:name].bytesize)
986
+ load_commands.bytes segment[:name] + "\0" * (16 - segment[:name].bytesize)
987
+
988
+ load_commands.bytes section[:vaddr], flexval_size
989
+ load_commands.bytes section[:vsize], flexval_size
990
+
991
+ load_commands.uint32 section[:file_offset]
992
+
993
+ align_value = Math.log2 section[:align]
994
+ raise "Section alignment is not a power of 2" if align_value.to_i != align_value
995
+
996
+ load_commands.uint32 align_value
997
+
998
+ load_commands.uint32 0 # reloff
999
+ load_commands.uint32 0 # nreloc
1000
+
1001
+ load_commands.uint32 section[:flags]
1002
+
1003
+ load_commands.uint32 0 # reserved 1
1004
+ load_commands.uint32 0 # reserved 2
1005
+
1006
+ if arch_type == 64
1007
+ load_commands.uint32 0 # reserved 3 (only in 64-bit)
1008
+ end
1009
+
1010
+ end
1011
+ end
1012
+
1013
+
1014
+
1015
+ # Symtab load command
1016
+
1017
+ if symbols != nil
1018
+ load_commands.uint32 0x2 # LC_SYMTAB
1019
+ load_commands.uint32 symtab_entry_size
1020
+
1021
+ load_commands.uint32 symtab_offset
1022
+ load_commands.uint32 symbols.size
1023
+
1024
+ load_commands.uint32 sym_strtab_offset
1025
+ load_commands.uint32 sym_strtab_contents.bytesize
1026
+ end
1027
+
1028
+
1029
+ prebuilt_lcs.each do |lc|
1030
+ load_commands.bytes lc[:bytes]
1031
+ end
1032
+
1033
+
1034
+ file_content.add load_commands
1035
+
1036
+
1037
+ file_content.add contents
1038
+
1039
+
1040
+ if codesign
1041
+ # Compute the hashes and replace them instead of the placeholder
1042
+
1043
+ out = file_content.result
1044
+
1045
+ hash_array_contents = ""
1046
+
1047
+ n_hashes.times do |hash_i|
1048
+ start_offset = hash_i * hashing_pagesize
1049
+ # Enf offset is either the end of the page or the code_limit field from code_directory
1050
+ end_offset = [start_offset + hashing_pagesize, total_hashing_size].min
1051
+
1052
+ hashed = Digest::SHA256.digest out[start_offset...end_offset]
1053
+
1054
+ hash_array_contents << hashed
1055
+ end
1056
+
1057
+
1058
+ hash_array_start = codesign_hash_array_offset
1059
+ hash_array_end = hash_array_start + hash_array_contents.bytesize
1060
+
1061
+ out[hash_array_start...hash_array_end] = hash_array_contents
1062
+
1063
+ return out
1064
+ end
1065
+
1066
+
1067
+ return file_content.result
1068
+ end
1069
+
1070
+
1071
+
1072
+ # Builds a code signature SuperBlob structure
1073
+ #
1074
+ # Arguments:
1075
+ # magic - SuperBlob's magic field value (default is 0)
1076
+ # blobs - blobs included in the superblob (structure below)
1077
+ #
1078
+ # Blob structure:
1079
+ # type - blob's type
1080
+ # content - blob's content
1081
+ #
1082
+ def self.build_superblob magic: 0, blobs: []
1083
+
1084
+ superblob = PackedBytes.new endianess: :be
1085
+
1086
+ superblob.uint32 0xfade0cc0 # magic
1087
+
1088
+ superblob_length_offset = superblob.result.bytesize
1089
+ superblob.uint32 0 # length (will be replaced)
1090
+
1091
+ superblob.uint32 1 # Number of entries
1092
+
1093
+
1094
+ index_entry_size = 8
1095
+ index_entries_size = index_entry_size * blobs.size
1096
+
1097
+ superblob_header_size = 4 * 3
1098
+
1099
+ blobs_offset = superblob_header_size + index_entries_size
1100
+
1101
+ blob_contents = ""
1102
+
1103
+ blobs.each do |blob|
1104
+ blob[:offset] = blobs_offset + blob_contents.bytesize
1105
+ blob_contents << blob[:content]
1106
+ end
1107
+
1108
+ # Index entries
1109
+ blobs.each do |blob|
1110
+ superblob.uint32 blob[:type]
1111
+ superblob.uint32 blob[:offset]
1112
+ end
1113
+
1114
+ # Blob contents write
1115
+ blobs.each do |blob|
1116
+ superblob.add blob[:content]
1117
+ end
1118
+
1119
+
1120
+ out = superblob.result
1121
+ out[superblob_length_offset...(superblob_length_offset + 4)] = [out.bytesize].pack("L>")
1122
+
1123
+ out
1124
+ end
1125
+
1126
+
1127
+ # Calculates the offset of the specified blob from the beginning of the superblob
1128
+ def self.calc_superblob_offset blobs, index
1129
+
1130
+ superblob_header_size = 4 * 3
1131
+
1132
+ index_entry_size = 8
1133
+
1134
+ index_entries_size = index_entry_size * blobs.size
1135
+
1136
+ total_offset = superblob_header_size + index_entries_size
1137
+
1138
+ blobs[...index].each do |blob|
1139
+ total_offset += blob[:content].bytesize
1140
+ end
1141
+
1142
+ total_offset
1143
+ end
1144
+
1145
+
1146
+
1147
+
1148
+ # Builds a symtab and a strtab based off input symbols
1149
+ def self.build_symtab symbols, arch_type: 64
1150
+
1151
+ case arch_type
1152
+ when 64
1153
+ flexval_size = 8
1154
+ when 32
1155
+ flexval_size = 4
1156
+ end
1157
+
1158
+ symtab_contents = PackedBytes.new
1159
+
1160
+ sym_strtab_contents = "\0"
1161
+
1162
+ symbols.each do |symbol|
1163
+ if symbol.keys.include? :name
1164
+ symbol[:name_offset] = sym_strtab_contents.bytesize
1165
+ sym_strtab_contents << symbol[:name] + "\0"
1166
+ else
1167
+ symbol[:name_offset] = 0
1168
+ end
1169
+
1170
+ symtab_contents.uint32 symbol[:name_offset]
1171
+ symtab_contents.uint8 symbol[:type]
1172
+ symtab_contents.uint8 symbol[:sect]
1173
+ symtab_contents.uint16 symbol[:desc]
1174
+ symtab_contents.bytes symbol[:value], flexval_size
1175
+ end
1176
+
1177
+
1178
+ return {symtab: symtab_contents.result, strtab: sym_strtab_contents}
1179
+ end
1180
+
1181
+
1182
+ # Converts a hash of label name-address pairs into section symbols suitable for the MachO.build method
1183
+ #
1184
+ # Arguments:
1185
+ # labels - the hash of labels
1186
+ # section_index - index of the section the symbols belong to (default is 1)
1187
+ # external - specifies if the label is external or not (default is false)
1188
+ # private_external - specifies if the label is private external or not (default is false)
1189
+ # debug_entry - specifies if the label is a debug entry or not (default is false)
1190
+ def self.labels_to_symbols labels,
1191
+ out = []
1192
+ labels.each do |name, value|
1193
+ out << create_symbol(name: name, value: value, type: :sect, external: true, section_number: 1)
1194
+ end
1195
+ return out
1196
+ end
1197
+
1198
+
1199
+ # Creates a symbol from input information suitable for the MachO.build method
1200
+ #
1201
+ # Arguments:
1202
+ # name - the symbol's name
1203
+ # value - the symbol's value
1204
+ # type - the symbol's type: :undef, :abs, :sect, :prebound, :indirect (default is :sect)
1205
+ # private_external - specifies whether the symbol is private external (default is false)
1206
+ # external - specifies whether the symbol is external (default is false)
1207
+ # debug_entry - specifies whether the symbol is a debugging entry (default is false)
1208
+ # options - options depending on the symbol's type
1209
+ #
1210
+ # Type == :sect options:
1211
+ # section_number - section number the symbol is defined in
1212
+ #
1213
+ def self.create_symbol name: nil, value: nil, type: :sect, private_external: false, external: false, debug_entry: false, **options
1214
+
1215
+ raise "No name provided for the symbol" if name == nil
1216
+ raise "No value provided for the symbol" if value == nil
1217
+
1218
+ symbol = {name: name, value: value}
1219
+
1220
+ type_encodings = {undef: 0x0, abs: 0x2, sect: 0xe, prebound: 0xc, indirect: 0xa}
1221
+ encoded_type = type_encodings[type]
1222
+
1223
+ raise "Unknown symbol type \"#{type}\" - must be :undef, :abs, :sect, :prebound, or :indirect." if encoded_type == nil
1224
+
1225
+
1226
+ pext_bit = 0x0
1227
+ if private_external == true
1228
+ pext_bit = 0x10
1229
+ end
1230
+
1231
+ ext_bit = 0x0
1232
+ if external == true
1233
+ ext_bit = 0x1
1234
+ end
1235
+
1236
+ debug_entry_bit = 0x0
1237
+ if debug_entry == true
1238
+ debug_entry_bit = 0xe
1239
+ end
1240
+
1241
+ type_field = encoded_type | pext_bit | ext_bit | debug_entry_bit
1242
+
1243
+ symbol[:type] = type_field
1244
+
1245
+
1246
+ case type
1247
+ when :undef
1248
+ symbol[:sect] = 0
1249
+ when :abs
1250
+ symbol[:sect] = 0
1251
+ when :sect
1252
+ section_number = options[:section_number] || 0
1253
+ raise "Section number too high - maximum is 255." if section_number > 255
1254
+ symbol[:sect] = section_number
1255
+ when :prebound
1256
+ symbol[:sect] = 0
1257
+ when :indirect
1258
+ symbol[:sect] = 0
1259
+ end
1260
+
1261
+ symbol[:desc] = 0
1262
+
1263
+ return symbol
1264
+ end
1265
+
1266
+
1267
+
1268
+ end # Kompiler::Wrappers::MachO
1269
+
1270
+ end # Kompiler::Wrappers
1271
+
1272
+ end # Kompiler
1273
+
1274
+