Future Convergence PRJ.

主にプログラミング関連で調べたことのメモ(趣味プログラムなので動作は保証しません)

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のホーム画面イメージ:
f:id:CHappy:20150104004505j:plain
※左側の赤矢印にそってマウスでドラッグすると通知が、右側赤矢印にそってマウスをドラッグすると設定が出てくる


[キーボード入力設定]
マウスだと同時押しができないんで、キーボードでタップの代わりができるように設定する方法。知恵袋の回答の方法を使ってます。リンク⇒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キーを押せば入力が完了する。