概要
はじめまして。イノベーションセンター所属の鄭(GitHub: nbhgytzheng)です。2021年入社し、現在はテクノロジー部門のOsecT-Ops プロジェクトに所属して、OsecTの開発・運用業務に取り組んでいます。
今回はOsecTで利用しているZeek(ネットワーク・セキュリティ・モニタリングツール)とZeekのプロトコル拡張ツールであるSpicy(Zeekで利用するC++のパーサーをC++で記述することなく簡易に生成できるツール)の概要及び使い方・利用例を紹介します。探した限り日本語での使い方の紹介記事がないため、今回が日本初紹介です。本記事の目標は、ZeekとSpicyを使って任意のプロトコルを含むトラフィック解析ができるようになることです。
OsecTとは
OsecTとは工場などの制御システム(OT; Operational Technology)のセキュリティリスクを可視化・検知するサービスです。 多様化する工場システムのセキュリティ脅威に対して、パケット解析するセンサー機器を設置するだけで、OTシステムへの影響なく、ネットワークの可視化と脅威・脆弱性検知ができます。早期にリスク感知できる状態を作り、工場停止による損失を未然に防ぐことができます。詳しくは過去のブログ記事に書いているので、興味がある人はぜひ見てください。(OsecTリリース・OsecT前編・OsecT後編)
Zeekとは
Zeekとはネットワークを監視し、トラフィックを解析して、IPアドレス、MACアドレスやプロトコルなどの情報をログとして出力するOSSです。Zeekは下記のような基本ログ(conn.log, dns.log, dhcp.logなど)を出力します。また必要に応じて、プラグインを利用することで出力ログの追加ができます。
# 生成のログの例:dns.log #separator \x09 #set_separator , #empty_field (empty) #unset_field - #path dns #open 2023-03-14-16-15-20 #fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p proto trans_id rtt query qclass qclass_name qtype qtype_name rcode rcode_name AA TC RD RA Z answers TTLs rejected pkts #types time string addr port addr port enum count interval string count string count string count string bool bool bool bool count vector[string] vector[interval] bool int 1539457598.376546 CHTBxA4O2ypY1PxYPg fd00:f81d:f5f:6b92:fd0d:9399:3d28:3984 5353 ff02::fb 5353 udp 0 - _ipp._tcp.local 1 C_INTERNET 12 PTR - - F F F F 0 - - F - 1539459646.378861 COMnQY2JaJZ3TllMYb fd00:f81d:f5f:6b92:fd0d:9399:3d28:3984 5353 ff02::fb 5353 udp 0 - _ipp._tcp.local 1 C_INTERNET 12 PTR - - F F F F 0 - - F - 1539463246.380625 Cgu5Zw1ASoGNZ2UGok fd00:f81d:f5f:6b92:fd0d:9399:3d28:3984 5353 ff02::fb 5353 udp 0 - _ipp._tcp.local 1 C_INTERNET 12 PTR - - F F F F 0 - - F - 1539466846.382677 C5X9v4snqSDPMj3u7 fd00:f81d:f5f:6b92:fd0d:9399:3d28:3984 5353 ff02::fb 5353 udp 0 - _ipp._tcp.local 1 C_INTERNET 12 PTR - - F F F F 0 - - F - ...
Spicyとは
プラグインを利用することで、出力ログを増やせますが、必要なログを出力するプラグインがないこともあります。例えば、以下の要件を満たすプラグインは存在しません。
- Zeekが対応しているプロトコルの出力ログ内に追加の情報を加えて出力したい場合
- Zeekが対応していないプロトコルの情報をログに出するしたい場合
ここで登場するのがSpicyです。SpciyとはZeekで利用するC++のパーサーをC++で記述することなく簡易に生成するためのツールです。Spicyを利用すれば、ログ情報の追加やZeek及びプラグインで解析できないプロトコルの解析ができるようになります。
Zeek・Spicyによるプロトコル解析
ここまではZeek, Spicyの概要について紹介しました。本章ではZeek, Spicyの利用方法について、環境構築からコーディングまで紹介します。
1. ZeekとSpicyのインストール
はじめに、Zeek, Spicyの実行環境を構築します。今回はUbuntu 22.04.1をベースに環境構築の方法を示します。その他のOSでのインストール方法は公式サイトを参照してください。
1.まずはZeekのリソース(GitHub)を取得するため、gitコマンドをインストールします。
~$ sudo apt install git
2.次にgitコマンドを利用して、Zeekのリソースをクローンします。
~$ git clone --recursive https://github.com/zeek/zeek -b v5.0.0
3.Zeekのリソースはcmakeコマンドを利用して自動インストールできるので、cmakeコマンドと関連パッケージをインストールします。
~$ sudo apt-get install cmake make gcc g++ flex libfl-dev bison libpcap-dev libssl-dev python3 python3-dev swig zlib1g-dev
4.最後にクローンしたZeekディレクトリへ移動し、以下のコマンドを実行すれば自動でインストールされます。makeは状況に応じて1時間以上かかる場合もあります。最後のmake installはスーパーユーザーで実行しないと、エラーになるため注意してください。
~$ ./configure --build-type=Release ...一部省略... ================================================================ -- Configuring done -- Generating done -- Build files have been written to: /home/xxx/zeek/build ~$ make ...一部省略... Consolidate compiler generated dependencies of target zeek-archiver make[3]: Leaving directory '/home/xxx/zeek/build' [100%] Built target zeek-archiver ~$ sudo make install ...一部省略... make[1]: Leaving directory '/home/xxx/zeek/build'
5.以上でZeekのインストールは完了です。以下のコマンドでZeekが利用できることを確認します。
# パス追加 ~$ export PATH="$PATH:/usr/local/zeek/bin" # zeekコマンド確認 ~$ zeek -version zeek version 5.0.0
6.Spicyのインストールは公式サイトでDEBファイルを取得し、以下のコマンドを順に実行すればインストールが完了です。
~$ sudo dpkg --install xxx.deb (例:spicy-dev.deb) Selecting previously unselected package spicy. (Reading database ... 212653 files and directories currently installed.) Preparing to unpack spicy-dev.deb ... Unpacking spicy (2.4.8) ... Setting up spicy (2.4.8) ...
7.インストールが完了したら、以下のコマンドでSpicyが利用できることを確認します。
# パス追加 ~$ export PATH="$PATH:/opt/spicy/bin/spicyc" # spicyコマンド確認 ~$ spicyz -version 1.3.16 ~$ spicyc -version spicyc v1.5.0 (d0bc6053)
以上でZeekとSpicyの実行環境の構築は完了です。
2. プロトコル仕様の確認
SpicyではIPベースのプロトコルだけでなく、イーサネットベースのプロトコルのパーサーも作成できます。今回はIPベースのUDPプロトコルを例に説明します。IPベースのプロトコル構造は以下のようになっています。IPベースでSpicyを利用する場合、Spicyが自動的に青い部分(イーサネットヘッダー、IPヘッダー、TCP/UDPヘッダー)を除き、オレンジ色の部分を処理します。
まず、ログに出力する内容を決めるために、解析対象のプロトコル仕様を確認します。プロトコル仕様はGoogleなどで検索して見つけられるケースもありますが、プロトコルに関わる各種団体・協会経由で仕様書を取得するケースもあります。プロトコル仕様からポート番号(今回の例では1111)と以下のようなパケットフォーマット定義を用意して、Spicyでのパーサー実装に進みます。
項目名/フィールド名 | サイズ(オクテット) | 内容 |
---|---|---|
header1 | 2 | プロトコルヘッダー |
header2 | 1 | プロトコルヘッダー |
command | 2 | リクエストのタイプを表す |
subCommand | 2 | 実行する動作を表す |
… | … | … |
次にパーサーで利用するSpicyファイル・evtファイル・Zeekファイルの作成方法について紹介します。
3. Spicyファイルを作成
本節では以下のテンプレートと前節で取得したプロトコル情報(ポート番号・パケットフォーマット定義)を利用したSpicyファイルの基本的な作成方法を紹介します。Spicyファイルでは、通信プロトコルのプロトコルフォーマットを定義します。
# spicyテンプレート module MYPROTOCOL; import zeek; import spicy; public type Message = unit { PROTOCOL_FIELD1: FIELD_TYPE &size=1; PROTOCOL_FIELD2: FIELD_TYPE &size=2; ... on %done { print self; zeek::confirm_protocol();} };
テンプレートの各部分を解説します。
module MYPROTOCOL;
:任意のモジュール名を宣言- 多くの場合、解析対象のプロトコル名を記述します。
import xxx
:Spicy関数の取り込み- 基本的には、Spicyの基本関数が入っている”spicy”とconn.logのservice列にプロトコル名を出力ための”zeek”が必要です。
public type Message = unit {}
:プロトコルのデータ部分を格納する変数- データ部分はこの
Message
に渡されます。
- データ部分はこの
PROTOCOL_FIELD1: FIELD_TYPE &size=1;
:データ部分をブロックごとに分解- 書き方はFIELD_TYPEによって異なるため公式サイトを参照してください。
on %done { print self; zeek::confirm_protocol();}
:Spicyが解析を終えたときに実行する関数print self;
:解析データの全てをprint(debug用)zeek::confirm_protocol();
:解析が終わったことをZeekに通知
そして、前節で取得したプロトコル情報を利用して、テンプレートを改造すると以下のようなSpicyファイルが作成できます。ここで、subCommand
までが必要なデータとした場合、最後にtmp
というPROTOCOL_FIELD
を追加で作成し、subCommand
以降のデータをtmpに入れることができます。
module protocol_name; # Spicyを単独で実行する場合は下の import zeek をコメントアウトする import zeek; import spicy; public type Message = unit { header1: bytes &size=2; header2: bytes &size=1; command: bytes &size=2; subCommand: bytes &size=2; tmp: bytes &eod; # Spicyを単独で実行する場合は zeek::confirm_protocol(); 部分をコメントアウトする on %done { print self; zeek::confirm_protocol();} };
実行すると、以下の出力が得られます。(Spicyのみ実行したい場合は上記のコードをコメントに従って変更する必要があります)
# 実行結果 ~$ printf '\x00\x00\x01\x02\x02\x03\x03\x11\x11\x11\x11\x11\x11\x11' | spicy-driver test.spicy [$header1=b"\x00\x00", $header2=b"\x01", $command=b"\x02\x02", $subCommand=b"\x03\x03", $tmp=b"\x11\x11\x11\x11\x11\x11\x11"]
4. evtファイルを作成
本節では以下のテンプレートとプロトコル情報(ポート番号・パケットフォーマット定義)・Spicyファイルを利用して、evtファイルの作成方法を紹介します。evtファイルでは解析したいプロトコルを指定します。
protocol analyzer spicy::MYPROTOCOL over UDP: parse with MYPROTOCOL::Message, port PORT_NUMBER/udp; import MYPROTOCOL; on MYPROTOCOL::Message -> event MYPROTOCOL::message($conn, self.PROTOCOL_FIELD1, ...);
例えば、以下のようにプロトコルのポート番号(1111)、プロトコル名(protocol_name)、Zeekに渡したいプロトコルフィールド(header1、header2など)を記述します。
protocol analyzer spicy::protocol_name over UDP: parse with protocol_name::Message, port 1111/udp; import protocol_name; on protocol_name::Message -> event protocol_name::message($conn, self.header1, self.header2, ...);
evtファイルを作成後、コンパイルしてオブジェクトファイルであるhtloファイルを生成します。
~$ spicyz -o test.hlto test.spicy test.evt ~$ # test.hltoが生成される
5. Zeekファイルを作成
最後に、以下のテンプレートを利用して、Zeekファイルの作成方法を紹介します。Zeekファイルでは、Spicyの処理結果をログに出力します。
# Spicy側のパーサーを利用するための宣言 module MYPROTOCOL; # 実行開始前に必要な変数の宣言(global変数) export { redef enum Log::ID += { LOG }; # この変数の内容はlogに書き出す type Info: record { ts: time &log; uid: string &log; id: conn_id &log; ## MYPROTOCOL data. PROTOCOL_FIELD1: string &log; ## MYPROTOCOL data. PROTOCOL_FIELD2: string &log; ## MYPROTOCOL data. PROTOCOL_FIELD3: string &log; ... ... final_block: count &optional; done: bool &default=F; }; global log_myprotocol: event(rec: Info); } global expected_data_conns: table[addr, port, addr] of Info; redef record connection += { myprotocol: Info &optional; }; # $pathで出力ファイル名を指定する event zeek_init() &priority=5 { Log::create_stream(MYPROTOCOL::LOG, [$columns = Info, $ev = log_myprotocol, $path="myprotocol"]); } # logに書き出す時の動作を書く関数 event MYPROTOCOL::message(c: connection, PROTOCOL_FIELD1: string, PROTOCOL_FIELD2: string, PROTOCOL_FIELD3: string, ... ) { local info: Info; info$ts = network_time(); info$uid = c$uid; info$id = c$id; info$PROTOCOL_FIELD1 = PROTOCOL_FIELD1; info$PROTOCOL_FIELD2 = PROTOCOL_FIELD2; info$PROTOCOL_FIELD3 = PROTOCOL_FIELD3; ... ... c$myprotocol = info; # info内の変数を全部logに書き出す Log::write(MYPROTOCOL::LOG, info); }
テンプレートの各部分を解説します。
module MYPROTOCOL;
:Spciyで作成したパーサーをインポートするために宣言export {…};
:Zeekで利用する変数の宣言- ログに出力したい項目はここで指定します。
event zeek_init() &priority=5 {…}
:出力ログのパスを指定event MYPROTOCOL::message(…) {…}
:パーサーの処理結果をログに出力- ログに出力するタイミングで加工することもできます。
上記のテンプレートと今までの結果を利用し、プロトコル名(protocol_name)、出力したいフィールド(header1など)、出力したいログファイルの名前($path="protocol_name")を書き換えることで、以下のようなZeekファイルを作成できます。
# Spicy側のパーサーを利用するための宣言 module protocol_name; # 実行開始前に必要な変数の宣言(global変数) export { redef enum Log::ID += { LOG }; # この変数の内容はlogに書き出す type Info: record { ts: time &log; uid: string &log; id: conn_id &log; ## MYPROTOCOL data. header1: string &log; ## MYPROTOCOL data. header2: string &log; final_block: count &optional; done: bool &default=F; }; global log_protocol_name: event(rec: Info); } global expected_data_conns: table[addr, port, addr] of Info; redef record connection += { protocol_name: Info &optional; }; # $pathで出力ファイル名を指定する event zeek_init() &priority=5 { Log::create_stream(protocol_name::LOG, [$columns = Info, $ev = log_protocol_name, $path="protocol_name"]); } # logに書き出す時の動作を書く関数 event protocol_name::message(c: connection, header1: string, header2: string ) { local info: Info; info$ts = network_time(); info$uid = c$uid; info$id = c$id; info$header1 = header1; info$header2 = header2; c$protocol_name = info; # info内の変数を全部logに書き出す Log::write(protocol_name::LOG, info); }
6. テスト用Pcapで出力ログの確認
最後にテスト用Pcapを使って出力ログを確認します。
~$ zeek -Cr cclink_ief_basic.pcap test.hlto test.zeek ~$ # protocol_name.logが生成される
生成されたログの中身を確認すると、以下のようになっています。最後の16進数の部分(P\x00 \x00
など)は今回出力したい部分です。今回は16進数のデータを出力しただけですが、Zeekファイルを作り込むことで人間が解釈できる文字列として出力もできます。
#separator \x09 #set_separator , #empty_field (empty) #unset_field - #path protocol_name #open 2023-06-12-15-53-06 #fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p header1 header2 #types time string addr port addr port string string 1655284124.859924 C3KZkP3l8QTJaS2vg7 172.16.134.128 61450 172.16.134.255 61450 P\x00 \x00 1655284124.953994 CG3NWX3t9Qf1Xrezl5 172.16.134.129 61450 172.16.134.128 61450 \xd0\x00 \x00 1655284125.375608 C3KZkP3l8QTJaS2vg7 172.16.134.128 61450 172.16.134.255 61450 P\x00 \x00 1655284125.484180 CG3NWX3t9Qf1Xrezl5 172.16.134.129 61450 172.16.134.128 61450 \xd0\x00 \x00 1655284125.500485 C3KZkP3l8QTJaS2vg7 172.16.134.128 61450 172.16.134.255 61450 P\x00 \x00 1655284125.610011 CG3NWX3t9Qf1Xrezl5 172.16.134.129 61450 172.16.134.128 61450 \xd0\x00 \x00 ... ...
Zeek・Spicyの活用例
ここまで、Zeek, Spicyの使い方まで紹介しました。本章ではイメージを深めるため、OsecTで実装したZeekが対応していないプロトコルの追加と、Zeekの基本ログへの情報追加の2つの例を紹介します。
CC-Linkファミリーへの対応
今回は市場ニーズを踏まえた上で、Zeekが対応していないOTプロトコル(CC-Linkファミリー)に対応しました。具体的なプロトコルはCC-Link IE FieldとCC-Link IE ControlとCC-Link IE Field Basicです。実装したコードは公開しているため、興味がある方は以下のリンクを参照ください。
CC-Link IE Field Basicの出力ログは以下のようになっています。ペイロードの奥深くまで情報を取得することもできますが、今回はパケットの種類(cyclicDataRes、cyclicDataReq)と使っているコマンド(cyclic)をログに出力しました。
#separator \x09 #set_separator , #empty_field (empty) #unset_field - #path cclink-ief-basic #open 2023-05-27-00-52-06 #fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p pdu cmd number ts_end #types time string addr port addr port string string int time 1655284124.953994 CIAp8bugKIZRVpAYk 172.16.134.129 61450 172.16.134.128 61450 cyclicDataRes - 222 1655284149.499713 1655284124.859924 Ckkc3929guO41BnpSa 172.16.134.128 61450 172.16.134.255 61450 cyclicDataReq cyclic 222 1655284149.392238 #close 2023-05-27-00-52-06
CC-Link IE FieldとCC-Link IE Controlの出力ログは以下のようになっています。ここではパケットの種類(select、scanなど)と種類別の情報(0x0001など)をログに出力しました。今回はテスト用Pcapを自作したため、全て0x0001
になっています。
#separator \x09 #set_separator , #empty_field (empty) #unset_field - #path cclink-ie #open 2023-03-15-16-56-36 #fields ts src_mac dst_mac service pdu_type cmd node_type node_id connection_info src_node_number number ts_end #types time string string string string string string int string string int time 1667903833.066101 00:11:11:11:11:11 00:00:00:00:00:01 cclink_ie_control select - - - - 0x0001 61 1667903833.134207 1667903833.065821 00:11:11:11:11:11 00:00:00:00:00:01 cclink_ie_control scan - - - - 0x0001 48 1667903833.129023 1667903833.064742 00:11:11:11:11:11 00:00:00:00:00:01 cclink_ie_control connectAck - - - - 0x0001 61 1667903833.133590 1667903833.065511 00:11:11:11:11:11 00:00:00:00:00:01 cclink_ie_control connect - - - - 0x0001 62 1667903833.134085 1667903833.067818 00:11:11:11:11:11 00:00:00:00:00:01 cclink_ie_control nTNTest - - - - 0x0001 53 1667903833.131018 1667903833.068939 00:11:11:11:11:11 00:00:00:00:00:01 cclink_ie_control dummy - - - - 0x0001 57 1667903833.133957 1667903833.065083 00:11:11:11:11:11 00:00:00:00:00:01 cclink_ie_control collect - - - - 0x0001 61 1667903833.133231 1667903833.064936 00:11:11:11:11:11 00:00:00:00:00:01 cclink_ie_control launch - - - - 0x0001 40 1667903833.132990 1667903833.066240 00:11:11:11:11:11 00:00:00:00:00:01 cclink_ie_control token - - - - 0x0001 57 1667903833.133351 #close 2023-03-15-16-56-36
Zeekの基本ログへの情報追加
DHCPv4はZeekが対応しているプロトコルですが、出力されるログの情報量に過不足がありました。そのため、Zeek, Spicyを利用して必要のない情報を削除・必要な情報を追加しました。実装したコードは公開しているため、興味がある方はMYDHCPを参照ください。
デフォルトの出力ログは以下のようになっています。
#separator \x09 #set_separator , #empty_field (empty) #unset_field - #path dhcp #open 2023-06-16-18-23-59 #fields ts uids client_addr server_addr mac host_name client_fqdn domain requested_addr assigned_addr lease_time client_message server_message msg_types duration #types time set[string] addr addr string string string string addr addr interval string string vector[string] interval 1624301983.013489 Cah1vz3icJOQ3kUtk4,C6uWeo2ldOmwsjKUj7 10.0.2.15 10.0.2.2 00:00:20:b0:60:b0 kali - - 10.0.2.15 10.0.2.15 86400.000000 - - REQUEST,ACK 0.000309 #close 2023-06-16-18-23-59
そして、Zeek, Spicyによって改造後のログは以下のように変更しました。本来のログのMACアドレスとタイムスタンプをそのまま利用し、SrcIP,Hostname,Parameter,ListClassId
を追加しました。
#separator \x09 #set_separator , #empty_field (empty) #unset_field - #path mydhcp #open 2023-06-16-18-23-59 #fields ts SrcIP SrcMAC Hostname ParameterList ClassId #types time addr string string vector[count] string 1624301983.013489 0.0.0.0 00:00:20:b0:60:b0 kali 1,2,6,12,15,26,28,121,3,33,40,41,42,119,249,252,17 - #close 2023-06-16-18-23-59
おわりに
今回はZeekとSpicyの概要及び使い方・利用例について紹介しました。この記事を参考に、プロトコルのパーサーを実装、ログ出力までできるようになれば幸いです。そして、Zeek, Spicyの活用例の章で紹介したスクリプトはすでにOsecTへ実装しリリース済です。OsecTもこれからZeek, Spicyを利用し、対応プロトコルの拡張などを続けていくので、興味を持たれた方はぜひご連絡ください。