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=[name="test",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为必选项。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,558评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,002评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,024评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,144评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,255评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,295评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,068评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,478评论 1 305
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,789评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,965评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,649评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,267评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,982评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,800评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,847评论 2 351

推荐阅读更多精彩内容