Wiresharkの解析プラグインを作る ssmjp 201409
-
Upload
minoru-kobayashi -
Category
Internet
-
view
4.049 -
download
0
description
Transcript of Wiresharkの解析プラグインを作る ssmjp 201409
Wireshark の解析プラグインを作る
@unkn0wnbit
2014/9/29 #ssmjp 2014/09
Agenda
Wireshark とは 解析プラグインを作るには 解析対象プロトコルを定義 解析スクリプトの作成 既存の解析プラグインの拡張 ビット演算 おまけ
2
01. WIRESHARK とは
3
4
Wireshark とは
パケットキャプチャツール 標準で多彩なプロトコルを解析できる
プラグインが付属 独自のプロトコルは解析できない
独自のプロトコルを解析する場合1. 人間デコーダになる2. 解析用のプラグインを書く
5
Wireshark のパケット処理の流れ
キャプチャフィルタ
ディスプレイフィルタ
解析プラグイン
Wireshark 画面出力
0100111010101010101110 ネットワーク上を流れるパケット
キャプチャするパケットをフィルタリング
表示するパケットをフィルタリング
パケットを解析
解析した結果を表示
02. 解析プラグインを作るには
6
7
解析プラグインを作るには
方法は 2 つ C/C++ 言語で作成 Wireshark に組み込まれた Lua スクリプ
ト言語で作成
今回は Lua で作成します
面倒そう…
きっとお手軽
8
Lua を使う準備 (1/2)
Wireshark で Lua が有効か確認 コンパイル時に Lua の使用を指定
公式版 Win/Mac バイナリともにデフォルトで有効
Linux はディストリビューションによるかも
http://wiki.wireshark.org/Lua より
9
Lua を使う準備 (2/2)
最新バージョン (1.12.1) はデフォルトで使用可能 古い設定ファイルを使い回している場合は確認
init.lua 内の以下の行を確認 disable_lua = true; の行を false に変更
Windows C:\Program Files\Wireshark\init.lua
Mac /Applications/Wireshark.app/Contents/Resources/share/
wireshark/init.lua Linux (Debian 7)
/usr/share/wireshark/init.lua
10
動作確認 (1/2)
Tools – Lua – Evaluate
Evaluate Lua ダイアログ
11
動作確認 (2/2)
ダイアログにプログラム入力
Evaluate ボタンを押すと…
local tw = TextWindow.new("Test Program");tw:set("Hello World!")
12
Lua スクリプト使用方法 (1/2)
通常はスクリプトをファイルに保存して、 Wireshark の起動時に読み込ませる。
-X オプションは複数回指定可能 ディレクトリのデリミタは “ \” or “/” tshark も同様( GUI の API は使えない)
tshark 版 Hello World
wireshark.exe -X lua_script:C:/wireshark_plugins/hoge.lua
print("Hello World!")
13
Lua スクリプト使用方法 (2/2)
毎回同じスクリプトを使う場合 init.lua に以下を追記
dofile(”C:/wireshark_plugins/hoge.lua”) Global configuration
C:\Program Files\Wireshark\init.lua Personal configuration
C:\Users\<user_name>\AppData\Roaming\Wireshark\init.lua
各フォルダの確認方法 Help – About Wireshark – Folders
03. 解析対象プロトコルを定義
14
15
解析対象プロトコルを定義 (1/4)
解析対象とする簡単なプロトコルを定義 乱数文字列生成プロトコル
クライアントはサーバに任意の長さの乱数を要求する
サーバは指定された長さの乱数を文字列として、クライアントに返す
16
解析対象プロトコルを定義 (2/4)
パケットフォーマット Command Code
データ長: 1 バイト Request or Response
Data Length データ長: 2 バイト Request 時:要求するデータ長を指定 Response 時: Data 部の長さを指定
Data データ長: Data Length で指定されたバイト数 Request 時:存在しない Response 時:応答する実データ
データの並びはビッグエンディアン
8 1Command Code
Data Length
bits
Data
17
解析対象プロトコルを定義 (3/4)
Command Code Request
0x01 Response
0x51 不明な Command Code が要求されたと
き 0xFF を返す
18
解析対象プロトコルを定義 (4/4)
UDP 版および TCP 版を実装 MTU を超えるデータをやりとりする場
合、 TCP の使用を想定。 使用ポート番号: 10000
04. 解析スクリプトの作成
19
20
Wireshark で見てみる( UDP版)
Response
Packet List
Packet Bytes
Packet Details
21
解析スクリプト( UDP 版)do udp_rngp_proto = Proto("RNGP_UDP", "Random Number Generater Protocol (UDP)")
command_prtf = ProtoField.new("RNGP command", "rngp_udp.command", ftypes.UINT8) length_prtf = ProtoField.new("RNGP length", "rngp_udp.length", ftypes.UINT16) random_prtf = ProtoField.new("RNGP Random Numbers", "rngp_udp.random", ftypes.STRING) udp_rngp_proto.fields = {command_prtf, length_prtf, random_prtf}
function udp_rngp_proto.dissector(buffer, pinfo, tree) local command_names = { [0x01] = "Request Random Numbers", [0x51] = "Response Random Numbers", [0xFF] = "Unknown Request/Response Command", }
local command = buffer(0,1):uint() local length = buffer(1,2):uint()
local subtree = tree:add(udp_rngp_proto, "Random Number Generater Protocol Data") subtree:add_packet_field(command_prtf, buffer:range(0,1), ENC_ASCII, "Command:", string.format("0x%02x", command), command_names[command]) subtree:add_packet_field(length_prtf, buffer(1,2), ENC_ASCII, "Length:", length)
if command >= 0x51 and command ~= 0xFF then disp_data(command, buffer(3, length):tvb(), subtree) end
pinfo.cols.protocol = "RNGP(UDP)" if command_names[command] == nil then pinfo.cols.info = "Malformed Request/Response Command" else pinfo.cols.info = command_names[command] end end
udp_table = DissectorTable.get("udp.port") udp_table:add(10000, udp_rngp_proto)end
function disp_data(command, buffer, tree) if command == 0x51 then tree:add_packet_field(random_prtf, buffer(0), ENC_ASCII) endend
新しいプロトコルを宣言
定義した dissector を10000/udp に登録
buffer : Wireshark から渡されるパケットのバッファ(tvb)pinfo : Packet List 情報tree : Packet Details 内ツリー情報
buffer(X, Y):uint()buffer の X バイト目から Y バイトを unsigned int として切り出す
tree:add(), add_packet_field()Packet Details 内のツリーにアイテムを追加
Packet List の Protocol カラムと Info カラムの内容を設定
dissector (パケットを解析する関数)を定義
(パケットを受け取るたびに呼び出される)
宣言したプロトコルで使用するフィールドを定義
22
解析スクリプト( UDP 版)
Response
23
Wireshark で見てみる( TCP版)
Response
24
解析スクリプト( TCP 版)do tcp_rngp_proto = Proto("RNGP_TCP", "Random Number Generater Protocol (TCP)")
command_prtf = ProtoField.new("RNGP command", "rngp_tcp.command", ftypes.UINT8) length_prtf = ProtoField.new("RNGP length", "rngp_tcp.length", ftypes.UINT16) random_prtf = ProtoField.new("RNGP Random Numbers", "rngp_tcp.random", ftypes.STRING) tcp_rngp_proto.fields = {command_prtf, length_prtf, random_prtf}
function tcp_rngp_proto.dissector(buffer, pinfo, tree) local command_names = { [0x01] = "Request Random Numbers", [0x51] = "Response Random Numbers", [0xFF] = "Unknown Request/Response Command", }
local command = buffer(0,1):uint() local length = buffer(1,2):uint()
if command == 0x51 and buffer:len() < (3 + length) then pinfo.desegment_len = DESEGMENT_ONE_MORE_SEGMENT return buffer:len() - (3 + length) end
local subtree = tree:add(tcp_rngp_proto, "Random Number Generater Protocol Data") subtree:add_packet_field(command_prtf, buffer:range(0,1), ENC_ASCII, "Command:", string.format("0x%02x", command), command_names[command]) subtree:add_packet_field(length_prtf, buffer(1,2), ENC_ASCII, "Length:", length)
if command >= 0x51 and command ~= 0xFF then disp_data(command, buffer(3, length):tvb(), subtree) end
pinfo.cols.protocol = "RNGP(TCP)" if command_names[command] == nil then pinfo.cols.info = "Malformed Request/Response Command" else pinfo.cols.info = command_names[command] end
return 3 + length end
tcp_table = DissectorTable.get("tcp.port") tcp_table:add(10000, tcp_rngp_proto)End
function disp_data(command, buffer, tree) if command == 0x51 then tree:add_packet_field(random_prtf, buffer(0), ENC_ASCII) endend
新しいプロトコルを宣言
dissector を定義
定義した dissector を10000/tcp に登録
buffer のデータ長がプロトコルで指定された長さよりも短い場合、必要なデータ長になるまで処理を行わない。次に dissector が呼び出される際、 bufferに後続のパケットのペイロードがアペンドされる。
TCP セグメント再構築
宣言したプロトコルで使用するフィールドを定義
25
解析スクリプト( TCP 版)
TCP セグメント再構築イメージ
1 パケット目 2 パケット目
1 パケット目で dissector に渡される buffer 内容必要なデータ長に満たないので、 pinfo.desegment_len にDESEGMENT_ONE_MORE_SEGMENT をセットして、dissector から return する。
サーバが返すデータ
2 パケット目で dissector に渡される buffer 内容
Wireshark は DESEGMENT_ONE_MORE_SEGMENT がセットされている場合、次のパケットのペイロードを buffer にアペンドして dissector に渡す。必要なデータ長か否かは dissector が判断する。
26
解析スクリプト( TCP 版)
Response
05. 既存の解析プラグインの拡張
27
28
ところで…
独自のプロトコルの解析より、既存の解析プラグインを拡張したい、と思うことの方が多いかもしれません。
この機能が使えます。 postdissector chained dissector TAP
29
postdissector
postdissector とは dissector が呼び出された後に呼び出され
る dissector 同じパケットを複数の dissector で処理する。 種類に関係なく、全てのパケットに対して
呼び出される。 通常の dissector と同様にパケットの情
報へのアクセスや Packet Details のツリーにアイテムを追加できる
30
postdissector のイメージ
dissector
postdissector
ARP IP
TCP UDP
…
…
25 80 53 68 …
各 dissector で処理が行われた後に必ず呼び出される dissector
31
postdissector 例
http://wiki.wireshark.org/Lua/Dissectors より
-- trivial postdissector example-- declare some Fields to be readip_src_f = Field.new("ip.src")ip_dst_f = Field.new("ip.dst")tcp_src_f = Field.new("tcp.srcport")tcp_dst_f = Field.new("tcp.dstport")-- declare our (pseudo) protocoltrivial_proto = Proto("trivial","Trivial Postdissector")-- create the fields for our "protocol"src_F = ProtoField.string("trivial.src","Source")dst_F = ProtoField.string("trivial.dst","Destination")conv_F = ProtoField.string("trivial.conv","Conversation","A Conversation")-- add the field to the protocoltrivial_proto.fields = {src_F, dst_F, conv_F}-- create a function to "postdissect" each framefunction trivial_proto.dissector(buffer,pinfo,tree) -- obtain the current values the protocol fields local tcp_src = tcp_src_f() local tcp_dst = tcp_dst_f() local ip_src = ip_src_f() local ip_dst = ip_dst_f() if tcp_src then local subtree = tree:add(trivial_proto,"Trivial Protocol Data") local src = tostring(ip_src) .. ":" .. tostring(tcp_src) local dst = tostring(ip_dst) .. ":" .. tostring(tcp_dst) local conv = src .. "->" .. dst subtree:add(src_F,src) subtree:add(dst_F,dst) subtree:add(conv_F,conv) endend-- register our protocol as a postdissectorregister_postdissector(trivial_proto)
パケット内で読み取るフィールドを宣言
新しいプロトコルを宣言
宣言したプロトコルで使用するフィールドを定義
postdissector を定義
postdissector を登録
TCP の場合に送信元/先 IP アドレスとポートの組み合わせを Packet Details に追加。
32
postdissector 例実行結果
定義したプロトコルやフィールドはディスプレイフィルタとして指定できる。
Packet Details にも情報を追加できる。この例では、 TCP の場合のみ Trivial Protocol Data ツリーが追加される。
33
chained dissector
chained dissector とは ある dissector が呼び出された後に続け
て呼び出される dissector 同じパケットを複数の dissector で処理する 特定のプロトコルに関してのみ呼び出され
る 通常の dissector と同様にパケットの情
報へのアクセスや Packet Details のツリーにアイテムを追加できる
34
chained dissector のイメージ
dissector
chaineddissector
ARP IP
TCP UDP
…
…
25 80 53 68 …
特定の dissector に紐付けられて呼び出される dissector
35
chained dissector 例do local http_suspicious_proto = Proto("http_suspicious", "Suspicious HTTP Traffic")
local F_suspicious_uri = ProtoField.string("http.suspicious_uri", "Suspicious Request URI") local F_suspicious_host = ProtoField.string("http.suspicious_host", "Suspicious Host Header") http_suspicious_proto.fields = {F_suspicious_uri, F_suspicious_host}
local f_request_uri = Field.new("http.request.uri") local f_host = Field.new("http.host") local original_http_dissector
function http_suspicious_proto.dissector(buffer, pinfo, tree) original_http_dissector:call(buffer, pinfo, tree)
if f_request_uri() then local uri = tostring(f_request_uri()) local host = tostring(f_host())
if string.match(uri, "%.exe") and string.match(host, "[^j][^p]:%d+") then local subtree = tree:add(http_suspicious_proto, buffer) subtree:add(F_suspicious_uri, buffer(), uri) :set_text("URI : " .. uri) subtree:add(F_suspicious_host, buffer(), host) :set_text("Host : " .. host) end end end
local tcp_dissector_table = DissectorTable.get("tcp.port") original_http_dissector = tcp_dissector_table:get_dissector(8080) tcp_dissector_table:add(8080, http_suspicious_proto)end
新しいプロトコルを宣言
宣言したプロトコルで使用するフィールドを定義
パケット内で読み取るフィールドを宣言
chained dissector を定義
chained dissector を登録
chained dissector の処理を行う前に、オリジナルの dissector で処理を行う。
8080/tcp に登録されているオリジナルの dissector をバックアップ。
リクエストされている URI と Hostヘッダから疑わしいか判断。
36
chained dissector 例実行結果
Packet Details にも情報を追加できる。この例では、疑わしい HTTP のリクエストに Suspicious HTTP Traffic ツリーが追加される。
定義したプロトコルやフィールドはディスプレイフィルタとして指定できる。
37
TAP
TAP とは 主に統計情報収集用として使用される。 全てのパケットに対して呼び出される。
フィルタを設定して、該当するパケットのみを処理することも可能。 キャプチャフィルタの影響は受ける。 ディスプレイフィルタの影響は受けない。
dissector とは異なり、 Packet Details のツリーにアイテムは追加できない。
38
TAP のイメージ
キャプチャフィルタ
ディスプレイフィルタ
解析プラグイン
Wireshark 画面出力
0100111010101010101110
TAP
dissector とは独立してパケットを解析。
39
TAP 例do local function menuable_tap() -- Declare the window we will use local tw = TextWindow.new("Address Counter")
-- This will contain a hash of counters of appearances of a certain address local ips = {}
-- this is our tap local tap = Listener.new();
function remove() -- this way we remove the listener than otherwise will remain running indifinitelly tap:remove(); end
-- we tell the window to call the remove() function when closed tw:set_atclose(remove)
-- this function will be called once for each packet function tap.packet(pinfo,tvb) local src = ips[tostring(pinfo.src)] or 0 local dst = ips[tostring(pinfo.dst)] or 0
ips[tostring(pinfo.src)] = src + 1 ips[tostring(pinfo.dst)] = dst + 1 end
-- this function will be called once every few seconds to update our window function tap.draw(t) tw:clear() for ip,num in pairs(ips) do tw:append(ip .. "\t" .. num .. "\n"); end end
-- this function will be called whenever a reset is needed -- e.g. when reloading the capture file function tap.reset() tw:clear() ips = {} end end
-- using this function we register our fuction -- to be called when the user selects the Tools->Test->Packets menu register_menu("Test/Packets", menuable_tap, MENU_TOOLS_UNSORTED)end http://www.wireshark.org/docs/wsug_html_chunked/wslua_tap_example.html より
TAP を生成
パケットを受け取るたびに
呼び出される関数
テキストウィンドウに結果を表示
Tools メニューにスクリプトを実行するメニュー
を追加
Listener.new(“frame”, “ip.addr == 10.0.0.0/8”) のようにフィルタリングをすることも可能。
送信元または送信先ごとにパケット数をカウント
40
TAP 例実行結果
Tools – Test – Packets が追加される。
メニューを実行して、パケットキャプチャを行うと、送信元または送信先ごとのパケット数がウィンドウに表示される。
Wireshark の名前解決を有効にすると、ドメイン名などが解決された結果で集計される。
06. ビット演算
41
42
Lua のビット演算
Lua 5.2 でビット演算がサポート 公式版 Windows バイナリは Lua 5.2 組み込み 公式版 Mac バイナリは Lua 5.1 組み込み
いずれも、 Wireshark 1.12.1 で確認 Linux はディストリビューションによるかも
Debian 7 では、 Wireshark 1.8.2 のパッケージ (Lua 5.1) Lua 5.1 では外部ライブラリを使えば可能
bitop : http://bitop.luajit.org/ Lua 5.1/5.2 用 使うならばこちらがお勧めだが、 Lua 5.2 と API が異なる Debian 7 ではパッケージが用意されている (lua-bitop)
bitlib : https://github.com/LuaDist/bitlib Lua 5.1 用 もうメンテナンスされていないっぽい
Lua ビット演算
API bit32.arshift, bit32.band, bit32.bnot, bit32.bor, bit32.btest,
bit32.bxor, bit32.bextract, bit32.lrotate, bit32.lshift, bit32.replace, bit32.rrotate, bit32.rshift
詳細: http://www.lua.org/manual/5.2/manual.html#6.7 “0x1234” と “ 0xFF00” の AND の動作確認
Tools – Lua – Evaluate で以下を入力
43
local tw = TextWindow.new("BitOp Test Program");band_val = bit32.band(0x1234, 0xFF00)tw:set("lua version: " .. _VERSION .."\n" .. band_val )
44
まとめ パケット単位の解析
今まさにキャプチャしたパケットのみが解析対象 過去のパケットの情報は取得できない
1 つのプロトコルにつき、 1 つの dissector 単一の dissector で Request と Response を解析 chained dissector などで既存の dissector の拡張は可能
Wireshark で Lua を使うにはコンパイル時に指定する必要あり 公式配布バイナリ (Win/Mac) 版は標準で組み込み済み
Windows バイナリ: Lua 5.2 Mac バイナリ: Lua 5.1
Linux 版はディストリビューションによって異なる可能性あり Lua 5.2未満の場合はビット演算ができない
外部ライブラリ (bitop) を使えば可能 ただし、本家 Lua と API が異なる
45
参考 Wireshark
Wireshark User’s Guide https://www.wireshark.org/docs/wsug_html_chunked/wsluarm.html https://www.wireshark.org/docs/wsug_html_chunked/lua_module_Proto.html#lua_class_ProtoField https://www.wireshark.org/docs/wsug_html_chunked/lua_module_Tree.html
The Wireshark Wiki http://wiki.wireshark.org/Lua http://wiki.wireshark.org/Lua/Dissectors http://wiki.wireshark.org/Lua/Examples http://wiki.wireshark.org/Lua/Taps
Wireshark 1.2.9 ソースコードアーカイブ内 README.developer README.tapping
Lua http://www.lua.org/manual/5.2/manual.html#6.7
その他 Googleブックス (http://books.google.co.jp/books)
Wireshark and Ethereal network protocol analyzer toolkit 実践パケット解析 : Wireshark を使ったトラブルシューティング
http://www.lua.org/ http://bitop.luajit.org/ http://luaforge.net/projects/bitlib/
46
Q & A
おまけ
Lua 以外のバインディング言語 SSL復号
サーバの証明書を使う ブラウザの暗号鍵を使う
47
今回やりません。
ググって!
おまけ 1Lua 以外のバインディング
48
Lua 以外のバインディング言語
About ダイアログをよくみると…
49
Python バインディングの実際のところ
without なので Wireshark wiki を見てみる
50
http://wiki.wireshark.org/Python より
pyreshark について
Lua のように TCP や UDP のポート毎に dissector を登録するような API が用意されていない。 tcp_table = DissectorTable.get("tcp.port") tcp_table:add(10000, tcp_rngp_proto)
TCP/IPヘッダを自力でゴリゴリ解析する必要がありそう。
面倒そうなので試すのをやめました…。 51
おまけ 2ブラウザ側の暗号鍵を使った SSL復号
52
Wireshark で SSL復号
よく見かける方法として、サーバの SSL秘密鍵をWireshark に設定して、パケットキャプチャの解析を行う手順が解説されている。
自分が SSL秘密鍵を持っていない場合、通信内容が分からない! 外部のサービスを使う場合とか Fiddler とか使えば見えますが
しかし、 SSL秘密鍵がなくても、ブラウザ側が持っている暗号鍵で SSL復号を行うことができる。
53
ブラウザの暗号鍵で復号
SSLKEYLOGFILE環境変数を設定する SSLKEYLOGFILE=/path/to/sslkeylog.txt
Wireshark で以下を設定 Edit – Preferences – Protocols – SSL – (Pre)-Master-
Secret log filename display filter で “ ssl” を設定
適当なパケットを選択して、 Follow SSL Stream Packet Bytes の Decrypted SSL data タブ等
鍵交換プロトコルが RSA でも ECDHE(Perfect Forward Secrecy) でも復号可能 PFSだとサーバの SSL秘密鍵を持っていても復号できな
い 54
SSLKEYLOGFILE フォーマット
CLIENT_RANDOM <space> <64 bytes of hex encoded client_random> <space> <96 bytes of hex encoded master secret>
RSA <space> <16 bytes of hex encoded encrypted pre master secret> <space> <96 bytes of hex encoded pre master secret>
55
使用ブラウザの制限
SSLKEYLOGFILE環境変数を見てログファイルを作るのは、 NSS(Network Security Services) ライブラリであるため、同様の手順が取れるのは同ライブラリを使用するブラウザ、ツールのみ。
NSS を使用する主なブラウザは、 Firefox, PC 版Chrome ( Win/Mac/Linux 版含む)のみ。 Android 版 Chrome は OpenSSL を使用 PC 版 Chrome も BoringSSL を使い始めた場合は使えな
くない 他のブラウザの場合はメモリダンプするくらいし
か手がないんじゃ…。56
参考
pyreshark https://github.com/ashdnazg/pyreshark
Psst. Your Browser Knows All Your Secrets. https://isc.sans.edu/diary/Psst.+Your+Browser+Knows+All+Your+Secrets./16415
NSS Key Log Format https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format
Chrome: From NSS to OpenSSL https://docs.google.com/document/d/1ML11ZyyMpnAr6clIAwWrXD53pQgNR-
DppMYwt9XvE6s/edit?pli=1#heading=h.n30fi956cpfk SSL/TLS & Perfect Forward Secrecy
http://vincent.bernat.im/en/blog/2011-ssl-perfect-forward-secrecy.html
ディフィー・ヘルマン鍵共有 http://ja.wikipedia.org/wiki/%E3%83%87%E3%82%A3%E3%83%95%E3%82%A3%E3%83%BC
%E3%83%BB%E3%83%98%E3%83%AB%E3%83%9E%E3%83%B3%E9%8D%B5%E5%85%B1%E6%9C%89
Diffie–Hellman key exchange http://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange
57
58
Q & A