Writing Scripts
Understanding Scripts
bro包括一种事件驱动的脚本语言,它为组织扩展和定制bro的功能提供了主要手段。实际上,bro生成的所有输出都是bro脚本生成的。把bro看作是处理连接和生成事件的幕后实体更容易理解,而bro的脚本语言是通讯的媒介。bro脚本有效地通知bro,如果存在我们定义的类型的事件,那么让我们获得有关连接的信息,以便对其执行一些功能。例如,ssl.log文件是由bro脚本生成的,该脚本遍历整个证书链,如果证书链上的任何步骤无效,则发出通知。整个过程是通过告诉bro如果看到服务器或客户机发出一条SSL hello消息来设置的,我们想知道有关该连接的信息。
通过查看完整的脚本并将其分解为可识别的组件,通常是最容易理解bro的脚本语言的方式。在本例中,我们将查看bro如何根据 Team Cymru Malware hash registry检查从网络流量中提取的各种文件的sha1哈希。Team Cymru Malware hash registry的一部分包括对格式为
以下是一个完整的脚本的例子
##! Detect file downloads that have hash values matching files in Team##! Cymru's Malware Hash Registry (http://www.team-cymru.org/Services/MHR/).
@load base/frameworks/files@load base/frameworks/notice@load frameworks/files/hash-all-files
module TeamCymruMalwareHashRegistry;
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.
option 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/;
## 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.
option match_sub_url = "https://www.virustotal.com/en/search/?query=%s";
## 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.
option notice_threshold = 10;}
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 " "
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);
}
}
}
}
event file_hash(f: fa_file, kind: string, hash: string)
{
if ( kind == "sha1" && f?$info && f$info?$mime_type &&
match_file_types in f$info$mime_type )
do_mhr_lookup(hash, Notice::create_file_info(f));
}
如果您不理解脚本的每个部分,请不要气馁;我们将在下面的部分中介绍脚本的基础知识以及更多内容。
detect-MHR.bro¶
@load base/frameworks/files
@load base/frameworks/notice
@load frameworks/files/hash-all-files
脚本的第一部分由@load指令组成,该指令处理正在加载的各个目录中的加载bro脚本。虽然在bro的完整生产部署中,这些额外的资源不太可能还没有加载,但是这是一个好习惯。本例中@load指令确保files框架、notice框架和用于散列所有文件的脚本已由bro加载。
detect-MHR.bro¶
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.
option 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/;
## 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.
option match_sub_url = "https://www.virustotal.com/en/search/?query=%s";
## 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.
option notice_threshold = 10;
}
export部分重新定义了一个可枚举常量,该常量描述了我们将使用notice框架生成的通知类型。bro允许可重新定义的常量,只有在bro开始运行之前才能改变。列出的通知类型允许使用notice函数生成类型TeamCymruMalwareHashRegistry::Match的通知,如下一节所述。通知允许bro在其默认日志类型之外生成某种额外的通知。通常,这个额外的通知以电子邮件的形式出现,生成并发送到预先配置的地址,但可以根据部署的需要进行更改。导出部分完成了一些常量的定义,这些常量列出了我们想要匹配的文件类型以及我们感兴趣的检测阈值的最小百分比。
到目前为止,脚本只做了一些基本的设置。在下一节中,脚本开始定义在给定事件中接受的指令。
detect-MHR.bro¶
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 " "
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!). //下面的代码用于生产一个Notice。
local n: Notice::Info = Notice::Info($note=Match, $msg=message, $sub=virustotal_url);
Notice::populate_file_info2(fi, n);
NOTICE(n);
}
}
}
}
event file_hash(f: fa_file, kind: string, hash: string) //事件回调
{
if ( kind == "sha1" && f?$info && f$info?$mime_type && //?操作符表示f对象的Info成员是否被赋值,如果赋值返回true
match_file_types in f$info$mime_type ) //如果mime_type匹配自己定义的类型
do_mhr_lookup(hash, Notice::create_file_info(f));//调用
file_hash事件允许脚本访问Bro文件分析框架生成文件对应哈希的关联信息。事件处理程序将文件本身作为f传递,摘要算法的kind用作类型,哈希是hash参数。
如果条件校验成功就调用自定义的查询函数,在该函数中,定义一个局部变量来保存一个字符串,该字符串由连接了.malware.hash.cymru.com的sha1哈希组成;利用这个值来查询注册的恶意软件hash值中是否包括它。
脚本的其余部分包含在when块中。简而言之,当bro需要执行异步操作(如dns查找)以确保性能不受影响时,使用when块。when块执行DNS TXT查找并将结果存储在本地变量mhr_result中。实际上,继续处理此事件,在收到lookup_hostname_txt返回的值后,执行when块。when块通过在文本空间上拆分并将返回的值存储在本地表变量中,将返回的字符串拆分为第一次检测恶意软件的日期和检测率的一部分。在do-mhr-u查找函数中,如果split1返回的表有两个条目,表示拆分成功,我们将检测日期存储在mhr-first-detected中,并使用适当的转换函数将速率存储在mhr-detected中。从这一点开始,bro知道它看到了一个传输的文件,这个文件有一个由团队cymru恶意软件哈希注册表看到的哈希,脚本的其余部分专门用于生成一个通知。
检测时间被处理成一个字符串表示形式,并存储在可读的“readable_first_detected”中。然后,脚本将检测率与前面定义的 notice_threshold进行比较。如果检测率足够高,脚本将创建通知的简明描述并将其存储在message变量中。它还创建了一个可能的URL来对照virustotal.com的数据库检查示例,并调用notice将相关信息传递给notice框架。
在大约几十行代码中,bro提供了一个惊人的实用程序,它将难以与其他产品一起实现和部署。事实上,声称bro在这么少的行中这样做是一个错误的方向;bro的幕后发生了大量的事情,但脚本语言的加入使分析师能够以简洁和定义良好的方式访问这些底层。
The Event Queue and Event Handlers
Bro的脚本语言是事件驱动的,与大多数用户以前使用过的大多数脚本语言相比,这是一种gear变化。在bro中编写脚本依赖于处理bro在处理网络流量时生成的事件,通过这些事件改变数据结构的状态,以及对所提供的信息做出决定。这种编写脚本的方法常常会给从过程语言或函数语言来Bro的用户带来混乱,但是一旦最初的冲击消失,习惯了,它就会变得更加清晰。
Bro的核心行为是将事件放入有序的“事件队列”,允许事件处理程序以先到先服务的方式处理它们。实际上,这是bro的核心功能,因为如果没有为事件执行离散操作而编写的脚本,那么就几乎没有可用的输出。因此,对事件队列、正在生成的事件以及事件处理程序处理这些事件的方式的基本了解不仅是学习为BRO编写脚本的基础,也是了解BRO本身的基础。
熟悉bro生成的特定事件是构建bro脚本思维模式的重要一步。BRO生成的大多数事件都是在 built-in-function文件(.bif)中定义的,该文件也是联机事件文档的基础。这些在线注释使用broxygen编译成在线文档系统。无论是从头开始一个脚本,还是阅读和维护其他人的脚本,拥有可用的内置事件定义都是手头上一个很好的资源。对于2.0版本,bro开发人员将大量精力投入到每个事件的组织和文档中。这项工作导致了内置函数文件的组织,使得每个条目都包含一个描述性的事件名称、传递给事件的参数以及对函数使用的简明解释。
## Generated for DNS requests. For requests with multiple queries, this event
## is raised once for each.
##
## See `Wikipedia `__ 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
event dns_request%(c: connection, msg: dns_msg, query: string, qtype: count, qclass: count%);
上面是事件dns请求文档的一部分(前面的链接指向由此生成的文档)。它的组织方式使文档、注释和参数列表优先于bro使用的实际事件定义。当bro检测到发起方发出的DNS请求时,它会发出此事件,然后任何数量的脚本都可以访问bro随事件传递的数据。在本例中,BRO不仅传递DNS请求的消息、查询、查询类型和查询类,还传递用于连接本身的记录。
The Connection Record Data Type
在bro定义的所有事件中,绝大多数事件通过连接记录数据类型,实际上,使其成为许多脚本解决方案的主干。连接记录本身,如我们稍后将看到的,是大量嵌套的数据类型,用于在连接的整个生命周期中跟踪连接的状态。让我们来浏览一下选择适当事件的过程,生成一些输出到标准输出,并剖析连接记录,以便对其进行概述。稍后我们将更详细地介绍数据类型。
虽然bro能够进行包级处理,但其优势在于发起者和响应者之间的连接。因此,存在为连接生命周期的主要部分定义的事件,例如:
在列出的事件中,能让我们更好地了解连接记录数据类型的事件是connection_state_remove。正如在线文档中详细介绍的,bro在决定从内存中删除此事件之前就生成了此事件。让我们来看一个简单的示例脚本,它将输出单个连接的连接记录。
connection_record_01.bro¶
1 2 3 4 5 6 |
@load base/protocols/conn event connection_state_remove(c: connection) { print c; } |
同样,我们从@load开始,这次导入包:base/protocols/conn脚本,它提供跟踪和记录一般信息和连接状态。我们处理connection_state_remove事件,并简单地打印传递给它的参数的内容。对于这个例子,我们将在“bare mode”(裸模式)下运行bro,它只加载最小数量的脚本以保持可操作性,并将加载所需脚本的负担留给正在运行的脚本。虽然bare模式是bro中的一个低级功能,但在本例中,我们将使用它来演示bro的不同功能如何添加越来越多的连接信息层。这将使我们有机会在不过度填充连接记录的情况下查看其内容。
$ bro -b -r http/get.trace connection_record_01.bro
[id=[orig_h=141.142.228.5, orig_p=59856/tcp, resp_h=192.150.187.43, resp_p=80/tcp], orig=[size=136, state=5, num_pkts=7, num_bytes_ip=512, flow_label=0, l2_addr=c8:bc:c8:96:d2:a0], resp=[size=5007, state=5, num_pkts=7, num_bytes_ip=5379, flow_label=0, l2_addr=00:10:db:88:d2:ef], start_time=1362692526.869344, duration=0.211484, service={
}, history=ShADadFf, uid=CHhAvVGS1DHFjwGM9, tunnel=
bro广泛使用嵌套数据结构来存储从连接分析中收集的状态和信息,作为一个完整的单元。要分解此信息集合,必须使用bro的字段分隔符$。例如,发起主机由c$id$orig_h引用,如果给出了一个叙述,则它与orig_h相关,c$id$orig_是ID的一个成员,它是被传递到事件处理程序的被称为C的数据结构的一个成员。假设响应程序端口c$id$resp_p是80/tcp,那么bro的基本http脚本可能会进一步填充连接记录。让我们加载base/protocols/http脚本并检查脚本的输出。
connection_record_02.bro¶
1 2 3 4 5 6 7 |
@load base/protocols/conn @load base/protocols/http event connection_state_remove(c: connection) { print c; } |
$bro -b -r http/get.trace connection_record_02.bro
和例1比输出结果会多打印http信息记录
添加base/protocols/http脚本将填充连接记录的http=[]成员。虽然bro在后台做了大量的工作,信息细节提炼,决策制定一般在脚本层实现。如果我们继续以“裸模式”运行,我们可以通过@load语句缓慢地继续添加基础结构。(这是一种非常好的bro学习方法)例如,如果我们要@load base/frameworks/logging,bro将在当前工作目录中为我们生成conn.log和http.log。如上所述,包括适当的@load语句不仅是一种好的实践,而且有助于指示脚本中使用的功能。花点时间运行不带-b标志的脚本,并在bro的所有功能应用于跟踪文件时检查输出。
Data Types and Data Structures
略,直接查看手册。