wiresharkの解析プラグインを作る ssmjp 201409

Post on 28-May-2015

4.050 Views

Category:

Internet

0 Downloads

Preview:

Click to see full reader

DESCRIPTION

How to make a Wireshark plugin to dissect an original protocol with Lua. And, How to decrypt SSL (using PFS (ex. ECDHE) key exchange) with Wireshark.

TRANSCRIPT

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

top related