DARK MATTER

CDI Engineer's Technical Blog

WiresharkのDissectorを使った独自プロトコル解析をやさしく解説してみました


本稿では、基本的なDissectorの作り方と、Dissectorを活用したパケット解析方法を紹介します。

WiresharkのDissectorをご存知でしょうか?DissectorはWiresharkのプロトコル解析部分で、バイト列を人が理解できる内容に変換し表示してくれます。
Wiresharkを使った事がある方なら、独自プロトコルのバイト列を人が理解できる表示にできないかなぁと思った経験があると思います。
Dissectorを自作しPluginとして追加すると独自プロトコル解析が容易になります。

なぜ今Dissectorを紹介するの?

技術部の安井です。 長年、制御システムを開発した経験から、現在は制御システムセキュリティを見ています。

現在、世の中の多くのプロトコルに対応したDissectorがWiresharkに搭載されています。しかし、制御システムやIoT機器など独自プロトコルのネットワーク通信は数多く存在しており、Dissectorの存在を知らずバイト列と格闘している方が大勢いるようです。

Dissectorの作り方の記述は、ネット上で見つかりますが、コーディングできない人にとっては、少しハードルが高く感じるかもしれません。また、Dissectorをフィルタやグラフと対応させて、解析にうまく活用できていない人もいるかもしれません。そのような方たちに、Dissectorの自作は簡単であり、うまく活用すると解析がとても容易になることを紹介したいと考えた次第です。

対象読者

Wiresharkを利用したことがあり独自プロトコル解析を行いたい人。
"Wireshark" "独自プロトコル" "Dissector" "lua" などを検索して調べたが、プログラムが理解できず諦めてしまった人。
Dissectorを作ってみたが、いざパケット解析しようとしたときに、表示フィルタ、表の列、入出力グラフと連動させて使いこなせていない人。

準備するもの

Wiresharkをインストール済みのPC

できるようになること

独自プロトコル解析するためのDissector作成
Dissectorコーディング内容がWiresharkの表示のどこに関連づくかの対応理解。
DIssectorを活用した解析(表示フィルタ、表への列追加、入出力グラフの表示)

記載していないこと

Wiresharkのインストール方法、Wiresharkの基本的な使い方、Luaが無効となっているWiresharkでLuaを有効とする方法。
効率的なプログラミング( 本稿は、Dissectorの理解に特化するため、変数を極力使わない内容としてあります。 )

Dissectorを入れる前と後での表示の違い

プログラムの説明の前に、自作したDissectorを使うと、どのように解析が容易になるかを説明します。説明に使用する独自プロトコルのデータは、0x00000008 576f6f6f というバイト列とします。

標準Wiresharkでの表示

図1に、自作したDissectorを入れていない標準状態のWiresharkでキャプチャした結果を示します。

f:id:yasuikj:20200313120828p:plain
図1:独自プロトコルのデータと標準Wiresharkでの表示
tree部分に、「00000008576f6f6f」 というbyte列が表示されています。
このようなbyte列を見ると、多くの人が「tree 部分に人が理解できる内容で表示をしてほしい」、「表の列にも表示したい」、「表示した内容でフィルタをかけて表示したい」と思うでしょう。

自作したDissectorを入れたWiresharkでの表示

図2に、自作したDissectorを入れたWiresharkでキャプチャした結果を示します。

f:id:yasuikj:20200313121425p:plain
図2:独自プロトコルデータとDissectorを入れたWiresharkでの表示
tree部分に、「独自プロトコル」、「独自プロトコルのサイズ:8」、「独自プロトコルの文字列:Wooo」 と表示されています。
pinfo部分に、「独自プロトコルのサイズ」「独自プロトコルの文字列」という列が追加され、「8」「Wooo」と表示されています。

treeとpinfoに表示されているので、解析が容易そうですね。

表示フィルタ、グラフ、統計表示

通信データの解析をする際は、表示フィルタ、統計(入出力グラフ)、統計(プロトコル階層)などを使うと便利です。

f:id:yasuikj:20200313121600p:plain
図3:"Wooo"でフィルタ、統計、グラフ表示
図3は、treeに登録した「独自プロトコルの文字列:Wooo」で表示フィルタをかけ、表示フィルタで絞り込んだ状態の「統計」ー「プロトコル階層」を表示した例です。右下部には、「統計」ー「入出力グラフ」を表示し、「独自プロトコルの文字列:Wooo」で表示フィルタをかけたグラフを表示しています。

このように、フィルタ、グラフなどを使うと、更に解析が容易になります。

Dissectorプログラムと登録手順

ここでは、前項までに紹介した表示を可能とするDissectorのプログラムと、DissectorプログラムをWiresharkに登録する方法を説明します。

Dissectorプログラム

Dissectorプログラム(sample_begginer.lua)は以下のとおりです。 -- で始まる行はコメント部分であり、実質11行のプログラムです。

-- sample_begginer.lua の概要説明
-- 解析対象とする独自プロトコルの説明:
-- 独自プロトコルのプロトコルとポート番号
--   TCP 3333
-- 独自プロトコルの名称
--   original
-- 独自プロトコルの構造
--  ------------------------------------------------------------------
--  | oritinal_size(4byte符号なし整数)) | original_data(4byte文字列) |
--  | 独自プロトコルのサイズ      |独自プロトコルの文字列     |
--  ------------------------------------------------------------------
--
-- Dissectorの構造
--  Dissectorテーブル     プロトコル      プロトコルフィールド
--  [DissectorTable:tcp.port]
--                     |---[Proto:original]
--                                  |---[ProtoField:original.original_size]
--                                  |---[ProtoField:original.original_data]
-- DissectroのTree構造
--      [tcp:Transmission Control Protocol]
--      [oritinal:独自プロトコル]
--              |---[original.original_size:独自プロトコルのサイズ]
--              |---[original.original_data:独自プロトコルの文字列]
-- sample_begginer.lua プログラム
-- プロトコルの定義
--    引数: Proto(A,B)
--          A:プロトコルの名称, B:プロトコルの概要
proto = Proto("original","独自プロトコル")

-- プロトコルフィールド定義
--  引数:  ProtoField.new(A, B, C)
--          A:プロトコルフィールドの名称(ツリーに表示される), B:フィルタ名, C:型式
--  概要: 独自フィールドのフォーマットを定義する。プロトコルフィールド定義したものはフィルタ対象にできる。
--  注意: プロトコルフィールド定義していないとフィルタ対象にできないため、フィルタ対象としたいものは、ここに登録する
original_size_F = ProtoField.new("独自プロトコルのサイズ","original.original_size",ftypes.UINT32)  -- 型式はuint32 4byte符号なし整数で定義
original_data_F = ProtoField.new("独自プロトコルの文字列","original.original_data",ftypes.STRING)  -- 型式はstring 文字列で定義
-- プロトコルフィールド定義をプロトコルフィールド配列へ登録
--  概要:  フィルタ可能対象、pinfoの列表示可能対象としたいプロトコルフィールド定義をフィールド配列へ登録する。
--  注意:  プロトコルフィールド配列に登録していないフィールドは、フィルタ対象とできない。フィルタ対象としたいものは、ここに登録する
proto.fields = {original_size_F, original_data_F}
-- Dissector
--  引数:   dissector(buffer,pinfo,tree)
--          buffer: パケット全体イメージのうち、独自プロトコル以降のデータが格納されている。
--          pinfo : パケットインフォメーション情報
--          tree  : パケット詳細部の情報
function proto.dissector(buffer, pinfo, tree)
    -- パケットインフォメーション情報のprotocolヘッダに表示する名称を設定
    pinfo.cols.protocol = "ORIGINAL"

    -- パケット詳細部のツリーの登録
    --   概要:パケット詳細部のツリーに表示する内容を定義する。ツリーは階層化可能。
    --   引数:
    --    *:add ( A, B, C )
    --       A:プロトコルフィールド定義, B:バッファ範囲, C:ツリー表示内容   (A,B,Cとも省略可能)
    --       A,B 両方を指定すると、AのフォーマットでBの内容がツリーに表示される。(本サンプルでは、この方式を使用)
    --       (参考)
    --       Aを指定していると、フイルタ指定、カラム登録が可能となる。
    --       Bを指定していると、ツリーを選択した際に、該当データbyte部分を反転表示するので対象範囲が見やすくなる。
    --       Cを指定していると、Bの内容に関わらずCの内容がツリーに表示される。(本サンプルでは未使用)

    -- ツリーに、サブツリーとしてproto(original)を追加。 bufferをprotoのフォーマット(独自プロトコル)で表示
    local subtree = tree:add(proto, buffer())
    -- proto(original)ツリーに original_size_Fを追加。bufferの0byte目から4byte分をoriginal_size_Fのフォーマットで表示
    subtree:add(original_size_F, buffer(0,4))
    -- proto(original)ツリーに original_data_Fを追加。bufferの4byte目から4byte分をoriginal_data_Fのフォーマットで表示
    subtree:add(original_data_F, buffer(4,4))
end
--  定義したプロトコル(Proto:original)をTCPポート番号を指定して既存のTCPのDissectorに紐づける
tcp_table = DissectorTable.get("tcp.port")   -- 既存のTCP dissector
tcp_table:add(3333, proto)                   -- 既存のTCP dissectorに、ポート番号3333を指定proto(Proto:original)を紐付け

プログラムコードとWireshark画面の表示項目の関係

図4に、プログラムのコードとWiresharkの表示項目の関係を示します。これを見ながらソースコードを読むとソースコードの内容が理解しやすいと思います。

f:id:yasuikj:20200313115244p:plain
図4:プログラムソースとWireshark画面の表示項目の関係

Dissectorプログラムの登録手順

図5を参考に、sample_begginer.lua プログラムを、[Wireshark]-[About Wireshark]-[フォルダ]で表示する「個人Luaプラグイン」もしくは「グローバルLuaプラグイン」のパスへ、sample__begginer.luaという名称で保存してください。

f:id:yasuikj:20200311194953p:plain
図5:Dissectorの登録場所
保存した後、Wiresharkを起動しなおすか、[分析]-[Luaプラグイン再読込]でDissectorが有効となります。

Dissectorを利用した解析

ここでは、Dissectorを利用した解析を行う際に知っておくと便利である表へ列を追加する手順、表示フィルタをかける手順、および、入出力グラフでフィルタをかける手順を説明します。

表へ列を追加する手順

図6に示すように、tree に表示した「独自プロトコルのサイズ」を右クリック - [列として適用] すると、

f:id:yasuikj:20200312181733p:plain
図6:表示項目を列として適用

図7に示すように、表に、「独自プロトコルのサイズ」の列が追加されます。列のヘッダ部分を右クリックすると、列の一覧が表示するので表示項目の選択が可能です。

f:id:yasuikj:20200312182046p:plain
図7:列に表示する項目の選択

表示フィルタをかける手順

図8に示すように、tree に表示した「独自プロトコルの文字列」を右クリック - [フィルタとして適用] - [選択済]を選択( 必要に応じて好きな項目を選ぶ )すると、表示フィルタ部分に、 original.original_data == "Wooo" と表示し、pinfo部分がフィルタされた内容で表示されます。

f:id:yasuikj:20200312155358p:plain
図8:選択した項目をフィルタとして適用

[ご参考] 自作のDissectorを使わない場合、TCPのパケットであれば、表示フィルタ部に、 tcp.payload contains "Wooo" と入力しても同じような表示は可能です。Dissectorがない場合の参考としてください。

入出力グラフ上でフィルタをかける手順

[統計]-[入出力グラフ] で入出力グラフを表示し、[+]を選択しフィルタを追加してください。Display Fileter 部分に、 表示フィルタに記載する書式と同一書式でoriginal.original_data == "Wooo" と記載することでフィルタ定義を行えます。

これ以上は、Wiresharkの解説になるので、ここでやめておきます。Wiresharkは他にも便利な機能があり、解説したサイトもたくさんあるのでぜひいろいろ試してみてください。

まとめ

本稿では、基本的なDissectorの作り方と、Dissectorを活用したパケット解析方法を紹介しました。

Dissectorはプログラム経験のない方でも比較的容易に作れ、Dissectorを使うことで解析が格段に容易になることを理解いただけたでしょうか。

本稿が独自プロトコルのバイト列と格闘している人達の助けになれば幸いです。

なお、本稿では、 符号なし整数、文字列を題材に説明をしましたが、制御システムのパケットを解析するには、Bit列の解析、エンディアン(Endian)の解析、時刻の解析などもDissectorで解析したい場合があります。これらについてはこちら↓で公開しています。
io.cyberdefense.jp


株式会社サイバーディフェンス研究所 / Cyber Defense Institute Inc.