2020-11-16 Zeek Script
@load
Zeek可以从其他文件中load脚本, 用@load就可以
@load misc/dump-events 用于输出所有日志, 用于debug
zeek默认路径, 可以直接load?
<prefix>/share/zeek
<prefix>/share/zeek/policy
<prefix>/share/zeek/site
@load操作最常用的脚本为/site/local.zeek, 此脚本包含部分zeek的配置信息, 可以添加一些不被默认装载的脚本.
目录<PREFIX>/share/zeek 中/base下的脚本全部会自动被装载.
目录<PREFIX>/share/zeek 中/policy 需要手动装载.
函数
function emphasize(s: string, p:string &default="*"): string{
return p+s+p;
}
event zeek_init(){
print emphasize("yes");
print emphasize("no","-");
}
*yes*
-no-
function 函数名(变量名:类型 &属性): 函数返回值类型{
}
变量
变量的声明是弱类型
常用的有local/global/const等接变量名
也可local/global/const 变量名:变量类型
ps: 没有i++只有++i
原生数据类型
https://docs.zeek.org/en/current/script-reference/types.html
print /one|two|three/ == "two"; # T
print /one|two|three/ == "ones"; # F (exact matching)
print /one|two|three/ in "ones"; # T (embedded matching)
print /[123].*/ == "2 two"; # T
print /[123].*/ == "4 four"; # F
features:
count, pattern(正则), time, interval, port, addr, subnet
Zeek 数据类型
-
time
network_time()得到当前时间点, 如果输入为报文则为报文时间. 通过double_to_time(d)函数将double格式的d转为time格式 -
strftime("%D %H:%M", c$start_time)
将标准时间格式转换成可读时间 -
interval
时间间隔类型, usec, msec, sec, min, hr, or day. -
端口
port, 带传输层协议, 例53/udp, 80/tcp -
地址
addr, 例 1.2.3.4, [2001:db8::1] -
网段
subnet, 192.168.0.0/16
event zeek_init()
{
print network_time();
local d:double=111111111.123456789;
print double_to_time(d);
local t:time=double_to_time(d);
local i:interval=network_time()-t;
print i;
local p:port=53/tcp;
print p;
if (127.0.0.1 in 127.0.0.0/31)
print 222222222222;
}
type casting
http://mailman.icsi.berkeley.edu/pipermail/zeek/2017-January/011178.html
- cat()
local a:addr=127.0.0.1;
print cat("foo", 3, T, a);
foo3T127.0.0.1 - as
function example(a: any)
{
local s: string;
if ( a is string )
s = (a as string);
}
for if 格式化print
event zeek_init()
{
local x = "3";
for ( c in "12345" )
{
if ( c == x )
{
print "Found it.";
# A preview of functions: fmt() does substitutions, outputs result.
print fmt("And by 'it', I mean %s.", x);
}
else
{
# A quick way to print multiple things on one line.
print "I'm looking for", x, "not", c;
}
}
}
for 和 while
- for (i in set()) print i;
输出set()中元素 - for (i in vector("a","b","c")) print i;
输出为vector下标 - for ( i in table([0]="a",[1]="b",[2]="c") ) print i;
输出为table下标 - break是break,continue变为next
switch语句接多个条件
event zeek_init()
{
local result = 0;
local input = "The Zeek Network Security Monitor";
for ( c in input )
{
switch ( c )
{
case "a", "e", "i", "o", "u":
++result;
break;
}
}
print result;
}
event
- 可以自定义事件
- 没有返回值
- 可以有多个定义, 被调用时按照&priority顺序执行
一些与协议无关的事件, 即不通过协议状态触发的事件
https://docs.zeek.org/en/current/scripts/base/bif/event.bif.zeek.html
global myevent: event(s: string);
global n = 0;
event myevent(s: string) &priority = -10
{
++n;
}
event myevent(s: string) &priority = 10
{
print "myevent", s, n;
}
event zeek_init()
{
print "zeek_init()";
event myevent("hi");
schedule 5 sec { myevent("bye") };
}
event zeek_done()
{
print "zeek_done()";
}
zeek_init()
myevent, hi, 0
myevent, bye, 1
zeek_done()
schedule 5 sec{}, 五秒后执行或在zeek进程结束前执行
hook
hook 是另一种形式的函数,和event的相同点是可以多次定义, 被调用时按照&priority顺序执行。
和event的不同点为:
- 有返回值
- 被调用立即执行
- 如果执行到末尾或return语句,则继续执行priority低的hook,但如遇到break语句,则不会执行其他的同名hook
global myhook: hook(s: string);
hook myhook(s: string) &priority = 10
{
print "priority 10 myhook handler", s;
s = "bye";
}
hook myhook(s: string)
{
print "break out of myhook handling", s;
break;
}
hook myhook(s: string) &priority = -5
{
print "not going to happen", s;
}
event zeek_init()
{
local ret: bool = hook myhook("hi");
if ( ret )
{
print "all handlers ran";
}
}
priority 10 myhook handler, hi
break out of myhook handling, hi
Set
集合, 通过add和delete操作.
local x: set[string]={"one","two","three"};
event zeek_init()
{
local x: set[string] = { "one", "two", "three" };
add x["four"];
print "four" in x; # T
delete x["two"];
print "two" !in x; # T
add x["one"]; # x is unmodified since 1 is already a member.
for ( e in x )
{
print e;
}
}
T
T
one
three
four
Table
- 表项(索引)唯一
- 添加直接赋值即可
- 通过delete删除
- 相当于其他语言的数组, 哈希表, map等
event zeek_init()
{
local x: table[count] of string = { [1] = "one",
[3] = "three",
[5] = "five" };
x[7] = "seven";
print 7 in x; # T
print x[7]; # seven
delete x[3];
print 3 !in x; # T
x[1] = "1"; # changed the value at index 1
for ( key in x )
{
print key,x[key];
}
}
T
seven
T
1, 1
5, five
7, seven
Vector
- 区别于集合的点为可以存相同的元素
event zeek_init()
{
local x: vector of string = { "one", "two", "three" };
print x; # [one, two, three]
print x[1]; # two
x[|x|] = "one";
print x; # [one, two, three, one]
for ( i in x )
{
print i; # Iterates over indices.
}
}
[one, two, three]
two
[one, two, three, one]
0
1
2
3
其中第6行为向容器中添加一个新元素, |x|表示容器的长度.
Record
类似结构体, 通过$
符号来索引结构体中成员(用.会跟ip地址冲突)
通过?$
判断成员是否存在.
type代表用户自定义类型
type MyRecord: record {
a: string;
b: count;
c: bool &default = T;
d: int &optional;
};
event zeek_init()
{
local x = MyRecord($a = "vvvvvv", $b = 6, $c = F, $d = -13);
if ( x?$d )
{
print x$d;
}
x = MyRecord($a = "abc", $b = 3);
print x$c; # T (default value of the field)
print x?$d; # F (optional field was not set)
}
Record 的重定义
record和enum这种用户自定义类型可以拓展
type MyRecord: record {
a: string &default="hi";
b: count &default=7;
} &redef;
redef record MyRecord += {
c: bool &optional;
d: bool &default=F;
#e: bool; # Not allowed, must be &optional or &default.
};
event zeek_init()
{
print MyRecord();
print MyRecord($c=T);
}
[a=hi, b=7, c=<uninitialized>, d=F]
[a=hi, b=7, c=T, d=F]
e.g.
- event new_connection()
- strftime("%D %H:%M", c$start_time)
将标准时间格式转换成可读时间 - connection_state_remove()
在连接从内存中移除时触发的事件, 可以方便地记录一个连接的interval类型的持续时间. - 内网ip在实际环境中在network.cfg文件中配置
global local_subnets: set[subnet] = { 192.168.1.0/24, 192.68.2.0/24, 172.16.0.0/20, 172.16.16.0/20, 172.16.32.0/20, 172.16.48.0/20 };
global my_count = 0;
global inside_networks: set[addr];
global outside_networks: set[addr];
event new_connection(c: connection)
{
++my_count;
if ( my_count <= 10 )
{
print fmt("The connection %s from %s on port %s to %s on port %s started at %s.", c$uid, c$id$orig_h, c$id$orig_p, c$id$resp_h, c$id$resp_p, strftime("%D %H:%M", c$start_time));
#print c$start_time;
}
if ( c$id$orig_h in local_subnets)
{
add inside_networks[c$id$orig_h];
}
else
add outside_networks[c$id$orig_h];
if ( c$id$resp_h in local_subnets)
{
add inside_networks[c$id$resp_h];
}
else
add outside_networks[c$id$resp_h];
}
event connection_state_remove(c: connection)
{
if ( my_count <= 10 )
{
print fmt("Connection %s took %s seconds", c$uid, c$duration);
}
}
event zeek_done()
{
print fmt("Saw %d new connections", my_count);
print "These IPs are considered local";
for (a in inside_networks)
{
print a;
}
print "These IPs are considered external";
for (a in outside_networks)
{
print a;
}
}
The connection Cy1Vgd4iLmOBM7JTD3 from 192.168.1.1 on port 626/udp to 224.0.0.1 on port 626/udp started at 11/18/09 08:00.
Connection CJj7Bl4if8upezvIAk took 163.0 msecs 820.028305 usecs seconds
Module
-
Module定义一个新的命名空间, 通过export块来使module外的文件调用module内的内容和函数
-
用户不能定义新的协议相关事件, 但可以通过Module实现新的解析器或实现特定功能的协议无关事件
-
export中的元素声明为global或type(user-defined type)才能被外部文件调用
-
Zeek packages中每个Module都包含最少两个文件,其中有load.zeek,且load.zeek须列出Module中其他所有文件。包含所有Module文件的目录名称即为Module的名字?因为当Zeek加载一个Module时,会找到这个目录并读取load.zeek文件。
main.zeek
@load factorial
event zeek_done()
{
local numbers: vector of count = vector(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
for ( n in numbers )
print fmt("%d", Factor::factorial(numbers[n]));
}
factorial.zeek
module Factor;
export {
global factorial: function(n: count): count;
}
function factorial(n: count): count
{
if ( n == 0 )
return 1;
else
return ( n * factorial(n - 1) );
}
Manipulating Logs
-
redef enum Log::ID += { LOG };
给Log::ID枚举(Zeek logging framework的一部分)添加一个新id,名字可以换,每个ID代表唯一的日志流。 -
&redef属性可以使所指global,const对象,亦或是type用户定义对象进行重定义
-
Log::create_stream需要在zeek_init()事件中创建日志流
main.zeek
@load factorial
event zeek_init()
{
# Create the logging stream.
Log::create_stream(Factor::LOG, [$columns=Factor::Info, $path="factor"]);
}
event zeek_done()
{
local numbers: vector of count = vector(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
for ( n in numbers )
Log::write( Factor::LOG, [$num=numbers[n],
$factorial_num=Factor::factorial(numbers[n])]);
}
factorial.zeek
module Factor;
export {
# Append the value LOG to the Log::ID enumerable.
redef enum Log::ID += { LOG };
# Define a new type called Factor::Info.
type Info: record {
num: count &log;
factorial_num: count &log;
};
global factorial: function(n: count): count;
}
function factorial(n: count): count
{
if ( n == 0 )
return 1;
else
return ( n * factorial(n - 1) );
}
Filtering Logs
- 声明filter函数和名称
local ttt: Log::Filter=[path_func=Module_name::filter_name]] - 添加filter
Log::add_filter(Module_name::LOG, ttt); - 移除默认filter
Log::remove_filter(Module_name::LOG, "default"); - 例中filter函数
function mod5(id: Log::ID, path: string, rec: Factor::Info) : string
其中rec为column的record数据结构,返回值为
main.zeek
@load factorial
event zeek_init()
{
Log::create_stream(Factor::LOG, [$columns=Factor::Info, $path="factor"]);
local filter: Log::Filter = [$name="split-mod5s", $path_func=Factor::mod5];
Log::add_filter(Factor::LOG, filter);
Log::remove_filter(Factor::LOG, "default");
}
event zeek_done()
{
local numbers: vector of count = vector(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
for ( n in numbers )
Log::write( Factor::LOG, [$num=numbers[n],
$factorial_num=Factor::factorial(numbers[n])]);
}
factorial.zeek
module Factor;
export {
# Append the value LOG to the Log::ID enumerable.
redef enum Log::ID += { LOG };
# Define a new type called Factor::Info.
type Info: record {
num: count &log;
factorial_num: count &log;
};
global factorial: function(n: count): count;
global mod5: function(id: Log::ID, path: string, rec: Factor::Info) : string;
}
function factorial(n: count): count
{
if ( n == 0 )
return 1;
else
return ( n * factorial(n - 1) );
}
function mod5(id: Log::ID, path: string, rec: Factor::Info) : string
{
if ( rec$factorial_num % 5 == 0 )
return "factor-mod5";
else
return "factor-non5";
}
Notice
https://docs.zeek.org/en/current/frameworks/notice.html
redef enum Notice::Type += { Interestring_Result};
NOTICE([$note=Factor::Interesting_Result, $msg = "Something happened!", $sub = fmt("result = %d", result)]);
SumStats Framework
-
Outputs
192.168.1.102 did 17 total and 6 unique DNS requests in the last 6 hours.
192.168.1.104 did 20 total and 10 unique DNS requests in the last 6 hours.
192.168.1.103 did 18 total and 6 unique DNS requests in the last 6 hours. -
DNS Scan e.g.
@load base/frameworks/sumstats
event zeek_init()
{
local r1 = SumStats::Reducer($stream="dns.lookup", $apply=set(SumStats::UNIQUE));
SumStats::create([$name="dns.requests.unique",
$epoch=6hrs,
$reducers=set(r1),
$epoch_result(ts: time, key: SumStats::Key, result: SumStats::Result) =
{
local r = result["dns.lookup"];
print fmt("%s did %d total and %d unique DNS requests in the last 6 hours.",
key$host, r$num, r$unique);
}]);
}
event dns_request(c: connection, msg: dns_msg, query: string, qtype: count, qclass: count)
{
if ( c$id$resp_p == 53/udp && query != "" )
SumStats::observe("dns.lookup", [$host=c$id$orig_h], [$str=query]);
}
-
首先SumStats通过dns_request事件来统计独立的DNS请求,通过SumStats的observe()建立一个observer来统计流中IP为第二个参数值,内含字符串第三个参数的request数量,并存进observation stream,命名此流为dns.lookup
-
第二步为统计i.e. "reduce" 流名为"dns.lookup"的observation stream. 首先声明一个Reducer,将流指向dns.lookup,后面的参数为统计i.e. "reduce"函数的集合,至少规定一种统计方法,这里用了UNIQUE,可以参阅文档sumstats reference
-
第三步为将前面的Reducer连接到Sumstats。SumStats本体也需要名字,为了被引用,这里命名为"dns.requests.unique". epoch设置了持续时间,类型为interval.
SumStats::create()的参数为一个用户自定义的record SumStats::SumStat,其中name, interval, reducers为必选项。