kompiler 0.3.2 → 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.
@@ -1,4 +1,6 @@
1
1
  require 'kompiler/wrappers/packed_bytes'
2
+ require 'securerandom'
3
+ require 'digest'
2
4
 
3
5
 
4
6
  module Kompiler
@@ -11,6 +13,29 @@ module MachO
11
13
  MH_MAGIC_32 = 0xfeedface
12
14
  MH_MAGIC_64 = 0xfeedfacf
13
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
+
14
39
 
15
40
  def self.wrap_obj code, symbols, arch_type: 64, cputype: 0, cpusubtype: 0
16
41
 
@@ -27,28 +52,427 @@ module MachO
27
52
  {
28
53
  name: "__text",
29
54
  vaddr: 0,
30
- vsize: 0,
31
- align: 1,
32
- flags: 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
33
219
  content: code,
34
220
  },
35
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,
36
232
  }
37
233
  ]
38
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
+
39
343
 
40
- output = MachO.build(segments: segments, symbols: symbols, arch_type: arch_type, cputype: cputype, cpusubtype: cpusubtype, filetype: 1, align_section_contents: false)
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)
41
345
 
42
346
  return output
347
+
348
+ end
43
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
44
374
  end
45
375
 
46
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
47
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
48
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
49
452
 
50
453
 
51
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
+
52
476
  def self.build_macho_header arch_type: 64, cputype: 0, cpusubtype: 0, filetype: 0, ncmds: 0, sizeofcmds: 0, flags: 0
53
477
 
54
478
  macho_header = PackedBytes.new
@@ -87,8 +511,10 @@ module MachO
87
511
  # symbols - list of symbols to be included inside a single symtab (structure below)
88
512
  # prebuilt_lcs - a list of other already built load commands (strings) to be included in the file
89
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.
90
515
  # cputype - Mach-O header cputype field
91
516
  # cpusubtype - Mach-O header cpusubtype field
517
+ # flags - Mach-O header flags field
92
518
  # filetype - Mach-O file type
93
519
  # align_section_contents - specifies whether to align the contents of each section to its alignment boundary (default is false)
94
520
  #
@@ -116,7 +542,25 @@ module MachO
116
542
  # sect - symbol's sect field value
117
543
  # desc - symbol's desc field value
118
544
  #
119
- def self.build segments: [], symbols: [], prebuilt_lcs: [], arch_type: 64, cputype: 0, cpusubtype: 0, filetype: 0, align_section_contents: false
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
120
564
 
121
565
  file_content = PackedBytes.new
122
566
 
@@ -137,24 +581,27 @@ module MachO
137
581
 
138
582
  symtab_entry_size = 4 * 6
139
583
 
584
+ code_signature_lc_size = 4 * 4
140
585
 
141
- n_sections = 1
586
+ n_code_signature_lcs = codesign ? 1 : 0
142
587
 
143
- prebuilt_lcs_size = prebuilt_lcs.map(&:bytesize).sum
588
+ n_sections = segments.map{(_1[:sections] || []).size}.sum
589
+
590
+ prebuilt_lcs_size = prebuilt_lcs.map{_1[:bytes].bytesize}.sum
144
591
 
145
- cmds_size = segment_entry_size + section_entry_size * n_sections + symtab_entry_size + prebuilt_lcs_size
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
146
593
 
147
594
 
148
- n_cmds = segments.size + 1 + prebuilt_lcs.size # One segment (with possibly multiple sections), one symtab
595
+ n_cmds = segments.size + n_symtabs + n_code_signature_lcs + prebuilt_lcs.size # One segment (with possibly multiple sections), one symtab
149
596
 
150
597
  macho_header = build_macho_header(
151
- arch_type: 64,
598
+ arch_type: arch_type,
152
599
  cputype: cputype,
153
600
  cpusubtype: cpusubtype,
154
601
  filetype: filetype,
155
602
  ncmds: n_cmds,
156
603
  sizeofcmds: cmds_size,
157
- flags: 0
604
+ flags: flags
158
605
  )
159
606
 
160
607
 
@@ -167,14 +614,93 @@ module MachO
167
614
 
168
615
 
169
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
170
641
 
171
642
 
172
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
+
173
665
  segments.each do |segment|
174
666
 
175
667
  raise "Segment name is larger than 16 characters" if segment[:name].bytesize > 16
176
668
 
177
- start_size = contents.bytesize
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
178
704
 
179
705
  segment[:sections].each_with_index do |section, section_i|
180
706
 
@@ -187,7 +713,7 @@ module MachO
187
713
  sec_offset = contents_offset + contents.bytesize
188
714
 
189
715
  pad_amount = 0
190
- if (sec_offset % section[:align] != 0) && align_sections
716
+ if (sec_offset % section[:align] != 0) && align_section_contents
191
717
  pad_amount = section[:align] - (sec_offset % section[:align])
192
718
  end
193
719
 
@@ -197,20 +723,21 @@ module MachO
197
723
  section[:file_offset] = sec_offset
198
724
 
199
725
  if section_i == 0
200
- segment[:file_offset] = sec_offset
201
- start_size = contents.bytesize
726
+ segment[:file_offset] ||= sec_offset
202
727
  end
203
728
 
729
+ contents_offsets << section[:file_offset]
730
+
204
731
  contents << section[:content]
205
732
 
206
733
  section[:vaddr] ||= 0
207
734
  section[:vsize] ||= section[:content].bytesize
208
735
  end
209
736
 
210
- end_size = contents.bytesize
737
+ end_offset = contents.bytesize + contents_offset
211
738
 
212
- segment[:file_offset] ||= 0
213
- segment[:filesize] = end_size - start_size
739
+ segment[:file_offset] ||= start_offset
740
+ segment[:filesize] ||= end_offset - segment[:file_offset]
214
741
 
215
742
  segment[:vaddr] ||= 0
216
743
  segment[:vsize] ||= segment[:filesize]
@@ -222,48 +749,209 @@ module MachO
222
749
  end
223
750
 
224
751
 
225
-
752
+ if codesign
226
753
 
227
- symtab_contents = PackedBytes.new
228
-
229
- sym_strtab_contents = "\0"
754
+ # The __LINKEDIT segment is last (logic written previously)
755
+ linkedit_segment = segments.last
756
+
230
757
 
231
- symbols.each do |symbol|
232
- if symbol.keys.include? :name
233
- symbol[:name_offset] = sym_strtab_contents.bytesize
234
- sym_strtab_contents << symbol[:name] + "\0"
235
- else
236
- symbol[:name_offset] = 0
237
- end
758
+ code_directory = PackedBytes.new endianess: :be
238
759
 
239
- symtab_contents.uint32 symbol[:name_offset]
240
- symtab_contents.uint8 symbol[:type]
241
- symtab_contents.uint8 symbol[:sect]
242
- symtab_contents.uint16 symbol[:desc]
243
- symtab_contents.bytes symbol[:value], flexval_size
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
+
244
872
  end
245
873
 
246
874
 
247
- symtab_offset = contents.bytesize + contents_offset
248
- symtab_align = 8
249
875
 
250
- if symtab_offset % symtab_align != 0
251
- pad = (symtab_align - (symtab_offset % symtab_align))
252
- contents << "\0" * pad
253
- symtab_offset += pad
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
+
254
918
  end
255
919
 
256
- contents << symtab_contents.result
257
920
 
258
- sym_strtab_offset = contents.bytesize + contents_offset
259
-
260
- contents << sym_strtab_contents
261
921
 
922
+ contents_offsets << symtab_offset if symtab_offset != nil
262
923
 
924
+ contents_offsets += prebuilt_lc_content_offsets
263
925
 
264
- load_commands = PackedBytes.new
265
926
 
266
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
+
267
955
  segments.each do |segment|
268
956
 
269
957
  case arch_type
@@ -301,7 +989,11 @@ module MachO
301
989
  load_commands.bytes section[:vsize], flexval_size
302
990
 
303
991
  load_commands.uint32 section[:file_offset]
304
- load_commands.uint32 section[:align]
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
305
997
 
306
998
  load_commands.uint32 0 # reloff
307
999
  load_commands.uint32 0 # nreloc
@@ -322,27 +1014,168 @@ module MachO
322
1014
 
323
1015
  # Symtab load command
324
1016
 
325
- load_commands.uint32 0x2 # LC_SYMTAB
326
- load_commands.uint32 symtab_entry_size
1017
+ if symbols != nil
1018
+ load_commands.uint32 0x2 # LC_SYMTAB
1019
+ load_commands.uint32 symtab_entry_size
327
1020
 
328
- load_commands.uint32 symtab_offset
329
- load_commands.uint32 symbols.size
330
-
331
- load_commands.uint32 sym_strtab_offset
332
- load_commands.uint32 sym_strtab_contents.bytesize
1021
+ load_commands.uint32 symtab_offset
1022
+ load_commands.uint32 symbols.size
333
1023
 
1024
+ load_commands.uint32 sym_strtab_offset
1025
+ load_commands.uint32 sym_strtab_contents.bytesize
1026
+ end
1027
+
334
1028
 
335
- load_commands.add prebuilt_lcs
1029
+ prebuilt_lcs.each do |lc|
1030
+ load_commands.bytes lc[:bytes]
1031
+ end
336
1032
 
337
1033
 
338
1034
  file_content.add load_commands
339
1035
 
340
1036
 
341
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
342
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
343
1150
 
344
- return file_content.result
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
345
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}
346
1179
  end
347
1180
 
348
1181
 
@@ -357,7 +1190,7 @@ module MachO
357
1190
  def self.labels_to_symbols labels,
358
1191
  out = []
359
1192
  labels.each do |name, value|
360
- out << create_symbol(name: name, value: value, type: :sect, external: false, section_number: 1)
1193
+ out << create_symbol(name: name, value: value, type: :sect, external: true, section_number: 1)
361
1194
  end
362
1195
  return out
363
1196
  end