Bro使用手册

BRO

0x01 简介

简介

Bro
Bro是一款被动的开源流量分析器。它主要用于对链路上所有深层次的可疑行为流量进行一个安全监控。更通俗点说就是,Bro支持在安全域之外进行大范围的流量分析,分析包括性能评估和错误定位。

站点部署BRO所能获得的最直接好处就是获得日志文件的扩展集,这些文件在高层次记录网络的行为。这些日志文件不仅全方位记录了所有线路上可见的每个连接,还记录了应用层传输,例如HTTP会话以及请求的URL、关键头、MIME类型、服务器反馈,DNS请求及反应,SSL证书,SMTP会话的关键内容等等。默认情况下BRO将这些信息写进结构化,tab键分隔的日志文件中以供其他软件后续处理。当然我们也可以选择其他输出格式和后端来和数据库对接。

当然除了日志,bro还有内建函数来完成分析和检测任务,包括在HTTP会话中抽取文件,在与其他注册点对接时监测恶意软件,报告脆弱的软件版本,识别web应用,监测SSH暴力破解,认证SSL证书链等等。

但是,理解BRO的关键在于意识到,即使本系统自带开箱即用的强大功能,从根本上来看这仍代表一个全定制化和可扩展的流量分析平台。BRO为用户提供了特定域的,图灵完备的脚本语言用于表达任意的分析任务。你可以将BRO视作特定域的Python或者Perl。如同Python,BRO自带大量预建功能,即标准库。如此你将不受限于系统,而能以编写自己的代码的方式来使用BRO。事实上BRO的默认分析,如日志就是基于这些脚本的,核心系统中没有硬编码任何特定分析。

特征

  • 部署
    • Unix-like系统上运行
    • 基于监控端口和网络窃听装置的全被动分析
    • 标准libpcap的抓包接口
    • 线上&线下分析
    • 支持簇的大规模部署
    • 统一管理框架
    • 开源
  • 分析
    • 离线分析和取证的全面日志记录
    • 应用层协议的独立端口分析
    • 支持多种应用层协议 DNS FTP HTTP IRC SMTP SSH SSL
    • 应用层协议交换的文件内容分析
    • 全IPv6支持
    • 隧道检测与分析 Ayiya Teredo GTPv1
    • 扩展的检查
    • 支持IDS的正则匹配
  • 脚本语言
    • 图灵完备
    • 基于事件
    • 特定域数据类型
    • 追踪与管理网络状态
  • 接口
    • 结构化ASCII日志输出
    • ElasticSearch和DataSeries后端
    • 将输入实时整合到分析工具中
    • bro将事件用C库与其他程序交换
    • 以脚本语言触发任意处理

0x02 架构

BRO架构

从此图上来看我们可以看到BRO被划分成两大组件,其一是核心事件引擎,他会减少进入的数据包流,成为一系列高层事件。这些事件反映了网络活动。如每个HTTP请求会变成http_request事件,承载了IP地址和端口,请求的URI以及HTTP版本信息。事件不会表达任何深入的意义。

其二是脚本解释器,他会根据BRO的脚本语言编写的程序执行一系列事件处理。该脚本会表达站点安全规则,例如当监控装置侦测到不同的活动时BRO该采取什么样的动作。他们可以从输入流量中产生任意属性和统计数字。脚本语言自带特定域的类型和支持功能,并允许脚本维护状态。脚本能够生成实时告警也能按需执行任意外部程序来触发对攻击的响应动作。

0x03 Bro的簇架构

Bro不是多线程的,因此一旦到达单处理器核心的限制,唯一的选择就是将附在分摊到多核心或者多物理机上。簇部署就是针对这样大系统的场景设计的解决方案。伴随Bro的工具和脚本提供了易于管理许多Bro进程的结构来检查包处理相关动作,从而形成一个整体。

Bro簇的架构

Bro簇主要组件

Tap

划分数据流来形成一个可检测的拷贝。

前端

前端是一个分离的硬件设备或主机技术,能够将流量划分为许多的stream或flow。

管理端

管理端进程有两个主要任务,即从该簇的其他节点接收日志消息以及提示。其输出是一个单一的日志,你需要将某行为和后期处理相结合,你当然可以选择重复提示。

管理端进程首先由BroControl启动,仅开放特定端口等待连接,他不会初始任何对其他簇的连接,一旦工作端启动并连接到管理端,日志和提示将开始从工作端抵达管理端进程。

代理端

管理同步状态的进程。变量能够跨进程自动同步。代理端帮助工作端,减轻工作端之间彼此的直连需求。

工作端

工作端嗅探网络流量并做协议分析。活动簇的绝大部分工作发生在工作端上,因此工作端需要最快的内存和CPU速度。但是其硬盘需求不大,因为日志直接发给了管理端。

前端选项

设置前端流分发器的时候存在许多选项,多数情况下采取多步骤流分发具有益处。

cPacket-分离硬件

若是监控多个10G网卡,建议使用cFlow或者cVu设备。这些设备能通过重写目的mac地址引发与特定流关联的包具有相同的目的mac从而实现链路层负载均衡。

OpenFlow交换机-分离硬件

使用OpenFlow交换机来直接实现基于流的负载均衡。

PF_RING-主机负载均衡

PF_RING软件具有簇特征,它会通过大量嗅探同网卡的进程来进行基于流的负载均衡。这会使你轻松利用同一物理机上多核的优势,因为Bro的事件主循环是单线程,且不能简单利用所有的核心。

Netmap

基于流的负载均衡

Click!软路由

简单配置 基于流的负载均衡。

0x04 安装

在ubuntu 14.04中安装较为方便,只需输入以下代码即可。

sudo sh -c "echo 'deb http://download.opensuse.org/repositories/network:/bro/xUbuntu_14.04/ /' >> /etc/apt/sources.list.d/bro.list"
sudo apt-get update
sudo apt-get install bro

具体其他系统关注 Bro安装手册

0x05 快速使用bro

以上方法去安装的bro,root用户的环境变量中不会存在bro的可执行程序所在的路径,而bro的安装目录的权限只允许root以及root所在的组访问,因此我们选择将可执行文件所在的路径添加进root用户的环境变量PATH中。

export PATH=/opt/bro/bin:$PATH

如此一来只需对三个配置文件进行修改就可以完成BroControl的最小安装,从而完成在本机上的独立使用。

  1. 修改$PREFIX/etc/node.cfg,设置监听网卡
  2. 修改$PREFIX/etc/network.cfg,注释默认设置,添加本地网络作为监控对象
  3. 修改$PREFIX/etc/broctl.cfg,设置MailTo属性,改为希望发送告警信息的邮箱,设置LogRotationInterval属性,改为将当前日志文件夹内的文件压缩的指定频率,归档文件夹以日期命名。

PS:$PREFIX是我们当时最初安装bro所在的目录。

随后以root用户执行命令

broctl

就可以进入broctl的shell,初次使用broctl需要先在shell中执行install和以start启动bro实例。由于抓包需要操作系统的root权限,所以最好是赋予你的账户抓包的权限。

[BroControl] > install
[BroControl] > start

你若是要停止bro实例。

[BroControl] > stop

随后我们打开浏览器,打开网页,查看$PREFIX/logs/current/http.log就可以看到了Bro对HTTP协议的分析结果。见下图。

基本可以看到其中的时间戳,uid,源地址&端口,目的地址&端口等信息。

部署Bro是一个更新规则来对不同的时间来采取不同的行为以及使用其脚本语言以一个精确的方式扩展器流量分析能力的递归过程。定制Bro过程的第一步是熟悉Bro的notice日志。

当我们已经定义我们要做的事之后,我们需要知道我们在哪做这些事了。使用Bro的脚本即可完成我们需要的任务。

Bro脚本

脚本一般装在$PREFIX/share/bro下且使用.bro扩展名。该目录下的文件不应该被直接编辑因为所做的修改会在升级新版本时丢失。而目录$PREFIX/share/bro/site下的文件是个例外,指定站点的文件能够被放置而不必担心失败。

$PREFIX/share/bro/base$PREFIX/share/bro/policy目录也是主要的脚本目录。Bro默认加载base文件夹下的脚本,除非使用-b来设定,该目录下的脚本处理收集网络活动的基础/有用状态或者提供框架/工具来拓展Bro的功能而不损失性能。policy目录下的脚本更加有条件或存在耗费,因此我们要选择是否加载这些脚本。

用于BroControl管理的独立Bro实例的配置的主入口点是脚本$PREFIX/share/bro/site/local.bro,基于他我们可以做一系列修改。

重定义脚本变量

简单的定制仅需要以自己的值来重定义标准Bro脚本中的变量,使用redef操作符来完成。脚本发布可调整的选项的方法是定义有&redef属性和const修饰的变量。可重定义的常量看起来很奇怪吧,但这意味着该变量的值不能在运行时被修改,但是其初始值能通过redef操作符在解析时修改。

$PREFIX/share/bro/base/frameworks/notice/main.bro中的Notice命名空间在此是必要的,因为变量在模块Notice中声明并导出,在模块外被引用。既然仍在模块内,若要引用变量,模块内被声明和引用的变量不会被纳入范围。

在对该脚本语言记录域成员的访问采用$而非.

需要记住的是,若要完成配置的修改需在BroControl中按顺序输入命令checkinstall以及restart

0x06 Bro日志分析

使用日志文件

在Bro日志文件夹下存在多个文件分别对应不同的类型的日志。

文件名 作用
conn.log 关于连接的日志
dpd.log 非标准端口协议的日志
dns.log dns活动日志
ftp.log ftp会话活动日志
files.log HTTP FTP SMTP 文件日志
http.log http请求和响应日志
known_certs.log SSL证书
smtp.log SMTP活动日志
ssl.log SSL会话,包括使用的证书
weird.log 意料外的协议层活动日志

使用bro-cut工具

这个工具是读取日志输出,根据日志中各项属性的名字来提取对应的值。

处理时间戳

施工中

使用Unique IDs

施工中

0x07 以Bro监听HTTP流量

Bro能够将整个HTTP流量记录到http.log日志中,该日志能够用于随后的分析和审计。

这部分的思想和技术当然也可适用于检测其他不同的协议。

初识HTTP日志

本日志包含所有的HTTP请求以及响应。如下图所示。

每行都由时间戳,uid,源地址,源端口,目的地址,目的端口组成。UID用于唯一标识一个活动,与UID相对应的是一个连接生命周期内的源地址-源端口-目的地址-目的端口四值对。其余部分则详述了这个活动。

侦测代理服务器

人们配置代理服务器来请求来自第三方系统的服务。代理用于管理网络并提供更好的封装特性。代理本身不存在安全威胁,但是一个错误配置的或者无鉴权的代理将使得网络内外的用户访问到任何站点,甚至是进行恶意活动。

代理服务器间的流量特征

与代理的对话流量长啥样呢?一般来说流量包含两部分:

  1. GET请求

  2. HTTP响应

     Request: GET http://www.baidu.com/ HTTP/1.1
     Reply:  HTTP/1.0 200 OK
    

这就将代理的流量与浏览器同Web服务器的流量区分出来了,因为浏览器发出的GET请求不应该会有'http'字段。

如此一来我们就可以根据这个去识别代理服务器了。

文件检查

0x08 动手写脚本

如何理解Bro脚本语言

Bro拥有一个事件驱动的脚本语言,它能够为组织提供对Bro功能进行扩展和定制的方法。Bro脚本能够高效地知会Bro有我们定义的事件发生了,让我们获得连接的信息,因此我们可以对其进行操作。

一般的Bro脚本划分为三个部分。首先是无缩进的加载库指令,以@load来加载module中定义的命名空间。随后是格式化缩进的自定义变量,使用标识符export来将变量写入脚本的命名空间。最终是对特定时间采取的特定指令。

@load base/frameworks/files
@load base/frameworks/notice
@load frameworks/files/hash-all-files

第一部分由@load命令组成,这些命令能够执行__load__.bro脚本。这些命令能够加载Files框架,Notice框架以及hash文件框架。

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;
}

第二部分定义了枚举常量,该常量描述了我们会从Notice框架产生的Notice类型。Bro允许重定义常量,这些常量都是非直观的。Notice类型允许使用NOTICE函数产生Match类型的Notice。Notice是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 "<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);
                }
            }
        }
    }
    
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)); 
}

首先这段脚本中提供了file_hash的事件处理器。file_hash事件允许脚本访问文件信息中的hash值,其中文件是f,hash函数是kind,hash值是hash。在file_hash事件处理函数中存在一个if语句用于检查hash函数类型和mime类型是否符合我们感兴趣的类型,随后调用do_mhr_lookup函数。其中when语句可以执行异步操作,而不影响性能。

事件队列与处理器

Bro的脚本是事件驱动的,它依赖于处理由Bro生成的事件,会修改事件的数据结构状态,根据信息做出决策。

Bro核心将事件放置到有序的事件队列中,允许处理器按先到先服务的原则处理他。事件和事件处理方式对于理解Bro以及脚本语言很关键。Bro产生的事件可以通过在线事件文档查阅。

连接记录数据类型

连接记录数据类型传递大量Bro定义的事件,连接记录本身是嵌套数据类型,用于追踪连接在其生命周期中的状态。由于Bro能够进行包层的处理,其强项在于源点和目的点之间的连接上下文。

数据类型和数据结构

Scope 域:Local和Global

要了解Bro的原生数据类型和数据结构得先掌握可用的scope和正确的使用时刻。Bro里有两种声明变量的方法(1. SCOPE name: TYPE 2. SCOPE name = EXPRESSION),变量声明的时候可以带也可以不带定义。若EXPRESSION的结果是TYPE类型的,那么两者效果相同。

全局变量

某脚本中的使用Global声明的变量能够在其他脚本中被访问。脚本使用module关键字会给脚本一个命名空间,我们必须更加关注全局变量的声明。当全局变量在有用命名空间的脚本中声明时,我们的变量将会有两个输出。其一是命名空间的输出,使用同样命名空间的脚本将可以访问声明的变量,而使用不同命名空间或无命名空间的将不能访问变量。若全局变量以export {...}声明,变量就可以被任何脚本以MODULE::variable_name的方式来访问了。

module关键字在脚本中使用时,就是说脚本中export出来的的变量被加入到了module后的命名空间中了。当全局变量未在模块内声明时,它可以按其名字来访问,而声明在模块内部的全局变量就必须导出且通过MODULE_NAME::VARIABLE_NAME来访问。

常量

Bro利用const关键字来定义常量。不同于全局变量的是,若使用了&redef,常量只能在解析时设置与修改。运行时,常量是无法改动的。重定义的常量是用作配置选项的容器。

局部变量

数据结构

基本数据类型

数据类型 描述
int 64位有符号整数
count 64位无符号整数
double 双精度浮点数
bool 布尔型
addr IPv4/v6地址
port 传输层端口
subnet 子网掩码
time 绝对时间
interval 时间间隔
pattern 正则表达式

sets

Sets用于存储统一数据类型不可重复的元素,是集合的概念。Sets的声明方式为SCOPE var_name : set[TYPE]。使用adddelete语句可以添加和删除集合中的元素。可以使用in操作符来遍历集合中的元素。

tables

Tables里是键值对的映射关系,键唯一而值不唯一。

Tables的键可以是由多个数据类型的变量,或者是名为'tuple‘的元素序列构成。Bro因表的复杂变得灵活,人们也为其灵活付出了巨大的代价。

vectors

向量为我们提供了以下标访问unsigned integer的数组的方法,然而他更加高效,且能够允许顺序访问。当我们需要连续存储相同类型的数据时,就可以使用向量。因为向量对其中元素采取连续存储的方法,对于向量中的元素我们就能以从0开始的偏移量来进行访问了。

声明一个向量需这么做SCOPE v: vector of T,其中v是变量名,T是数据类型。

使用for语句可以快速遍历向量。

for (i in vector)
    print vector[i] ;

详述基本数据类型

addr

地址类型。IPv4用默认的点分隔四段格式类型,IPv6则用方框包住了整个地址。

port

端口类型。<unsigned integer>/<protocol name>22/tcp,53/udp。Bro支持TCP,UDP,ICMP以及UNKNOWN作为支持的协议类型。ICMP实际并不具有实际的端口,BRO以ICMP消息类型和ICMP消息码的方式支持ICMP端口的概念。

端口号可以用==或者!=来进行比较或者排序,协议之间也有顺序,即unknown<tcp<udp<icmp,如65535/tcp就比0/udp小。

subnet

子网类型。Bro对CIDR标记的子网类型能够完全支持,这样我们就无需以两个独立的实体来管理IP以及子网掩码了。

在Bro脚本里,通常不需要任何网络分析,只需要使用address in subnet就可以对IP地址是否属于该子网进行判断,如10.0.0.1 in 10.0.0.0/8就会返回true。

time

我们不能自己创建time常量,但是可以通过两个函数network_timecurrent_time来返回一个time数据类型,这两个返回的time标准不同:current_time返回OS设置的墙上时间;而network_time返回的是在线数据流或离线数据包中处理的最后一个报文中的时间戳。这两个时间的表达形式都是以秒计数的,使用函数strftime可以将其转换成可阅读的形式,就像这样:strftime("%Y/%M/%d %H:%m:%S",network_time())。这里用到了时间戳的格式化字符串,具体有哪些格式化字符串和都代表啥意思,就得看我在justniffer使用手册里介绍的部分了。

interval

时间间隔,作为一个数据类型它代表着时间差,表示方法就是数值常量后跟着一个时间单位,如3 hr。Bro支持以下时间单位

符号 含义
usec 微秒
msec 毫秒
sec
min
hr
day

时间间隔变量中间的空格可有也可无。当然时间单位也分为单复数,虽然都可以表示。和上一句话的意思结合起来就是42hrs42 hr是一个意思,都对。时间间隔可为负数,- 10 mins表示10分钟之前。

在Bro里时间间隔可以加减乘除,也可以进行比较。两个time类型相减会返回一个时间间隔。

Pattern

正则表达式。Bro当然支持在脚本中采用正则表达式来对文本进行快速的搜索,我们生声明的正则表达式对象以两个/来包裹正则文本。常用的匹配方式是采用in操作符,如pattern in string

split函数则是通过匹配的正则表达式来划分字符串,输出一个表,和python里差不多。

正则表达式和字符串之间可以使用==!=来进行比较,当字符串完全与正则表达式匹配时,==返回true,否则就返回false,另一个操作符反之。

record数据类型

Bro支持多种数据类型和数据结构,当然也支持创建自定义的数据类型,这个数据类型由基本数据类型以及数据结构组成。这样的自定义类型采用type关键字来定义record数据类型,就像在c里用typedef和struct定义新的结构体。

声明一个record数据类型时,应采用一个类型描述名和几个独立的域。如:

type Service: record {
    name: string;
    ports: set[port];
    rfc: count;
};

当然record数据类型也可以进行嵌套,如:

type System: record {
    name: string;
    services: set[Service];
    };

type也可以用于给某数据结构改名,如type addr_set: set[addr];

定制日志

为了理解Bro中的数据类型和数据结构,最好的办法自然是阅读bro的框架。框架里最吸引人的是Logging框架。为了设计一个能够抽象创建文件并添加有序有组织的数据到这个文件中的过程,Logging框架使用潜在的命名系统。Log Streams,Filters和Writers用于维护高速进入的日志。

我们将基于脚本中的决策将数据写入Log Stream,其中的一条日志记录将以 名-值 对填充到其值域中。随后数据将被过滤,修改,重定向到Logging Filter。Filter能够用于截断日志文件或者复制信息到其他输出。数据的最终输出由Writer来定义,默认情况下日志输出是以tab来分割的ASCII文件,当然日志也支持DataSeriesElasticsearch(其writer还在开发中)。

看到这么多的新名词和新思路是不是有点晕了呢?是不是觉得Logging Framework很难用很难学呢?

其实按En Wiki的说法,Logging Framework的学习曲线不是很陡峭。Logging Framework的大部分脚本对于初学者来说不一定要全部读完。实际上,写入日志文件,定义数据格式,让Bro知道要创建一个新的日志,调用Log::write方法输出日志都很简单。

所以说日志框架的话,你照着他写的越多,自己写的越多,你就越会把他当成你的第二语言。

在Logging Framework中每个默认log stream都会产生一个自定义时间,该事件能够被任何想基于写入的数据做些事的人处理。这些事件是log_x的格式,其中x是logging stream的名字。例如由HTTP解析器发送给LoggingFramework的日志所引发的的事件都被称作log_http

我们可以在创建logging stream的时候对流进行配置,设置他可以引发的事件,事件一般以global log_x:event(var:Type);在export中声明。随后在其他脚本中使用以下脚本来处理事件。

event log_x( var : Type){}

这样就可以完成指定事件的处理了。具体应用到我们的流量事件触发处理操作还需进一步研究。

发出提醒

学习中

0x09 Bro IDS

学习中

0x0A 获取HTTP请求中的POST数据

我们想在输出的HTTP日志中添加一列,该列为HTTP请求中的POST数据。这样我们就在PREFIX/share/bro/site/local.bro中添加一部分脚本,用以实现该功能。添加的代码为:

@load base/protocols/http

module HTTP_PostBody;

export {
    ## The length of POST bodies to extract.
    const extract_length = 100 &redef;
}

redef record HTTP::Info += {
    post_body: string &log &optional;
};

event log_post_bodies(f: fa_file, data: string)
{
    for ( cid in f$conns )
        {
        local c: connection = f$conns[cid];
        if ( ! c$http?$post_body )
            c$http$post_body = "";

        c$http$post_body = c$http$post_body + data;
        if ( |c$http$post_body| > extract_length )
            {
            c$http$post_body = c$http$post_body[0:extract_length] + "...";
            Files::remove_analyzer(f, Files::ANALYZER_DATA_EVENT, [$stream_event=log_post_bodies]);
            }
        }
}

event file_over_new_connection(f: fa_file, c: connection, is_orig: bool)
{
    if ( is_orig && c?$http && c$http?$method && c$http$method == "POST" )
        {
        Files::add_analyzer(f, Files::ANALYZER_DATA_EVENT, [$stream_event=log_post_bodies]);
        }
}

如下图


0x0B Bro优缺点

优势

  1. 开源
  2. 有脚本能够对其输出的日志进行定制
  3. 存在控制台可以让系统作为守护进程,通过读取配置文件以及脚本完成日常的流量分析和日志记录工作
  4. 脚本由事件驱动,可以根据特殊的系统日志情况进行有针对性的操作
  5. 能够以分布式部署在多台机器上,对多个网络进行监控,并通过网络将数据集中到服务器,统一筛选查看

劣势

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

推荐阅读更多精彩内容