从0到1学习v8漏洞之路(一)
sn0w 发表于 加拿大 二进制安全 369浏览 · 2024-11-01 03:28

环境搭建

注意这个搭建的过程最好使用科学上网 可以减少很多不必要的麻烦
如果是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

脚本指令

通过在脚本中事先添加两种代码来辅助之后的调试过程:

  1. %DebugPrint(arg1)——该方法会打印目标对象的内存地址并对其主要信息进行输出。
  2. %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

0 条评论
某人
表情
可输入 255