Wiresharkで独自プロトコルを解析する(その2)
前回、UDPで伝送される独自プロトコルメッセージの解析プラグインの作成方法について記事をまとめました。
記事でも書いた通り、UDPで伝送されるメッセージについては、シンプルなメッセージフォーマットであれば、お作法さえ覚えてしまえば割と簡単に解析プラグインを作成することができました。
しかし、TCPの場合は、メッセージの送信タイミングによっては、1つのIPパケットの中に複数のメッセージが格納されて送信されてしまうことがあるため、これらを解析できるようにするためには一工夫が必要です。
今回は1つのIPパケットに複数メッセージが含まれるケースを考慮した独自プロトコル解析プラグインの作成方法についてまとめます。
この記事の目次
1.解析対象の独自プロトコル
今回は以下の図1-1のメッセージフォーマットのプロトコルを例に、解析プラグインを作成していきます。
Message SizeにはMessageのデータ長[byte]を格納し、Messageには、UTF-8でエンコードされた文字列を格納します。
この記事では上記のメッセージフォーマットのメッセージをサンプルプロトコルと呼ぶことにします。
2.解析プラグインのサンプルコード(複数メッセージ対応未考慮)
まずは、複数メッセージ対応が考慮されていない場合のサンプルコードを以下に示します。
-- 独自プロトコルの定義 SampleProto = Proto.new("sample", "Sample Protocol") -- プロトコルフィールドの定義 f_MsgSize = ProtoField.new("Message Size", "sample.size", ftypes.UINT16) f_Msg = ProtoField.new("Message", "sample.msg", ftypes.STRING) -- 定義したプロトコルフィールドをプロトコルフィールド配列に登録 SampleProto.fields = {f_MsgSize, f_Msg} -- dissector関数の定義 function SampleProto.dissector(buffer, pinfo, tree) -- dissector関数内で使用するローカル変数の定義 local MsgNum = 0 -- 1パケット内に含まれるSample Protocolのメッセージ数 local BufLeft = buffer:len() -- buffer内の未読込データの残りバイト数(初期値は引数bufferのデータ長) local BufPos = 0 -- bufferのデータ読込開始位置 local r_BufRange -- サンプルプロトコルメッセージのデータ範囲 local r_MsgSize -- Message Sizeのデータ範囲 local r_Msg -- Messageのデータ範囲 ---- bufferからデータを読込み ---- -- Message Sizeを読込み r_MsgSize = buffer(0, 2) -- Messageを読込み r_Msg = buffer(2, r_MsgSize:uint()) -- Sample Protocolメッセージ全体を読込み r_BufRange = buffer(0, 2 + r_MsgSize:uint()) ---- プロトコルツリーに情報を追加 -- treeにサブツリーとして"Sample Protocol"を追加 local subtree = tree:add( SampleProto, r_BufRange ) -- subtreeに上記で定義した各プロトコルフィールドの値を追加 subtree:add( f_MsgSize, r_MsgSize ) -- Message Size subtree:add( f_Msg, r_Msg, r_Msg:string(ENC_UTF_8), "Message : " .. r_Msg:string(ENC_UTF_8) ) -- Msg ---- プロトコル情報を設定 -- "Protocol"列の表示 pinfo.cols.protocol = "Sample" -- "info"列の表示 pinfo.cols.info = "Message Size:" .. r_MsgSize:uint() .. " Message:" .. r_Msg:string(ENC_UTF_8) end -- 定義したプロトコルをTCPポート番号を指定した紐づけ tcp_table = DissectorTable.get("tcp.port") tcp_table:add(20406, SampleProto)
各行の細かい解説については今回は省略しますので、前回の記事を参照してください。
上記コードのプラグインでサンプルプロトコルの解析を行った場合、1つのIPパケットに複数のサンプルプロトコルメッセージが含まれる場合の解析結果は以下の図2-1のように表示されることになります。
図2-1 複数メッセージを含むパケットの解析結果(複数メッセージ対応未考慮)
見てわかる通り、1番目のメッセージは解析され、Message SizeとMessageの内容が表示されますが、後に続くメッセージについては解析が行われず、何も表示されない状態となってしまっています。
そこで上記のコードに対して、以下の改良を行います。
(1)1つのIPパケットに複数のサンプルプロトコルメッセージが含まれていても、全て解析を実行できるようにする。
(2)各メッセージのMessage Size及びMesssageの値を、プロトコルツリーのSample Protocolの文字列の右側に表示できるようにする
(2)については、この記事の本筋からは外れますが、知っておくと便利なので、併せて解説します。
3.解析プラグインのサンプルコード(複数メッセージ対応考慮)
改良を行ったコードを以下に示します。
--独自プロトコルの定義 SampleProto = Proto.new("sample","SampleProtocol") --プロトコルフィールドの定義 f_MsgSize = ProtoField.new("MessageSize","sample.size",ftypes.UINT16) f_Msg = ProtoField.new("Message","sample.msg",ftypes.STRING) --定義したプロトコルフィールドをプロトコルフィールド配列に登録 SampleProto.fields = {f_MsgSize,f_Msg} -- dissector関数の定義 function SampleProto.dissector(buffer, pinfo, tree) -- dissector関数内で使用するローカル変数の定義 local MsgNum = 0 -- 1パケット内に含まれるSampleProtocolのメッセージ数 local BufLen = buffer:len() -- 引数bufferのデータ長 local BufLeft = buffer:len() -- buffer内の未読込データの残りバイト数(初期値はBuflen) local BufPos = 0 -- bufferのデータ読込開始位置 local r_BufRange -- サンプルプロトコルメッセージのデータ範囲 local r_MsgSize -- MessageSizeのデータ範囲 local r_Msg -- Messageのデータ範囲 -- BufLeftが3以上のとき、解析処理を継続 while BufLeft >= 3 do ---- bufferからデータを読込み ---- -- MessageSizeを読込み r_MsgSize = buffer(BufPos, 2) -- Messageを読込み r_Msg = buffer(BufPos + 2, r_MsgSize:uint()) -- SampleProtocolメッセージ全体を読込み r_BufRange = buffer(BufPos, 2 + r_MsgSize:uint()) ---- プロトコルツリーに情報を追加 -- treeにサブツリーとして"SampleProtocol"を追加 local subtree = tree:add(SampleProto, r_BufRange) -- subtreeに上記で定義した各プロトコルフィールドの値を追加 subtree:add(f_MsgSize, r_MsgSize) -- MessageSize subtree:add(f_Msg, r_Msg, r_Msg:string(ENC_UTF_8), "Message:" .. r_Msg:string(ENC_UTF_8)) -- Msg -- subtreeのトップにMessageSizeとMessageの情報を追記する subtree:append_text(",MessasgeSize:" .. r_MsgSize:uint() .. ",Message:" .. r_Msg:string(ENC_UTF_8)) ---- bufferのデータ読込開始位置を更新 BufPos = BufPos + 2 + r_MsgSize:uint() ---- buffer内の未読込データ数を更新 BufLeft = BufLeft - (2 + r_MsgSize:uint()) -- SampleProtocolメッセージ数を更新 MsgNum = MsgNum + 1 end ---- プロトコル情報を設定 -- "Protocol"列の表示 pinfo.cols.protocol = "Sample" -- "info"列の表示(MsgNumの値に応じて分岐) if MsgNum == 1 then -- メッセージ数が1個の場合は、MessageSizeとMessageの値を表示 pinfo.cols.info = "MessageSize:" .. r_MsgSize:uint() .. "Message:" .. r_Msg:string(ENC_UTF_8) else -- メッセージ数が2個以上の場合は、メッセージ数を表示 pinfo.cols.info = "Thispacketcontains" .. MsgNum .. "messages." end end -- 定義したプロトコルをTCPポート番号を指定した紐づけ tcp_table = DissectorTable.get("tcp.port") tcp_table:add(20406, SampleProto)
改良といってもそんなに大したことはしていません。
(1)1つのIPパケットに複数のサンプルプロトコルメッセージが含まれていても、全て解析を実行できるようにする。
↑に対しては、whileループを組んで、bufferのデータ長に対して、未読込のデータ数が3byte以上残っていれば、解析処理を継続するようにコードを修正しました。
(2)各メッセージのMessage Size及びMesssageの値を、プロトコルツリーのSample Protocolの文字列の右側に表示できるようにする
↑に対しては、subtree:append_text("追記文")の形式で、プロトコルツリーの表示に追記を行いました。
上記コードのプラグインでサンプルプロトコルの解析を行うと、1つのIPパケットに複数メッセージが含まれる場合の解析結果は図3-1のようになります。
図3-1 複数メッセージを含むパケットの解析結果(複数メッセージ対応考慮)
見てわかる通り、TCPプロコルのヘッダツリーの後に、サンプルプロトコルのツリーが2つ追加されるようになりました。
また、各ツリーのSample Protocolのテキスト表示の右側に、Message Size及びMeesageの内容を表示できるようになりました。