suricata 开源工具学习-自定义协议开发

介绍

suricata所有的协议都是通过向框架(AppLayerParserRegisterProtocolParsers)提供注册回调完成的。

suricata 开源工具学习-自定义协议开发_第1张图片

开发一个新的协议,按照相同结构格式完成即可。

一、编写协议解析文件

suricata可以通过工具脚本执行直接生成协议解析文件,文章使用的suricata 4.1.3版本。那么下载同版本工具,工具我已经clone一份到我的git了,default 分支为4.1x版本

git clone https://github.com/Liuchunhui0526/suricata-1.git

下载完成放在系统路径下并完成解压,解压后的内容是

suricata 开源工具学习-自定义协议开发_第2张图片

1、生成协议解析文件

在工具路径执行

python scripts/setup-app-layer.py Liu

成功生成对应协议文件

suricata 开源工具学习-自定义协议开发_第3张图片

两个文件生成在工具文件下的src目录,手动把文件提出来放在suricata4.1.3编译环境即可

scp ../../suricata-1-master-4.1.x/src/app-layer-liu.* .
这步骤以自己实际路径完成

2、分析生成的文件内容

生成的c文件,RegisterxxxParsers接口就是用来提供给框架的注册接口。

2.1、suricata.yaml配置文件中是否存在协议且enable

suricata 开源工具学习-自定义协议开发_第4张图片

suricata.yaml配置文件app-layer部分存在对应使能配置

suricata 开源工具学习-自定义协议开发_第5张图片

2.2、协议注册

suricata协议解析存在PM、PP、PE三种模式,其中PM为数据报关键特征匹配,即匹配报文字段(类似规则中的content字段),PP为端口匹配,即服务器目的端口值匹配命中即确定为协议,最后一个PE为字节流匹配,目前常规只有FTP-DATA协议,其协议会根据FTP commend计算并创建紧跟的子协议。

本篇文章以工具生成内容介绍,默认为PP模式注册;

suricata 开源工具学习-自定义协议开发_第6张图片

RunmodeIsUnittests这个不重要,判断系统以什么模式启动,这里判断是否为测试状态。

AppLayerProtoDetectPPParseConfPorts这个接口作用是判断yaml配置文件中“liu”这个协议是否存在默认端口号,如果类似存在443,则以配置文件设置的端口为准,不通过AppLayerProtoDetectPPRegister注册。(AppLayerProtoDetectPPRegister这个接口的作用是注册了一个服务器方向匹配LIU_DEFAULT_PORT端口的规则,还附加了最小有效长度为LIU_MIN_FRAME_LEN:即小于LIU_MIN_FRAME_LEN长度就默认不是这个协议)

2.3、协议处理接口注册

当一条流量被解析成对应的协议,在双边流量经过设备时,需要通过每方向的处理逻辑进行检测或信息搜集。

suricata 开源工具学习-自定义协议开发_第7张图片

可以看到这里注册了很多的回调,用于框架不同功能和阶段。本篇文章,只简单使用双边处理接口,其他接口在协议开发后续章节提供。

2.4、双边数据包接口

请求处理接口实现的是将数据包payload部分,放进一块申请的空间中;如果遇到长度大于0且首位值为0的数据包,就产生事件

static int LiuParseRequest(Flow *f, void *statev,
    AppLayerParserState
*pstate, uint8_t *input, uint32_t input_len,
   
void *local_data, const uint8_t flags)
{
    LiuState
*state = statev;

SCLogNotice("Parsing liu request: len=%"PRIu32, input_len);

/* Likely connection closed, we can just return here. */
    if ((input == NULL || input_len == 0) &&
        AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF)) {
       
return 0;
    }

/* Probably don't want to create a transaction in this case
     * either. */
    if (input == NULL || input_len == 0) {
       
return 0;
    }

/* Normally you would parse out data here and store it in the
     * transaction object, but as this is echo, we'll just record the
     * request data. */

/* Also, if this protocol may have a "protocol data unit" span
     * multiple chunks of data, which is always a possibility with
     * TCP, you may need to do some buffering here.
     *
     * For the sake of simplicity, buffering is left out here, but
     * even for an echo protocol we may want to buffer until a new
     * line is seen, assuming its text based.
     */

/* Allocate a transaction.
     *
     * But note that if a "protocol data unit" is not received in one
     * chunk of data, and the buffering is done on the transaction, we
     * may need to look for the transaction that this newly recieved
     * data belongs to.
     */
    LiuTransaction *tx = LiuTxAlloc(state);  //申请空间

   if (unlikely(tx == NULL)) {
        SCLogNotice(
"Failed to allocate new Liu tx.");
       
goto end;
    }

//这有打印信息,可以证明收到数据包

    SCLogNotice("Allocated Liu tx %"PRIu64".", tx->tx_id);

/* Make a copy of the request. */
    tx->request_buffer = SCCalloc(1, input_len);  //复制数据包内容

    if (unlikely(tx->request_buffer == NULL)) {
       
goto end;
    }
    memcpy(tx
->request_buffer, input, input_len);
    tx
->request_buffer_len = input_len;

/* Here we check for an empty message and create an app-layer
     * event. */

//产生事件

    if ((input_len == 1 && tx->request_buffer[0] == '\n') ||
        (input_len == 2 && tx->request_buffer[0] == '\r')) {
        SCLogNotice(
"Creating event for empty message.");
        AppLayerDecoderEventsSetEventRaw(
&tx->decoder_events,
            LIU_DECODER_EVENT_EMPTY_MESSAGE);
    }

end:
   
return 0;
}

响应数据包

遍历请求构成的状态数据,这里应存在事件处理。将响应数据包保存到对应请求结构中的响应空间中。

static int LiuParseResponse(Flow *f, void *statev, AppLayerParserState *pstate,
   
uint8_t *input, uint32_t input_len, void *local_data,
   
const uint8_t flags)
{
    LiuState
*state = statev;
    LiuTransaction
*tx = NULL, *ttx;

SCLogNotice("Parsing Liu response.");

/* Likely connection closed, we can just return here. */
    if ((input == NULL || input_len == 0) &&
        AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF)) {
       
return 0;
    }

/* Probably don't want to create a transaction in this case
     * either. */
    if (input == NULL || input_len == 0) {
       
return 0;
    }

/* Look up the existing transaction for this response. In the case
     * of echo, it will be the most recent transaction on the
     * LiuState object. */

/* We should just grab the last transaction, but this is to
     * illustrate how you might traverse the transaction list to find
     * the transaction associated with this response. */
    TAILQ_FOREACH(ttx, &state->tx_list, next) {

       //这里遍历结构查看是否存在异常,比如请求处理中加入的事件。

        tx = ttx;
    }

if (tx == NULL) {
        SCLogNotice(
"Failed to find transaction for response on state %p.",
            state);
       
goto end;
    }

SCLogNotice("Found transaction %"PRIu64" for response on state %p.",
        tx
->tx_id, state);

/* If the protocol requires multiple chunks of data to complete, you may
     * run into the case where you have existing response data.
     *
     * In this case, we just log that there is existing data and free it. But
     * you might want to realloc the buffer and append the data.
     */
    if (tx->response_buffer != NULL) {
        SCLogNotice(
"WARNING: Transaction already has response data, "
            "existing data will be overwritten.");
        SCFree(tx
->response_buffer);
    }

//这里申请空间,数据同样保存到对应请求的结构中

/* Make a copy of the response. */
    tx->response_buffer = SCCalloc(1, input_len);
   
if (unlikely(tx->response_buffer == NULL)) {
       
goto end;
    }
    memcpy(tx
->response_buffer, input, input_len);
    tx
->response_buffer_len = input_len;

/* Set the response_done flag for transaction state checking in
     * LiuGetStateProgress(). */
    tx->response_done = 1;

end:
   
return 0;
}

二、编译

加入协议宏:在app-layer-protos.h文件中加入对应宏变量

suricata 开源工具学习-自定义协议开发_第8张图片

注册协议接口:在AppLayerParserRegisterProtocolParsers接口中插入协议的注册接口,在这个文件中要加入头文件。

suricata 开源工具学习-自定义协议开发_第9张图片

Makefile添加源文件:在am_suricata_OBJECTS变量中加入我们的文件。

suricata 开源工具学习-自定义协议开发_第10张图片

三、测试

1、数据包

有一个简单的tcp通信数据包,客户端向服务器push了一段字符串。服务器端口为1001

suricata 开源工具学习-自定义协议开发_第11张图片

2、yaml文件配置

加入协议并给定服务器端口为1001

liu:

      enabled: yes

      detection-ports:

        dp: 1001

将日志级别设置为debug

3、提供一个对应协议规则

alert liu any any -> any any (msg:"Detect www in payload"; content:"HTTP";sid:1000001;)

4、启动suricata

以af-packet模式运行suricata

suricata -c suricata.yaml --af-packet

5、验证

监视fast.log文件,查看告警状态

你可能感兴趣的:(suricata应用开发,开源,学习,elasticsearch,linux,c语言,网络)