一、下载suricata源码
到https://github.com/OISF/suricata下载发布版:
二、安装依赖
1、libhtp
需要到https://github.com/OISF/libhtp下载源码
解压后进入源码目录libhtp-0.5.41
,执行
./configure --prefix=/opt/libhtp-0.5.41-ubuntu-x64
make
make install
然后配置环境变量,编辑/etc/profile
,添加:
export LIBHTP_ROOT=/opt/libhtp-0.5.41-ubuntu-x64
export LD_LIBRARY_PATH=$LIBHTP_ROOT/lib:$LD_LIBRARY_PATH
export CPATH=$LIBHTP_ROOT/include:$CPATH
export LIBRARY_PATH=$LIBHTP_ROOT/lib:$LIBRARY_PATH
export PKG_CONFIG_PATH=$LIBHTP_ROOT/lib/pkgconfig:$PKG_CONFIG_PATH
2、其他
apt-get install libpcre3-dev
apt-get install libjansson-dev
apt-get install libyaml-dev
apt-get install libmagic-dev
apt-get install libnss3-dev
apt-get install libcap-ng-dev
apt-get install liblz4-dev
三、编译安装
解压后进入源码目录suricata-6.0.8
,执行:
./configure --prefix=/opt/suricata-6.0.8-ubuntu-x64
make
到此步骤,suricata已经编译完成,由于suricata是一个可独立运行的进程,如果继续执行make install
命令,将会把软件程序安装到指定目录/opt/suricata-6.0.8-ubuntu-x64
下。如:
四、把suricata作为组件
在某些情况下,我们希望suricata不作为独立进程运行,而是希望把它当作某个进程中的一个模块组件,则需要把suricata的源码以及其依赖的头文件/库集成到主进程中。
由于suricata是使用C编写,如果主进程使用C++实现,那么不能直接调用suricata提供的接口,为了最小化修改到suricata源码,可以增加代理模块进行接口代理,在代理模块中对接口进C++兼容,例如,增加代理模块:
proxy.h
#pragma once
#ifdef __cplusplus
extern "C"
{
#endif
/**
* @brief 启动Suricata
*/
void startSuricata(int argc, char** argv);
/**
* @brief 停止Suricata
*/
void stopSuricata();
#ifdef __cplusplus
}
#endif
proxy.c
#include "proxy.h"
#include <stdio.h>
#include <string.h>
#include "../suricata-6.0.8/src/suricata.h"
void startSuricata(int argc, char** argv)
{
SuricataMain(argc, argv);
}
void stopSuricata()
{
EngineStop();
}
如果要增加对告警信息的事件监听,则可以如下操作,在suricata-6.0.8/src/
目录下添加文件:
alert-define.h
#pragma once
#include <stdlib.h>
#ifdef __cplusplus
extern "C"
{
#endif
/**
* @brief 告警信息
*/
typedef struct
{
char timebuf[64]; /* 时间, 格式: "10/16/2022-15:42:45.160103" */
char protocol[32]; /* 协议 */
char srcIp[46]; /* 源IP */
int srcPort; /* 源端口 */
char dstIp[46]; /* 目的IP */
int dstPort; /* 目的端口 */
int priority; /* 等级 */
char classification[512]; /* 分类信息描述 */
char msg[1024]; /* 消息 */
} st_alert_info;
/**
* @brief 告警回调
* @param info 告警信息
* @return 0-不写日志, 1-写日志
*/
typedef int (*alert_callback)(st_alert_info info);
/**
* @brief 设置告警回调
* @param callback 回调
*/
void setAlarmCallback(alert_callback callback);
/**
* @brief 响应告警回调
* @param info 告警信息
* @return 0-不写日志, 1-写日志
*/
int onAlarmCallback(st_alert_info info);
#ifdef __cplusplus
}
#endif
alert-define.c
#include "alert-define.h"
static alert_callback s_alertCallback = NULL; /* 告警回调 */
void setAlarmCallback(alert_callback callback)
{
s_alertCallback = callback;
}
int onAlarmCallback(st_alert_info info)
{
if (s_alertCallback)
{
return s_alertCallback(info);
}
return 1;
}
修改alert-fastlog.c
文件接口int AlertFastLogger(ThreadVars *tv, void *data, const Packet *p)
:
#include "alert-define.h"
int AlertFastLogger(ThreadVars *tv, void *data, const Packet *p)
{
AlertFastLogThread *aft = (AlertFastLogThread *)data;
int i;
char timebuf[64];
int decoder_event = 0;
CreateTimeString(&p->ts, timebuf, sizeof(timebuf));
char srcip[46], dstip[46];
if (PKT_IS_IPV4(p)) {
PrintInet(AF_INET, (const void *)GET_IPV4_SRC_ADDR_PTR(p), srcip, sizeof(srcip));
PrintInet(AF_INET, (const void *)GET_IPV4_DST_ADDR_PTR(p), dstip, sizeof(dstip));
} else if (PKT_IS_IPV6(p)) {
PrintInet(AF_INET6, (const void *)GET_IPV6_SRC_ADDR(p), srcip, sizeof(srcip));
PrintInet(AF_INET6, (const void *)GET_IPV6_DST_ADDR(p), dstip, sizeof(dstip));
} else {
decoder_event = 1;
}
/* Buffer to store the generated alert strings. The buffer is
* filled with alert strings until it doesn't have room to store
* another full alert, only then is the buffer written. This is
* more efficient for multiple alerts and only slightly slower for
* single alerts.
*/
char alert_buffer[MAX_FASTLOG_BUFFER_SIZE];
char proto[16] = "";
const char *protoptr;
if (SCProtoNameValid(IP_GET_IPPROTO(p))) {
protoptr = known_proto[IP_GET_IPPROTO(p)];
} else {
snprintf(proto, sizeof(proto), "PROTO:%03" PRIu32, IP_GET_IPPROTO(p));
protoptr = proto;
}
uint16_t src_port_or_icmp = p->sp;
uint16_t dst_port_or_icmp = p->dp;
if (IP_GET_IPPROTO(p) == IPPROTO_ICMP || IP_GET_IPPROTO(p) == IPPROTO_ICMPV6) {
src_port_or_icmp = p->icmp_s.type;
dst_port_or_icmp = p->icmp_s.code;
}
for (i = 0; i < p->alerts.cnt; i++) {
const PacketAlert *pa = &p->alerts.alerts[i];
if (unlikely(pa->s == NULL)) {
continue;
}
const char *action = "";
if ((pa->action & ACTION_DROP) && EngineModeIsIPS()) {
action = "[Drop] ";
} else if (pa->action & ACTION_DROP) {
action = "[wDrop] ";
}
/* Create the alert string without locking. */
int size = 0;
if (likely(decoder_event == 0)) {
PrintBufferData(alert_buffer, &size, MAX_FASTLOG_ALERT_SIZE,
"%s %s[**] [%" PRIu32 ":%" PRIu32 ":%"
PRIu32 "] %s [**] [Classification: %s] [Priority: %"PRIu32"]"
" {%s} %s:%" PRIu32 " -> %s:%" PRIu32 "\n", timebuf, action,
pa->s->gid, pa->s->id, pa->s->rev, pa->s->msg, pa->s->class_msg, pa->s->prio,
protoptr, srcip, src_port_or_icmp, dstip, dst_port_or_icmp);
} else {
PrintBufferData(alert_buffer, &size, MAX_FASTLOG_ALERT_SIZE,
"%s %s[**] [%" PRIu32 ":%" PRIu32
":%" PRIu32 "] %s [**] [Classification: %s] [Priority: "
"%" PRIu32 "] [**] [Raw pkt: ", timebuf, action, pa->s->gid,
pa->s->id, pa->s->rev, pa->s->msg, pa->s->class_msg, pa->s->prio);
PrintBufferRawLineHex(alert_buffer, &size, MAX_FASTLOG_ALERT_SIZE,
GET_PKT_DATA(p), GET_PKT_LEN(p) < 32 ? GET_PKT_LEN(p) : 32);
if (p->pcap_cnt != 0) {
PrintBufferData(alert_buffer, &size, MAX_FASTLOG_ALERT_SIZE,
"] [pcap file packet: %"PRIu64"]\n", p->pcap_cnt);
} else {
PrintBufferData(alert_buffer, &size, MAX_FASTLOG_ALERT_SIZE, "]\n");
}
}
/* 构造告警信息 */
st_alert_info info;
memset(&info, 0, sizeof(info));
if (timebuf && strlen(timebuf) > 0)
{
memcpy(info.timebuf, timebuf, strlen(timebuf));
}
if (protoptr && strlen(protoptr) > 0)
{
memcpy(info.protocol, protoptr, strlen(protoptr));
}
if (strlen(srcip) > 0)
{
memcpy(info.srcIp, srcip, strlen(srcip));
}
info.srcPort = src_port_or_icmp;
if (strlen(dstip) > 0)
{
memcpy(info.dstIp, dstip, strlen(dstip));
}
info.dstPort = dst_port_or_icmp;
info.priority = pa->s->prio;
if (pa->s->class_msg && strlen(pa->s->class_msg) > 0)
{
memcpy(info.classification, pa->s->class_msg, strlen(pa->s->class_msg));
}
if (pa->s->msg && strlen(pa->s->msg) > 0)
{
memcpy(info.msg, pa->s->msg, strlen(pa->s->msg));
}
/* 调用告警回调 */
if (onAlarmCallback(info))
{
/* Write the alert to output file */
AlertFastLogOutputAlert(aft, alert_buffer, size);
}
}
return TM_ECODE_OK;
}
在main.cpp
文件中,增加告警事件回调设置,例如:
#include "../suricata-6.0.8/src/alert-define.h"
#include "proxy.h"
/**
* @brief 响应告警回调
* @param info 告警信息
* @return 0-不写日志, 1-写日志
*/
int onAlertCallback(st_alert_info info)
{
INFO_LOG(s_logger, "协议: {}, 源地址: {}:{}, 目的地址: {}:{}, 等级: {}, 类别: {}, 消息: {}", info.protocol, info.srcIp, info.srcPort,
info.dstIp, info.dstPort, info.priority, info.classification, info.msg);
return 1;
}
int main(int argc, char* argv[])
{
setAlarmCallback(onAlertCallback); /* 设置告警回调 */
/* 主循环 */
while (1)
{
utility::PathInfo(Config::getValue(cfgkey::PathLog).toString() + "/suricata").create();
/* 创建参数列表 */
int argCount = 0;
char** argList = NULL;
{
std::lock_guard<std::mutex> locker(s_mutexArgVec);
s_argVec.clear();
s_argVec.emplace_back(argv0);
s_argVec.emplace_back("-c");
s_argVec.emplace_back("/proc_test/suricata.yaml");
s_argVec.emplace_back("-i");
s_argVec.emplace_back("enp10");
INFO_LOG(s_logger, "suricata模块启动参数: {}", utility::StrTool::join(s_argVec, " "));
argList = utility::StrTool::convertToArgv(s_argVec, argCount);
}
/* 启动suricata */
if (argCount > 0 && argList)
{
startSuricata(argCount, argList);
}
/* 销毁删除列表 */
destroyArgv(argCount, argList);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
return 0;
}
在上一步的make
操作后,会在src目录下自动生成两个头文件,在后面的操作中需要集成到工程里
由于suricata使用了rust
实现的模块,源码目录为:
因此,需要把rust的源码编译成.a
静态库,提供给C语言调用。在上一步执行make
操作后,会自动在rust目录下帮我们生成静态库,如:
现在我们编写基于CMake
的工程构建脚本,如:
# CMake版本
cmake_minimum_required(VERSION 3.18.0)
# `std::make_unique`要求最低C++14
set(CMAKE_CXX_STANDARD 14)
# 工程名
project(proc_intrusion)
####################################### 添加线程库 #######################################
find_package(Threads REQUIRED)
##########################################################################################
####################################### 添加libhtp库 #######################################
if ("$ENV{LIBHTP_ROOT}" STREQUAL "") # 自动查找
find_path(LIBHTP_INCLUDE_DIR NAMES htp.h)
else () # 如果有手动配置LIBHTP环境变量LIBHTP_ROOT, 则从环境变量中获取
set(LIBHTP_INCLUDE_DIR $ENV{
LIBHTP_ROOT}/include/htp)
endif ()
find_library(LIBHTP_LIBRARIES NAMES htp HINTS ${LIBHTP_INCLUDE_DIR}/../../lib)
set(LIBHTP_ROOT_DIR ${LIBHTP_INCLUDE_DIR}/../../)
message(STATUS "libhtp root dir: ${LIBHTP_ROOT_DIR}")
message(STATUS "libhtp include path: ${LIBHTP_INCLUDE_DIR}")
message(STATUS "libhtp libraries: ${LIBHTP_LIBRARIES}")
if ("${LIBHTP_ROOT_DIR}" STREQUAL "LIBHTP_ROOT_DIR-NOTFOUND" OR
"${LIBHTP_INCLUDE_DIR}" STREQUAL "LIBHTP_INCLUDE_DIR-NOTFOUND" OR
"${LIBHTP_LIBRARIES}" STREQUAL "LIBHTP_LIBRARIES-NOTFOUND")
message(WARNING "libhtp not found")
return()
else ()
include_directories(${LIBHTP_INCLUDE_DIR})
endif ()
##########################################################################################
# 添加宏定义
add_definitions(-DHAVE_CONFIG_H=1)
add_definitions(-DLOCAL_STATE_DIR="/home/proc_intrusion")
# 添加头文件包含目录
include_directories(/usr/include/nss)
include_directories(/usr/include/nspr)
include_directories(suricata-6.0.8/src)
include_directories(suricata-6.0.8/rust/dist)
# 添加suricata源文件
set(proc_files)
get_cxx_files(suricata-6.0.8/src src_list)
list(APPEND proc_files ${src_list})
list(REMOVE_ITEM proc_files ${CMAKE_CURRENT_SOURCE_DIR}/suricata-6.0.8/src/main.c) # 这里去除源码中的主文件
# 添加主进程源文件
get_cxx_files(src src_list)
list(APPEND proc_files ${src_list})
message("proc_files files:")
foreach(filename ${proc_files})
message(" " ${filename})
endforeach()
# 构建可执行文件
add_executable(proc_intrusion ${proc_intrusion})
# 链接依赖库文件
target_link_libraries(proc_intrusion Threads::Threads
${LIBHTP_LIBRARIES}
pcap
dl
rt
m
lz4
magic
cap-ng
jansson
yaml
z
pcre
ssl3
smime3
nss3
nssutil3
plds4
plc4
nspr4
/root/workspace/suricata-6.0.8/rust/target/release/libsuricata.a)