通过查看完整的脚本将其分解为可识别的组件来了解Bro的脚本语言通常是最容易的。在这个例子中,我们将看到Bro如何检查从网络流量提取的各种文件的SHA1哈希与Team Cymru Malware哈希注册表。 Cymru Malware Hash注册表的一部分包括使用格式 .malware.hash.cymru.com在域上执行主机查找的功能,其中是文件的SHA1哈希。团队Cymru也填充他们的DNS响应的TXT记录与“首见”时间戳和数字“检测率”。要了解的重要方面是Bro已经通过Files框架生成文件的哈希,但它是脚本detect-MHR.bro,负责生成适当的DNS查找,解析响应,并生成是否适用的通知。
export { redef enum Notice::Type += { ## The hash value of a file transferred over HTTP matched in the ## malware hash registry. Match };
## File types to attempt matching against the Malware Hash Registry. const match_file_types = /application\/x-dosexec/ | /application\/vnd.ms-cab-compressed/ | /application\/pdf/ | /application\/x-shockwave-flash/ | /application\/x-java-applet/ | /application\/jar/ | /video\/mp4/ &redef;
## The Match notice has a sub message with a URL where you can get more ## information about the file. The %s will be replaced with the SHA-1 ## hash of the file. const match_sub_url = "https://www.virustotal.com/en/search/?query=%s" &redef;
## The malware hash registry runs each malware sample through several ## A/V engines. Team Cymru returns a percentage to indicate how ## many A/V engines flagged the sample as malicious. This threshold ## allows you to require a minimum detection rate. const notice_threshold = 10 &redef; }
function do_mhr_lookup(hash: string, fi: Notice::FileInfo) { local hash_domain = fmt("%s.malware.hash.cymru.com", hash);
when ( local MHR_result = lookup_hostname_txt(hash_domain) ) { # Data is returned as "<dateFirstDetected> <detectionRate>" local MHR_answer = split_string1(MHR_result, / /);
if ( |MHR_answer| == 2 ) { local mhr_detect_rate = to_count(MHR_answer[1]);
if ( mhr_detect_rate >= notice_threshold ) { local mhr_first_detected = double_to_time(to_double(MHR_answer[0])); local readable_first_detected = strftime("%Y-%m-%d %H:%M:%S", mhr_first_detected); local message = fmt("Malware Hash Registry Detection rate: %d%% Last seen: %s", mhr_detect_rate, readable_first_detected); local virustotal_url = fmt(match_sub_url, hash); # We don't have the full fa_file record here in order to # avoid the "when" statement cloning it (expensive!). local n: Notice::Info = Notice::Info($note=Match, $msg=message, $sub=virustotal_url); Notice::populate_file_info2(fi, n); NOTICE(n); } } } }
export { redef enum Notice::Type += { ## The hash value of a file transferred over HTTP matched in the ## malware hash registry. Match };
## File types to attempt matching against the Malware Hash Registry. const match_file_types = /application\/x-dosexec/ | /application\/vnd.ms-cab-compressed/ | /application\/pdf/ | /application\/x-shockwave-flash/ | /application\/x-java-applet/ | /application\/jar/ | /video\/mp4/ &redef;
## The Match notice has a sub message with a URL where you can get more ## information about the file. The %s will be replaced with the SHA-1 ## hash of the file. const match_sub_url = "https://www.virustotal.com/en/search/?query=%s" &redef;
## The malware hash registry runs each malware sample through several ## A/V engines. Team Cymru returns a percentage to indicate how ## many A/V engines flagged the sample as malicious. This threshold ## allows you to require a minimum detection rate. const notice_threshold = 10 &redef; }
function do_mhr_lookup(hash: string, fi: Notice::FileInfo) { local hash_domain = fmt("%s.malware.hash.cymru.com", hash);
when ( local MHR_result = lookup_hostname_txt(hash_domain) ) { # Data is returned as "<dateFirstDetected> <detectionRate>" local MHR_answer = split_string1(MHR_result, / /);
if ( |MHR_answer| == 2 ) { local mhr_detect_rate = to_count(MHR_answer[1]);
if ( mhr_detect_rate >= notice_threshold ) { local mhr_first_detected = double_to_time(to_double(MHR_answer[0])); local readable_first_detected = strftime("%Y-%m-%d %H:%M:%S", mhr_first_detected); local message = fmt("Malware Hash Registry Detection rate: %d%% Last seen: %s", mhr_detect_rate, readable_first_detected); local virustotal_url = fmt(match_sub_url, hash); # We don't have the full fa_file record here in order to # avoid the "when" statement cloning it (expensive!). local n: Notice::Info = Notice::Info($note=Match, $msg=message, $sub=virustotal_url); Notice::populate_file_info2(fi, n); NOTICE(n); } } } }
## Generated for DNS requests. For requests with multiple queries, this event ## is raised once for each. ## ## See `Wikipedia <http://en.wikipedia.org/wiki/Domain_Name_System>`__ for more ## information about the DNS protocol. Bro analyzes both UDP and TCP DNS ## sessions. ## ## c: The connection, which may be UDP or TCP depending on the type of the ## transport-layer session being analyzed. ## ## msg: The parsed DNS message header. ## ## query: The queried name. ## ## qtype: The queried resource record type. ## ## qclass: The queried resource record class. ## ## .. bro:see:: dns_AAAA_reply dns_A_reply dns_CNAME_reply dns_EDNS_addl ## dns_HINFO_reply dns_MX_reply dns_NS_reply dns_PTR_reply dns_SOA_reply ## dns_SRV_reply dns_TSIG_addl dns_TXT_reply dns_WKS_reply dns_end ## dns_full_request dns_mapping_altered dns_mapping_lost_name dns_mapping_new_name ## dns_mapping_unverified dns_mapping_valid dns_message dns_query_reply ## dns_rejected non_dns_request dns_max_queries dns_session_timeout dns_skip_addl ## dns_skip_all_addl dns_skip_all_auth dns_skip_auth global dns_request: event(c: connection , msg: dns_msg , query: string , qtype: count , qclass: count );
## Generated for every new connection. This event is raised with the first ## packet of a previously unknown connection. Bro uses a flow-based definition ## of "connection" here that includes not only TCP sessions but also UDP and ## ICMP flows. global new_connection: event(c: connection ); ## Generated when a TCP connection timed out. This event is raised when ## no activity was seen for an interval of at least ## :bro:id:`tcp_connection_linger`, and either one endpoint has already ## closed the connection or one side never became active. global connection_timeout: event(c: connection ); ## Generated when a connection's internal state is about to be removed from ## memory. Bro generates this event reliably once for every connection when it ## is about to delete the internal state. As such, the event is well-suited for ## script-level cleanup that needs to be performed for every connection. This ## event is generated not only for TCP sessions but also for UDP and ICMP ## flows. global connection_state_remove: event(c: connection );
在开始探索Bro的本地数据类型和数据结构之前,了解Bro中可用的不同级别的可用范围以及在脚本中使用它们的适当时间非常重要。 Bro中变量的声明有两种形式。变量可以使用或不使用SCOPE名称中的定义来声明:TYPE或SCOPE name = EXPRESSION;如果EXPRESSION评估为与TYPE相同的类型,则每个都会产生相同的结果。关于使用哪种类型的声明的可由个人偏好和可读性决定。
1 2 3 4 5 6 7 8 9 10 11
data_type_declaration.bro
event bro_init() { local a: int; a = 10; local b = 10;
Data Type Description int 64 bit signed integer count 64 bit unsigned integer double double precision floating precision bool boolean(T/F) addr IP address, IPv4 and IPv6 port transport layer port subnet CIDR subnet mask time absolute epoch time interval a time interval pattern regular expression
event bro_init() { local ssl_ports: set[port]; local non_ssl_ports = set( 23/tcp, 80/tcp, 143/tcp, 25/tcp ); }
如你所见,使用格式SCOPE var_name:set [TYPE]声明集合。使用add和delete语句实现在集合中添加和删除元素。一旦你有元素插入到集合中,你可能需要迭代该集合或测试集合中的成员资格,这两个都由 in 运算符覆盖。在迭代一个集合的情况下,结合使用 for 语句和 in 运算符将允许你顺序处理集合的每个元素,如下所示。
1 2 3 4 5 6 7
data_struct_set_declaration.bro
for ( i in ssl_ports ) print fmt("SSL Port: %s", i);
for ( i in non_ssl_ports ) print fmt("Non-SSL Port: %s", i);
这里,for语句循环存储临时变量i中的每个元素的集合的内容。对于for循环的每次迭代,选择下一个元素。由于集合不是有序数据类型,因此不能保证元素作为for循环过程的顺序。 要测试集合中的成员资格,in语句可以与if语句组合,以返回true或false值。如果条件中的确切元素已经在集合中,则条件返回true,并且正文执行。 in语句也可以被否定!运算符创建条件的逆。虽然我们可以重写相应的行,如同(!(587 / tcp in ssl_ports))尽量避免使用这个结构;相反,取消in运算符本身。虽然功能是相同的,使用!in是更有效的,以及一个更自然的结构,这将有助于您的脚本的可读性。
1 2 3 4 5
data_struct_set_declaration.bro
# Check for SMTPS if ( 587/tcp !in ssl_ports ) add ssl_ports[587/tcp];
# Insert one key-yield pair into the table. ssl_services["IMAPS"] = 993/tcp;
# Check if the key "SMTPS" is not in the table. if ( "SMTPS" !in ssl_services ) ssl_services["SMTPS"] = 587/tcp;
# Iterate over each key in the table. for ( k in ssl_services ) print fmt("Service Name: %s - Common Port: %s", k, ssl_services[k]); }
执行data_struct_table_declaration.bro脚本:
1 2 3 4 5
# bro data_struct_table_declaration.bro Service Name: SSH - Common Port: 22/tcp Service Name: HTTPS - Common Port: 443/tcp Service Name: SMTPS - Common Port: 587/tcp Service Name: IMAPS - Common Port: 993/tcp
在本例中,我们编译了一个启用SSL的服务及其公共端口的表。表的显式声明和构造函数在两个不同的行上,并且布置keys(strings)的数据类型和yields(port)的数据类型,然后填充一些示例键值对。您还可以使用表访问器将一个键值对插入表中。当在表上使用 in 运算符时,你有效地使用表的键。在if语句的情况下,in运算符将检查键集合中的成员资格,并返回true或false值。该示例显示如何检查SMTPS是否不在ssl_services表的键集合中,如果条件成立,我们将键值对添加到表中。最后,该示例显示如何使用for语句来迭代表中当前的每个键。 除了简单的例子,表可能变得非常复杂,因为表的键和值变得更复杂。表可以具有由多种数据类型组成的键,甚至包括一系列称为“元组”的元素。在Bro中使用复杂表格所获得的灵活性意味着编写脚本的人的高复杂性成本,但是由于Bro作为网络安全平台的强大性,有效性得到了提高。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
data_struct_table_complex.bro
event bro_init() { local samurai_flicks: table[string, string, count, string] of string;
for ( [d, s, y, a] in samurai_flicks ) print fmt("%s was released in %d by %s studios, directed by %s and starring %s", samurai_flicks[d, s, y, a], y, s, d, a); }
执行data_struct_table_complex.bro脚本:
1 2 3 4 5
# bro -b data_struct_table_complex.bro Harakiri was released in 1962 by Shochiku Eiga studios, directed by Masaki Kobayashi and starring Tatsuya Nakadai Goyokin was released in 1969 by Fuji studios, directed by Hideo Gosha and starring Tatsuya Nakadai Tasogare Seibei was released in 2002 by Eisei Gekijo studios, directed by Yoji Yamada and starring Hiroyuki Sanada Kiru was released in 1968 by Toho studios, directed by Kihachi Okamoto and starring Tatsuya Nakadai
event connection_established(c: connection) { print fmt("%s: New connection established from %s to %s\n", strftime("%Y/%M/%d %H:%m:%S", network_time()), c$id$orig_h, c$id$resp_h); }
当脚本执行时,我们得到一个输出,显示已建立的连接的细节。
1 2 3 4 5 6 7 8 9 10
# bro -r wikipedia.trace data_type_time.bro 2011/06/18 19:03:08: New connection established from 141.142.220.118 to 208.80.152.118\x0a 2011/06/18 19:03:08: New connection established from 141.142.220.118 to 208.80.152.3\x0a 2011/06/18 19:03:08: New connection established from 141.142.220.118 to 208.80.152.3\x0a 2011/06/18 19:03:08: New connection established from 141.142.220.118 to 208.80.152.3\x0a 2011/06/18 19:03:08: New connection established from 141.142.220.118 to 208.80.152.3\x0a 2011/06/18 19:03:08: New connection established from 141.142.220.118 to 208.80.152.3\x0a 2011/06/18 19:03:08: New connection established from 141.142.220.118 to 208.80.152.3\x0a 2011/06/18 19:03:08: New connection established from 141.142.220.118 to 208.80.152.2\x0a 2011/06/18 19:03:09: New connection established from 141.142.220.235 to 173.192.163.128\x0a
# bro -r wikipedia.trace data_type_interval.bro 2011/06/18 19:03:08: New connection established from 141.142.220.118 to 208.80.152.118 2011/06/18 19:03:08: New connection established from 141.142.220.118 to 208.80.152.3 Time since last connection: 132.0 msecs 97.0 usecs 2011/06/18 19:03:08: New connection established from 141.142.220.118 to 208.80.152.3 Time since last connection: 177.0 usecs 2011/06/18 19:03:08: New connection established from 141.142.220.118 to 208.80.152.3 Time since last connection: 2.0 msecs 177.0 usecs 2011/06/18 19:03:08: New connection established from 141.142.220.118 to 208.80.152.3 Time since last connection: 33.0 msecs 898.0 usecs 2011/06/18 19:03:08: New connection established from 141.142.220.118 to 208.80.152.3 Time since last connection: 35.0 usecs 2011/06/18 19:03:08: New connection established from 141.142.220.118 to 208.80.152.3 Time since last connection: 2.0 msecs 532.0 usecs 2011/06/18 19:03:08: New connection established from 141.142.220.118 to 208.80.152.2 Time since last connection: 7.0 msecs 866.0 usecs 2011/06/18 19:03:09: New connection established from 141.142.220.235 to 173.192.163.128 Time since last connection: 817.0 msecs 703.0 usecs
# bro data_type_pattern_01.bro The brown fox jumps over the dog. 模式也可以用于通过==和!=运算符分别使用等式和不等式运算符来比较字符串。但是,当以这种方式使用时,字符串必须完全匹配才能解析为true。例如,下面的脚本使用两个三元条件语句来说明==运算符与模式的使用。基于模式和字符串之间的比较结果改变输出。
data_type_pattern_02.bro
event bro_init() { local test_string = "equality";
local test_pattern = /equal/; print fmt("%s and %s %s equal", test_string, test_pattern, test_pattern == test_string ? "are" : "are not");
type Service: record { name: string; ports: set[port]; rfc: count; };
function print_service(serv: Service) { print fmt("Service: %s(RFC%d)",serv$name, serv$rfc);
for ( p in serv$ports ) print fmt(" port: %s", p); }
event bro_init() { local dns: Service = [$name="dns", $ports=set(53/udp, 53/tcp), $rfc=1035]; local http: Service = [$name="http", $ports=set(80/tcp, 8080/tcp), $rfc=2616];
##! This script will generate a notice if an apparent SSH login originates ##! or heads to a host with a reverse hostname that looks suspicious. By ##! default, the regular expression to match "interesting" hostnames includes ##! names that are typically used for infrastructure hosts like nameservers, ##! mail servers, web servers and ftp servers.
@load base/frameworks/notice
module SSH;
export { redef enum Notice::Type += { ## Generated if a login originates or responds with a host where ## the reverse hostname lookup resolves to a name matched by the ## :bro:id:`SSH::interesting_hostnames` regular expression. Interesting_Hostname_Login, };
## Strange/bad host names to see successful SSH logins from or to. const interesting_hostnames = /^d?ns[0-9]*\./ | /^smtp[0-9]*\./ | /^mail[0-9]*\./ | /^pop[0-9]*\./ | /^imap[0-9]*\./ | /^www[0-9]*\./ | /^ftp[0-9]*\./ &redef; }
function check_ssh_hostname(id: conn_id, uid: string, host: addr) { when ( local hostname = lookup_addr(host) ) { if ( interesting_hostnames in hostname ) { NOTICE([$note=Interesting_Hostname_Login, $msg=fmt("Possible SSH login involving a %s %s with an interesting hostname.", Site::is_local_addr(host) ? "local" : "remote", host == id$orig_h ? "client" : "server"), $sub=hostname, $id=id, $uid=uid]); } } }
event ssh_auth_successful(c: connection, auth_method_none: bool) { for ( host in set(c$id$orig_h, c$id$resp_h) ) { check_ssh_hostname(c$id, c$uid, host); } }
ACTION_NONE Take no action ACTION_LOG Send the notice to the Notice::LOG logging stream. ACTION_EMAIL Send an email with the notice in the body. ACTION_ALARM Send the notice to the Notice::Alarm_LOG stream.
Name Description Data Type Notice::ignored_types Ignore the Notice::Type entirely set[Notice::Type] Notice::emailed_types Set Notice::ACTION_EMAIL to this Notice::Type set[Notice::Type] Notice::alarmed_types Set Notice::ACTION_ALARM to this Notice::Type set[Notice::Type] Notice::not_suppressed_types Remove suppression from this Notice::Type set[Notice::Type] Notice::type_suppression_intervals Alter the $suppress_for value for this Notice::Type table[Notice::Type] of interval