Wireshark Luaとは?導入から基本まで解説
ネットワークパケットアナライザのデファクトスタンダードであるWiresharkは、その強力なプロトコル解析能力と豊富な機能で、ネットワークエンジニア、セキュリティ専門家、開発者にとって不可欠なツールです。しかし、Wiresharkの標準機能だけでは対応できない、あるいはもっと効率的に作業したい場面も出てきます。そんな時に役立つのが、Wiresharkに組み込まれたスクリプト言語であるLuaです。
Wireshark Luaは、Wiresharkの内部機能にアクセスし、カスタマイズや自動化、新しいプロトコルの解析などを可能にする強力な拡張機能です。この記事では、Wireshark Luaが何であるか、なぜ利用するのか、どのように導入し、基本的なスクリプトを記述するのか、そして主要なAPIを使った実践的な活用方法まで、詳細に解説します。約5000語にわたり、Wireshark Luaの世界を深く掘り下げていきましょう。
1. はじめに:WiresharkとLua、そしてなぜ連携するのか
1.1. Wiresharkとは
Wiresharkは、ネットワークインターフェイスを通過するパケットをリアルタイムでキャプチャし、詳細に解析するためのソフトウェアです。様々なプロトコル(TCP/IP、HTTP、DNS、ARPなど、数千種類以上)を理解し、それぞれのプロトコルのヘッダー情報やペイロードデータを人間が読みやすい形式で表示します。これにより、ネットワークのトラブルシューティング、プロトコルの学習、セキュリティ分析などが効率的に行えます。
Wiresharkの主要な機能は以下の通りです。
- パケットキャプチャ: 指定したネットワークインターフェイス上のパケットを取得します。
- プロトコル解析(Dissection): キャプチャしたパケットを、そのプロトコル仕様に基づいて構造的に解析し、各フィールドの意味や値を表示します。Wiresharkは、非常に多くのプロトコルディセクタを内蔵しています。
- フィルタリング: 大量のパケットの中から、特定の条件(IPアドレス、ポート番号、プロトコルなど)に合致するパケットのみを表示したり、キャプチャしたりします。
- 統計情報: パケットの種類、通信量、応答時間など、キャプチャしたデータに関する様々な統計情報を生成します。
- ストリーム追跡: TCPやUDPなどのコネクションにおける一連のパケットを再構成し、アプリケーション層のデータを表示します。
1.2. Luaとは
Luaは、軽量で拡張性が高く、組み込み用途に適したスクリプト言語です。シンプルで習得しやすい構文を持ちながら、強力なデータ構造(特にテーブル)や高速な実行性能を備えています。C言語などで書かれたホストアプリケーションに組み込んで使用されることが多く、柔軟な設定や機能拡張の手段として広く採用されています。Wiresharkも、このLuaをスクリプト実行環境として内部に組み込んでいます。
1.3. WiresharkとLuaの連携のメリット
WiresharkにLuaが組み込まれていることで、ユーザーはWiresharkの機能を柔軟に拡張・カスタマイズできるようになります。そのメリットは多岐にわたります。
- 新しいプロトコルの解析: 標準のWiresharkが対応していないカスタムプロトコルや、最新のプロトコル、あるいは特定のアプリケーションが使用する独自プロトコルなどを解析するディセクタを自分で作成できます。これはWireshark Luaの最も強力な機能の一つです。
- データの自動集計・分析: キャプチャしたパケットの中から特定の情報を抽出し、集計や統計処理を行うスクリプトを作成できます。例えば、特定のAPI呼び出しの回数をカウントしたり、HTTPリクエストのユーザーエージェントを一覧表示したり、特定のセキュリティイベントの発生頻度を調べたりすることが可能です。
- 表示のカスタマイズ: パケットの詳細表示ツリーに独自の情報を追加したり、表示カラムに特定のフィールド値を表示したり、パケットリストの表示をカスタマイズしたりできます。
- ワークフローの自動化: 定型的な解析作業や、特定のイベントが発生した際の通知などをスクリプトで自動化できます。
- 学習と実験: プロトコルの仕組みやWiresharkの内部動作について、実際にコードを書いて実験しながら深く理解することができます。
Wireshark Luaを利用することで、Wiresharkを単なるパケットビューアとしてだけでなく、強力な分析プラットフォームとしてさらに活用できるようになるのです。
2. Wireshark Luaの基本
Wiresharkは、起動時に特定のディレクトリにあるLuaスクリプトファイルを読み込み、実行します。これにより、スクリプトはWiresharkの実行環境に組み込まれ、その機能の一部となります。
2.1. Luaインタープリタの組み込み
Wiresharkのビルド時にLuaサポートが有効になっている必要があります。公式に配布されているバイナリ版のWiresharkは、通常Luaが有効になっています。ご自身でソースコードからビルドする場合は、CMakeのオプションでLuaを有効にする必要があります。
Wiresharkに組み込まれているLuaのバージョンは、Wiresharkのバージョンによって異なりますが、一般的にはLua 5.1またはLua 5.2が使われることが多いです。スクリプトを記述する際は、使用しているWiresharkのバージョンがサポートしているLuaのバージョンを確認すると良いでしょう。Wiresharkのヘルプメニューから「About Wireshark」を選び、「Configuration」タブを見れば、Luaのバージョンが表示されています。
2.2. スクリプトの種類
Wireshark Luaスクリプトは、その主な用途によっていくつかの種類に分けられますが、最も一般的で強力なのは「Dissector」と「Tap」です。
-
Dissector スクリプト:
新しいプロトコルの解析、または既存のプロトコル解析の修正を行います。パケットのバイナリデータを構造化された情報(フィールド)に分解し、Wiresharkの詳細ペインに表示されるツリー構造を構築します。最も複雑ですが、Wiresharkの核となるプロトコル解析機能を拡張するものです。 -
Tap スクリプト:
パケットの内容を調べ、情報を集計したり、特定のイベントを検出したりするのに使われます。各パケットがディセクタによって解析された後、Tapスクリプトにその解析結果が渡されます。Tapスクリプトはパケットの詳細表示には直接関与しませんが、統計情報の生成やログ出力などに利用できます。Tapは、Tap
クラスを使って実装されます。
他にも、初期設定を行うためのスクリプトや、メニュー項目を追加するスクリプトなどがありますが、主に利用されるのはDissectorとTapです。
2.3. 実行方法:スクリプトの保存場所
Wiresharkは起動時に、特定のディレクトリにある.lua
ファイルを自動的に読み込んで実行します。このディレクトリは「Personal Lua Plugins directory」または「Global Lua Plugins directory」と呼ばれます。
これらのディレクトリの場所は、オペレーティングシステムやWiresharkのバージョンによって異なりますが、Wiresharkのヘルプメニューから「About Wireshark」を選び、「Folders」タブを見れば正確な場所を確認できます。
- Personal Lua Plugins directory: ユーザー個人の設定ディレクトリ内にあります。ここにスクリプトを置くのが一般的です。他のユーザーの設定に影響を与えません。例:
C:\Users\YourUsername\AppData\Roaming\Wireshark\plugins\lua
(Windows),~/.config/wireshark/plugins/lua
(Linux),~/Library/Application Support/Wireshark/plugins/lua
(macOS) - Global Lua Plugins directory: Wiresharkのインストールディレクトリ内にあります。管理者権限が必要な場合があり、すべてのユーザーに影響します。
通常は、Personal Lua Plugins directoryに.lua
ファイルを作成して保存すれば、次回Wireshark起動時に自動的に読み込まれます。
コマンドラインオプション:
Wiresharkをコマンドラインから起動する際に、特定のLuaスクリプトを実行することも可能です。
wireshark -X lua_script:<ファイルパス>
: 指定したスクリプトファイルを読み込みます。wireshark -X lua_script:<スクリプト名>
: プラグインディレクトリ内の指定したスクリプトを読み込みます(ファイル名のみの場合)。wireshark -X lua_script1:... -X lua_script2:...
: 複数のスクリプトを指定できます。
これらのオプションは、特定の解析タスクのために一時的にスクリプトを読み込みたい場合などに便利です。
2.4. 基本的なLua構文(Wireshark Luaでよく使うもの)
Wireshark Luaスクリプトを書く上で必要となる基本的なLuaの構文要素をいくつか紹介します。Luaは非常にシンプルで、C言語やPython、JavaScriptなどの経験があれば容易に習得できます。
-
コメント:
一行コメントは--
、複数行コメントは--[[ ... ]]
または--[=[ ... ]=]
を使用します。
“`lua
— これは一行コメントです–[[
これは
複数行コメントです
]]
“` -
変数:
変数の宣言にキーワードは不要です。初めて代入する際に変数が作成されます。デフォルトはnil
です。大文字・小文字を区別します。
lua
local my_variable = 123 -- ローカル変数 (推奨)
global_variable = "hello" -- グローバル変数 (非推奨)
local
キーワードを使うことで、変数のスコープを限定できます。Wireshark Luaスクリプトでは、他のスクリプトとの名前空間の衝突を避けるため、ローカル変数を使うことが強く推奨されます。 -
データ型:
Luaの基本的なデータ型には、nil
,boolean
,number
(浮動小数点数)、string
,function
,thread
,userdata
,table
があります。Wireshark Luaではtable
,number
,string
,boolean
,function
を主に使用します。また、Wireshark固有の型(Proto
,ProtoField
,Tvb
,Packet
など)も登場します。 -
テーブル:
Luaのテーブルは、配列としてもハッシュマップ(連想配列)としても使える強力なデータ構造です。Wireshark Luaでは、プロトコルのフィールド定義や情報の集計など、様々な場所でテーブルを使用します。
“`lua
— 配列風
local my_array = {“apple”, “banana”, “cherry”}
print(my_array[1]) — Luaの配列は1から始まるインデックス— ハッシュマップ風
local my_dict = { name = “Alice”, age = 30, city = “Tokyo” }
print(my_dict.name)
print(my_dict[“age”])— 混合
local mixed_table = { [1] = “first”, [“key”] = “value”, name = “Bob” }
“` -
関数:
function
キーワードを使って関数を定義します。
“`lua
function greet(name)
return “Hello, ” .. name .. “!” — 文字列連結は ..
endlocal message = greet(“World”)
print(message)
“` -
制御構造:
if
,elseif
,else
,end
による条件分岐、while
,for
によるループがあります。
“`lua
— if文
local x = 10
if x > 0 then
print(“Positive”)
elseif x < 0 then
print(“Negative”)
else
print(“Zero”)
end— forループ (数値範囲)
for i = 1, 5 do
print(i) — 1から5まで
end— forループ (テーブルの要素)
local fruits = {“apple”, “banana”, “cherry”}
for i, fruit in ipairs(fruits) do — ipairsは配列要素を順に辿る
print(i, fruit)
endlocal person = { name = “Alice”, age = 30 }
for key, value in pairs(person) do — pairsはハッシュマップ要素を順不同で辿る
print(key, value)
end
“` -
比較演算子:
==
,~=
(等しくない),<
,>
,<=
,>=
-
論理演算子:
and
,or
,not
-
Wireshark固有のグローバル変数:
Wireshark Lua環境では、Wiresharkの機能にアクセスするためのグローバルなオブジェクトがいくつか提供されています。これらはAPIの中心となります。例えばProto
,Tap
,Pinfo
,Tvb
,console
などです。これらはWireshark Lua APIのセクションで詳しく解説します。
3. 導入方法と最初のスクリプト
3.1. Wiresharkのインストール(Luaサポート付き)
まず、お使いのOSにWiresharkがインストールされていることを確認します。公式ウェブサイト(https://www.wireshark.org/)から最新の安定版をダウンロードしてインストールするのが最も簡単です。通常、バイナリディストリビューションにはLuaインタープリタが組み込まれており、Luaサポートが有効になっています。
インストール後、Wiresharkを起動し、メニューの Help
-> About Wireshark
-> Configuration
タブで、Lua version
の項目にバージョン情報が表示されているか確認してください。表示されていれば、Luaサポートは有効です。
3.2. スクリプトの保存場所の特定
Wireshark Luaスクリプトを自動的に読み込ませるためには、適切なディレクトリに配置する必要があります。
- Wiresharkを起動します。
- メニューから
Help
->About Wireshark
を選択します。 - 開いたウィンドウで
Folders
タブを選択します。 - リストの中から
Personal Lua Plugins
またはGlobal Lua Plugins
という名前の項目を見つけます。その右側に表示されているパスが、スクリプトを保存すべきディレクトリです。 - このパスを開きます(ファイルエクスプローラーやターミナルなどでそのディレクトリに移動します)。通常、このディレクトリは最初は存在しないか、空の場合があります。もし存在しない場合は手動で作成してください。
例:
* Windows: C:\Users\YourUsername\AppData\Roaming\Wireshark\plugins\lua
* Linux: ~/.config/wireshark/plugins/lua
* macOS: ~/Library/Application Support/Wireshark/plugins/lua
今後作成するLuaスクリプト(拡張子.lua
)はこのディレクトリに保存します。
3.3. 簡単なスクリプトの作成と実行
最初に、Wireshark Luaが正しく動作するか確認するための簡単なスクリプトを作成してみましょう。このスクリプトは、Wiresharkの起動時にメッセージを表示するだけのものです。
テキストエディタ(メモ帳、VS Code、Sublime Textなど)を開き、以下のコードを入力します。
“`lua
— my_first_script.lua
— Wiresharkのコンソールにメッセージを出力する
— consoleはWireshark Luaが提供するグローバルオブジェクト
console.message(“Hello from Wireshark Lua!”)
console.message(“This script was loaded successfully.”)
— スクリプトが読み込まれたことを示すメッセージ
print(“my_first_script.lua loaded.”) — このprintはWiresharkのデバッグ出力などには出るが、ユーザーには見えにくい場合がある。console.messageが推奨。
— スクリプトが正常に読み込まれたことをWiresharkに通知する
— このreturn trueは、スクリプトがDissectorやTapとして登録される場合に重要だが、
— 単に実行するだけのスクリプトでも習慣としてつけておくと良い場合がある。
— ここでは必須ではないが、スクリプトが「成功した」ことを示す意図で記載。
return true
“`
このファイルを、先ほど確認した Personal Lua Plugins
ディレクトリに my_first_script.lua
という名前で保存します。
保存したら、Wiresharkを一度閉じてから再度起動します。
Wiresharkが起動すると、通常はメインウィンドウが表示されます。メニューの Tools
-> Lua
-> Console
を選択して、Luaコンソールウィンドウを開いてみてください。
Luaコンソールに、先ほどスクリプトに記述した以下のメッセージが表示されているはずです。
Hello from Wireshark Lua!
This script was loaded successfully.
もしメッセージが表示されていれば、Wiresharkは正しくLuaスクリプトを読み込み、実行できています。おめでとうございます!これでWireshark Lua開発の第一歩を踏み出しました。
もし表示されない場合は、以下の点を確認してください。
- スクリプトファイルの名前が
.lua
で終わっているか。 - スクリプトファイルが、
About Wireshark
->Folders
タブで確認したPersonal Lua Plugins
ディレクトリに保存されているか。 - Wiresharkを再起動したか。
About Wireshark
->Configuration
タブでLuaサポートが有効になっているか。- Luaコンソールが開いているか。
また、Wiresharkの起動時にエラーが発生している場合、Wiresharkのメインウィンドウ下部にあるステータスバーにエラーメッセージが表示されたり、ターミナルからWiresharkを起動した場合はターミナルにエラー出力が表示されたりすることがあります。
4. 主要なWireshark Lua API
Wireshark LuaスクリプトからWiresharkの機能にアクセスするためには、Wiresharkが提供する様々なAPIを利用します。ここでは、特に重要でよく使用されるAPIについて詳しく解説します。
4.1. Dissector クラス (Proto
, ProtoField
, Dissector
)
新しいプロトコルディセクタを作成するためのAPI群です。Wireshark Luaの最も強力な機能であり、最も複雑な部分でもあります。
-
Proto.new(protocol_name, protocol_abbrev)
:
新しいプロトコルを作成します。protocol_name
は人が読めるプロトコル名(例: “My Custom Protocol”)、protocol_abbrev
はWireshark内部で使用される省略名(例: “myproto”)です。省略名は、表示フィルタやカラールールなどで使用できます。戻り値は新しく作成されたProto
オブジェクトです。
lua
local my_protocol = Proto.new("My Custom Protocol", "myproto") -
ProtoField.new(name, abbrev, type, valuestring, mask, ett, description)
:
プロトコル内のフィールドを定義します。これはフィールドが持つべきメタデータ(名前、型など)を定義するものであり、実際のパケットデータの解析はこの後に行います。name
: フィールドの人が読める名前(例: “Sequence Number”)。abbrev
: フィールドの省略名(例: “myproto.seq_num”)。表示フィルタなどで使用します。必ず親プロトコルの省略名で始まる必要があります。type
: フィールドのデータ型を指定します。ftypes
グローバルテーブルに定義されている定数を使用します。例:ftypes.UINT32
(符号なし32ビット整数),ftypes.BYTES
(バイト列),ftypes.STRING
(文字列) など多数あります。valuestring
(オプション): 数値などを人が読める文字列に変換するためのテーブルを指定します。例えば、enum値など。mask
(オプション): フィールドがマスクで抽出されるビットの一部である場合に指定します。ett
(オプション): このフィールド自身がツリーの展開可能なノードである場合に、そのためのETT(Expansion Tree Type)を指定します。description
(オプション): フィールドの説明文字列。
戻り値は新しく作成されたProtoField
オブジェクトです。このオブジェクトは、Proto
オブジェクトのテーブルに、その省略名をキーとして追加する必要があります。
“`lua
— フィールドの定義
local my_protocol_seq_num = ProtoField.new(“Sequence Number”, “myproto.seq_num”, ftypes.UINT32)
local my_protocol_data = ProtoField.new(“Data”, “myproto.data”, ftypes.BYTES)
— プロトコルにフィールドを追加
my_protocol.fields = { my_protocol_seq_num, my_protocol_data }
— またはテーブル形式で
— my_protocol.fields = {
— my_protocol_seq_num,
— my_protocol_data
— }
``
Proto
フィールドを追加するテーブルは、オブジェクトの
fields` 属性に設定します。 -
Proto:dissector(tvb, pinfo, tree)
:
プロトコルディセクタの本体となる関数です。この関数は、Wiresharkがパケットを解析する際に、該当するパケット(またはパケットの一部)に対して呼び出されます。tvb
:Tvb
オブジェクト。解析対象のバイナリデータ(バイト列)へのアクセスを提供します。pinfo
:Pinfo
オブジェクト。現在のパケットに関する情報(フレーム番号、タイムスタンプ、長さなど)を提供します。-
tree
:TreeItem
オブジェクト。Wiresharkの詳細ペインに表示される解析ツリーのルートノード(または親ノード)です。ここに解析結果を追加していきます。
この関数の中で、tvb
を読み取り、その値を定義済みのProtoField
に関連付け、tree
に表示ノードとして追加する処理を行います。
“`lua
function my_protocol.dissector(tvb, pinfo, tree)
— プロトコルヘッダーが十分な長さがあるかチェック
local header_len = 8 — 例: シーケンス番号4バイト + データ長4バイト
if tvb:len() < header_len then
— データが不完全な場合は解析を中止
pinfo.desegment_len = header_len – tvb:len() — 不足バイト数を知らせる
return
end— 解析ツリーにこのプロトコルのノードを追加
local subtree = tree:add(my_protocol, tvb, “My Custom Protocol Data”)— シーケンス番号 (オフセット0から4バイト) を読み取り、ツリーに追加
local seq_num = tvb:rto_uint32(0)
subtree:add(my_protocol_seq_num, tvb:range(0, 4), seq_num)— データ長 (オフセット4から4バイト) を読み取り
local data_len = tvb:rto_uint32(4)— データ本体 (オフセット8からdata_lenバイト) を読み取り、ツリーに追加
local data_start_offset = 8
if tvb:len() >= data_start_offset + data_len then
subtree:add(my_protocol_data, tvb:range(data_start_offset, data_len))
else
— データが不完全な場合
pinfo.desegment_len = data_start_offset + data_len – tvb:len()
subtree:add(my_protocol_data, tvb:range(data_start_offset, tvb:len() – data_start_offset)):set_text(“Incomplete Data”)
return
end— パケットリストのInfoカラムに情報を追加
pinfo.cols.info:append(” Seq:” .. seq_num .. ” Len:” .. data_len)
end
``
tvb:rto_*関数は、指定したオフセットから指定した型の値をネットワークバイトオーダー(ビッグエンディアン)で読み取ります。
tvb:range(offset, length)は、指定した範囲の
Tvbオブジェクトのサブセットを返します。
TreeItem:add(field, tvb_range, value)` は、解析ツリーにフィールドノードを追加します。
-
ディセクタの登録:
作成したディセクタをWiresharkに認識させるためには、特定のトランスポート層プロトコル(TCPやUDPなど)のポート番号に関連付けるのが一般的です。
“`lua
— UDPポート 12345 にこのプロトコルを登録する
local udp_port = Dissector.get(“udp.port”)
udp_port:add(12345, my_protocol)— TCPポート 54321 にこのプロトコルを登録する
local tcp_port = Dissector.get(“tcp.port”)
tcp_port:add(54321, my_protocol)
``
Dissector.get(“protocol.attribute”)は、既存のディセクタの特定の属性(例:
udp.portはUDPディセクタのポートテーブル)を取得します。その属性に対して
:add(value, dissector)` を呼び出すことで、指定した値(例: ポート番号)に対応するディセクタとして、作成したプロトコルを登録できます。 -
他のディセクタの呼び出し (
call_dissector
)
プロトコル解析の途中で、他のプロトコルディセクタに処理を委ねたい場合があります(例: ペイロードがHTTPデータである場合など)。
“`lua
— HTTPディセクタを取得
local http_dissector = Dissector.get(“http”)— 残りのデータ(tvb:range(…))をHTTPディセクタに解析させる
— tvb:range(offset) はoffset以降全てのデータ
http_dissector:call(tvb:range(header_len), pinfo, tree)
``
Dissector:call(tvb_range, pinfo, tree)を呼び出すことで、指定した
tvb_range` に対して他のディセクタを実行させることができます。 -
ポストディセクタの登録 (
register_postdissector
)
すべてのプロトコルディセクタによる解析が完了した後に実行されるディセクタを登録できます。これは、複数のプロトコルの情報に基づいて分析を行いたい場合などに便利です。
“`lua
local my_post_dissector = Proto.new(“My Post Dissector”, “mypost”)function my_post_dissector.dissector(tvb, pinfo, tree)
— 例: TCPとHTTPの情報を使って何かする
local tcp_layer = pinfo.layers.tcp
local http_layer = pinfo.layers.httpif tcp_layer and http_layer then local seq = tcp_layer.seq -- TCPシーケンス番号にアクセス local method = http_layer.request.method -- HTTPメソッドにアクセス -- 解析ツリーのルートに追加することも可能 local subtree = tree:add(my_post_dissector, tvb:range(0,0), "Post Analysis") subtree:append_text(": TCP-Seq=" .. tostring(seq) .. ", HTTP-Method=" .. tostring(method)) end
end
— ポストディセクタとして登録
register_postdissector(my_post_dissector)
``
Tvb
ポストディセクタは、パケット全体のを受け取りますが、通常はパケット情報
pinfoを通じて、既に解析された各プロトコル層の情報 (
pinfo.layers.protocol_abbrev`) にアクセスして処理を行います。
4.2. Tap クラス (Tap
)
Tapは、各パケットがディセクタによって完全に解析された後に呼び出され、統計情報の収集や特定のイベントの検出などを行うための機能です。
-
Tap.new(tap_filter, tap_name)
:
新しいTapオブジェクトを作成します。tap_filter
(オプション): Wiresharkの表示フィルタ構文で、このTapが処理対象とするパケットを限定します。例えば “tcp.port == 80” とすると、TCPポート80のパケットのみがTapに渡されます。nil
または空文字列の場合は、すべてのパケットが対象となります。tap_name
(オプション): このTapの名前です。統計ウィンドウなどに表示される可能性があります。
戻り値は新しく作成されたTap
オブジェクトです。
lua
local http_req_tap = Tap.new("http.request", "HTTP Request Counter")
local dns_tap = Tap.new("dns", "DNS Query Analyzer")
local all_packet_tap = Tap.new(nil, "All Packet Counter")
-
Tap:packet(pinfo, tvb, edp)
:
Tapの本体となる関数です。対象となるパケットが見つかるたびに呼び出されます。この関数内で、パケット情報pinfo
や解析結果edp
を利用して必要な処理を行います。pinfo
:Pinfo
オブジェクト。現在のパケットに関する情報。tvb
:Tvb
オブジェクト。現在のパケットのバイナリデータ。Dissectorとは異なり、Tapでは通常tvb
を直接読むより、既に解析されたフィールド情報 (edp
) にアクセスする方が効率的です。edp
: Extracted Dissector Protocol data。ディセクタによって解析されたフィールド情報を含むテーブルです。これがTapの主要な入力となります。edp
はプロトコル省略名(例: “tcp”, “http”)をキーとして、そのプロトコルの解析結果へのアクセスを提供します。
“`lua
— HTTPリクエスト数をカウントするTapの例
local http_request_count = 0
local user_agents = {} — ユーザーエージェントを集計するテーブルlocal http_tap = Tap.new(“http.request”, “HTTP Request Analyzer”)
function http_tap.packet(pinfo, tvb, edp)
— edp から HTTP プロトコル情報にアクセス
local http = edp.httpif http then -- HTTPレイヤーが存在することを確認 (Tapフィルタを使っている場合は不要な確認かもしれない) http_request_count = http_request_count + 1 -- HTTPリクエストメソッド (http.request.method フィールド) にアクセス -- edpテーブルは、field_abbrev = field_value の形式でフィールド値にアクセスできることが多い local method = http["http.request.method"] if method then -- フィールドの値は通常、Wireshark Lua固有のオブジェクト(StringValueなど) -- tostring() や field:value() でLuaの基本型に変換する必要がある場合がある method = tostring(method) -- 文字列に変換 -- print("HTTP Request Method:", method) -- デバッグ出力 end -- HTTP User-Agent (http.user_agent フィールド) にアクセス local user_agent_field = http["http.user_agent"] if user_agent_field then local ua_string = tostring(user_agent_field) user_agents[ua_string] = (user_agents[ua_string] or 0) + 1 end -- Pinfoオブジェクトからパケット番号や時間を取得 local frame_num = pinfo.number local time_str = pinfo.abs_ts_str -- Luaコンソールにログ出力 console.message(string.format("Packet %d (%s): HTTP Request found. Method: %s, User-Agent: %s", frame_num, time_str, method or "N/A", ua_string or "N/A")) end
end
— キャプチャ終了時(または統計ウィンドウ表示時など)に呼び出される関数
— Tapの処理結果を集計して表示するのに使う
function http_tap.draw(stats_tree)
stats_tree:add_text(“— HTTP Request Summary —“)
stats_tree:add_text(“Total HTTP Requests: ” .. http_request_count)stats_tree:add_text("User Agents:") -- user_agents テーブルをループして表示 -- pairs はテーブルのキーと値を辿るイテレータ for ua, count in pairs(user_agents) do stats_tree:add_text(" - " .. ua .. ": " .. count) end stats_tree:add_text("--------------------------")
end
— Tapを有効にする (通常は Tap.new() を呼び出した時点で有効)
— tap:enable() は明示的に有効化する場合に使用
— tap:disable() で無効化できる
— http_tap:enable()
``
Tap:packet()関数はパケットごとに呼び出されるため、処理は高速である必要があります。重い処理やファイルI/Oなどは避け、情報の収集や簡単な計算に留めるのが良いでしょう。集計結果の表示などは、キャプチャ終了時などに呼び出される
Tap:draw()関数(あるいは
Tap:reset()`)で行うのが一般的です。 -
Tap:draw(stats_tree)
:
統計ウィンドウなどが表示される際に呼び出される関数です。stats_tree
オブジェクトを使って、Wiresharkの統計ウィンドウにカスタムの統計情報を表示できます。
stats_tree:add_text(text)
でテキスト行を追加できます。
上記の例では、この関数で収集したHTTPリクエスト数やユーザーエージェントの集計結果を表示しています。 -
Tap:reset()
:
統計情報がリセットされる際に呼び出される関数です。新しいキャプチャを開始する前などに、Tapが保持している統計情報をリセットするために使用します。上記の例であれば、http_request_count = 0
やuser_agents = {}
のように、集計用変数を初期化する処理をここに記述します。
4.3. その他の重要なAPI
-
Pinfo
(Packet Info):
現在のパケットに関する情報を提供するオブジェクトです。Tap:packet
やProto:dissector
関数の第2引数として渡されます。pinfo.number
: フレーム番号 (整数)。pinfo.abs_ts
: 絶対タイムスタンプ (Lua number)。pinfo.abs_ts_str
: 絶対タイムスタンプ文字列。pinfo.rel_ts
: 相対タイムスタンプ (キャプチャ開始からの経過時間)。pinfo.fd
: FrameData オブジェクト。追加情報あり。pinfo.len
: パケットのオンワイヤ長 (整数)。pinfo.cap_len
: キャプチャされたパケット長 (整数)。pinfo.cols
: 各表示カラムに表示されるテキストを操作するためのオブジェクト。pinfo.cols.protocol
: Protocolカラムのテキスト。pinfo.cols.source
: Sourceカラムのテキスト。pinfo.cols.destination
: Destinationカラムのテキスト。pinfo.cols.length
: Lengthカラムのテキスト。pinfo.cols.info
: Infoカラムのテキスト。
各カラムオブジェクトは:set(text)
でテキストを設定、:append(text)
でテキストを追加できます。
pinfo.private
: ディセクタ間で共有できるプライベートなテーブル。pinfo.layers
: ポストディセクタなどで、既に解析された各プロトコル層の情報にアクセスするためのテーブル。キーはプロトコルの省略名(例:pinfo.layers.tcp
,pinfo.layers.http
)。
-
Tvb
(TV buffer):
パケットのバイナリデータへのアクセスを提供するオブジェクトです。Proto:dissector
関数の第1引数、Tap:packet
関数の第2引数として渡されます。Tvbオブジェクトは、バイト列そのものではなく、バイト列への参照とオフセット、長さを保持します。tvb:len()
: Tvbが参照しているバイト列の長さ。tvb:captured_len()
: キャプチャされたバイト列の長さ(スライスされている場合など)。tvb:offset()
: 元のパケットデータにおける、このTvbの開始オフセット。tvb:raw()
: 生のバイト列を含むLua文字列を返しますが、大きなパケットではメモリ消費に注意が必要です。tvb:range(offset, length)
: 指定したオフセットと長さを持つ新しいTvbオブジェクトを作成します。オフセットは元のTvbの開始位置からの相対オフセットです。tvb:get_uint*(offset)
: 指定したオフセットの符号なし整数値をホストバイトオーダーで読み取ります。例:tvb:get_uint8(offset)
,tvb:get_uint16(offset)
,tvb:get_uint32(offset)
.tvb:rto_uint*(offset)
: 指定したオフセットの符号なし整数値をネットワークバイトオーダー(ビッグエンディアン)で読み取ります。例:tvb:rto_uint8(offset)
,tvb:rto_uint16(offset)
,tvb:rto_uint32(offset)
.tvb:get_string(offset, length)
: 指定した範囲のバイト列をLua文字列として読み取ります。tvb:get_nstring(offset, length)
: 指定した範囲のバイト列をnull終端文字列として読み取ります。tvb:get_bit(offset, bit_offset)
: 指定したバイトオフセット内の特定ビット(0-7)を取得します。
バイナリデータ解析の中心となるオブジェクトです。
-
TreeItem
:
プロトコル詳細ペインに表示される解析ツリーのノードを表すオブジェクトです。Proto:dissector
関数の第3引数として渡されるtree
オブジェクトは、そのプロトコル解析のルートノード(または親ノード)です。tree:add(field, tvb_range, value)
: 指定したフィールド、データ範囲、値を使って、現在のノードの子ノードを追加します。field
はProtoField
オブジェクト、tvb_range
はTvb
オブジェクトの一部(通常はtvb:range(...)
で作成)、value
はそのフィールドの実際の値です。tree:add_packet_field(field)
: パケットデータ全体に関連付けられたフィールドを追加します。tree:add_text(text)
: データ範囲に関連付けられていない、単なるテキストノードを追加します。tree:append_text(text)
: 現在のノードのラベルにテキストを追加します。tree:set_text(text)
: 現在のノードのラベルをテキストで置き換えます。tree:add_expert_info(expert_type, expert_group, error_level, text)
: エキスパート情報を追加します(例: Checksumエラーなど)。
プロトコルディセクタが解析結果をユーザーに表示するための主要な手段です。
-
console
:
WiresharkのLuaコンソールウィンドウにメッセージを出力するためのオブジェクトです。デバッグやスクリプトの実行状況確認に便利です。console.message(text)
: 情報メッセージを出力します。console.warning(text)
: 警告メッセージを出力します。console.error(text)
: エラーメッセージを出力します。
-
prefs
:
Wiresharkの設定(Preference)にアクセスするためのオブジェクトです。Luaスクリプト独自のカスタム設定項目を追加したり、既存の設定値を読み取ったりできます。
“`lua
— カスタム設定項目の定義 (スクリプトの冒頭で行う)
local my_protocol_pref_port = Pref.uint(my_protocol, “port”, “UDP Port”, “UDP port to dissect as My Protocol”, 12345)— 設定値の利用 (dissector関数内など)
local configured_port = my_protocol_pref_port.value
— … そのポート番号を使ってディセクタを登録するなど …
“`
設定項目は、WiresharkのPreferenceウィンドウにLuaスクリプトのセクションとして表示され、GUIから値を変更できるようになります。これは、ポート番号など、スクリプトの動作を外部から設定可能にするのに非常に便利です。 -
wslua_dofile(filename)
:
指定したLuaファイルを読み込んで実行します。これにより、スクリプトを複数のファイルに分割して管理できます。パスは、Wiresharkの検索パス(プラグインディレクトリやグローバルディレクトリなど)からの相対パスで指定できます。
これらのAPIは、Wireshark Luaスクリプトの機能を構築するための基礎となります。公式のWireshark開発者ドキュメントやLuaのAPIリファレンスを参照すると、さらに詳細な情報や利用可能な関数、定数などを確認できます。
5. 実践的な例
ここでは、前述のAPIを使って、より具体的なタスクを実行するWireshark Luaスクリプトの例をいくつか紹介します。
例1:シンプルなカスタムプロトコルを解析するディセクタ
ここでは、UDPポート 12345 を使用する、以下のような単純なカスタムプロトコルを想定します。
- 先頭4バイト: 符号なし32ビット整数(ビッグエンディアン)で、ペイロードの長さを示す。
- 続くNバイト: ペイロードデータ(ペイロード長フィールドの値と同じバイト数)。
このプロトコルを解析するディセクタを作成します。
“`lua
— simple_custom_proto_dissector.lua
— 1. プロトコルの定義
local my_custom_protocol = Proto.new(“Simple Custom Protocol”, “simple_custom”)
— 2. フィールドの定義
local proto_field_payload_len = ProtoField.new(“Payload Length”, “simple_custom.payload_len”, ftypes.UINT32)
local proto_field_payload_data = ProtoField.new(“Payload Data”, “simple_custom.payload_data”, ftypes.BYTES)
— 3. プロトコルにフィールドを関連付け
my_custom_protocol.fields = {
proto_field_payload_len,
proto_field_payload_data
}
— 4. ディセクタ関数の実装
function my_custom_protocol.dissector(tvb, pinfo, tree)
— このディセクタが処理するパケットのバイト範囲
local this_tvb = tvb
— 親プロトコルのペイロードとして呼び出される場合、tvbは親のオフセットを含む可能性がある
— ここでは、UDPペイロードの先頭から解析を開始すると仮定
-- プロトコルヘッダー(ペイロード長フィールド)の長さ
local header_len = 4
-- ヘッダーの長さが十分にあるか確認
if this_tvb:len() < header_len then
-- データが不完全な場合、Wiresharkにさらにデータが必要であることを通知
pinfo.desegment_len = header_len - this_tvb:len()
return
end
-- 解析ツリーにこのプロトコル用のノードを追加
-- tree:add() の第1引数は、そのノードがどのプロトコルに関連するかを示す
local subtree = tree:add(my_custom_protocol, this_tvb, "Simple Custom Protocol Data")
-- ペイロード長の読み取りとツリーへの追加
local payload_len = this_tvb:rto_uint32(0) -- オフセット0から4バイト、ビッグエンディアン
subtree:add(proto_field_payload_len, this_tvb:range(0, header_len), payload_len)
-- ペイロードデータのオフセットと期待される終端オフセット
local payload_start_offset = header_len
local expected_end_offset = payload_start_offset + payload_len
-- ペイロードデータの長さが十分にあるか確認
if this_tvb:len() < expected_end_offset then
-- データが不完全な場合
pinfo.desegment_len = expected_end_offset - this_tvb:len()
-- 存在する部分だけをツリーに追加し、不完全であることを示す
subtree:add(proto_field_payload_data, this_tvb:range(payload_start_offset, this_tvb:len() - payload_start_offset)):set_text("Payload Data (Incomplete)")
return
end
-- ペイロードデータの読み取りとツリーへの追加
subtree:add(proto_field_payload_data, this_tvb:range(payload_start_offset, payload_len))
-- パケットリストのInfoカラムを更新
pinfo.cols.info:append(" Len=" .. payload_len)
-- 解析されたバイト数をWiresharkに通知
-- これにより、次のプロトコルディセクタが正しい位置から解析を開始できる
-- このシンプルな例では、このディセクタが最後まで解析するため不要だが、
-- チェーンする場合やパディングがある場合などは重要になる
-- return expected_end_offset
end
— 5. UDPポート12345にディセクタを登録
— まずUDPポートディセクタテーブルを取得
local udp_port_table = Dissector.get(“udp.port”)
— UDPポート12345にmy_custom_protocolディセクタを登録
udp_port_table:add(12345, my_custom_protocol)
— スクリプトが読み込まれたことをコンソールに表示
console.message(“Simple Custom Protocol dissector loaded for UDP port 12345.”)
return true — スクリプトの正常読み込みを通知
“`
使用方法:
- 上記のコードを
simple_custom_proto_dissector.lua
という名前で Personal Lua Plugins directory に保存します。 - Wiresharkを再起動します。
-
UDPポート 12345 を使用するパケットをキャプチャします。例えば、Pythonなどで以下のような簡単なUDPサーバー/クライアントを作成して通信させてみます。
サーバー (
udp_server.py
):
“`python
import socket
import structUDP_IP = “127.0.0.1”
UDP_PORT = 12345sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind((UDP_IP, UDP_PORT))print(“UDP server listening on port”, UDP_PORT)
while True:
data, addr = sock.recvfrom(1024) # buffer size is 1024 bytes
print(“received message from”, addr)
# パケットの構造は Length (4 bytes) + Data (Length bytes)
# サーバー側では解析しないが、パケットを受信する
# 返信する場合は同様の構造でデータを送信# 例として、受信データを確認して返信する if len(data) >= 4: payload_len = struct.unpack("!I", data[:4])[0] # !I はネットワークバイトオーダーの符号なし32ビット整数 payload_data = data[4:] print(f" Received Length: {payload_len}, Data: {payload_data.decode('utf-8', errors='ignore')}") # 簡単な返信を返す (Length 5 + "Reply") reply_payload = b"Reply" reply_len = len(reply_payload) reply_packet = struct.pack("!I", reply_len) + reply_payload sock.sendto(reply_packet, addr) print(" Sent Reply")
“`
クライアント (
udp_client.py
):
“`python
import socket
import struct
import timeUDP_IP = “127.0.0.1”
UDP_PORT = 12345sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
message_data = b”Hello, Wireshark Lua!”
message_len = len(message_data)パケット構造: Length (4 bytes) + Data (Length bytes)
struct.pack(“!I”, message_len) は、整数 message_len をネットワークバイトオーダー(!), 符号なし整数(I) の4バイトに変換
packet_data = struct.pack(“!I”, message_len) + message_data
print(f”Sending {len(packet_data)} bytes to {UDP_IP}:{UDP_PORT}”)
sock.sendto(packet_data, (UDP_IP, UDP_PORT))
print(“Sent message:”, message_data)サーバーからの返信を受信する(オプション)
sock.settimeout(5) # タイムアウトを設定
try:
reply_data, server_addr = sock.recvfrom(1024)
print(“Received reply from”, server_addr)
if len(reply_data) >= 4:
reply_len = struct.unpack(“!I”, reply_data[:4])[0]
reply_payload = reply_data[4:]
print(f” Reply Length: {reply_len}, Data: {reply_payload.decode(‘utf-8′, errors=’ignore’)}”)
else:
print(” Received incomplete reply”)
except socket.timeout:
print(“No reply received within timeout.”)sock.close()
“`
両方のPythonスクリプトを実行し、通信させてみてください。 -
Wiresharkで
localhost
または適切なインターフェイスを選択し、キャプチャを開始します。 - キャプチャされたパケットの中に、UDPポート12345 のパケットが表示されるはずです。このパケットを選択し、詳細ペインを確認すると、新しく定義した “Simple Custom Protocol” が表示され、ペイロード長とペイロードデータが解析されていることが確認できます。
このようにして、Wiresharkが標準で対応していない独自のプロトコルを解析するディセクタを作成できます。
例2:特定のフィールド値を集計するタップ
次に、Tapスクリプトを使って、HTTPリクエストに含まれる User-Agent
ヘッダーの値を集計する例を示します。
“`lua
— http_user_agent_tap.lua
— ユーザーエージェントの出現回数を記録するテーブル
local user_agent_counts = {}
local total_http_requests = 0
— Tapオブジェクトを作成。HTTPリクエストのみを対象とするフィルタを適用。
local http_req_tap = Tap.new(“http.request”, “HTTP User-Agent Statistics”)
— パケットごとに呼び出される関数
function http_req_tap.packet(pinfo, tvb, edp)
— edp から HTTP プロトコル情報にアクセス
local http_info = edp.http
-- http_info が存在し、かつそれがHTTPリクエストであるフィルタがかかっていることを確認
-- Filter "http.request" は http_info が存在し、かつ request フィールドがある場合に true
if http_info and http_info.request then
total_http_requests = total_http_requests + 1
-- http.user_agent フィールドの値を取得
-- edpテーブルは、field_abbrev = field_value の形式でフィールド値にアクセスできる
local user_agent_value = http_info["http.user_agent"]
if user_agent_value then
-- Field オブジェクトから Lua 文字列に変換
local ua_string = tostring(user_agent_value)
-- テーブルでカウントを増やす
user_agent_counts[ua_string] = (user_agent_counts[ua_string] or 0) + 1
-- デバッグ用にコンソール出力
-- console.message("Found User-Agent: " .. ua_string .. " (Packet " .. pinfo.number .. ")")
end
end
end
— 統計情報ウィンドウに表示される関数
function http_req_tap.draw(stats_tree)
stats_tree:add_text(“— HTTP User-Agent Statistics —“)
stats_tree:add_text(“Total HTTP Requests Analyzed: ” .. total_http_requests)
stats_tree:add_text(“”) — 空行
stats_tree:add_text("User-Agent Counts:")
-- user_agent_counts テーブルをループして表示
-- pairs はテーブルのキーと値を順不同で辿る
-- 結果をソートしたい場合は、キーを取り出してソートしてから表示するなどの処理が必要
local sorted_uas = {}
for ua, count in pairs(user_agent_counts) do
table.insert(sorted_uas, {ua=ua, count=count})
end
-- カウントが多い順にソート (簡単な例)
table.sort(sorted_uas, function(a, b) return a.count > b.count end)
for _, item in ipairs(sorted_uas) do
stats_tree:add_text(string.format(" - %s: %d", item.ua, item.count))
end
stats_tree:add_text("---------------------------------")
end
— 統計情報のリセット時に呼び出される関数
function http_req_tap.reset()
console.message(“Resetting HTTP User-Agent Statistics.”)
user_agent_counts = {} — テーブルをクリア
total_http_requests = 0 — カウントをリセット
end
— スクリプト読み込み完了メッセージ
console.message(“HTTP User-Agent Tap loaded.”)
return true — スクリプトの正常読み込みを通知
“`
使用方法:
- 上記のコードを
http_user_agent_tap.lua
という名前で Personal Lua Plugins directory に保存します。 - Wiresharkを再起動します。
- HTTPトラフィックを含むパケットをキャプチャします(Webブラウザを開いてサイトを閲覧するなど)。
- キャプチャを停止します。
- メニューから
Statistics
->Lua
->HTTP User-Agent Statistics
を選択します。 - 開いたウィンドウに、キャプチャされたHTTPリクエストの総数と、User-Agentごとのカウントが表示されます。
この例では、Tapを使って特定のプロトコルのフィールド値にアクセスし、それを集計してWiresharkの統計ウィンドウに表示する方法を示しています。Tapは、プロトコル解析自体を変更することなく、パケットデータから特定の情報を抽出し、分析するのに非常に役立ちます。
例3:特定のイベントをコンソールに出力する
この例では、Tapを使ってTCP接続の開始(SYN)と終了(FINまたはRST)を検出し、Luaコンソールにログを出力します。
“`lua
— tcp_connection_logger_tap.lua
— Tapオブジェクトを作成。TCPパケットのみを対象とするフィルタを適用。
local tcp_tap = Tap.new(“tcp”, “TCP Connection Logger”)
— 接続の状態を追跡するためのテーブル(簡略化のためここでは未使用だが、実際は必要になる場合がある)
— local active_connections = {}
— パケットごとに呼び出される関数
function tcp_tap.packet(pinfo, tvb, edp)
— edp から TCP プロトコル情報にアクセス
local tcp_info = edp.tcp
if tcp_info then
-- Pinfoオブジェクトから送信元/宛先アドレスとポートを取得
-- src_ip/dst_ip は Addr オブジェクト。tostring() で文字列に変換。
local src_addr = tostring(pinfo.src) -- IPアドレスまたは解決されたホスト名
local dst_addr = tostring(pinfo.dst)
-- src_port/dst_port は Field オブジェクト。value で数値を取得。
local src_port = tcp_info.srcport.value
local dst_port = tcp_info.dstport.value
-- 接続識別子を作成(例: src_ip:src_port -> dst_ip:dst_port)
-- 実際には、コネクションは双方向なので、(addr1:port1, addr2:port2) のように正規化してキーを作成する必要がある
local conn_id_fwd = string.format("%s:%d -> %s:%d", src_addr, src_port, dst_addr, dst_port)
local conn_id_bwd = string.format("%s:%d -> %s:%d", dst_addr, dst_port, src_addr, src_port)
-- TCPフラグをチェック
local flags = tcp_info.flags
-- SYNフラグをチェック (新規接続)
-- flagsはFieldオブジェクト。valueで数値を取得し、ビット演算子でチェック。
-- tcp.flags.syn は ProtoField オブジェクトとして定義されているはずだが、直接アクセスできるか確認が必要
-- より確実なのは、フラグ値に対してビットマスクをかける方法、あるいはedpからフラグフィールド値にアクセスする方法
local is_syn = flags["tcp.flags.syn"] and flags["tcp.flags.syn"].value == 1 -- tcp.flags.syn フィールド値にアクセス
local is_fin = flags["tcp.flags.fin"] and flags["tcp.flags.fin"].value == 1
local is_rst = flags["tcp.flags.rst"] and flags["tcp.flags.rst"].value == 1
local event_type = nil
if is_syn then
event_type = "Connection Start (SYN)"
elseif is_fin then
event_type = "Connection End (FIN)"
elseif is_rst then
event_type = "Connection Reset (RST)"
end
-- イベントが見つかったらコンソールにログ出力
if event_type then
local log_message = string.format("Packet %d (%s): %s: %s",
pinfo.number, pinfo.abs_ts_str, event_type, conn_id_fwd)
console.message(log_message)
-- ここで active_connections テーブルを更新するなど、接続状態を追跡する処理を追加できる
-- 例えば、SYNの場合はテーブルに追加、FIN/RSTの場合はテーブルから削除
end
end
end
— Tapは統計情報表示は不要なので、draw関数は省略可
— 統計情報のリセット時に呼び出される関数 (今回は特にリセットするものはないが、例として残す)
function tcp_tap.reset()
console.message(“Resetting TCP Connection Logger (No state to reset).”)
— active_connections = {} — 状態を追跡する場合はここでリセット
end
— スクリプト読み込み完了メッセージ
console.message(“TCP Connection Logger Tap loaded.”)
return true
“`
使用方法:
- 上記のコードを
tcp_connection_logger_tap.lua
という名前で Personal Lua Plugins directory に保存します。 - Wiresharkを再起動します。
- TCPトラフィックを含むパケットをキャプチャします(Webサイトにアクセスしたり、Telnet/SSH接続を行ったりなど)。
- キャプチャ中に、Luaコンソール (
Tools
->Lua
->Console
) を開いておきます。 - TCP接続が確立される(SYNパケット)、終了する(FIN/RSTパケット)たびに、コンソールにメッセージが出力されます。
この例は、Tapを使って特定のプロトコルイベント(ここではTCPフラグの状態変化)を検出する方法を示しています。検出したイベントに基づいて、ログ出力、カウンターの更新、他の処理のトリガーなど、様々なアクションを実行できます。
6. デバッグとトラブルシューティング
Wireshark Luaスクリプトの開発中に発生する可能性のあるエラーや問題に対処するための基本的なデバッグ手法とトラブルシューティングのヒントです。
6.1. 構文エラーとロードエラー
Luaスクリプトの構文に誤りがある場合や、Wiresharkがスクリプトファイルを読み込めない場合、通常はWiresharkの起動時にエラーが発生します。
- Wiresharkのステータスバー: ウィンドウ下部のステータスバーにエラーメッセージが表示されることがあります。「Lua: An error occurred during script loading: [Error Details]」のようなメッセージが表示されていないか確認してください。
- ターミナル出力: コマンドラインからWiresharkを起動している場合 (
wireshark.exe
やwireshark
)、ターミナルに詳細なエラーメッセージが出力されることがあります。構文エラーの場合は、ファイル名と行番号が表示されることが多いです。 - Luaコンソール: スクリプトが部分的にロードされた後でエラーが発生した場合、Luaコンソール (
Tools
->Lua
->Console
) にエラーメッセージが出力されることがあります。console.error()
で出力したカスタムエラーメッセージもここに表示されます。
対処法:
* エラーメッセージに表示されているファイル名と行番号を確認し、該当箇所を修正します。
* Luaの基本的な構文(:
, .
, local
, function
, end
, then
など)を確認します。
* Personal Lua Plugins
ディレクトリにファイルが正しく配置されているか、ファイル名や拡張子 (.lua
) が正しいか確認します。
* Wiresharkを再起動して、スクリプトが再度読み込まれるようにします。
6.2. ランタイムエラー
スクリプトの構文は正しくても、実行時(例えばディセクタ関数やTap関数が呼び出された時)にエラーが発生することがあります。これは、予期しないパケットデータを受け取った場合や、APIの誤った使い方をした場合などに起こります。
- Luaコンソール: ランタイムエラーは通常、Luaコンソールに詳細なエラーメッセージとバックトレース(エラーが発生した関数の呼び出し履歴)と共に出力されます。
- Wiresharkの動作異常: スクリプトのエラーが原因で、Wiresharkがクラッシュしたり、特定のパケットで解析が停止したり、表示が崩れたりすることがあります。
対処法:
* Luaコンソールのエラーメッセージを確認します。特に、エラーが発生したファイル名と行番号、そしてエラーの種類(例: “attempt to index a nil value” – nil値のフィールドにアクセスしようとした、”bad argument #1 to ‘range’ (number expected, got nil)” – range関数の引数にnilを渡した、など)は重要な情報です。
* エラーが発生した箇所(ディセクタ関数やTap関数内の特定の行など)を特定し、その行のコードが想定通りに動作するか、特に変数が nil
になっていないかなどを確認します。
* console.message()
関数を使って、スクリプトの実行フローや変数の値をログ出力し、問題箇所を特定します。
lua
function my_protocol.dissector(tvb, pinfo, tree)
console.message("Dissector called for packet: " .. pinfo.number)
local header_len = 4
console.message("tvb length: " .. tvb:len())
if tvb:len() < header_len then
console.message("Not enough data for header.")
pinfo.desegment_len = header_len - tvb:len()
return
end
-- ... 処理 ...
end
* 特定のパケットでのみ問題が発生する場合は、そのパケットの詳細情報を確認し、スクリプトがそのパケットの構造を正しく扱えるか(オフセット、長さ、値など)をデバッグします。
* print()
関数も使えますが、出力先がWiresharkのデバッグログなど、コンソールとは異なる場合があり、扱いが少し難しいことがあります。通常は console.message()
を使うのが推奨されます。
6.3. Luaデバッガの利用
より高度なデバッグには、外部のLuaデバッガをWiresharkにアタッチして使用することも可能です。ZeroBrane StudioなどのIDEにはLuaデバッグ機能があり、WiresharkのLua環境に接続してステップ実行やブレークポイントの設定ができます。ただし、これにはWiresharkとデバッガ側の設定が必要であり、セットアップは少し複雑になります。公式ドキュメントやZeroBrane StudioのWiresharkデバッグに関する情報などを参照してください。
6.4. よくある落とし穴
- Luaのインデックスは1から始まる: C、Java、Pythonなどの多くの言語と異なり、Luaの配列や文字列のインデックスは1から始まります。
tvb:range()
などの関数でもオフセットは0から始まりますが、テーブル(配列として使う場合)の要素アクセスはmy_array[1]
のようになります。混同しないように注意が必要です。 nil
値の扱い: Luaで存在しないテーブルのキーや未初期化の変数にアクセスするとnil
が返されます。nil
値に対してメソッドを呼び出したり、算術演算を行ったりするとエラーになります。アクセスする前にif value ~= nil then ... end
のようにnil
チェックを行うか、テーブルアクセス時にデフォルト値を提供するvalue or default_value
のようなパターンを使うのが一般的です。- APIの引数と戻り値: Wireshark LuaのAPIは、独自のオブジェクト(
Tvb
,Pinfo
,Field
など)を引数にとったり返したりします。これらのオブジェクトのメソッドや属性にアクセスする際は、APIリファレンスを確認し、正しい使い方をする必要があります。特に、フィールド値はそのままではLuaの基本型(string, numberなど)ではない場合が多いので、tostring()
や.value
などを使って変換する必要があることを覚えておきましょう。 - メモリとパフォーマンス: ディセクタ関数やTap関数は、多数のパケットに対して何度も呼び出される可能性があります。これらの関数内で大量のメモリを確保したり、計算量の多い処理を行ったりすると、Wiresharkの動作が遅くなったり、メモリ不足になったりする可能性があります。特に大きなパケットデータ (
tvb:raw()
) を扱う際は注意が必要です。Tapで集計を行う場合などは、集計結果をテーブルに保持し、draw()
関数でまとめて表示するなど、効率的な処理を心がけましょう。
7. 応用と高度なトピック
Wireshark Luaでは、基本的なプロトコル解析や集計以外にも、様々な応用が可能です。
7.1. Wireshark設定の操作 (prefs
API)
前述の通り、prefs
APIを使うことで、WiresharkのPreference(設定)ウィンドウに独自の項目を追加し、GUIからスクリプトの動作を制御できるようになります。
“`lua
— prefs_example.lua
local my_script_pref = Proto.new(“My Script Preferences”, “myscript_prefs”)
— チェックボックス設定の追加
local pref_enable_feature = Pref.bool(my_script_pref, “enable_feature”, “Enable My Feature”, “Check to enable my custom feature”, true)
— 整数設定の追加
local pref_threshold = Pref.uint(my_script_pref, “threshold”, “Threshold Value”, “Value used for threshold checks”, 100)
— 文字列設定の追加
local pref_target_ip = Pref.string(my_script_pref, “target_ip”, “Target IP Address”, “IP address to monitor”, “192.168.1.1”)
— 設定値は .value でアクセス
— local is_feature_enabled = pref_enable_feature.value
— local current_threshold = pref_threshold.value
— local target_ip_str = pref_target_ip.value
— ディセクタやタップ関数内で設定値を利用する例
— … (例えば、Prefを定義したスクリプトと同じファイル内で) …
— function my_tap.packet(pinfo, tvb, edp)
— if pref_enable_feature.value then
— — feature が有効な場合の処理
— if pinfo.src == Address.ip(pref_target_ip.value) then
— — 送信元IPが設定値と一致する場合の処理
— …
— end
— end
— end
“`
Preference項目は、Wiresharkの起動時に読み込まれ、GUI上で変更されると .value
を通じてスクリプトからその変更を反映させることができます。これにより、スクリプトの汎用性が高まります。
7.2. UI要素との連携 (ui
API)
ui
APIは限定的ですが、WiresharkのUI要素にアクセスできます。例えば、現在選択されているパケット情報を取得したり、フィルタリングに関する情報を取得したりできます。
“`lua
— ui_example.lua
— Luaコンソールから実行することも可能
— 現在選択されているパケットの番号を取得
local selected_packet_num = ui.current_row
if selected_packet_num ~= nil then
console.message(“Selected packet number: ” .. selected_packet_num)
-- 選択されているパケットオブジェクトを取得 (キャプチャファイルがロードされている場合)
-- ローカルファイルとして保存されているキャプチャファイルが必要です
-- wireshark.get_packet(frame_number) で取得できます
local packet = wireshark.get_packet(selected_packet_num)
if packet then
console.message("Selected packet info length: " .. packet.len)
-- packetオブジェクトからPinfoやTvbにアクセス可能
-- local pinfo = packet.pinfo
-- local tvb = packet.tvb
-- ... 解析済みフィールド値へのアクセスなどは、TapやDissectorの edp や pinfo.layers を使う方が一般的
else
console.message("Could not get packet object for frame " .. selected_packet_num .. ". (Possibly not loaded from file?)")
end
else
console.message(“No packet selected.”)
end
— 現在適用されている表示フィルタを取得
local display_filter = ui.get_filter()
console.message(“Current display filter: ” .. display_filter)
— 新しい表示フィルタを設定 (注意: これは手動で適用する必要がある文字列を生成する例)
— Luaから直接フィルタを適用するAPIは存在しないようです(バージョンによる可能性あり)
— local new_filter = “ip.src == ” .. pref_target_ip.value .. ” and tcp.port == 80″
— console.message(“Generated filter (copy and paste): ” .. new_filter)
“`
ui
APIは主に情報取得に使われ、UIを直接操作するような複雑な処理は限定的です。
7.3. 外部ライブラリの利用
Wiresharkに組み込まれているLuaインタープリタは、標準のLuaライブラリ(string
, table
, math
, io
, os
など)の一部を利用できます。ただし、io
や os
のようなファイルシステムやシステムプロセスにアクセスするライブラリは、セキュリティ上の理由から制限されている場合があります。
また、Wiresharkのビルド時に特定の外部Luaライブラリ(例: LuaSocket, LuaFileSystem)が一緒に組み込まれている場合は、それらを利用できる可能性もあります。しかし、これはWiresharkの配布元やバージョンに依存するため、移植性の高いスクリプトを作成する場合は、標準ライブラリの範囲内で開発するのが無難です。
独自のC/C++ライブラリを作成し、それをLuaから呼び出す(Lua拡張モジュール)ことも技術的には可能ですが、これはWiresharkのビルドシステムと深く連携する必要があり、非常に高度なトピックとなります。
7.4. 性能に関する考慮事項
大規模なキャプチャファイルや、非常に高速なネットワークトラフィックを扱う場合、Luaスクリプトの性能はWireshark全体のパフォーマンスに影響を与える可能性があります。
- Tapのフィルタを適切に設定する: Tapスクリプトがすべてのパケットに対して呼び出されるのを避けるために、
Tap.new()
の第一引数に適切な表示フィルタを指定します。これにより、関連性のないパケットでTapのpacket
関数が呼び出される回数を大幅に減らすことができます。 packet
関数内の処理を軽量に保つ:Tap:packet()
やProto:dissector()
関数は、パケットごとに(またはパケットの一部ごとに)繰り返し呼び出されます。これらの関数内で時間のかかる処理(大量の文字列操作、複雑な計算、ファイルI/Oなど)は避けるべきです。情報の集計などは、packet
関数内で収集し、draw
関数でまとめて処理・表示するのが効率的です。tvb:raw()
の使用に注意:tvb:raw()
はパケットのバイナリデータをLua文字列としてコピーするため、大きなパケットに対して繰り返し呼び出すと大量のメモリを消費する可能性があります。必要な部分だけをtvb:range()
やtvb:get_*
関数で読み取るのが推奨されます。- C/C++ディセクタの検討: Luaディセクタは開発が容易ですが、非常に高性能なプロトコル解析が必要な場合や、ビット単位の複雑な操作が多い場合は、C/C++で記述されたコンパイル済みディセクタの方が優れたパフォーマンスを発揮することがあります。Luaはプロトタイプの開発や比較的小規模なタスクに適しています。
8. まとめ
この記事では、Wireshark Luaについて、その概要から導入方法、基本的なAPI、そして実践的なコード例まで、詳細に解説しました。
- Wireshark Luaは、Wiresharkの内部に組み込まれたLuaスクリプト実行環境であり、Wiresharkの機能を拡張・カスタマイズするための強力な手段です。
- Luaスクリプトを Personal Lua Plugins directory に
.lua
拡張子で保存することで、Wireshark起動時に自動的に読み込まれます。 - 主要なスクリプトの種類には、新しいプロトコルを解析する Dissector と、パケット情報を集計・分析する Tap があります。
- プロトコルディセクタは
Proto
,ProtoField
,Tvb
,Pinfo
,TreeItem
などのAPIを使って実装し、バイナリデータを構造的に解析して表示ツリーを構築します。 - Tapスクリプトは
Tap
,Pinfo
,edp
などのAPIを使って実装し、解析済みのフィールド情報を利用して集計やログ出力などを行います。統計情報ウィンドウへの表示にはdraw
関数を使用します。 console
APIはデバッグ出力に、prefs
APIはスクリプトの設定を外部から変更可能にするのに役立ちます。- デバッグはLuaコンソールやターミナル出力のエラーメッセージを確認し、
console.message
を活用するのが基本です。 - パフォーマンスを考慮し、
packet
関数内の処理は軽量に保ち、Tapフィルタを適切に設定することが重要です。
Wireshark Luaを習得することで、Wiresharkの能力を最大限に引き出し、独自のニーズに合わせた高度なネットワーク解析が可能になります。標準機能だけでは対応できないプロトコルの解析、特定のイベントの自動検出、カスタム統計情報の生成など、その応用範囲は非常に広いです。
この記事が、Wireshark Luaの世界への第一歩を踏み出す助けとなり、皆様のネットワーク解析作業の一助となれば幸いです。さらに深い知識を得るためには、Wiresharkの公式ドキュメント(特にDeveloper’s Guide内のLuaに関する章)や、他の開発者が公開しているLuaスクリプトのコードを参考にすることをお勧めします。
さあ、あなた独自のWireshark Luaスクリプトを作成し、ネットワークの可視化と分析をさらに進化させましょう!