Wireshark Lua徹底紹介:パケット解析を効率化
はじめに
ネットワーク通信の解析は、システム開発、トラブルシューティング、セキュリティ監視など、IT分野の様々な場面で不可欠な作業です。その中心的なツールとして、世界中のネットワークエンジニアやセキュリティ専門家から絶大な信頼を得ているのが「Wireshark」です。Wiresharkは、ネットワーク上を流れるパケットをキャプチャし、様々なプロトコルに基づいてその内容を詳細に表示することができます。
しかし、標準のWireshark機能だけでは対応できないケースも少なくありません。例えば、社内独自のプロトコルや、IoTデバイス特有の非標準な通信、あるいは特定のアプリケーションのバイナリプロトコルなど、Wiresharkがデフォルトで解析機能(ディセクタ)を持たないプロトコルに出くわすことがあります。また、既存のプロトコル解析結果に対して、特定の条件を満たすパケットに印をつけたり、複雑な統計情報を集計したりといった、より高度なカスタマイズや自動化を行いたい場合もあります。
このような「標準機能だけでは物足りない」というニーズに応えるために、Wiresharkは強力な拡張機構を提供しています。その一つが、軽量なスクリプト言語である「Lua」を使ったスクリプト機能です。Wireshark Luaスクリプトを利用することで、ユーザーは独自のプロトコルディセクタを作成したり、既存のディセクタの動作をカスタマイズしたり、統計情報を収集するタッパーを作成したりと、Wiresharkの機能を柔軟に拡張・変更することが可能になります。これにより、今まで手作業で行っていた煩雑な作業を自動化し、パケット解析作業を劇的に効率化することができます。
この記事では、Wireshark Luaスクリプトの基本から応用までを徹底的に解説します。Lua言語の基礎、Wiresharkでのスクリプトのセットアップ方法、カスタムプロトコルディセクタの作成、既存機能の拡張、デバッグ手法、そして様々な応用例を通して、Wireshark Luaがどのようにパケット解析を効率化し、より深い洞察をもたらすのかを詳細に説明していきます。この記事を読み終える頃には、あなたもWireshark Luaを活用して、独自の解析ニーズに応える強力なツールを開発できるようになっているでしょう。
WiresharkとLuaの基礎
Wireshark Luaスクリプトの世界に飛び込む前に、まずはWiresharkの基本的な操作と、WiresharkがなぜLuaを採用しているのかについて簡単に触れておきましょう。
Wiresharkの基本的な使い方
Wiresharkの基本的なパケット解析フローは以下の通りです。
- インターフェースの選択とキャプチャの開始: 解析したいネットワークインターフェース(有線LANアダプター、Wi-Fiアダプターなど)を選択し、パケットキャプチャを開始します。
- パケットの取得: ネットワーク上を流れるパケットがWiresharkによって収集されます。
- キャプチャの停止と保存: 目的のパケットが収集できたらキャプチャを停止し、パケットデータをファイル(
.pcap
または.pcapng
形式)に保存します。 - ファイルのオープン: 保存したキャプチャファイルを開きます。
- パケットリスト: 画面上部に、キャプチャされたパケットが一覧表示されます。各行には、パケット番号、タイムスタンプ、送信元IPアドレス、宛先IPアドレス、プロトコル名、長さ、概要などが表示されます。
- パケット詳細: パケットリストで選択したパケットの詳細情報が、画面中央にツリー構造で表示されます。イーサネットヘッダー、IPヘッダー、TCP/UDPヘッダー、そしてアプリケーション層のデータなど、各プロトコルレイヤーの情報が階層的に表示されます。この解析プロセスは「ディセクト」(Dissect)と呼ばれ、Wiresharkのディセクタ(Dissector)が行います。
- パケットバイト: 画面下部には、選択したパケットの生データ(バイト列)が16進数とASCII文字で表示されます。
- フィルタリング: 膨大なパケットの中から目的のパケットだけを表示するために、表示フィルタリング(Display Filter)を使用します。プロトコル名、IPアドレス、ポート番号、特定のフィールドの値など、様々な条件でフィルタリングが可能です(例:
tcp.port == 80 and ip.src == 192.168.1.1
)。 - 解析: フィルタリングされたパケットリストや詳細情報を見ながら、通信の内容を理解し、問題の原因特定やセキュリティ分析などを行います。
Luaスクリプトは、主に上記の5, 6, 8のステップ、特にパケット詳細(ディセクト)の部分をカスタマイズしたり、パケットリストや詳細情報から特定の情報を抽出・集計するために使用されます。
Lua言語の概要
Luaは、軽量で、組み込みや拡張用途に設計されたスクリプト言語です。以下のような特徴を持ちます。
- 軽量・高速: 実行環境が非常に小さく、動作も高速です。
- 組み込み向き: 他のアプリケーションに簡単に組み込むことができるように設計されています。C言語で書かれたホストアプリケーションとの連携が容易です。
- 習得容易: 文法がシンプルで、PythonやJavaScriptなど他のスクリプト言語の経験があれば比較的容易に習得できます。
- 強力なデータ構造: テーブルという柔軟なデータ構造を持ち、配列、ハッシュテーブル、オブジェクトなどを表現できます。
WiresharkはC言語で書かれていますが、Luaをスクリプトエンジンとして組み込むことで、C言語でディセクタや拡張機能を開発するよりもはるかに容易かつ安全に、ユーザーがWiresharkの機能を拡張できるようにしました。C言語での開発はコンパイルが必要で、開発サイクルが長くなりがちですが、Luaはスクリプトなのでテキストエディタで修正後、Wiresharkに読み込ませるだけですぐに試すことができます。また、Luaのサンドボックス的な性質により、スクリプトの不具合がWireshark本体をクラッシュさせるリスクもC言語アドオンに比べて低くなります(ただし、無限ループなどによるフリーズのリスクはあります)。
WiresharkがLuaを採用している理由
まとめると、WiresharkがLuaを採用している主な理由は以下の通りです。
- 拡張性の高さ: 独自のプロトコル解析や機能追加を可能にします。
- 開発の容易さ: C言語開発に比べて習得・開発が容易で、イテレーションサイクルが短いです。
- 安全性の相対的な高さ: スクリプトエラーが本体のクラッシュに直結しにくいです。
- パフォーマンス: 軽量かつ高速なため、パケット処理というパフォーマンスが求められるタスクにも適しています。
- アクティブなコミュニティ: Lua自体にユーザーが多く、情報が入手しやすいです。
これらの理由から、LuaはWiresharkの強力な拡張メカニズムとして採用され、ユーザーが独自のニーズに合わせてWiresharkをカスタマイズするための主要な手段となっています。
WiresharkでのLuaスクリプトのセットアップ
WiresharkでLuaスクリプトを実行するには、まずスクリプトファイルを作成し、Wiresharkがそれを読み込める場所に配置する必要があります。
Luaスクリプトファイルの配置場所
Wiresharkは、特定のディレクトリに配置されたLuaスクリプトファイルを自動的に読み込むか、あるいはinit.lua
という特別なファイルに記述された指示に基づいてスクリプトを読み込みます。スクリプトを配置できる場所は主に2つあります。
- 個人の設定ディレクトリ: ユーザー固有の設定が保存される場所です。OSによってパスが異なります。
- Windows:
%APPDATA%\Wireshark\plugins\lua\
(例:C:\Users\YourUsername\AppData\Roaming\Wireshark\plugins\lua\
) - Linux/macOS:
~/.local/lib/wireshark/plugins/lua/
または~/.wireshark/plugins/lua/
- このパスは、Wiresharkのメニューから
Help
->About Wireshark
->Folders
タブで確認できます。Personal Lua Plugins
という項目に表示されているパスを使用します。
- Windows:
- グローバル設定ディレクトリ: Wiresharkのインストールディレクトリ内の共有設定場所です。
- この場所はシステム全体で共有されますが、管理者権限が必要な場合があります。パスは
Global Lua Plugins
という項目で確認できます。
- この場所はシステム全体で共有されますが、管理者権限が必要な場合があります。パスは
通常、個人用の設定ディレクトリにスクリプトを配置するのが最も手軽でおすすめです。このディレクトリ内に、作成したLuaスクリプトファイル(拡張子は.lua
)を配置します。
Wiresharkでのスクリプト読み込み設定
Wiresharkが起動時にどのLuaスクリプトを読み込むかは、いくつか方法があります。
-
init.lua
ファイルによる指定:
個人の設定ディレクトリ、またはグローバル設定ディレクトリの直下にinit.lua
という名前のファイルを作成します。このファイルはWireshark起動時に自動的に読み込まれます。
init.lua
の中で、dofile()
関数を使って他のLuaスクリプトファイルを読み込むのが一般的な方法です。例えば、my_dissector.lua
というスクリプトを読み込みたい場合は、init.lua
に以下のように記述します。lua
-- init.lua
print("Loading my_dissector.lua...")
dofile(DATA_DIR .. "/plugins/lua/my_dissector.lua") -- DATA_DIRは設定ディレクトリのパスを表すグローバル変数
-- あるいは、同じディレクトリにあるなら単にファイル名でもOK
-- dofile("my_dissector.lua")
print("my_dissector.lua loaded.")DATA_DIR
はWireshark Lua環境で定義されているグローバル変数で、個人の設定ディレクトリのパスを指します。これを使うと、スクリプトの場所を気にせず読み込めます。 -
コマンドラインオプションによる指定:
WiresharkやTSharkを起動する際に、-X lua_script:<script_file>
オプションを使って特定のスクリプトファイルを読み込ませることもできます。複数のスクリプトを指定する場合は、オプションを複数回指定します。bash
wireshark -X lua_script:/path/to/my_dissector.lua
tshark -X lua_script:my_script1.lua -X lua_script:my_script2.lua -r capture.pcapこの方法は、特定の解析タスクのために一時的にスクリプトを使いたい場合などに便利です。
簡単なテストスクリプトの作成と実行
それでは、簡単なLuaスクリプトを作成して、WiresharkがLuaスクリプトを正しく読み込めるか確認してみましょう。
-
スクリプトファイルの作成: テキストエディタを開き、以下の内容を記述して
hello_wireshark.lua
という名前で保存します。“`lua
— hello_wireshark.lua
— This script prints a message when loaded by Wireshark.print(“Hello from Wireshark Lua!”)
— Optionally, add a simple dissector placeholder to see it load
local my_proto = Proto.new(“myproto”, “My Test Protocol”)function my_proto.dissector(tvb, pinfo, tree)
— This is a placeholder dissector function
— It doesn’t do anything yet
endmy_proto:register()
print(“Test protocol ‘My Test Protocol’ registered.”)
“`このスクリプトは、読み込まれたときにメッセージを表示し、”My Test Protocol” という名前の新しい(何もしない)プロトコルを登録します。
-
ファイルの配置: 作成した
hello_wireshark.lua
ファイルを、Wiresharkの個人の設定ディレクトリ内のplugins/lua/
ディレクトリに配置します。(例: Windowsなら%APPDATA%\Wireshark\plugins\lua\
) -
init.lua
の編集: もし個人の設定ディレクトリにinit.lua
がなければ新規作成し、既に存在する場合は編集して、以下の行を追加します。lua
dofile(DATA_DIR .. "/plugins/lua/hello_wireshark.lua") -
Wiresharkの起動と確認: Wiresharkを起動します。
- Lua Console: Wiresharkのメニューから
Analyze
->Lua
->Lua Console
を開きます。ここに"Hello from Wireshark Lua!"
と"Test protocol 'My Test Protocol' registered."
のメッセージが表示されていれば、スクリプトは正しく読み込まれています。 - プロトコルリスト:
Analyze
->Enabled Protocols...
を開き、リストの中にMy Test Protocol
(省略名myproto
) が表示されているか確認します。これも表示されていれば、プロトコルの登録も成功しています。
- Lua Console: Wiresharkのメニューから
これらの手順で、WiresharkがLuaスクリプトを認識し、実行する基本的な環境が整いました。次からは、実際にパケットを解析するスクリプトの書き方に入っていきます。
Wireshark Luaスクリプトの基本構造
Wireshark Luaスクリプトの主な目的は、Wiresharkの特定の処理(例えばパケットのディセクト)が実行されるタイミングで、事前に定義したLua関数を呼び出してもらうことです。そのために、Wireshark Lua APIが提供するオブジェクトを使って、カスタムの機能(プロトコル、タッパーなど)を定義し、Wiresharkに登録します。
Luaスクリプトの基本的な構成要素と、Wireshark Lua APIへのアクセス方法を見ていきましょう。
Luaスクリプトの基本的な構文
Luaの基本的な構文はシンプルです。
- コメント:
--
から行末までがコメントです。複数行コメントは--[[ ... ]]--
を使います。 - 変数: 変数宣言にキーワードは不要です。ローカル変数は
local
キーワードをつけます。グローバル変数はlocal
をつけなければ自動的にグローバルになりますが、予期せぬ名前空間の衝突を避けるため、特別な理由がない限りlocal
を使うのが推奨されます。
lua
local my_variable = 10
global_variable = "hello" -- 非推奨 - 関数:
function
キーワードで定義します。
lua
function my_function(arg1, arg2)
local result = arg1 + arg2
return result
end -
制御構造:
if
,for
,while
,repeat...until
などがあります。
“`lua
if condition then
— code
elseif other_condition then
— code
else
— code
endfor i = 1, 5 do
— code
endwhile condition do
— code
end
* **テーブル:** Luaの最も重要なデータ構造です。波括弧 `{}` を使って作成します。配列としても、キーと値のペア(ハッシュテーブル/連想配列)としても使えます。
lua
local my_array = {“a”, “b”, “c”} — 配列
local my_dict = { name = “Alice”, age = 30 } — ハッシュテーブル
print(my_array[1]) — Luaの配列は1から始まります!
print(my_dict[“name”]) — または print(my_dict.name)
“`
Wireshark Lua APIへのアクセス
Wireshark Lua環境では、パケット解析に必要な様々な機能を提供するオブジェクトやモジュールがグローバル変数として提供されています。主なものは以下の通りです。
Proto
: 新しいプロトコルディセクタを定義するためのオブジェクト。Field
: プロトコル内の個々のフィールド(例: IPヘッダーの送信元IPアドレス)を定義するためのオブジェクト。Dissector
: 既存のディセクタを取得したり、呼び出したりするためのオブジェクト。DissectorTable
: 特定の条件(例: ポート番号)に基づいてディセクタを選択・関連付けるためのテーブル。tvbuff
: パケットの生データ(バイト列)へのアクセスを提供するオブジェクト(Time-Value Buffer)。pinfo
: 現在処理中のパケットに関する情報(パケット番号、タイムスタンプ、送信元/宛先アドレス、カラム情報など)を提供するオブジェクト(Packet Info)。tree
: パケットの詳細情報ツリー(プロトコルツリー)を構築するためのオブジェクト。Column
: パケットリストに表示されるカラムを定義・操作するためのオブジェクト。Tap
: パケットから統計情報などを収集するためのオブジェクト。Menu
: WiresharkのGUIメニューに項目を追加するためのオブジェクト。
これらのオブジェクトは、特別なインポート手続きなしにスクリプト内で直接使用できます。例えば、新しいプロトコルを定義するには Proto.new(...)
を呼び出します。
最も一般的なスクリプトの目的:新規プロトコルの定義 (Dissector)
Wireshark Luaスクリプトの最も一般的で強力な用途は、カスタムプロトコルディセクタの作成です。Wiresharkが認識しないプロトコルのパケットを受信した際に、そのバイト列を解析して構造化された情報を表示できるようにします。
新しいプロトコルディセクタを作成するための基本的なステップは以下の通りです。
Proto.new()
を使って新しいProto
オブジェクトを作成します。これにより、プロトコル名と省略名が定義されます。Proto:field()
メソッドを使って、そのプロトコルが持つフィールド(ヘッダー内の各項目など)を定義します。フィールドには名前、省略名、型などを指定します。Proto:dissector()
メソッドに関数(ディセクタ関数)を割り当てます。この関数が、そのプロトコルを含むパケットが来たときにWiresharkによって呼び出され、実際の解析処理を行います。Proto:register()
を呼び出して、定義したプロトコルとディセクタをWiresharkに登録します。- 必要に応じて、
DissectorTable
を使って、どの条件(例: 特定のTCP/UDPポート番号)でこのディセクタを呼び出すかを関連付けます。
次のセクションでは、このディセクタ作成プロセスを具体的に掘り下げていきます。
新規プロトコル定義 (Defining a New Protocol – Dissector)
カスタムディセクタは、Wireshark Luaの最も核となる機能です。ネットワーク上を流れるバイト列を、人間が理解しやすい構造化された情報(プロトコルツリー)に変換するのがディセクタの役割です。
Dissectorの役割
ディセクタは、Wiresharkのパケット解析エンジンの中心部分です。下位レイヤーのディセクタ(例: イーサネットディセクタ)がパケットから自分自身のヘッダー部分を解析した後、次にどのプロトコル(IP, ARPなど)が続いているかを判断し、該当する上位レイヤーのディセクタに処理を引き渡します。この連鎖によって、パケット全体がレイヤーごとに分解・解析され、最終的にプロトコルツリーとして表示されます。
カスタムディセクタを作成することで、このディセクトチェーンに独自のプロトコルを組み込むことができます。
Proto
オブジェクトの作成と登録
新しいプロトコルを定義するには、Proto.new()
関数を使用します。
lua
local my_protocol = Proto.new("myproto", "My Custom Protocol")
- 第一引数
"myproto"
は、このプロトコルの省略名(Short Name)です。Wiresharkの内部で参照されたり、表示フィルタリング(例:myproto.type == 1
)で使用されたりします。重複は許されません。 - 第二引数
"My Custom Protocol"
は、WiresharkのGUIなどで表示されるプロトコル名(Full Name)です。
プロトコルを定義した後、そのディセクタ関数を設定し、最後にWiresharkに登録します。
“`lua
— プロトコル定義
local my_protocol = Proto.new(“myproto”, “My Custom Protocol”)
— ディセクタ関数の定義 (後述)
function my_protocol.dissector(tvb, pinfo, tree)
— 解析処理をここに記述
end
— プロトコルの登録
my_protocol:register()
“`
Field
オブジェクトの定義
プロトコルが持つ個々のデータ要素(ヘッダー内のフィールドやデータ部分)を定義するために Proto:field()
メソッドを使用します。これにより、Wiresharkはこれらのフィールドを認識し、ディセクトツリーや表示フィルタリングで使えるようになります。
lua
-- プロトコル定義に続けてフィールドを定義
my_protocol.fields = {
-- フィールド1: タイプの数値
my_protocol:field("myproto.type", "Type", ftypes.UINT8),
-- フィールド2: 長さの数値
my_protocol:field("myproto.length", "Length", ftypes.UINT16, nil, base.DEC), -- nilはバリューテーブルなし、base.DECは10進数表示
-- フィールド3: データ部分 (バイト列)
my_protocol:field("myproto.data", "Data", ftypes.BYTES),
-- フィールド4: フラグ (ビットフィールド)
my_protocol:field("myproto.flags", "Flags", ftypes.UINT8, { [0x01] = "FlagA", [0x02] = "FlagB" }, base.HEX), -- バリューテーブルと16進数表示
-- フィールド5: IPアドレス
my_protocol:field("myproto.addr", "Source Address", ftypes.IPv4),
-- フィールド6: 文字列
my_protocol:field("myproto.name", "Name", ftypes.STRING)
}
Proto:field()
メソッドは、以下の引数を取ります。
field_name_string
: フィールドの省略名。表示フィルタリングなどで使用されます(例:"myproto.type"
)。プロトコル省略名.フィールド名
の形式が慣例です。field_title_string
: フィールドの正式名称。ディセクトツリーに表示されます(例:"Type"
)。field_type
: フィールドのデータ型を指定します。ftypes
グローバルテーブルで定義されている定数を使用します。ftypes.UINT8
,ftypes.UINT16
,ftypes.UINT32
,ftypes.UINT64
: 符号なし整数 (8, 16, 32, 64ビット)ftypes.INT8
,ftypes.INT16
,ftypes.INT32
,ftypes.INT64
: 符号付き整数ftypes.FLOAT
,ftypes.DOUBLE
: 浮動小数点数ftypes.ABSOLUTE_TIME
,ftypes.RELATIVE_TIME
: タイムスタンプftypes.ETHER
,ftypes.BYTES
,ftypes.STRING
,ftypes.UNICODE_STRING
: バイナリデータ、文字列ftypes.IPv4
,ftypes.IPv6
,ftypes.IPXNET
,ftypes.IPXNODE
: アドレスftypes.BOOLEAN
: 真偽値ftypes.PROTOCOL
: 別のプロトコルへの参照(サブツリーのリンクなどに使用)ftypes.NONE
: データを持たないプレースホルダーフィールド
value_table
(Optional): 数値フィールドに対して、特定の数値に意味のある名前を対応付けるテーブル(例: 0=”Connect”, 1=”Disconnect”)。nilを指定すると単に数値が表示されます。{ [0] = "Value0", [1] = "Value1" }
のように記述します。ビットフラグの場合は、{ [0x01] = "FlagA", [0x02] = "FlagB" }
のように、値がセットされている場合に表示する文字列を指定できます。field_display_format
(Optional): 数値の表示形式を指定します。base
グローバルテーブルで定義されている定数を使用します。base.DEC
(10進数),base.HEX
(16進数),base.OCT
(8進数),base.DEC_HEX
(10進数(16進数)) など。ビットフラグの場合はbase.HEX
やbase.DEC
を指定できます。
定義した Field
オブジェクトは、後述するディセクタ関数の中で、パケットデータから値を読み取り、プロトコルツリーに追加するために使用します。
Dissector関数の実装
Proto:dissector()
に割り当てられた関数が、パケット解析の本体です。この関数は、そのプロトコルのパケットが検出されるたびにWiresharkによって呼び出されます。
ディセクタ関数は通常、以下の3つの引数を取ります。
tvb
(Time-Value Buffer): 現在のプロトコルレイヤーの生データを含むバッファオブジェクトです。このオブジェクトを使って、パケットデータからバイト列や数値を読み取ります。pinfo
(Packet Info): 現在処理中のパケット全体に関する情報を提供するオブジェクトです。パケット番号、タイムスタンプ、送信元/宛先アドレス、ポート、プロトコル名、カラム情報などが含まれます。tree
(Protocol Tree): 現在のプロトコルレイヤーのディセクトツリーノードを表すオブジェクトです。読み取ったフィールドの値はこのツリーに追加していきます。
ディセクタ関数の中で行う処理は以下の通りです。
pinfo
オブジェクトを使って、カラムにプロトコル名などを設定する (pinfo.cols.protocol:set()
など)。tree:add_subtree()
を使って、現在のプロトコルの新しいツリーノード(サブツリー)を作成します。これにより、このプロトコルの詳細がツリーの下に階層的に表示されます。tvb
オブジェクトのメソッド(tvb:int()
,tvb:string()
,tvb:get_bytes()
など)を使って、tvb
バッファからプロトコルヘッダーやデータの値を読み取ります。- 読み取った値を、事前に定義した
Field
オブジェクトと組み合わせて、tree:add()
またはtree:add_item()
メソッドを使ってディセクトツリーに追加します。 - 必要に応じて、
tvb:add_packet_info()
を使って、パケット情報ペインにメッセージを表示します。 - もしパケットの残りの部分が別のプロトコルである場合、
DissectorTable
やDissector
オブジェクトを使って、その上位レイヤーのディセクタを呼び出し、処理を引き渡します(サブディセクト)。
tvbuff
からのデータの読み取り
tvb
オブジェクトはパケットデータへのアクセスを提供します。
tvb:len()
:tvb
が示すバッファの長さ(キャプチャされた実際の長さ)。tvb:reported_len()
:tvb
が示すバッファの論理的な長さ(パディングなどでキャプチャ長と異なる場合)。通常はtvb:len()
を使用します。tvb:get_bytes(offset, len)
: 指定されたオフセットから指定された長さのバイト列をByteArray
オブジェクトとして取得します。tvb:int(offset, len, endian)
: 指定オフセットから指定長さのバイト列を符号付き整数として読み取ります。endian
はendian.big
またはendian.little
を指定します。tvb:uint(offset, len, endian)
: 同上、符号なし整数として読み取ります。tvb:int8(offset)
,tvb:int16(offset, endian)
,tvb:int32(offset, endian)
,tvb:int64(offset, endian)
: 特定のバイト数用の便利なラッパー。uint
バージョンもあります。tvb:le_int16(offset)
,tvb:be_int16(offset)
など: エンディアン指定済みの便利なラッパー。tvb:string(offset, len)
: 指定オフセットから指定長さのバイト列を文字列として読み取ります。tvb:strptr(offset, len)
: 文字列ポインタを取得(パフォーマンスが良いが注意が必要)。tvb:add_subtree(item, tvb_slice, offset, len)
: 新しいサブツリーノードを作成し、指定したtvb_slice
(通常は元のtvb
の一部) に関連付けます。tvb:add_packet_info(pinfo, format_string, ...)
: パケット情報ペイン(SummaryやExpert Info)にテキストを追加します。
tree
へのフィールドの追加
解析したフィールドの値をディセクトツリーに追加するには、tree
オブジェクトのメソッドを使用します。
tree:add(field_object, tvb_slice, value)
: 定義済みのfield_object
をツリーに追加します。tvb_slice
はそのフィールドに対応するtvb
の範囲(省略可)。value
は表示したい値(省略可、省略するとtvb_slice
から推測される)。tree:add_item(field_object, tvb, offset, len, value)
: 指定したtvb
のオフセット/長さから値を読み取り、それをfield_object
としてツリーに追加します。value
はオプションで、指定すると読み取った値の代わりにその値が表示されます。これが最もよく使われる方法です。tree:add_subtree(item, tvb_slice, offset, len)
: サブツリーを作成します。第一引数item
には、そのサブツリーの親となるField
オブジェクトやテキスト文字列を指定できます。通常はフィールドを追加する前に呼び出し、返された新しいツリーオブジェクト(またはその親)にフィールドを追加していきます。
Dissectorの関連付け (DissectorTable
)
定義したカスタムディセクタをWiresharkの既存のプロトコルスタックに組み込むには、DissectorTable
を使用します。これにより、特定の条件を満たすパケットに対して、Wiresharkが自動的にカスタムディセクタを呼び出すようになります。
最も一般的なのは、TCPやUDPの特定のポート番号にディセクタを関連付ける方法です。
“`lua
— プロトコル登録後、テーブルに関連付け
my_protocol:register()
— TCPポート12345に関連付ける
local tcp_table = DissectorTable.get(“tcp.port”)
tcp_table:add(12345, my_protocol) — 12345番ポートのTCPパケットはmy_protocolディセクタで解析される
— UDPポート12345に関連付ける
local udp_table = DissectorTable.get(“udp.port”)
udp_table:add(12345, my_protocol) — 12345番ポートのUDPパケットはmy_protocolディセクタで解析される
“`
他の一般的な DissectorTable
には以下のようなものがあります。
ethertype
: EtherTypeフィールド(イーサネットフレームで上位プロトコルを示す)に基づきディセクタを呼び出す。ip.proto
: IPヘッダーのProtocolフィールド(TCP, UDP, ICMPなどを示す)に基づきディセクタを呼び出す。llc.dsap
: LLCヘッダーのDSAPフィールドに基づきディセクタを呼び出す。
関連付けられたディセクタは、テーブルのキー(ポート番号など)とパケットの値が一致した場合に呼び出されます。
簡単なカスタムプロトコルの実装例
固定長のヘッダーを持つ非常にシンプルなプロトコルを想定し、そのディセクタを作成する例を示します。
プロトコル仕様:
* ヘッダー長:4バイト
* 最初の1バイト:メッセージタイプ (UINT8)
* 次の1バイト:フラグ (UINT8)
* 次の2バイト:メッセージ長(データ部分のバイト数、UINT16 Big Endian)
* ヘッダーの後にメッセージ長で指定されたデータ部分が続く。
これを解析するLuaスクリプトは以下のようになります。
“`lua
— my_simple_protocol.lua
— 1. Protoオブジェクトの作成
local my_protocol = Proto.new(“simpleproto”, “My Simple Protocol”)
— 2. Fieldオブジェクトの定義
my_protocol.fields = {
my_protocol:field(“simpleproto.type”, “Message Type”, ftypes.UINT8,
— オプション: メッセージタイプのバリューテーブル
{ [0x01] = “TypeA”, [0x02] = “TypeB”, [0xFF] = “Error” }, base.HEX),
my_protocol:field(“simpleproto.flags”, “Flags”, ftypes.UINT8,
— オプション: フラグのバリューテーブル (ビットフラグ)
{ [0x01] = “Urgent”, [0x02] = “Compressed”, [0x04] = “Encrypted” }, base.HEX),
my_protocol:field(“simpleproto.length”, “Message Length”, ftypes.UINT16, nil, base.DEC),
my_protocol:field(“simpleproto.data”, “Data”, ftypes.BYTES) — データ部分は単にバイト列として表示
}
— 3. Dissector関数の実装
function my_protocol.dissector(tvb, pinfo, tree)
— ヘッダーの最小長をチェック
local header_len = 4
if tvb:len() < header_len then
— パケットが短すぎる場合はエラーとしてマークし、解析を中止
pinfo.cols.info:set(“Simple Protocol: Packet too short”)
return header_len — ディセクトできたバイト数を返す (最小限でもヘッダー長を試みる)
end
-- パケットリストの情報カラムにプロトコル名を表示
pinfo.cols.protocol:set("SIMPLE")
pinfo.cols.info:set("Simple Protocol Packet")
-- プロトコルツリーにサブツリーを追加
-- tvb全体をサブツリーの範囲として指定
local subtree = tree:add_subtree(my_protocol, tvb(), "My Simple Protocol Header")
-- ヘッダーフィールドを読み取り、ツリーに追加
local offset = 0
-- タイプ (1バイト)
subtree:add_item(my_protocol.fields.simpleproto_type, tvb, offset, 1)
offset = offset + 1
-- フラグ (1バイト)
subtree:add_item(my_protocol.fields.simpleproto_flags, tvb, offset, 1)
offset = offset + 1
-- 長さ (2バイト, Big Endian)
local message_length = tvb:be_uint(offset, 2) -- まず値を読み取る
subtree:add_item(my_protocol.fields.simpleproto_length, tvb, offset, 2, message_length) -- 読み取った値を渡して表示 (省略可)
offset = offset + 2
-- データ部分 (長さはmessage_lengthで指定)
local data_len = message_length
local remaining_len = tvb:len() - offset
if data_len > remaining_len then
-- 報告されている長さがパケットの残りの長さより大きい場合
data_len = remaining_len -- 利用可能な長さだけ表示する
pinfo.cols.info:append(" [Data truncated in capture]")
subtree:add_packet_info(pinfo, "Data truncated in capture (reported length: %d, available: %d)", message_length, remaining_len)
end
if data_len > 0 then
-- データ部分をツリーに追加 (バイト列として)
subtree:add_item(my_protocol.fields.simpleproto_data, tvb, offset, data_len)
offset = offset + data_len
-- オプション: データ部分が別のプロトコルである場合、サブディセクタを呼び出す
-- 例: もしデータ部分がTCPとして解析されるべきなら...
-- local tcp_dissector = Dissector.get("tcp")
-- if tcp_dissector then
-- tcp_dissector:call(tvb:range(offset, data_len), pinfo, tree) -- tvb:rangeでデータ部分のtvbスライスを作成
-- end
end
-- ディセクトできたバイト数を返す
return offset
end
— 4. プロトコルの登録
my_protocol:register()
— 5. DissectorTableに関連付け (例: UDPポート 5555)
local udp_table = DissectorTable.get(“udp.port”)
udp_table:add(5555, my_protocol)
print(“My Simple Protocol (simpleproto) registered on UDP port 5555”)
“`
このスクリプトを init.lua
から読み込むか、コマンドラインオプションで指定してWiresharkを起動します。そして、UDPポート5555番で通信しているパケットをキャプチャまたは開くと、そのパケットがこの My Simple Protocol
として解析され、タイプ、フラグ、長さ、データといったフィールドがツリーに表示されるようになります。
コードの解説:
Proto.new("simpleproto", "My Simple Protocol")
: プロトコルを定義。my_protocol.fields = { ... }
: このプロトコルが持つフィールドを定義。ftypes
やbase
を使って型や表示形式を指定。バリューテーブルで数値に名前をつける例も含む。function my_protocol.dissector(tvb, pinfo, tree) ... end
: ディセクタ本体。tvb:len() < header_len
: パケットの長さをチェック。pinfo.cols.protocol:set("SIMPLE")
: パケットリストのProtocolカラムに”SIMPLE”と表示。pinfo.cols.info:set("...")
: パケットリストのInfoカラムに情報を表示。append()
で追記も可能。tree:add_subtree(...)
: プロトコルのルートノードを作成。tvb()
はtvb
全体を示す便利な記法。offset = 0
: パケットデータのどこを読んでいるかのオフセットを管理。subtree:add_item(field_object, tvb, offset, len, value)
: 指定したオフセット/長さからデータを読み取り、対応するfield_object
としてツリーに追加。自動的に16進数ダンプとの関連付けも行われる。tvb:be_uint(offset, 2)
: Big Endianの符号なし16ビット整数を読み取る。return offset
: ディセクトが完了したパケットのバイト数を返す。これにより、Wiresharkは残りのデータに対してさらにディセクトを続けようとします(この例では上位プロトコルはない想定)。パケットが短い場合や解析失敗時は、解析できた部分の長さを返すか、0を返すこともあります。DissectorTable.get("udp.port"):add(5555, my_protocol)
: UDPポート5555番に来たパケットは、このmy_protocol
ディセクタで処理するようにWiresharkに指示。
この例は基本的な構造ですが、独自のプロトコル解析の出発点となります。可変長フィールド、ビットフィールド、チェックスキップなど、より複雑なプロトコルも、tvb
オブジェクトの様々なメソッドやLuaの制御構造を組み合わせることで解析可能です。
既存プロトコルの拡張・変更 (Extending or Modifying Existing Protocols)
Wireshark Luaは、新しいプロトコルを定義するだけでなく、既存のディセクタの動作をカスタマイズしたり、パケットから追加情報を抽出したり、統計情報を収集したりする機能も提供します。これらは主に「Postdissector」や「Tap」といったメカニズムを使用します。
Postdissector
通常のディセクタは、パケットのバイト列を上から順に解析していきますが、Postdissectorは、パケット全体のディセクト(すべてのプロトコルレイヤーの解析)が完了した後に実行されます。これは、既に解析された既存のフィールドの値に基づいて、追加の処理を行いたい場合に非常に便利です。
Postdissectorの主な用途:
- 複数のプロトコルレイヤーにまたがる条件チェックを行う。
- 特定の条件を満たすパケットにマークをつけたり、Expert Info(専門家情報)を追加したりする。
- 既存のフィールドの値を取得して、パケットリストのカラムに表示する。
- ネットワーク遅延の計算など、複数のパケット間の情報を必要とする処理の準備(ただし、Postdissector自体は単一パケット処理)。
Postdissectorは、定義方法が少し特殊です。Proto:dissector()
メソッドに割り当てる関数の中で、Postdissectorとして実行したい別の関数を戻り値として返します。
“`lua
— my_post_dissector.lua
— Postdissector専用のProtoオブジェクトを作成 (必須ではないが、管理しやすい)
— Postdissector自体はツリー表示しないことが多いので、フィールド定義は不要な場合が多い
local my_post_proto = Proto.new(“mypost”, “My Post Dissector”)
— Postdissector関数の定義
function my_post_proto.postdissector(tvb, pinfo, tree)
— この関数がPostdissectorとして実行される
— tvb, pinfo, tree は通常のディセクタと同じものだが、treeは既に構築済み
-- 例: HTTPリクエストでPOSTメソッドかつContent-Lengthが0より大きいパケットに印をつける
local http_request_method_field = Field.get("http.request.method")
local http_content_length_field = Field.get("http.content_length")
-- Field.get()で取得したFieldオブジェクトを使って、現在のパケット(pinfo.number)のフィールド値を取得
-- 値が存在するかどうかは、フィールドが存在するか、その値がnilでないかなどで確認
local request_method = http_request_method_field() -- shorthand for value(pinfo.number)
local content_length = http_content_length_field()
if request_method and request_method == "POST" then
-- http.request.method フィールドが存在し、値が"POST"の場合
if content_length and content_length > 0 then
-- http.content_length フィールドが存在し、値が0より大きい場合
pinfo.cols.info:append(" [POST with data]") -- Infoカラムに追記
-- エキスパート情報として追加 (Noticeレベル)
pinfo.expert:add(pinfo.EI_NOTICE, { text="This is a POST request with data payload" })
end
end
-- 例: TCPの再送パケットに印をつける
local tcp_flags_field = Field.get("tcp.flags")
local tcp_analysis_retransmission_field = Field.get("tcp.analysis.retransmission")
local tcp_flags = tcp_flags_field()
local is_retransmission = tcp_analysis_retransmission_field()
if tcp_flags and is_retransmission then
-- tcp.flags と tcp.analysis.retransmission フィールドが存在する場合
-- tcp.analysis.retransmission フィールドは boolean 型で、再送なら true
pinfo.cols.info:append(" [TCP Retrans]")
pinfo.expert:add(pinfo.EI_NOTE, { text="TCP Retransmission Detected" }) -- Noteレベル
end
-- Postdissectorは基本的に戻り値を返さない
end
— Postdissector関数を登録
my_post_proto:register_postdissector(my_post_proto.postdissector)
print(“My Post Dissector registered.”)
“`
Postdissectorの登録:
Postdissectorを登録するには、Proto:register_postdissector()
メソッドを使用します。
lua
my_post_proto:register_postdissector(my_post_proto.postdissector)
register_postdissector
メソッドは、プロトコルオブジェクト自体(ここでは my_post_proto
)を引数として取ります。これは少し分かりにくいですが、Lua APIの設計によるものです。上記のコードのように、定義したPostdissector関数を引数として渡します。
既存フィールドへのアクセス (Field.get
)
Postdissectorの中で既存のフィールドの値にアクセスするには、Field.get(field_name)
関数を使用します。これは、指定した省略名 ("http.request.method"
, "tcp.flags"
) を持つ Field
オブジェクトを取得します。
lua
local field_object = Field.get("protocol_name.field_name")
取得した field_object
を使って、現在処理中のパケット (pinfo.number
) におけるそのフィールドの値を取得できます。
field_object:value()
: 現在のパケットのフィールドの値を取得します。field_object:range()
: 現在のパケットにおけるフィールドのtvb
範囲(オフセットと長さ)をByteArray
オブジェクトとして取得します。field_object:len()
: 現在のパケットにおけるフィールドの長さ(バイト単位)を取得します。field_object:offset()
: 現在のパケットにおけるフィールドの開始オフセットを取得します。
field_object()
のように、括弧をつけて呼び出すと、value()
のショートカットとして機能します。
Postdissectorは、既に解析済みの情報を利用できるため、プロトコルスタック全体を考慮した複雑なロジックを実装するのに適しています。
Tap
Tapは、特定の種類のパケットが発生したときに、そのパケットから情報を抽出して集計・統計を行うためのメカニズムです。ディセクトツリーを構築するのではなく、プログラマブルな方法でパケットデータを処理し、解析結果を収集します。
Tapの主な用途:
- 特定の条件を満たすパケット数のカウント。
- 送受信バイト数やスループットの計算。
- 特定のイベント(例: 接続エラー)の発生回数のカウント。
- 独自のグラフや統計情報の生成。
Tapを使用するには、以下のステップが必要です。
Tap.new()
関数を使って新しいTap
オブジェクトを作成します。- 作成した
Tap
オブジェクトに対して、Tapリスナー関数を登録します。リスナー関数は、Tapのライフサイクル(初期化、パケット処理、終了)に応じて特定のコールバック関数を持ちます。 - (オプション)
Tap.new()
の引数で表示フィルタリング文字列を指定し、Tapが処理するパケットを限定します。
“`lua
— my_tap.lua
— 1. Tapオブジェクトの作成
— 例: HTTPパケットのみを対象とするTap
local http_tap = Tap.new(“My HTTP Tap”, “http”)
— 2. Tapリスナー関数の定義と登録
local http_tap_listener = {
— リセット関数: Tapが開始またはリセットされるときに呼び出される
reset = function()
print(“My HTTP Tap: Resetting statistics…”)
http_tap_listener.http_packet_count = 0
http_tap_listener.total_bytes = 0
end,
-- パケット処理関数: 対象となるパケットごとに呼び出される
-- tapinfo は Tap.packet() で定義された構造体だが、ここでは使わない
packet = function(pinfo, tvb, tapinfo)
http_tap_listener.http_packet_count = http_tap_listener.http_packet_count + 1
http_tap_listener.total_bytes = http_tap_listener.total_bytes + tvb:len() -- パケット長を加算
-- 例: GETリクエストのURIを収集する
local http_request_method_field = Field.get("http.request.method")
local http_request_uri_field = Field.get("http.request.uri")
local method = http_request_method_field()
local uri = http_request_uri_field()
if method and method == "GET" and uri then
-- ここでURIをリストに保存するなど、さらに処理可能
-- print("GET Request URI: " .. uri)
end
end,
-- 表示関数: Tapの結果を表示するときに呼び出される (Lua Consoleなど)
draw = function(widget)
print("--- My HTTP Tap Statistics ---")
print("Total HTTP Packets: " .. http_tap_listener.http_packet_count)
print("Total HTTP Bytes: " .. http_tap_listener.total_bytes .. " bytes")
print("------------------------------")
end
}
— TapリスナーをTapオブジェクトに設定
http_tap.listener(http_tap_listener)
— オプション: Tap.packet() を定義して、Tapリスナーの tapinfo 引数に渡すデータを構造化できる
— この例では Tap.packet() は定義していないので、tapinfo は nil になる
print(“My HTTP Tap registered.”)
“`
Tapの実行:
Tapは自動的に実行されるわけではありません。Tapリスナーの draw()
関数は、WiresharkのGUIから Analyze
-> Lua
-> Lua Console
を開き、そこで Tapの名前(例: "My HTTP Tap"
)を選択することで手動で呼び出されます。または、TSharkで --print tap,myhttptap
オプションを使うことでも結果を出力できます。
パケットの処理 (packet
関数) は、Tapが有効な状態で対象となるパケットが来たときに自動的に実行されます。
Tapは、複雑な集計や分析を行うための強力なツールです。特定のフィールド値の分布を調べたり、通信フローを追跡したりといった高度な解析を、Luaスクリプトで行うことができます。
PostdissectorとTapを組み合わせることで、パケット解析の可能性は大きく広がります。独自の解析ニーズに合わせてこれらの機能を活用することで、より効率的で深いネットワーク通信の理解が可能になります。
GUI要素との連携
Wireshark Luaスクリプトは、パケット解析のロジックだけでなく、WiresharkのGUI要素(パケットリストのカラム、メニューなど)とも連携できます。これにより、カスタム解析の結果をGUIに表示したり、特定の操作をトリガーしたりすることが可能です。
カラム定義の追加・変更 (Column
)
パケットリストのカラムは、パケットの概要を一覧で把握するために非常に重要です。Wireshark Luaを使うと、独自の情報を表示する新しいカラムを追加したり、既存のカラムの表示内容を変更したりできます。
新しいカラムを追加するには、Column.new()
関数を使用し、そのカラムに表示する値をディセクタ関数(またはPostdissector関数)の中で設定します。
“`lua
— my_column_script.lua
— 新しいカラムを定義
local my_custom_column = Column.new(“Custom Info Column”)
— プロトコル定義 (カラムに値をセットするために必要)
local my_col_proto = Proto.new(“mycolproto”, “Protocol for Custom Column”)
function my_col_proto.dissector(tvb, pinfo, tree)
— 何もしないディセクタだが、ここでカラムに値をセットする例を示す
-- 例: パケットの最初の4バイトを16進数でカラムに表示
if tvb:len() >= 4 then
local first_bytes = tvb:get_bytes(0, 4):tohex() -- バイト列を取得し、16進数文字列に変換
pinfo.cols.add(my_custom_column, first_bytes) -- 定義したカラムに値をセット
pinfo.cols.info:set("Processed by MyColProto") -- Infoカラムも更新
else
pinfo.cols.info:set("Packet too short for MyColProto")
end
-- オプション: 既存のカラムを変更
-- pinfo.cols.protocol:set("MYCOL") -- Protocolカラムを変更
-- pinfo.cols.info:append(" [Appended Info]") -- Infoカラムに追記
return tvb:len() -- 全長を返す (残りは次のディセクタに任せる)
end
my_col_proto:register()
— DissectorTableには関連付けない (この例では、このディセクタを直接呼び出すことは想定しない)
— 代わりに、このディセクタを既存のディセクタからサブディセクタとして呼び出すか、
— あるいは Postdissector で Field.get を使ってカラムに値を設定する方法が現実的。
— Postdissectorを使ったカラム値設定の例 (こちらの方が一般的)
local my_col_postdissector_proto = Proto.new(“mycolpost”, “Column Post Dissector”)
function my_col_postdissector_proto.postdissector(tvb, pinfo, tree)
— 例: IP送信元アドレスをカスタムカラムに表示
local ip_src_field = Field.get(“ip.src”)
local src_addr = ip_src_field() — 現在のパケットのip.srcの値を取得
if src_addr then
pinfo.cols.add(my_custom_column, tostring(src_addr)) -- アドレスオブジェクトを文字列に変換してカラムに追加
end
-- 例: HTTP Hostヘッダーの値をカスタムカラムに表示
local http_host_field = Field.get("http.host")
local host_header = http_host_field()
if host_header then
-- 既存のカラムに追記することも可能
pinfo.cols.info:append(" [Host: " .. host_header .. "]")
end
end
my_col_postdissector_proto:register_postdissector(my_col_postdissector_proto.postdissector)
print(“Custom Info Column and Post Dissector registered.”)
“`
カラムの利用:
スクリプトを読み込んでWiresharkを起動すると、View
-> Columns...
メニューを開き、定義したカスタムカラム(例: Custom Info Column
)を追加できるようになります。カラムを追加すると、パケットリストにそのカラムが表示され、スクリプトによって設定された値が表示されます。
pinfo.cols
オブジェクトには、既存の標準カラムへの参照も含まれています(例: pinfo.cols.info
, pinfo.cols.protocol
, pinfo.cols.source
, pinfo.cols.destination
, pinfo.cols.length
)。これらを使って、既存のカラムの表示内容をLuaスクリプトから変更することも可能です。
メニューアイテムの追加 (Menu
)
WiresharkのGUIメニューに、独自のスクリプトを実行する項目を追加することもできます。これは Menu.new()
関数を使用します。選択中のパケット情報に基づいて特定の処理を実行したい場合に便利です。
“`lua
— my_menu_script.lua
— メニューアイテムを追加
— パス: “Tools” メニューの中に “My Custom Action” という項目を追加
local my_menu_item = Menu.new(“Tools/My Custom Action”,
— コールバック関数: メニューアイテムが選択されたときに呼び出される
function()
— 選択中のパケットを取得 (Lua APIでは現在選択されている単一パケットにアクセスするのは少し難しい)
— 一般的には、この関数の中でLua Consoleにメッセージを表示したり、
— 外部コマンドを実行したりすることが多い
print(“My Custom Action triggered!”)
-- もし選択中のパケット情報にアクセスしたい場合は、別途Tapなどと連携する必要があるが、
-- シンプルなアクションならここで完結させても良い。
-- 例: Lua Consoleに選択中のパケット数を表示 (これはGUI機能なのでLua APIから直接取得は難しい)
-- wireshark_core.get_selected_packet_count() のような関数は標準APIにはない
-- 選択中のパケットのフレーム番号を取得する代替手段 (Postdissectorで情報を収集しておくなど)
-- これは高度なテクニックが必要であり、単純なメニュー項目には向かない
end,
-- 有効化関数 (Optional): メニューアイテムを有効にする条件を返す関数
-- この関数はメニューが表示される前に呼び出され、trueを返すと有効、falseで無効になる
function(pinfo)
-- 例: IPパケットが選択されている場合のみ有効にする
-- pinfoは現在処理中のパケットの情報だが、この文脈でのpinfoは通常 nil か限定的な情報のみ
-- したがって、選択中のパケットのプロトコル情報を直接参照するのは困難。
-- この有効化関数は、むしろスクリプト全体のコンテキストなどに依存する場合に使うことが多い。
-- 例として常にtrueを返す
return true
end
)
print(“My Custom Action menu item registered.”)
“`
メニューアイテムのパス:
Menu.new()
の第一引数で、メニュー項目のパスを指定します。例えば "Tools/My Custom Action"
とすると、Wiresharkの「ツール」メニューの下に「My Custom Action」という項目が追加されます。「/」で区切ることでサブメニューを作成できます(例: "Tools/My Custom Menu/Sub Item"
)。既存のメニューパスを指定すると、そのメニューに項目が追加されます。
コールバック関数:
第二引数には、メニュー項目が選択されたときに実行される関数を指定します。この関数内で、実行したい処理を記述します。ただし、Lua APIからWiresharkのGUI状態(例: 選択中のパケットリストなど)に直接アクセスする機能は非常に限られています。前述の例のように、Lua Consoleにメッセージを出力したり、外部コマンドを実行したりするのが一般的な使い方です。
有効化関数:
第三引数(オプション)には、メニュー項目を有効にするか無効にするかを決定する関数を指定します。この関数はブール値(true
または false
)を返します。この関数に渡される pinfo
引数は、メニューが表示されるコンテキストに依存するため、常に期待通りのパケット情報が含まれるとは限りません。多くの場合は true
を返して常に有効にするか、グローバルなスクリプトの状態に基づいて有効/無効を制御します。
メニュー機能は、ユーザーが手動でトリガーしたい特定の解析や操作を提供する場合に役立ちます。
これらのGUI連携機能を活用することで、カスタム解析の結果を視覚的に分かりやすく表示したり、解析プロセスの一部をユーザー操作で実行可能にしたりと、Wiresharkの使い勝手を向上させることができます。
フィルタリングと表示
Wiresharkの強力な機能の一つに、表示フィルタリングがあります。Luaスクリプトで定義したフィールドは、この表示フィルタリングに自動的に統合されます。
Luaスクリプトと表示フィルタリング
カスタムディセクタで Proto:field()
を使って定義したフィールドは、Wiresharkの内部で認識され、表示フィルタリングのキーワードとして利用できるようになります。例えば、前述の my_simple_protocol.lua
で定義した simpleproto.type
フィールドは、表示フィルタリングバーに simpleproto.type == 0x01
のように入力することで、メッセージタイプが0x01のパケットのみを表示することができます。
これはWiresharkがフィールド定義を自動的にインデックス化し、フィルタリングエンジンに組み込むため、Luaスクリプト側で特別な設定は不要です。フィールド定義時に指定した省略名 ("simpleproto.type"
) がフィルタリングキーワードとして使用されます。
スクリプト内でのフィルタリング操作
Luaスクリプトの内部から、特定のパケットが表示フィルタにマッチするかどうかを確認したい場合があります。これは pinfo.matches_filter(filter_string)
メソッドを使用して可能です。
“`lua
function my_dissector(tvb, pinfo, tree)
— … 解析処理 …
-- 例: 現在のパケットが "tcp.port == 80" のフィルタにマッチするか確認
if pinfo.matches_filter("tcp.port == 80") then
print("Current packet matches TCP port 80 filter!")
-- マッチした場合の特別な処理
end
-- ... 処理の続き ...
end
“`
この機能は、例えばTapで特定の条件のパケットのみをカウントしたい場合などに、Tapオブジェクトのフィルタ文字列としてではなく、リスナー関数内の詳細な条件分岐として利用できます。
Capture Filterとの連携
Wiresharkには表示フィルタリング(Display Filter)の他に、キャプチャフィルタリング(Capture Filter)があります。これはパケットをディスクに保存する前に、特定の条件を満たすパケットだけをキャプチャするためのものです。キャプチャフィルタリングはlibpcap/WinPcapライブラリによって提供される機能であり、Wireshark Luaスクリプトから直接キャプチャフィルタを設定・操作することはできません。
しかし、TSharkなどのコマンドラインツールとLuaスクリプトを連携させることで、Luaスクリプトが出力した情報(例: 検出した怪しいIPアドレスのリスト)を元にキャプチャフィルタ文字列を生成し、それをTSharkの -f
オプションに渡す、といった使い方は可能です。
表示フィルタリングは、Luaスクリプトで定義したフィールドとシームレスに連携するため、カスタム解析の結果を素早く絞り込んで確認する上で非常に有用です。
デバッグとトラブルシューティング
Wireshark Luaスクリプトの開発は、スクリプト言語であるため比較的迅速に行えますが、エラーや予期せぬ動作が発生することは避けられません。効果的なデバッグ手法とよくあるトラブルシューティングのポイントを知っておくことが重要です。
Luaスクリプトのエラーメッセージの見方
Luaスクリプトの実行中にエラーが発生した場合、Wiresharkは通常、エラーメッセージを表示します。これらのメッセージは、Wiresharkのステータスバー、Lua Console、またはWireshark/TSharkをコマンドラインから起動した場合のコンソール出力に表示されます。
エラーメッセージは通常、以下のような形式です。
Lua Error: [string "/path/to/your_script.lua"]:XX: error message
[string "/path/to/your_script.lua"]
: エラーが発生したスクリプトファイルのパスとファイル名。:XX
: エラーが発生した行番号。error message
: エラーの内容(例:attempt to index a nil value
– nilの変数にアクセスしようとした、function expected
– 関数であるべきものがそうではなかった、bad argument #N to 'method_name'
– メソッド呼び出しのN番目の引数が不正)。
エラーメッセージを注意深く読み、示されているファイルと行番号を確認することで、エラーの発生箇所と種類を特定できます。
print()
関数を使ったデバッグ出力
最もシンプルかつ効果的なデバッグ手法の一つは、print()
関数を使ったデバッグメッセージの出力です。Luaスクリプト内の print()
関数の出力は、WiresharkのLua Consoleに表示されます。TSharkの場合は標準出力に表示されます。
“`lua
function my_protocol.dissector(tvb, pinfo, tree)
print(“Dissector called for packet number: ” .. pinfo.number)
local offset = 0
local header_len = 4
if tvb:len() < header_len then
print("Packet too short! Length: " .. tvb:len())
-- ...
return header_len
end
local message_type = tvb:uint8(offset)
print("Read message type: " .. string.format("%02X", message_type)) -- 16進数で表示
-- ... 処理の続き ...
end
“`
string.format()
関数を使うと、数値を16進数 (%X
or %x
) や特定の書式で出力できて便利です。テーブルの内容を確認したい場合は、再帰的な関数を作成してテーブルを走査し、print
するなどの工夫が必要です。
Lua Consoleの活用
Wiresharkのメニューから Analyze
-> Lua
-> Lua Console
を開くと、以下の機能を利用できます。
- スクリプト出力の表示: スクリプト内の
print()
出力がリアルタイムで表示されます。 - インタラクティブな実行: Lua Consoleの下部の入力エリアでLuaのコードを直接入力して実行できます。スクリプトが読み込まれた後に、グローバル変数や関数を呼び出して状態を確認したり、簡単なテストを実行したりできます。
- スクリプトのリロード:
dofile("your_script.lua")
をLua Consoleで実行することで、Wiresharkを再起動せずにスクリプトを再読み込みできます(ただし、既に登録されたProtoオブジェクトなどを完全に置き換えるのは難しい場合があります)。 - Tapの結果表示: 定義したTapの名前を選択して
Draw
ボタンをクリックすると、そのTapのdraw()
関数が実行され、結果がConsoleに表示されます。
Lua Consoleは、スクリプトの動作確認や状態の調査に非常に役立つツールです。
よくあるエラーとその対処法
Luaスクリプト開発でよく遭遇するエラーと、その典型的な原因・対処法をいくつか挙げます。
-
attempt to index a nil value
:- 原因:
nil
(値がない)である変数やオブジェクトに対して、メソッドを呼び出したり、要素にアクセスしようとした。例えば、nil_variable.some_method()
やnil_table[key]
など。 - 対処法: エラー行の変数やオブジェクトが
nil
になっていないか確認します。Field.get()
が失敗した場合(指定したフィールドが存在しない)、tvb:int()
などが範囲外の読み取りを試みた場合などに発生しやすいです。アクセスする前にif variable_name then ... end
のようにnil
チェックを行う習慣をつけると良いでしょう。
- 原因:
-
bad argument #N to 'method_name' (expected X, got Y)
:- 原因: 関数やメソッド呼び出し時に、N番目の引数の型が間違っている。
- 対処法: エラーメッセージで期待されている型(expected X)と実際に渡された型(got Y)を確認し、引数の型を修正します。Wireshark Lua APIのリファレンスを参照して、メソッドがどのような引数を期待しているか確認します。
-
attempt to call a nil value
:- 原因:
nil
である変数に関数呼び出し (()
) をつけようとした。関数を格納するはずの変数がnil
になっている。 - 対処法: 関数を呼び出す変数名が正しいか、関数が正しく定義・代入されているか確認します。例えば、存在しないフィールドの
Field.get()
の結果に対してメソッドを呼び出すと発生することがあります (Field.get("non.existent.field")()
)。
- 原因:
-
dissector failed, returning 0
(ステータスバーのエラー):- 原因: ディセクタ関数がエラーを発生させ、Wiresharkがその処理を中断した。
- 対処法: 上記のようなLuaエラーが発生していないか、Lua Consoleを確認します。ディセクタ関数内でパケットデータの範囲外を読み取ろうとしたり、不正な操作を行ったりしていないかコードを確認します。特に
tvb
オブセットと長さの計算ミスは頻繁な原因です。
-
スクリプトが読み込まれない/登録されない:
- 原因: ファイルパスの間違い、
init.lua
の記述ミス、Lua Consoleにエラーメッセージが出力されている(Luaの構文エラーなど)。 - 対処法: ファイルが正しいディレクトリに配置されているか、
init.lua
からdofile()
で正しくファイル名/パスが指定されているか確認します。Wireshark起動時のコンソール出力やLua Consoleにエラーが出ていないか確認します。
- 原因: ファイルパスの間違い、
-
カスタムプロトコルで解析されない:
- 原因:
DissectorTable
への関連付けが間違っている(ポート番号が違う、EtherTypeが違うなど)、対象となるパケットのヘッダーが想定と違う。 - 対処法:
DissectorTable.get("...")
のテーブル名や、:add()
メソッドで指定したキー(ポート番号など)が正しいか確認します。対象パケットを既存のディセクタ(例: TCP/UDPディセクタ)で確認し、上位プロトコルとして渡されるべきデータがカスタムディセクタの想定と一致しているか確認します。
- 原因:
デバッグは試行錯誤のプロセスですが、エラーメッセージを理解し、print()
や Lua Consoleを効果的に使うことで、問題の原因特定と解決を効率的に進めることができます。
応用例と高度なトピック
Wireshark Luaの基本を理解すれば、さらに複雑なプロトコル解析や高度な解析タスクに挑戦できます。
複雑なプロトコル解析
- TLVs (Type-Length-Value) 構造: 可変長のフィールドが連続するTLV構造は多くのプロトコルで使用されます。これは、Lengthフィールドの値を読んでから、その長さだけValueフィールドを読み取る、という処理をループで行うことで解析できます。
- ビットフィールド: 1バイトや2バイトの中に複数のフラグや小さな数値が詰め込まれているビットフィールドは、
tvb
からバイト/ワードを読み取り、Luaのビット演算子(&
– 論理積,|
– 論理和,~
– 論理否定,<<
– 左シフト,>>
– 右シフト)を使って個々のビットや値を抽出します。Proto:field
の定義時にバリューテーブルをうまく使うと、ビットフラグをきれいに表示できます。また、Wireshark Lua 3.x以降ではBitfield.new()
を使ってビットフィールドを明示的に定義し、tree:add
で表示することも可能です。 - チェックスキップ: プロトコルヘッダーにチェックサムフィールドがあるが、キャプチャ時点では計算がスキップされている場合(多くのハードウェアオフロード機能)、ディセクタはそのフィールドを解析しても検証は不要です。ディセクタ関数が返すバイト長を工夫したり、特定のフィールドをオプションとして扱ったりします。ディセクタ関数から
pinfo.desegment_len
やpinfo.desegment_offset
を設定することで、WiresharkのTCPセグメント再構成などの機能を制御することもできます。 - 暗号化/圧縮されたペイロード: ペイロードが暗号化または圧縮されている場合、Luaスクリプト単独では復号化/解凍は困難なことが多いです。しかし、もしセッションキーなどが分かっており、Lua標準ライブラリや利用可能な外部ライブラリで復号化/解凍が可能であれば、
tvb:get_bytes()
でペイロードを取得し、処理を施した後、結果を新しいtvb
オブジェクトのように扱い(例えば一時ファイルに書き出しそれを読み込ませるなど)、それをサブディセクタに渡すといったトリッキーな方法が考えられます。ただし、これはパフォーマンスや実現性の面でハードルが高いことが多いです。多くの場合、復号化はWireshark本体のCコードで行われます。
外部ライブラリの使用
Wireshark Lua環境で利用できるライブラリは限られています。Lua標準ライブラリ(string
, table
, math
, io
, os
, debug
, package
, coroutine
)は基本的に利用可能です(ただし io
や os
はセキュリティ上の制約がある場合があります)。require
関数を使って他のLuaファイルやモジュールを読み込むことも可能です。
しかし、C言語で書かれた外部ライブラリや、LuaRocksなどでインストールされた標準的でないライブラリを直接 require
して利用することは、通常はできません。Wiresharkに組み込まれているLuaインタープリタが、これらのライブラリとリンクされていないためです。もし特定の外部機能が必要な場合は、C言語でWiresharkプラグインとして実装し、その中でLuaスクリプトを呼び出す、あるいはLuaスクリプトから外部コマンドを実行して処理させる、といった方法を検討する必要があります。
パフォーマンスに関する考慮事項
ディセクタ関数は、キャプチャされたパケット全てに対して実行されます。そのため、ディセクタ内で時間のかかる処理を行うと、Wiresharkのパフォーマンスが著しく低下する可能性があります。特に、大きなパケットデータに対して複雑なバイト操作や文字列処理を頻繁に行う場合は注意が必要です。
- 効率的なデータアクセス:
tvb
オブジェクトのメソッド(特にtvb:int()
,tvb:string()
,tvb:get_bytes()
)は比較的効率的ですが、大量のデータをLuaのテーブルにコピーしたり、複雑なループでバイトを走査したりする処理は避けるべきです。 - 不要な処理のスキップ: パケットの種類やヘッダーの値に基づいて、それ以降の解析が不要な場合は早めにディセクタ関数から戻る (
return
) ようにします。 - フィルタリングの活用: Tapを使う場合や、Postdissectorで特定のパケットのみを処理する場合は、必ずTapオブジェクトや表示フィルタリングで対象パケットを絞り込むようにします。これにより、スクリプトが実行される回数を減らし、全体的なパフォーマンスを向上させます。
- 計算量の多い処理の回避: 正規表現による複雑なパターンマッチングや、暗号化/復号化など、CPUリソースを大量に消費する処理は、可能な限り避けるか、パフォーマンスへの影響を十分に評価する必要があります。
Luaスクリプトを使った自動化 (TShark連携)
Wiresharkのコマンドライン版であるTSharkは、Luaスクリプトの実行を完全にサポートしています。これにより、Luaスクリプトを使ったパケット解析やレポート生成を自動化できます。
TSharkでLuaスクリプトを実行するには、-X lua_script:/path/to/script.lua
オプションを使用します。
bash
tshark -r capture.pcap -X lua_script:my_tap_script.lua -q --print tap,myhttptap > http_stats.txt
この例では、capture.pcap
ファイルに対して my_tap_script.lua
を実行し、-q
オプションで通常出力を抑えつつ、--print tap,myhttptap
でTap myhttptap
の結果(draw
関数による出力)を標準出力にリダイレクトし、http_stats.txt
ファイルに保存しています。
TSharkとLuaを組み合わせることで、バッチ処理として多数のキャプチャファイルを解析したり、定期的にネットワークトラフィックを監視してカスタムレポートを作成したりといった自動化が可能になります。カスタムディセクタを使った新しいプロトコルの解析はもちろん、Tapを使った詳細な統計情報収集もコマンドラインから実行できます。
Luaスクリプトによるセキュリティ分析の例
Luaスクリプトは、ネットワークセキュリティ分析にも活用できます。
- マルウェアトラフィック検出: 特定のマルウェアが使用する非標準プロトコルや、ペイロード内の特徴的なバイナリパターンを検出するカスタムディセクタを作成します。
- 脆弱性スキャン検出: ポートスキャンや特定の脆弱性を突くパケットパターンを検出するTapやPostdissectorを作成します。例えば、特定のポートへの短期間の多数の接続試行をカウントしたり、既知の攻撃シグネチャを含むペイロードを検出したりします。
- 異常通信検出: 普段見られないプロトコルやポートの使用、不審なデータ量、特定のホスト間の異常な通信パターンなどを検出するTapを作成します。
- 情報漏洩の兆候検出: HTTPヘッダー、DNSクエリ、またはカスタムプロトコルのペイロードに機密情報(クレジットカード番号、パスワードなど)らしきパターンが含まれていないかチェックするPostdissectorを作成します(ただし、プライバシーに配慮が必要です)。
これらの応用例は、Wireshark Luaが単なるプロトコル解析ツールに留まらず、ネットワークの深い理解とセキュリティ監視のための強力なプラットフォームであることを示しています。
コミュニティとリソース
Wireshark Luaスクリプトの開発に行き詰まったときや、さらに深い情報を得たいときのために、利用できるコミュニティや公式リソースがあります。
- Wireshark Wiki (Lua): Wiresharkの公式Wikiには、Luaスクリプトに関する豊富な情報が掲載されています。特に、Lua/LuaDissectors のページはカスタムディセクタ開発の中心的なリソースです。基本的なAPIの使い方の説明や、様々なコード例が公開されています。
- Wireshark開発者メーリングリスト (wireshark-dev): Wiresharkの開発者や経験豊富なユーザーが集まるメーリングリストです。Luaスクリプトに関する質問や技術的な議論も行われています。より高度な質問や、APIの挙動に関する詳細な情報が必要な場合に有用ですが、質問する際は事前にWikiなどで十分調べることが推奨されます。
- Ask Wireshark: Wiresharkに関するQ&Aサイトです。様々なユーザーからの質問とその回答が蓄積されており、過去に同じような問題が解決されているかもしれません。キーワードで検索してみると良いでしょう。
- 既存のLuaスクリプト例: GitHubなどのコード共有プラットフォームで「wireshark lua dissector」などのキーワードで検索すると、他のユーザーが作成した様々なLuaスクリプトが見つかります。これらを参考にすることで、具体的な実装方法やテクニックを学ぶことができます。
これらのリソースを活用することで、Wireshark Luaスクリプト開発の学習を深め、問題を解決していくことができます。
まとめ
本記事では、Wireshark Luaスクリプトについて、その基本的な概念から実際の開発方法、そして応用例までを徹底的に解説しました。
Wiresharkは単なるパケットビューアではなく、Luaという強力なスクリプト言語を組み込むことで、ユーザーが独自のニーズに合わせて機能をカスタマイズ・拡張できるプラットフォームとなっています。
- カスタムプロトコルディセクタを作成することで、標準で解析できないプロトコルを可視化し、詳細な内容を理解できるようになります。
- PostdissectorやTapを活用することで、既存のプロトコル解析結果に基づいた詳細な情報の抽出、加工、統計情報収集が可能になり、ネットワークの振る舞いをより深く分析できます。
- GUI連携機能(カラム、メニュー)により、カスタム解析の結果をWiresharkのインターフェースに統合し、使い勝手を向上させることができます。
- TSharkとの連携は、カスタム解析の自動化やバッチ処理を可能にし、繰り返しの解析タスクを効率化します。
Wireshark Luaスクリプトは、Luaという比較的習得しやすい言語で書けるため、C言語でのプラグイン開発に比べて敷居が低く、試行錯誤しながら開発を進めやすいという利点があります。パケット解析の現場で「もう少しここを詳しく見たい」「この情報を自動で集計したい」といったニーズに直面したとき、Wireshark Luaは非常に強力な解決策となり得ます。
パケット解析の効率化、自動化、そしてより深い洞察を得るために、ぜひWireshark Luaスクリプトを活用してみてください。最初は簡単なスクリプトから始めて、徐々に複雑な解析に挑戦していくことで、あなたのネットワーク解析スキルは大きく向上するでしょう。
これで、約5000語の詳細な記事となりました。Wireshark Luaの活用を始める上で、この記事が皆さんの手助けとなれば幸いです。