基于Bro 2.5.3 文档,按照原文结构整理,非原文翻译,建议阅读英文文档后阅读,如有错误请不吝指出。
时间 | 修改内容 |
2018.02.24 | 完成全文 |
一、理解Bro脚本语言
Bro主要提供两项能力:
- 解析流量并生成event,整个过程不对使用者开放,也不对生成的event进行分析。
- 分析event并产生输出,Bro自实现了一套脚本语言,并内置了常用功能,使用者可以根据需要实现分析脚本。注意,Bro脚本语言是基于event驱动的。
Bro脚本主要由三部分组成,导入和导出在parse-time执行,业务逻辑在run-time执行:
- 导入:@load+相对路径,结尾无分号。Bro搜索路径+相对路径=导入文件绝对路径。相对路径为目录,则导入该路径下的__load__.bro文件,该文件多包含一组@load指令,类似python包的__init__.py文件;相对路径为文件,可忽略.bro后缀。导入将递归执行,重复导入将被忽略。
- 导出:export{scope name: type = value;},export代码块结束无分号,声明及初始化语句后有。用于声明或重定义公用变量,可供当前和其他脚本使用。需要重定义的一般为常量,常量允许在parse-time重定义,run-time不允许修改,与变量不同。
- 业务逻辑:event/hook/function等可执行代码块,实现业务功能。
二、了解event queue和event handler
关于event queue(事件队列):
- event queue是Bro底层(event生成器)和Bro脚本共用的有序队列(先进先出)。
- event生成器根据解析的流量生成event推入event queue,相当于生产者。这部分功能没有Bro脚本参与,不对使用者开放,基本没有其他有效输出。
- Bro脚本从event queue取出event并触发对应event handler完成既定处理,相当于消费者。
关于event handler(事件处理器):
- Bro脚本声明并初始化handler,handler被event触发后接收event携带的信息,并基于这些信息进行决策,也可能修改它们。
- Bro在內建函数脚本和*.bif.bro脚本中使用event关键字声明event handler,涵盖全部內建event类型,event将触发对应名称的event handler。
- handler包含用户对event的自定义处理,因此,哪些需要初始化、如何初始化一般由用户根据业务场景自定义。相同类型的handler可以被初始化多次并指定优先级,Bro将在parse-time合并handler,优先级高低与数值大小正相关,默认为0,接受负数。
- Logging Framework可以配置生成自定义event,用户需要自行声明并初始化log_x event handler,以便完成处理。
三、了解connection record
- record提供了一个key-value模式的数据类型框架,使用户可以通过type和record关键字自定义数据类型。文档中的record类型多泛指这类自定义数据类型,connection就是其中一种。
- 一条connection数据对应一个连接,连接的生命周期内可能触发多个event handler,connection数据也在这个过程中被不断修改和完善。connection类型就是用于跟踪记录一个连接在其生命周期内的状态变化。
四、数据类型和数据结构
4.1 了解scope(作用域):
- Bro脚本有两种等价的参数声明方式:
- SCOPE name: TYPE;
- SCOPE name = EXPRESSION;
- SCOPE支持三种关键字:global(全局变量)、const(全局常量)和local(局部变量)。
- global一般定义在parse-time,作用于整个命名空间,只能在run-time修改。
- const一般定义在parse-time,作用于整个命名空间,有&redef属性可以在parse-time修改。
- local只能定义在run-time,作用于当前代码块,只能在run-time的当前代码块修改。注意:event和hook可能由多代码块组成。
- 当前命名空间由当前文件+已导入且module name相同的其他文件组成,所有已导入且module name不同的文件的export区域将根据module name合并后成为当前命名空间的子空间,并通过module name引用。
- Bro启动时将处理脚本中除可执行类型(event/hook/function)以外内容,称为parse-time;然后等待事件触发相应可执行类型,完成处理,即所谓的事件驱动,该阶段称为run-time。
- 相同作用域内,global、const和local均不允许重名。
4.2 数据类型
4.2.1 基本数据类型—数字类型—int类型
int类型相关总结请参考下表:
int | |
简要说明: | 有符号整形 |
显式声明及初始化: | scope name: int; name = return_int_expression; scope name: int = return_int_expression; # 相比隐式声明,可读性和可维护性更好 |
隐式声明及初始化: | scope name = return_int_expression; |
数据格式: | 略 |
算数运算: | 略 |
比较运算: | 略 |
逻辑运算: | 不可自动转换为bool类型,因此不可参与逻辑运算 |
添加成员: | 容器类型操作,不具备 |
删除成员: | 容器类型操作,不具备 |
成员数量: | 容器类型操作,不具备 |
成员是否存在: | 容器类型操作,不具备 |
索引成员: | 容器类型操作,不具备 |
分片: | 容器类型操作,不具备 |
遍历操作: | 容器类型操作,不具备 |
內建函数: | 略 |
4.2.2 基本数据类型—数字类型—count类型
count类型相关总结与int类型基本一致,请参考下表:
count | |
简要说明: | 无符号整形 |
显式声明及初始化: | scope name: count; name = return_count_expression; scope name: count = return_count_expression; # 相比隐式声明,可读性和可维护性更好 |
隐式声明及初始化: | scope name = return_count_expression; |
数据格式: | 略 |
算数运算: | 略 |
比较运算: | 略 |
逻辑运算: | 不可自动转换为bool类型,因此不可参与逻辑运算 |
添加成员: | 容器类型操作,不具备 |
删除成员: | 容器类型操作,不具备 |
成员数量: | 容器类型操作,不具备 |
成员是否存在: | 容器类型操作,不具备 |
索引成员: | 容器类型操作,不具备 |
分片: | 容器类型操作,不具备 |
遍历操作: | 容器类型操作,不具备 |
內建函数: | 略 |
4.2.3 基本数据类型—数字类型—double类型
double类型相关总结与int类型基本一致,请参考下表:
double | |
简要说明: | 略 |
显式声明及初始化: | scope name: double; name = return_double_expression; scope name: double = return_double_expression; # 相比隐式声明,可读性和可维护性更好 |
隐式声明及初始化: | scope name = return_double_expression; |
数据格式: | 略 |
算数运算: | 略 |
比较运算: | 略 |
逻辑运算: | 不可自动转换为bool类型,因此不可参与逻辑运算 |
添加成员: | 容器类型操作,不具备 |
删除成员: | 容器类型操作,不具备 |
成员数量: | 容器类型操作,不具备 |
成员是否存在: | 容器类型操作,不具备 |
索引成员: | 容器类型操作,不具备 |
分片: | 容器类型操作,不具备 |
遍历操作: | 容器类型操作,不具备 |
內建函数: | 略 |
4.2.4 基本数据类型—string类型
string类型相关总结请参考下表:
string | |
简要说明: | 字符串类型,与python字符串类型非常类似 支持负索引;具备容器部分特征 注意使用双引号描述字符串 |
显式声明及初始化: | scope name: string; name = return_str_expression; scope name: string = return_str_expression; # 相比隐式声明,可读性和可维护性更好 |
隐式声明及初始化: | scope name = return_str_expression; |
数据格式: | "hello world" |
算数运算: | 支持加法运算,用于字符串连接,其他不支持 |
比较运算: | 支持,判断相等时,注意 str1 == str2 和 my_pattern == str |
逻辑运算: | 不可自动转换为bool类型,因此不可参与逻辑运算 |
添加成员: | 容器类型操作,不具备 |
删除成员: | 容器类型操作,不具备 |
成员数量: | 支持,使用||操作符 |
成员是否存在: | 支持,使用in或!in,注意 str1 in str2 和 my_pattern in str |
索引成员: | 支持,使用从0开始递增的数字索引,支持负索引 |
分片: | 支持,使用[from : to]操作符,from和to为索引,支持负索引、索引越界、索引缺失 |
遍历操作: | 支持,使用for (...in...) |
內建函数: | 略 |
4.2.5 基本数据类型—bool类型
bool类型相关总结请参考下表:
bool | |
简要说明: | 布尔类型,使用T表示真,使用F表示假 |
显式声明及初始化: | scope name: bool; name = return_bool_expression; scope name: bool = return_bool_expression; # 相比隐式声明,可读性和可维护性更好 |
隐式声明及初始化: | scope name = return_bool_expression; |
数据格式: | T或F |
算数运算: | 不支持 |
比较运算: | 不支持 |
逻辑运算: | 支持,使用&&和||操作符 |
添加成员: | 容器类型操作,不具备 |
删除成员: | 容器类型操作,不具备 |
成员数量: | 容器类型操作,不具备 |
成员是否存在: | 容器类型操作,不具备 |
索引成员: | 容器类型操作,不具备 |
分片: | 容器类型操作,不具备 |
遍历操作: | 容器类型操作,不具备 |
內建函数: | 略 |
4.2.6 基本数据类型—网络数据类型—addr类型
addr类型相关总结请参考下表:
addr | |
简要说明: | 表示网络层地址,支持IPv4和IPv6,Bro自定义数据类型之一 |
显式声明及初始化: | scope name: addr; name = return_addr_expression; scope name: addr = return_addr_expression; # 相比隐式声明,可读性和可维护性更好 |
隐式声明及初始化: | scope name = return_addr_expression; |
数据格式: | IPv4:10.136.17.35 IPv6: 域名:www.baidu.com # 域名将进行自动转换,因域名可能对应多个IP,转换为set[addr] # Bro运行过程中,域名-IP映射关系不会更新,建议用于域名稳定的场景 # 因域名自动转换为set[addr],使用显式声明会导致声明和初始化数据类型不一致的报错 |
算数运算: | 不支持 |
比较运算: | 支持 |
逻辑运算: | 不可自动转换为bool类型,因此不可参与逻辑运算 |
添加成员: | 容器类型操作,不具备 |
删除成员: | 容器类型操作,不具备 |
成员数量: | 容器类型操作,不具备 |
成员是否存在: | 容器类型操作,不具备 |
索引成员: | 容器类型操作,不具备 |
分片: | 容器类型操作,不具备 |
遍历操作: | 容器类型操作,不具备 |
內建函数: | 略 |
4.2.7 基本数据类型—网络数据类型—port类型
port类型相关总结请参考下表:
port | |
简要说明: | 表示传输层端口,支持UDP/TCP/ICMP/UNKNOWN协议,Bro自定义数据类型之一 |
显式声明及初始化: | scope name: port; name = return_port_expression; scope name: port = return_port_expression; # 相比隐式声明,可读性和可维护性更好 |
隐式声明及初始化: | scope name = return_port_expression; |
数据格式: | 端口号/协议 UDP:514/udp TCP:80/tcp ICMP:/icmp,没有端口概念,Bro将ICMP类型和编码设置为来源和目的端口 UNKNOWN:/unknown |
算数运算: | 不支持 |
比较运算: | 支持,默认unknown < tcp < udp < icmp |
逻辑运算: | 不可自动转换为bool类型,因此不可参与逻辑运算 |
添加成员: | 容器类型操作,不具备 |
删除成员: | 容器类型操作,不具备 |
成员数量: | 容器类型操作,不具备 |
成员是否存在: | 容器类型操作,不具备 |
索引成员: | 容器类型操作,不具备 |
分片: | 容器类型操作,不具备 |
遍历操作: | 容器类型操作,不具备 |
內建函数: | 略 |
4.2.8 基本数据类型—网络数据类型—subnet类型
subnet类型相关总结请参考下表:
subnet | |
简要说明: | 表示子网,使用CIDR记法,支持容器类型部分特征,Bro自定义数据类型之一 |
显式声明及初始化: | scope name: subnet; name = return_subnet_expression; scope name: subnet = return_subnet_expression; # 相比隐式声明,可读性和可维护性更好 |
隐式声明及初始化: | scope name = return_subnet_expression; |
数据格式: | 10.136.17.35/24 |
算数运算: | 不支持 |
比较运算: | 仅支持==和!= |
逻辑运算: | 不可自动转换为bool类型,因此不可参与逻辑运算 |
添加成员: | 容器类型操作,不具备 |
删除成员: | 容器类型操作,不具备 |
成员数量: | 支持,使用||操作符 |
成员是否存在: | 支持,使用in或!in,用于判断IP是否属于该子网 |
索引成员: | 容器类型操作,不具备 |
分片: | 容器类型操作,不具备 |
遍历操作: | 容器类型操作,不具备 |
內建函数: | 略 |
4.2.9 基本数据类型—时间类型—time类型
time类型相关总结请参考下表:
time | |
简要说明: | 表示时间戳,Bro自定义数据类型之一 |
显式声明及初始化: | scope name: time; name = return_time_expression; scope name: time = return_time_expression; # 相比隐式声明,可读性和可维护性更好 |
隐式声明及初始化: | scope name = return_time_expression; |
数据格式: | 无法直接初始化,需要借助current_time()或network_time()函数 |
算数运算: | 支持,注意time类型相减返回interval类型,注意time类型和interval类型的加减法 |
比较运算: | 支持 |
逻辑运算: | 不可自动转换为bool类型,因此不可参与逻辑运算 |
添加成员: | 容器类型操作,不具备 |
删除成员: | 容器类型操作,不具备 |
成员数量: | 容器类型操作,不具备 |
成员是否存在: | 容器类型操作,不具备 |
索引成员: | 容器类型操作,不具备 |
分片: | 容器类型操作,不具备 |
遍历操作: | 容器类型操作,不具备 |
內建函数: | current_time() # 返回操作系统当前时间 network_time() # 返回最后一个已处理数据包内记录的时间,未曾处理数据包则返回当前时间 strftime("%Y-%m-%d %H:%M:%S", current_time()) # 将time转换为可读字符串,注意时区 |
4.2.10 基本数据类型—时间类型—interval类型
interval类型相关总结请参考下表:
interval | |
简要说明: | 表示相对时间,Bro自定义数据类型之一 |
显式声明及初始化: | scope name: interval; name = return_interval_expression; scope name: interval = return_interval_expression; # 相比隐式声明,可读性和可维护性更好 |
隐式声明及初始化: | scope name = return_interval_expression; |
数据格式: | 数字+0或1个空格+单位 数字支持整数、浮点数、负数 单位支持:微秒(usec)、毫秒(msec)、秒(sec)、分( min)、时(hr)和天(day) 单位单数复数均可,即:print 1hr == 1 hrs; # return T |
算数运算: | 支持,注意time类型相减返回interval类型,注意time类型和interval类型的加减法 |
比较运算: | 支持 |
逻辑运算: | 不可自动转换为bool类型,因此不可参与逻辑运算 |
添加成员: | 容器类型操作,不具备 |
删除成员: | 容器类型操作,不具备 |
成员数量: | 容器类型操作,不具备 |
成员是否存在: | 容器类型操作,不具备 |
索引成员: | 容器类型操作,不具备 |
分片: | 容器类型操作,不具备 |
遍历操作: | 容器类型操作,不具备 |
內建函数: | 略 |
4.2.11 基本数据类型—时间类型—pattern类型
pattern类型相关总结请参考下表:
pattern | |
简要说明: | 表示正则表达式,用于对文本快速检索,具备容器类型的部分特征,Bro自定义数据类型之一 |
显式声明及初始化: | scope name: pattern; name = return_pattern_expression; scope name: pattern = return_pattern_expression; # 相比隐式声明,可读性和可维护性更好 |
隐式声明及初始化: | scope name = return_pattern_expression; |
数据格式: | /regular expression/ |
算数运算: | 不支持 |
比较运算: | 仅支持文本完整匹配,使用==或!=操作符 |
逻辑运算: | 不可自动转换为bool类型,因此不可参与逻辑运算 |
添加成员: | 容器类型操作,不具备 |
删除成员: | 容器类型操作,不具备 |
成员数量: | 容器类型操作,不具备 |
成员是否存在: | 支持文本嵌入式匹配,使用in或!in,正则表达式必须位于左侧 |
索引成员: | 容器类型操作,不具备 |
分片: | 容器类型操作,不具备 |
遍历操作: | 容器类型操作,不具备 |
內建函数: | split(str, my_pattern) # 返回数字索引的table,索引由1开始,有序保存分割后的字符串 |
4.2.12 容器类型—set类型
set类型相关总结请参考下表:
set | |
简要说明: | 表示成员唯一的集合,类似于python中的set,但要求成员类型一致 |
显式声明及初始化: | scope name: set[int]; name = set(1, 2, 3); scope name: set[int] = set(1, 2, 3); # 可读性和可维护性更好 |
隐式声明及初始化: | scope name = set(1, 2, 3); |
数据格式: | |
算数运算: | 不支持 |
比较运算: | 不支持 |
逻辑运算: | 不可自动转换为bool类型,因此不可参与逻辑运算 |
添加成员: | 支持,使用add关键字:local s = set(1, 2); add s[3]; print s; # return {1, 2, 3} |
删除成员: | 支持,使用delete关键字,用法同add |
成员数量: | 支持,使用||操作符 |
成员是否存在: | 支持,使用in或!in |
索引成员: | 不支持 |
分片: | 不支持 |
遍历操作: | 支持,使用for (...in...) |
內建函数: | 略 |
4.2.13 容器类型—table类型
table类型相关总结请参考下表:
table | |
简要说明: | 表示key-value间的映射,key唯一,类似python的dict,但key类型一致且value类型一致 |
显式声明及初始化: | scope name: table[string] of string; name = table(["a"]="b", ["c"]="b"); scope name: table[string] of string =table(["a"]="b", ["c"]="b"); # 可读性和可维护性更好 |
隐式声明及初始化: | scope name = table(["a"]="b", ["c"]="b"); |
数据格式: | 支持多个基本数据类型组合作为key,如:local t: table[int, string] of int = table([1, "2"]=3); 后续初始化和赋值时需要注意key需要遵循声明时的格式 |
算数运算: | 不支持 |
比较运算: | 不支持 |
逻辑运算: | 不可自动转换为bool类型,因此不可参与逻辑运算 |
添加成员: | 支持,使用赋值语句即可 |
删除成员: | 支持,使用delete关键字,用法同set |
成员数量: | 支持,使用||操作符 |
成员是否存在: | 支持,使用in或!in,注意,仅针对key进行检测,如: local t = table([1, "2"] = 3); print [1, "2"] in t; # return T |
索引成员: | 支持 |
分片: | 不支持 |
遍历操作: | 支持,使用for (...in...),注意,仅针对key进行遍历,对组合key的遍历只能整体遍历,如: for ([i, j] in table([1, "2"] = 3)) { print i; } # return 1 |
內建函数: | 略 |
4.2.14 容器类型—vector类型
vector类型相关总结请参考下表:
vector | |
简要说明: | 表示有序数组,类似python的list,但要求成员类型一致 |
显式声明及初始化: | scope name: vector of int; name = vector(1, 1, 2); scope name: vector of int = vector(1, 1, 2); # 可读性和可维护性更好 |
隐式声明及初始化: | scope name = vector(1, 1, 2); |
数据格式: | 索引由0开始递增,连续不间断 |
算数运算: | 不支持 |
比较运算: | 不支持 |
逻辑运算: | 不可自动转换为bool类型,因此不可参与逻辑运算 |
添加成员: | 支持,使用赋值语句即可,注意索引连续 |
删除成员: | 不支持 |
成员数量: | 支持,使用||操作符 |
成员是否存在: | 支持,使用in或!in,注意,仅针对索引进行检测 |
索引成员: | 支持 |
分片: | 不支持 |
遍历操作: | 支持,使用for (...in...),注意,仅针对索引进行遍历 |
內建函数: | 略 |
4.2.15 自定义类型—type和record关键字
type关键字可以用于定义数据类型别名,提高代码可读性和可维护性,如:
type string_array: table[count] of string;
type和record关键字结合使用可以用于自定义数据类型,相关总结请参考下表:
自定义数据类型 | |
简要说明: | 使用type和record关键字自定义数据类型,类似于C语言的结构体 可以任意定义数据类型中每个成员key和value的数据类型,但使用时必须按定义初始化或赋值 |
类型自定义 | type Service: record { name: string; ports: set[port]; }; 自定义数据类型时允许嵌套包含其他自定义的数据类型 类型定义需在Bro解析阶段完成,即类型定义必须位于可执行代码(event/hook/function)外部 |
显式声明及初始化: | scope name: Service; name = Service($name="dns", $ports=set(53/tcp, 53/udp)); scope name: Service = Service($name="dns", $ports=set(53/tcp, 53/udp)); 后者可读性和可维护性更好 初始化时在key前添加$符号 |
隐式声明及初始化: | scope name = Service($name="dns", $ports=set(53/tcp, 53/udp)); |
数据格式: | 略 |
算数运算: | 不支持 |
比较运算: | 不支持 |
逻辑运算: | 不可自动转换为bool类型,因此不可参与逻辑运算 |
添加成员: | 容器类型操作,不具备 |
删除成员: | 容器类型操作,不具备 |
成员数量: | 支持,使用||操作符 |
成员是否存在: | 容器类型操作,不具备 |
索引成员: | 访问自定义类型数据的成员使用$符号 |
分片: | 容器类型操作,不具备 |
遍历操作: | 容器类型操作,不具备 |
內建函数: | 略 |
五、如何自定义日志
- 日志生成主要涉及Log Stream、Filter和Writer三部分,具体含义如下:
- Log Stream:定义字段种类、字段类型、日志类型、日志名等日志处理过程中的基本规则,以便用户根据这些要求提供源数据。
- Filter:对源进行过滤、修改、重定向等处理,默认Filter不进行任何处理。
- Writer:决定日志输出方式和格式,默认writer将日志输出为tab分隔的文本文件,也支持ES等输出方式。
- 使用Logging Framework的步骤如下:
- Bro解析阶段:注册Log Stream ID、自定义数据类型Info描述日志内容
- Bro运行初始化阶段:创建Log Stream、修改Log Stream的Filter
- Bro运行阶段:写入Log Steam。
- Logging Framework可以配置生成自定义的event,用户需要自行声明并初始化log_x event handler,并在其t中进行后置处理,避免了解析文本日志再分析的冗余操作,提高分析实时性。
六、如何触发通知
使用Notice Framework的步骤如下:
- Bro解析阶段:注册Notice Type。
- Bro运行阶段:根据Notice::Info生成notice数据,通过NOTICE函数传入Notice Framework。
- Notice Framework:通过hook修改notice数据并触发相应通知行为(可以在Bro解析阶段定义Notice Policy shortcut简化该步骤)。