VB.NETでパケットキャプチャ2~HTTPリクエストボディ・レスポンスボディの取得
VB.NETでパケットキャプチャ作成 - Future Convergence PRJ.の続き。
VB.NETのパケットキャプチャでHTTPリクエストボディ・レスポンスボディを取得するのが目標。utf8でエンコードしてファイルに書き込んだ場合、HTTPを取り扱ったパケットだと以下のように表示される。
(IPヘッダ・TCPヘッダ部分の文字化け) HTTP/1.1 200 OK Date: Sun, 04 Jan 2015 08:14:04 GMT Server: Apache Vary: Client-Version,Accept-Encoding X-Powered-By: XXX Platform server_version: 20120101 user_id: 99999 version_up: 0 status_code: 200 authorize: timeStamp=1420359244&version=1.0&token=xxxxxxxxxx&requestTimeStamp=1420359244 X-Message-Code: aaaaaaaaaaaaaaaaaaaaaaaaaaaaa Content-Encoding: gzip Content-Length: 885 Content-Type: application/json; charset=utf-8 Connection: close (gzipによる文字化け)
(gzipによる文字化け)と書いた部分がレスポンスボディにあたる部分で、ヘッダに「Content-Encoding: gzip」と記載がある通り、gzip圧縮されているために文字化けしており、解凍しないと内容を見ることができない。そこで、ボディ部分のみ取得してgzip解凍の処理をする必要がある。httpの決まりでヘッダとボディの間には改行コード(CRLF)が2回入る。これはバイナリ表記だと「0A 0D 0A 0D」なので、これを判別して以降をボディとして取得するようにする。
あとは、パケットの終わりまで取得すれば終わりといいたいところだけど残念ながらそうはいかず…1パケットにボディ部が全て入っていれば問題ないんだけど、たいてい複数パケットに分かれて送られてくるんで、パケットの最後までの取得が終わったら、取得したバイト数とヘッダの「Content-Length: 」に記載されているサイズを比較して、すべて取得できているか判別する必要がある。もしまだ「Content-Length: 」のサイズに達していないのなら次のパケットからボディ部の続きを取得するようにする。
この、次にくるパケット以降にはhttpヘッダがないので、「0A 0D 0A 0D」による判別は使えない。tcpヘッダの終わった直後からをボディ部として取得するようにする。これはパケットの先頭からIPヘッダサイズとtcpヘッダサイズの和の分を進んだ先からになる。パケットの最後まで取得が終わったらまた「Content-Length: 」のサイズと比較、「Content-Length: 」のサイズに達したらボディの取得は終了、まだなら次のパケットをまた待つ。
こうしてボディ部の取得が完了したら一度gzipファイルとして書き出し、出来たファイルを読み込み・gzip解凍すれば目的は達成。厳密にやるならTcpの順序制御とかを見てデータの結合しないとダメなんだろうけど、通信対象と1か所にしとけば今のところ問題なく動いたのでその処理は割愛。ソースは以下の通り。
Imports System.Net Imports System.Net.Sockets Imports System.Text Imports System.IO.Compression Imports System.IO Module PakCap 'IPヘッダ長 Private Const IP_HEADER_LENGTH As Integer = 20 Sub Main() '文字コードはUTF8にする Dim enc As Encoding = Encoding.UTF8 '自分のIPアドレスを設定する Dim ip As String = "192.168.3.100" Dim socket As New Socket(AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.IP) socket.Bind(New IPEndPoint(IPAddress.Parse(ip), 0)) socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AcceptConnection, True) socket.IOControl(IOControlCode.ReceiveAll, New Byte() {1, 0, 0, 0}, New Byte() {0, 0, 0, 0}) Dim packetCounter As Integer = 0 Dim fileCounter As Integer = 0 Dim isBuffered As Boolean = False Dim contentSize As Integer = 0 Dim contentCounter As Integer = 0 Dim contentBuff As Byte() = Nothing Dim isGzip As Boolean = False Using ws As New System.IO.StreamWriter("C:\test\packet.txt", False, enc) Do Dim buff As Byte() = New Byte(4095) {} socket.Receive(buff) 'IPパケットヘッダ情報の取得 Dim packetSize As Integer = buff(2) * 256 + buff(3) Dim packetId As Integer = buff(4) * 256 + buff(5) Dim packetFlg As Integer = buff(6) * 256 + buff(7) Dim protocolNum As Integer = buff(9) '送信元IP Dim srcIP As String = String.Format("{0}.{1}.{2}.{3}", buff(12), buff(13), buff(14), buff(15)) '送信先IP Dim dstIP As String = String.Format("{0}.{1}.{2}.{3}", buff(16), buff(17), buff(18), buff(19)) '送信元Port Dim srcPort As Integer = buff(20) * 256 + buff(21) '送信先Port Dim dstPort As Integer = buff(22) * 256 + buff(23) 'utf8の文字列 Dim utf8Str As String = enc.GetString(buff, 0, packetSize) Console.WriteLine("packetSize={0}", packetSize) Console.WriteLine("送信元{0}/{1} - 送信先{2}/{3}", srcIP, srcPort, dstIP, dstPort) Console.WriteLine() '特定サーバとの通信に絞って処理をする '他のサーバとのパケットが混ざるとボディの取得が失敗する可能性が高いと思われるため Dim targetIP as String = "210.xxx.xxx.xxx" If srcIP = targetIP Or dstIP = targetIP Then If isBuffered Then 'バッファリング待機中の場合 If protocolNum = 6 Then 'TCPの場合ヘッダ長を取得 Dim tcpHeaderLength As Integer = Convert.ToInt32(Convert.ToString(buff(32), 2).PadLeft(8, "0").Substring(0, 4), 2) * 4 For i As Integer = IP_HEADER_LENGTH + tcpHeaderLength To packetSize - 1 contentBuff(contentCounter) = buff(i) contentCounter = contentCounter + 1 'ContentSizeを超えるデータ量だった場合は中断(パケット取り違えの場合) If contentCounter >= contentBuff.Length Then Exit For End If Next End If 'もしパケットにContent-Lengthが含まれていたらバッファリングは中断する If utf8Str.Contains("Content-Length:") Then isBuffered = False End If End If If Not isBuffered Then 'バッファリング待機なしの場合 'コンテンツバッファカウンターのクリア contentCounter = 0 'Content-Lengthのクリア contentSize = 0 'httpヘッダのContent-Lengthを取得 If utf8Str.Contains("Content-Length:") Then Dim r As New System.Text.RegularExpressions.Regex("Content-Length: ([0-9]+)", System.Text.RegularExpressions.RegexOptions.IgnoreCase Or System.Text.RegularExpressions.RegexOptions.Singleline) Dim mc As System.Text.RegularExpressions.MatchCollection = r.Matches(enc.GetString(buff)) For Each m As System.Text.RegularExpressions.Match In mc '正規表現に一致したグループの文字列を表示 contentSize = m.Groups(1).Value Exit For Next End If If contentSize > 0 Then 'gzipかどうかの判別 isGzip = utf8Str.Contains("Content-Encoding: gzip") 'Contentがスタートする場所を取得 Dim contentStart As Integer = packetSize For i As Integer = 4 To packetSize - 1 If buff(i - 4) = 13 AndAlso buff(i - 3) = 10 AndAlso buff(i - 2) = 13 AndAlso buff(i - 1) = 10 Then contentStart = i Exit For End If Next 'Contentの取得 contentBuff = New Byte(contentSize - 1) {} For i As Integer = contentStart To packetSize - 1 contentBuff(contentCounter) = buff(i) contentCounter = contentCounter + 1 Next End If End If If contentSize > 0 AndAlso contentBuff IsNot Nothing Then If contentCounter >= contentSize - 1 Then 'コンテンツが全部取得できた isBuffered = False 'ファイルに書き出す Dim filePath As String = "C:\test\Contents[" & packetCounter.ToString.PadLeft(6, "0") & "]" Dim thawFilePath As String = filePath If isGzip Then filePath = filePath & ".gz" End If Using fs As New System.IO.FileStream(filePath, System.IO.FileMode.Create, System.IO.FileAccess.Write) fs.Write(contentBuff, 0, contentBuff.Length) End Using If isGzip Then Dim gzipFileStrm As New System.IO.FileStream(filePath, System.IO.FileMode.Open, System.IO.FileAccess.Read) Using gzipStrm As New System.IO.Compression.GZipStream(gzipFileStrm, System.IO.Compression.CompressionMode.Decompress) Using outFileStrm As New System.IO.FileStream(thawFilePath, System.IO.FileMode.Create, System.IO.FileAccess.Write) Dim gzipBuff(1024) As Byte While True '書庫から展開されたデータを読み込む Dim readSize As Integer = gzipStrm.Read(gzipBuff, 0, gzipBuff.Length) '最後まで読み込んだ時は、ループを抜ける If readSize = 0 Then Exit While End If '展開先のファイルに書き込む outFileStrm.Write(gzipBuff, 0, readSize) End While End Using End Using '圧縮ファイルを削除する File.Delete(filePath) End If Else 'コンテンツが全部取得できていないので次のパケットを待つ isBuffered = True End If End If ws.WriteLine("[PacketCount={0}]", packetCounter.ToString.PadLeft(6, "0")) ws.WriteLine("送信元{0}/{1} - 送信先{2}/{3}", srcIP, srcPort, dstIP, dstPort) ws.WriteLine("packetSize={0}, protocolNum={1}, contentSize={2}", packetSize, protocolNum, contentSize) ws.WriteLine(utf8Str) ws.WriteLine() ws.Write("----------------------- BINARY -----------------------") 'パケットの内容を16進数で表記 For i As Integer = 0 To packetSize - 1 If (i Mod 16) = 0 Then ws.WriteLine() ws.Write("{0}:", (i / 16).ToString().PadLeft(4, "0")) End If ws.Write(" {0}", Convert.ToString(buff(i), 16).PadLeft(2, "0")) Next ws.WriteLine() ws.WriteLine("------------------------------------------------------") ws.WriteLine() ws.WriteLine() packetCounter = packetCounter + 1 End If Loop End Using End Sub End Module
VB.NETでパケットキャプチャ作成
VB.NETでパケットキャプチャー作成のメモ。
参考にしたサイトのURL:http://sekki.org/wordpress/?p=212
ソースはこんな感じ。
Imports System.Net Imports System.Net.Sockets Imports System.Text Module PacTest Sub Main() '文字コードはUTF8にする Dim enc As Encoding = Encoding.UTF8 '自分のIPアドレスを設定する Dim ip As String = "192.168.3.100" 'パケットのカウント Dim packetCounter As Integer = 0 Dim socket As New Socket(AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.IP) socket.Bind(New IPEndPoint(IPAddress.Parse(ip), 0)) socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AcceptConnection, True) socket.IOControl(IOControlCode.ReceiveAll, New Byte() {1, 0, 0, 0}, New Byte() {0, 0, 0, 0}) Using ws As New System.IO.StreamWriter("C:\test\packet.txt", False, enc) Do Dim buff As Byte() = New Byte(4095) {} socket.Receive(buff) 'IPパケットヘッダ情報の取得 'パケットサイズ Dim packetSize As Integer = buff(2) * 256 + buff(3) 'プロトコル番号 Dim protocolNum As Integer = buff(9) '送信元IP Dim srcIP As String = String.Format("{0}.{1}.{2}.{3}", buff(12), buff(13), buff(14), buff(15)) '送信先IP Dim dstIP As String = String.Format("{0}.{1}.{2}.{3}", buff(16), buff(17), buff(18), buff(19)) '(ここから先はIPヘッダではない) '送信元Port Dim srcPort As Integer = buff(20) * 256 + buff(21) '送信先Port Dim dstPort As Integer = buff(22) * 256 + buff(23) 'パケットをUTF8での文字列で取得 Dim utf8Str As String = enc.GetString(buff, 0, packetSize) 'コンソールにパケットカウント数と送信元IP/ポート、送信先IP/ポートを表示 'コンソールにUTF8での文字列を表示させようとすると、エラーになるので表示させない '理由:IPヘッダ部分とうはUTF8で文字にできないので化け文字になる→化け文字はコンソール表示不可で落ちる Console.WriteLine("packetCount={0}", packetCounter) Console.WriteLine("送信元{0}/{1} - 送信先{2}/{3}", srcIP, srcPort, dstIP, dstPort) Console.WriteLine() 'ファイルへの書き出し(ファイルは化け文字があっても問題がない) ws.WriteLine("[PacketCount={0}]", packetCounter.ToString.PadLeft(6, "0")) ws.WriteLine("送信元{0}/{1} - 送信先{2}/{3}", srcIP, srcPort, dstIP, dstPort) ws.WriteLine("packetSize={0}, protocolNum={1}", packetSize, protocolNum) ws.WriteLine(utf8Str) ws.WriteLine() ws.Write("----------------------- BINARY -----------------------") 'パケットの内容を16進数で表記 For i As Integer = 0 To packetSize - 1 If (i Mod 16) = 0 Then ws.WriteLine() ws.Write("{0}:", (i / 16).ToString().PadLeft(4, "0")) End If ws.Write(" {0}", Convert.ToString(buff(i), 16).PadLeft(2, "0")) Next ws.WriteLine() ws.WriteLine("------------------------------------------------------") ws.WriteLine() ws.WriteLine() packetCounter = packetCounter + 1 Loop End Using End Sub End Module
以下注意点
Windows7(たぶんWindows Vista以降)ではセキュリティが強化されてる関係で、管理者権限で実行する必要がある。管理者権限でないと以下のソースの部分で「アクセス許可で禁じられた方法でソケットにアクセスしようとしました。」と例外が発生して落ちる。なので、デバッグでの実行はできない。リリースビルドして出来たexeファイルを管理者権限で実行する。
Dim socket As New Socket(AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.IP)
また、動かしてみると送信パケットしかキャプチャされていないことに気が付く。調べてみると、Windowsファイアウォールが受信パケットを遮断しているらしい(これもWindows Vista以降?)。
コントロールパネルのWindowsファイアウォールから詳細設定を選択し、受信規則で新しい規則を作成、すべてのプログラムを接続許可できるように設定することで、受信パケットもキャプチャできるようになる。
スクフェスをPCでプレイする
スクフェスをPCでプレイする方法のメモ。
使用ソフト:BlueStacks
[インストール]
最新版をインストールしてスクフェスを起動してみたところ画面が真っ白になる。
一応ボタンがあるところをクリックすれば動くようだが実用には堪えない…
調べたところ、旧バージョンを使えばちゃんと動くっぽいので取得してインストール。
旧バージョンのURL:http://bluestacks-app-player.jp.uptodown.com/old
BuleStacksのインストール自体は難しい所もなく完了する(設定はすべてデフォルトを選択)。
BuleStacksを起動したら今度はスクフェスのインストール。
ホーム画面?から検索を選択して、「sukufes」と入れてgoogle Playの検索をする。
なんでアルファベットかというと、デフォルト状態で日本語入力できないから。
検索結果でスクフェスが出てくるので後はスマホやipadでする手順と同様にアプリをインストールする。
初回の場合、google Play検索のタイミングでgoogleのアカウント情報を要求されるので、既存のアカウントを選択し、ID、Passを入力する。この辺は画面の指示を見ればできる内容だった。
昔取ったgmailのアカウントが問題なく使えたので、アカウント持ってない人はgoogleのページからアカウントを新規取得してからやればOKだと思う。gmailアカウントの取得方法は昔なんで忘れた、まあたぶん簡単。
なお、昔ベータ版だった頃は無料らしかったんだけど、今は違うようで、月2ドル払うか、無料のまま使う場合はスポンサーアプリのインストールが定期的に必要になる。たまに選択画面がでてきて、それ以上の操作ができなくなるので、そういう場合はスポンサーアプリをインストールすればまた使えるようになる。
BlueStacksのホーム画面イメージ:
※左側の赤矢印にそってマウスでドラッグすると通知が、右側赤矢印にそってマウスをドラッグすると設定が出てくる
[キーボード入力設定]
マウスだと同時押しができないんで、キーボードでタップの代わりができるように設定する方法。知恵袋の回答の方法を使ってます。リンク⇒http://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q12129677282
C:\ProgramData\BlueStacks\UserData\InputMapper
のフォルダに「klb.android.lovelive.cfg」ってファイルを置けばOK。
ProgramDataは隠しフォルダなので表示されない場合は、フォルダのオプションの表示タブで「隠しファイル~表示する」にチェックを入れる。
klb.android.lovelive.cfg:
[keys] 2 = Tap (10,25) 3 = Tap (15,50) 4 = Tap (20,70) 5 = Tap (35,80) Space = Tap (50,90) 7 = Tap (65,80) 8 = Tap (80,70) 9 = Tap (85,50) 0 = Tap (89,25)
※スクフェスをPCでプレイする2~キーボードマッピングの追加 - Future Convergence PRJ.に追加のキーボード入力設定を記載
[日本語入力設定]
google Playから「Shimeji」ってアプリをインストールする。
インストールしたらShimejiをキーボードとして使うように設定する。この辺りも画面の指示に従っていくだけ。これで設定は終わり。
入力したい欄をダブルクリックするとテキスト入力欄が出てくるから、そこに文字を打ち込んで画面上の完了ボタンを押すか、Enterキーを押せば入力が完了する。