【2023年】これからブログを始める人へおすすめの書籍

Kissy

WireSharkを使って独自プロトコルを解析する方法

作成: 更新:

Wiresharkを使って独自プロトコルを解析する方法

ここでは、Wiresharkを使って独自プロトコルを解析する方法を記載します。

通信を行うプログラムを作成する際に、独自のプロトコルを実装する場合があります。実装したプログラムの動作確認を行う際やデバッグする際に実際にどのような通信を行っているかを調べたい場合があります。そのようなときに使うのがWiresharkなどのパケットキャプチャツールです。

パケットキャプチャツールは、HTTPなどのオープンなプロトコルは標準で解析することができますが、独自プロトコルを解析するには独自プロトコルを解析するプラグインを作成する必要があります。Wiresharkの場合はLuaというスクリプト言語で簡単にプラグインを作成することができます。

なお、Wiresharkのプロトコル解析プラグインのことを’Dissector’と呼びます。調べる際は「Wireshark Lua」や「Wireshark Dissector」でググると良いでしょう。

Wiresharkのインストール

公式からダウンロードしてインストールします。インストールはウィザードに従って進めるだけですので、解説は省略します。Ethernetのパケットキャプチャであればインストールウィザードはデフォルトのまま進めればよいです。

Wireshark - Download

事前準備

Wiresharkのプラグインのフォルダ(C:\Program Files\Wireshark\plugins)に作成した独自プラグインファイルをコピーするだけです。あとはWiresharkが自動的にロードしてくれます。

ただし、v2.6より前のバージョンを使用している場合は、デフォルトではプラグインの使用が無効になっているため、有効にする必要があります。また、独自プラグインは自動でロードされないため、独自プラグインをロードする設定をする必要があります。

(v2.6より前のバージョンでの設定方法を以下に記載しますが、以降本記事ではv2.6以降のバージョンを使用しているものとします。)

v2.6より前のバージョンを使用している場合

Wiresharkの設定ファイル(init.lua)を以下のように変更します。

  • disable_luafalseにする。
  • run_user_scripts_when_superusertrueにする。
  • init.luaの最後の行にdofile(<独自プラグインのファイルパス>)を追加する。
-- Set disable_lua to true to disable Lua support.
disable_lua = false

if disable_lua then
    return
end

-- If set and we are running with special privileges this setting
-- tells whether scripts other than this one are to be run.
run_user_scripts_when_superuser = true

dofile(DATA_DIR.."myprotocol.lua")

<独自プラグインのファイルパス>はこれから作成する独自プラグインファイルのパスです。DATA_DIRはWiresharkがインストールされたディレクトリのパスを表すマクロです。Wiresharkがインストールされるのは通常C:\Program Files\Wireshark\ですので、上記例では、C:\Program Files\Wireshark\myprotocol.luaの独自プラグインをロードする設定になります。

独自プラグインの作成方法

基本的な流れ

dissectorを使った独自プラグインの基本的な処理の流れは、以下のようになります。

  • プロトコルを定義する
  • プロトコル解析用の関数を定義する
  • TCPやUDPのポートとプロトコルを紐づける

ソースコード

簡単なサンプルを以下に記載します。

このサンプルプラグインでは"MyProto"という独自プロトコルの解析をしています。"MyProto"は2byteのコマンドと1byteのフラグをフィールドに持つ簡単なプロトコルです。

-- 1.プロトコルを定義する
myproto = Proto("MyProto", "My Protocol")

-- 2.プロトコル解析用の関数を定義する
function myproto.dissector(buffer, pinfo, tree)
  pinfo.cols.protocol = "MyProto" -- (1)
  local subtree = tree:add(myproto, buffer(), "My Protocol Data") -- (2)
  subtree:add(buffer(0,2),"command: ", buffer(0,2):uint()) -- (3)
  subtree:add(buffer(2,1),"flag: ", buffer(2,1):uint()) -- (4)
end

-- 3.TCPのポートとプロトコルを紐づける
tcp_table = DissectorTable.get("tcp.port") -- TCPポートのテーブルを取得する
tcp_table:add(777,myproto) -- TCPの777番ポートとプロトコルの紐付けする

上記を任意の名前.luaでWiresharkのプラグインのフォルダ(C:\Program Files\Wireshark\plugins)に保存します。これで独自プロトコルのパケットをキャプチャすると解析され、以下のように表示されます。

独自プロトコルの解析結果

ソースコードの解説

ソースコードの解説をします。先ほどの解析結果の画像と見比べながら見ると理解できると思います。

  1. プロトコルを定義する
     Protoオブジェクトを生成します。Protoは独自プロトコルを表すオブジェクトです。Proto()の第1引数はプロトコルの名前、第2引数はプロトコルの説明です。プロトコルの名前は他のプロトコルと重複してはいけません。
  2. プロトコル解析用の関数dissector()を定義する
     dissector()の第1引数bufferはパケットバッファーが格納されています。第2引数pinfoはパケットの情報を取得・設定するオブジェクトです。第3引数treeはパケットの解析結果を表示するツリーのオブジェクトです。
     
     (1)はパケット一覧で表示されるプロトコル名を設定しています。
     (2)は「My Protocol Data」というサブツリーを作成しています。
     (3)はパケットバッファーの先頭(0byte目)から2byteを取得し、uint()で整数に変換して、(2)で作成したサブツリーに追加しています。
     (4)はパケットバッファーの3byte目から1byteを取得し、uint()で整数に変換して、(2)で作成したサブツリーに追加しています。
  3. TCPやUDPのポートとプロトコルを紐づける
     サンプルではTCPの777番ポートとプロトコルを紐づけています。

いろいろな表現の仕方

通信プロトコルのデータには数値(8進数/10進数/16進数)や文字列、ビット列など様々な型があります。それぞれのデータにあった表現で表示させたい場合にはプロトコルフィールドを使います。

使い方は簡単です。サンプル同様にプロトコルを定義した後に、プロトコルフィールドを定義します。そしてdissector()内でツリーにデータを追加する際にプロトコルフィールドを参照するだけです。これでデータをいろいろな表現で表示することができます。

以下によく使うデータ表現のサンプルを記載します。

-- 1.プロトコルを定義する
myproto = Proto("MyProto", "My Protocol")
  
-- 2.プロトコルフィールドを定義する
--- 数値(base.DEC, base.HEX or base.OCT, base.DEC_HEX, base.HEX_DEC, base.UNIT_STRING or base.RANGE_STRING)
local pf_command_dec = ProtoField.uint16("myproto.command_dec", "command", base.DEC)
local pf_command_hex = ProtoField.uint16("myproto.command_hex", "command", base.HEX)
local pf_command_oct = ProtoField.uint16("myproto.command_oct", "command", base.OCT)
local pf_command_dechex = ProtoField.uint16("myproto.command_dechex", "command", base.DEC_HEX)
local pf_command_hexdec = ProtoField.uint16("myproto.command_hexdec", "command", base.HEX_DEC)

-- 単位表示
--- [valuestring] = unit_table = {"単数単位", "複数単位"}
local unit_table = {"esc", "secs"}
local pf_command_unitstring = ProtoField.uint16("myproto.command_unitstring", "command", base.UNIT_STRING, unit_table)

-- 範囲表示
--- [valuestring] = range_table = {{min, max, "string"}, ...}
local range_table = {{0, 100, "1 handred"}}
local pf_command_rangestring = ProtoField.uint16("myproto.command_rangestring", "command", base.RANGE_STRING, range_table)

-- ビット表示
--- ProtoField.new(name, abbr, type, [valuestring], [base], [mask], [descr])
local pf_flags = ProtoField.new ("Flags", "myproto.flags", ftypes.UINT8, nil, base.HEX)

local pf_flag_response = ProtoField.new ("Response", "myproto.flags.response", ftypes.BOOLEAN, {"this is a response","this is a query"}, 8, 0x80, "is the message a response?")

local pf_flag_opcode = ProtoField.new ("Opcode", "myproto.flags.opcode", ftypes.UINT8, nil, base.DEC, 0x0F, "operation code")
  
-- 文字列表示
--- ProtoField.string(abbr, [name], [display], [desc])
local pf_locale = ProtoField.string("locale", "locale", base.ANSI)
  
myproto.fields = {pf_command_dec,pf_command_hex,pf_command_oct,pf_command_dechex,pf_command_hexdec,pf_command_unitstring,pf_command_rangestring,pf_locale,pf_flags,pf_flag_response, pf_flag_opcode}

-- 3.プロトコル解析用の関数を定義する
function  myproto.dissector(buffer,pinfo,root)
  pinfo.cols.protocol = "MyProto"
  
  local subtree = root:add(dns, buffer(), "My Protocol Data")
  
  -- 数値
  subtree:add(pf_command_dec, buffer(0,2), buffer:range(0,2):uint())
  subtree:add(pf_command_hex, buffer(0,2), buffer:range(0,2):uint())
  subtree:add(pf_command_oct, buffer(0,2), buffer:range(0,2):uint())
  subtree:add(pf_command_dechex, buffer(0,2), buffer:range(0,2):uint())
  subtree:add(pf_command_hexdec, buffer(0,2), buffer:range(0,2):uint())
  
  -- 単位表示
  subtree:add(pf_command_unitstring, buffer(0,2), buffer:range(0,2):uint())
  
  -- 範囲表示
  subtree:add(pf_command_rangestring, buffer(0,2), buffer:range(0,2):uint())
  
  -- ビット表示
  local flagrange = buffer:range(2,1)
  local flag_tree = subtree:add(pf_flags, flagrange)
  local query_flag_tree = flag_tree:add(pf_flag_response, flagrange)
  flag_tree:add(pf_flag_opcode, flagrange)
  
  -- 文字列表示
  subtree:add(pf_locale, buffer(13, 2), buffer(13, 2):string())
end

これをWiresharkで表示すると以下のように表示されます。難しくないのでソースコードと比較しながら読むと理解できると思います。
プロトコルデータの色々な表現

もっと詳しく知りたい方は、ProtoFieldを参照してください。ProtoFieldの詳細が記載されています。

参考サイト

  • Lua/Examples - The Wireshark Wiki
    • WiresharkのLuaのチュートリアルです。サンプルのプラグイン(.lua)とパケットキャプチャ(.pcap)が公開されているので、これを真似すると理解が進みます。
  • Wireshark’s Lua Reference Manual
    • WiresharkのLuaのリファレンスマニュアルです。LuaのAPIを調べる際に参照します。
  • lua-users wiki: Sample Code
    • Luaのサンプルコード集です。こういう場合はどうやって実装するんだろう?という場合に探してみると見つかるかも。

最後まで読んでいただきありがとうございます。
また読んでくださいませ。
そんじゃーね。

関連記事

SPONSORED LINK
SPONSORED LINK