环境搭建
注意这个搭建的过程最好使用科学上网 可以减少很多不必要的麻烦
如果是clash for windows 开启TUN模式
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/x64.debug/d8
./out/x64.debug/shell
赛题环境
但是这种一般是编译最新版本的v8,而题目中一般是通过编译特定版本的
一般题目都会给出有漏洞的版本的commitid
,所以编译之前先把源码的版本reset
到和题目一致的版本,在把题目给出的diff
文件应用到源码中:
git reset --hard 6dc88c191f5ecc5389dc26efa3ca0907faef3598
git apply < oob.diff
#同步模块
gclient sync
# 编译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
在编译任意版本时候可能会出现一些脚本由于更新导致python版本差异的问题
类似
类似上面这种问题 就要手动或者利用批量一键转化python格式去弄
这里一定要注意 如果是同步的时候也就是
gclient sync -D出现的问题
到你修改后直接提交 会提示让你cd到某个地方 git add . 然后git commit提交 这里一定要注意 不能直接执行,如果直接执行的话 会发现你修改的地方 是没修改的,一定要先切换一个分支!!! git
git checkout -b "1"
git add .
git commit "提交信息"
然后一个个手修过去 就可以了
调试和结构基础
gdb调试文件
使用GDB
进行调试,V8团队写了一个专门调试的脚本:
# Copyright 2014 the V8 project authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# 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
# Print TurboFan graph node.
define pn
call _v8_internal_Node_Print((void*)($arg0))
end
document pn
Print a v8 TurboFan graph node
Usage: pn node_address
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
# Code imported from chromium/src/tools/gdb/gdbinit
python
import os
import subprocess
import sys
compile_dirs = set()
def get_current_debug_file_directories():
dir = gdb.execute("show debug-file-directory", to_string=True)
dir = dir[
len('The directory where separate debug symbols are searched for is "'
):-len('".') - 1]
return set(dir.split(":"))
def add_debug_file_directory(dir):
# gdb has no function to add debug-file-directory, simulates that by using
# `show debug-file-directory` and `set debug-file-directory <directories>`.
current_dirs = get_current_debug_file_directories()
current_dirs.add(dir)
gdb.execute(
"set debug-file-directory %s" % ":".join(current_dirs), to_string=True)
def newobj_handler(event):
global compile_dirs
compile_dir = os.path.dirname(event.new_objfile.filename)
if not compile_dir:
return
if compile_dir in compile_dirs:
return
compile_dirs.add(compile_dir)
# Add source path
gdb.execute("dir %s" % compile_dir)
# Need to tell the location of .dwo files.
# https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html
# https://crbug.com/603286#c35
add_debug_file_directory(compile_dir)
# Event hook for newly loaded objfiles.
# https://sourceware.org/gdb/onlinedocs/gdb/Events-In-Python.html
gdb.events.new_objfile.connect(newobj_handler)
gdb.execute("set environment V8_GDBINIT_SOURCED=1")
end
脚本指令
通过在脚本中事先添加两种代码来辅助之后的调试过程:
-
%DebugPrint(arg1)
——该方法会打印目标对象的内存地址并对其主要信息进行输出。 -
%SystemBreak()
——该方法可以在脚本中下断点。
GDB指令
使用GDB对编译出来的d8文件进行调试,并设定参数
set args --allow-natives-syntax test.js 使其加载指定的版本
然后使用r
命令运行后,脚本就会暂停在%SystemBreak()
处,并且已经执行的%DebugPrint()
方法会输出指定对象的信息。
job
job
指令可以对指定内存地址上的对象进行信息输出
用于可视化显示JavaScript
对象的内存结构
%DebugPrint方法输出
DebugPrint: 0x17df00288ec9: [JSArray]
- map: 0x17df0008d33d <Map[16](PACKED_DOUBLE_ELEMENTS)> [FastProperties]
- prototype: 0x17df0008cca9 <JSArray[0]>
- elements: 0x17df00288ea1 <FixedDoubleArray[4]> [PACKED_DOUBLE_ELEMENTS]
- length: 4
- properties: 0x17df00000775 <FixedArray[0]>
- All own properties (excluding elements): {
0x17df00000dc1: [String] in ReadOnlySpace: #length: 0x17df002517b1 <AccessorInfo name= 0x17df00000dc1 <String[6]: #length>, data= 0x17df00000069 <undefined>> (const accessor descriptor, attrs: [W__]), location: descriptor
}
- elements: 0x17df00288ea1 <FixedDoubleArray[4]> {
0: 1.1
1: 2.3
2: 3.4
3: 4.4
}
0x17df0008d33d: [Map] in OldSpace
- map: 0x17df00081a35 <MetaMap (0x17df00081a85 <NativeContext[301]>)>
- type: JS_ARRAY_TYPE
- instance size: 16
- inobject properties: 0
- unused property fields: 0
- elements kind: PACKED_DOUBLE_ELEMENTS
- enum length: invalid
- back pointer: 0x17df0008d2f9 <Map[16](HOLEY_SMI_ELEMENTS)>
- prototype_validity cell: 0x17df00000ab1 <Cell value= 1>
- instance descriptors #1: 0x17df0008d2c1 <DescriptorArray[1]>
- transitions #1: 0x17df0008d365 <TransitionArray[5]>
Transitions #1:
0x17df00000e85 <Symbol: (elements_transition_symbol)>: (transition to HOLEY_DOUBLE_ELEMENTS) -> 0x17df0008d381 <Map[16](HOLEY_DOUBLE_ELEMENTS)>
- prototype: 0x17df0008cca9 <JSArray[0]>
- constructor: 0x17df0008c985 <JSFunction Array (sfi = 0x17df00256cf9)>
- dependent code: 0x17df00000785 <Other heap object (WEAK_ARRAY_LIST_TYPE)>
- construction counter: 0
v8结构体
map 表明了一个对象的类型对象b为PACKED_DOUBLE_ELEMENTS类型
prototype prototype
elements 对象元素
length 元素个数
properties 属性
map属性详解
对象的map
(数组是对象)是一种数据结构,其中包含以下信息:
对象的动态类型,即 String,Uint8Array,HeapNumber 等
对象的大小,以字节为单位
对象的属性及其存储位置
数组元素的类型,例如 unboxed 的双精度数或带标记的指针
对象的原型(如果有)
属性名称通常存储在Map
中,而属性值则存储在对象本身中几个可能区域之一中。然后,map
将提供属性值在相应区域中的确切位置。
本质上,映射定义了应如何访问对象:
对于对象数组:存储的是每个对象的地址
对于浮点数组:以浮点数形式存储数值
所以,如果将对象数组的map
换成浮点数组 -> 就变成了浮点数组,会以 浮点数的形式存储对象的地址;如果将对 浮点组的 map 换成对象数组 -> 就变成了对象数组,打印浮点数存储的地址。
对象和对象数组
也就是说,对象数组里面,存储的是别的对象的地址。
JavaScript两种类型的变量结构
demo
a = [1.1,5,6];
%DebugPrint(a);
%SystemBreak();
b = {"a": 45};
%DebugPrint(b);
%SystemBreak();
c = [b];
%DebugPrint(c);
%SystemBreak();
浮点型的时候 elements后面会紧跟着a的结构,这也就意味着如果存在溢出的话 是可以覆盖到map的,而map的作用是标识类型 也就是说,可以起到类型混淆的作用,这就是我们任意地址读和任意地址写的关键,这个的原理会在下一篇文章中讲到,
参考文献:
https://www.anquanke.com/post/id/199702#h3-4
https://mp.weixin.qq.com/s?__biz=MzAxNDY2MTQ2OQ==&mid=2650957738&idx=1&sn=a7885f088a304e86592a455640489b4a&chksm=80792598b70eac8e69600d474f0adbc21c7375e5639ba48f5d7d66eaea46f48c4d0dc55f280c&scene=21#wechat_redirect