美文网首页
2020-11-16 Zeek Script

2020-11-16 Zeek Script

作者: 黑小柴 | 来源:发表于2020-11-20 15:53 被阅读0次

    @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为必选项。

    相关文章

      网友评论

          本文标题:2020-11-16 Zeek Script

          本文链接:https://www.haomeiwen.com/subject/exaabktx.html