自分だけのクイズ作成 - Quipha公開中

【C#】自作アプリでブラウザの通信をキャプチャ【Tshark】

C#
スポンサーリンク

はじめに

今回は、ブラウザの通信を随時キャプチャして、何か処理するアプリを作ってみようと思います。

ブラウザの通信内容を、自作アプリの方で捕捉できれば、例えば情報をリアルタイムで解析したり、アイディア次第で面白いものが作れると思います

また、暗号化された通信も復号化した上で、アプリ側で捕捉してみます。

今回は、C#でWindows自作アプリを作り、パケットキャプチャ部分はWireshark(Tshark)を使用して実現しました。

プログラミング言語は何でも良かったのですが、Windowsアプリケーションを手軽に作れるのでC#を採用しました。

ちなみに、C#で通信のコアなところまで実装すると、他の言語への汎用性がないので、通信部分はWireshark (Tshark) を使いました。

自作アプリではその通信内容を取得しているだけですので、例えばPython等、他の言語でも同様のことはできるかと思います。

Wiresharkのインストール

Wiresharkとは、PC上の通信(パケット)を見るためのツールです。
WiresharkにはTsharkが同梱されますので、以下の記事をもとに構築と設定を済ませて下さい。

Visual Studio 2019のインストール

今回は、C#でアプリケーションを作成します。
以下の手順を参考に、構築を済ませて下さい。

自作アプリでPCの通信インターフェースの一覧を表示

いよいよ自作アプリを作成します。🙂
パケットキャプチャについてはWireshark(Tshark)を使用します。

仕組みとしては、アプリ(C#)からプロセスを実行して、標準出力を受け取っているだけです。
ですので、C#に限らず他の言語でも同様のことは可能だと思います。

フォームの名称を変更しました。
ここからは最低限の実装のみ解説しますが、見た目やリファクタリングなどはお好みで実装してみてください。

細かい例外処理や制御は書いておりません。(ボタンを連打するとか)
まずは本質的な処理の流れを実装しています。

まずは、PCのネットワークインターフェースの一覧を表示する処理を実装します。
ボタンとテキストボックスを配置します。

Processを使用して、Tsharkコマンドを実行しますので、クラス内変数を定義します。

   public partial class Form1 : Form
   {

       // プロセス tshak インターフェース一覧を表示
       Process processTsInterface = null;

ボタンクリックイベントは以下です。

tshark.exeのパスは各自の環境に合わせて変更して下さい。

       /// <summary>
       /// インターフェース一覧 ボタンクリック
       /// </summary>
       /// <param name="sender"></param>
       /// <param name="e"></param>
       private void button1_Click(object sender, EventArgs e)
       {

           // コマンド
           string executeCommand = "C:\\Program Files\\Wireshark\\tshark.exe";

           // コマンド引数
           string args = "-D";

           // プロセスを起動
           processTsInterface = new Process();
           ProcessStartInfo processStartInfo = new ProcessStartInfo(executeCommand, args);
           processStartInfo.CreateNoWindow = true;
           processStartInfo.UseShellExecute = false;
           processStartInfo.RedirectStandardOutput = true;
           processStartInfo.RedirectStandardError = true;

           // 文字化けするので、文字コードを指定する
           processStartInfo.StandardErrorEncoding = Encoding.UTF8;
           processStartInfo.StandardOutputEncoding = Encoding.UTF8;

           // 起動
           processTsInterface = Process.Start(processStartInfo);

           // 受信イベント
           processTsInterface.OutputDataReceived += dataReceivedTsInterface;
           processTsInterface.ErrorDataReceived += errReceivedTsInterface;

           processTsInterface.BeginErrorReadLine();
           processTsInterface.BeginOutputReadLine();
       }

Tsharkの標準出力の結果を取得する関数を定義します。
一応エラー出力も捕捉します。

       /// <summary>
       /// tshrak インターフェース一覧を表示 出力
       /// </summary>
       /// <param name="sender"></param>
       /// <param name="e"></param>
       void dataReceivedTsInterface(object sender, DataReceivedEventArgs e)
       {
           string packetText = e.Data;
           if (packetText != null && packetText.Length > 0)
           {
               PrintTextBoxByThread(packetText, textBox1);
           }
       }

       /// <summary>
       /// tshrak インターフェース一覧を表示 エラー
       /// </summary>
       /// <param name="sender"></param>
       /// <param name="e"></param>
       void errReceivedTsInterface(object sender, DataReceivedEventArgs e)
       {
           string packetText = e.Data;
           if (packetText != null && packetText.Length > 0)
           {
               PrintTextBoxByThread("ERR:" + packetText, textBox1);
           }
       }

上記の受信イベントは別スレッドで動作するため、テキストボックスに表示を行うためには細工が必要です。
以下のような関数を定義します。

       /// <summary>
       /// テキストボックスに出力
       /// </summary>
       /// <param name="msg"></param>
       /// <param name="tb"></param>
       private void PrintTextBox(string msg, TextBox tb)
       {
           if (string.IsNullOrEmpty(tb.Text))
           {
               tb.Text = msg;
           }
           else
           {
               tb.Text = tb.Text + "\r\n" + msg;
           }

           // キャレット位置を末尾に移動
           tb.SelectionStart = tb.Text.Length;
           tb.Focus();
           tb.ScrollToCaret();
       }

       /// <summary>
       /// 別スレッドからのテキストボックス更新
       /// </summary>
       /// <param name="msg"></param>
       /// <param name="tb"></param>
       private void PrintTextBoxByThread(string msg, TextBox tb)
       {
           // 別スレッドからテキストの更新
           this.Invoke((MethodInvoker)(() => PrintTextBox(msg, tb)));
       }

最後に、フォームのクローズイベントで以下の処理を行います。

       /// <summary>
       /// フォームクローズ
       /// </summary>
       /// <param name="sender"></param>
       /// <param name="e"></param>
       private void Form1_FormClosed(object sender, FormClosedEventArgs e)
       {
           if (processTsInterface != null && !processTsInterface.HasExited)
           {
               // プロセス終了
               processTsInterface.Kill();
           }
       }

それでは、アプリケーションを実行してみましょう。

「インターフェース一覧ボタン」をクリックすると、インターフェースの一覧が出力されました。
仕組みは簡単で、Tsharkをコマンド実行し、標準出力を受け取っているだけです。

自作アプリでTsharkの通信をC#で取得

続いて、Tsharkを使いブラウザの通信をアプリ側で取得したいと思います。

インターフェースを指定するテキストボックスと、キャプチャ開始ボタン、通信内容を表示するテキストボックスを用意します。

実装の要領は先程と同じです。

Processを使用して、Tsharkコマンドを実行しますので、クラス内変数を定義します。

       // プロセス Tshark パケットキャプチャを表示
       Process processTsCap = null;

「パケットキャプチャ開始ボタン」のクリックイベントを実装します。
前回のWiresharkの記事同様、自サイトの通信をキャプチャするサンプルです。

とりあえず試したいのであれば、本サイトにアクセスするか、各自、特定のサーバーIPアドレスを指定するか、IPアドレスの指定をせず全通信のキャプチャを取るかなど、コードを書き換えて下さい。

        /// <summary>
        /// パケットキャプチャ開始 ボタンクリック
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button2_Click(object sender, EventArgs e)
        {
            // 必須チェック
            if (string.IsNullOrEmpty(textBox2.Text))
            {
                MessageBox.Show("インターフェース番号を指定してください");
                return;
            }

            // コマンド
            string executeCommand = "C:\\Program Files\\Wireshark\\tshark.exe";

            // コマンド引数
            // TODO: 適当に条件変えて下さい
            string args = String.Format("-i {0} -l -e text -Tfields " +
                "-f \"net 183.90.240.49\"",
                textBox2.Text
                );

            // プロセスを起動
            processTsCap = new Process();
            ProcessStartInfo processStartInfo = new ProcessStartInfo(executeCommand, args);
            processStartInfo.CreateNoWindow = true;
            processStartInfo.UseShellExecute = false;
            processStartInfo.RedirectStandardOutput = true;
            processStartInfo.RedirectStandardError = true;

            // 文字化けするので、文字コードを指定する
            processStartInfo.StandardErrorEncoding = Encoding.UTF8;
            processStartInfo.StandardOutputEncoding = Encoding.UTF8;

            // 起動
            processTsCap = Process.Start(processStartInfo);

            // 受信イベント
            processTsCap.OutputDataReceived += dataReceivedTsCap;
            processTsCap.ErrorDataReceived += errReceivedTsCap;

            processTsCap.BeginErrorReadLine();
            processTsCap.BeginOutputReadLine();
        }

Tsharkの標準出力の結果を取得する関数を定義します。
一応エラー出力も捕捉します。

       /// <summary>
       /// tshrak パケットキャプチャを表示 出力
       /// </summary>
       /// <param name="sender"></param>
       /// <param name="e"></param>
       void dataReceivedTsCap(object sender, DataReceivedEventArgs e)
       {
           string packetText = e.Data;
           if (packetText != null && packetText.Length > 0)
           {
               PrintTextBoxByThread(packetText, textBox3);
           }
       }

       /// <summary>
       /// tshrak パケットキャプチャを表示 エラー
       /// </summary>
       /// <param name="sender"></param>
       /// <param name="e"></param>
       void errReceivedTsCap(object sender, DataReceivedEventArgs e)
       {
           string packetText = e.Data;
           if (packetText != null && packetText.Length > 0)
           {
               PrintTextBoxByThread("ERR:" + packetText, textBox3);
           }
       }

最後に、フォームのクローズイベントに処理を追加します。

       /// <summary>
       /// フォームクローズ
       /// </summary>
       /// <param name="sender"></param>
       /// <param name="e"></param>
       private void Form1_FormClosed(object sender, FormClosedEventArgs e)
       {
           if (processTsInterface != null && !processTsInterface.HasExited)
           {
               // プロセス終了
               processTsInterface.Kill();
           }

           if (processTsCap != null && !processTsCap.HasExited)
           {
               // プロセス終了
               processTsCap.Kill();
           }
       }

それでは、アプリを起動してみます。
インターフェース番号を指定して、パケットキャプチャ開始ボタン」をクリックします。
ちなみに、インターフェース名の表示については、エラー出力で取れるようです。

Chromeは一旦すべて閉じて、自作アプリを実行したまま、Chromeのショートカットを起動します。
この時、自作アプリを必ず先に実行します。後から実行では通信をうまく取得できません。

Chromeのショートカットについては、 「Wiresharkのインストール」記事を参考にして下さい。

自サイトへアクセスして、通信を確認してみましょう。
暗号化された通信内容が、リアルタイムで復号化されて取得できました。

改めて、ソースの全容を載せておきます。(Form1.cs)

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        // プロセス tshak インターフェース一覧を表示
        Process processTsInterface = null;

        // プロセス Tshark パケットキャプチャを表示
        Process processTsCap = null;

        public Form1()
        {
            InitializeComponent();
        }

        /// <summary>
        /// インターフェース一覧 ボタンクリック
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button1_Click(object sender, EventArgs e)
        {

            // コマンド
            string executeCommand = "C:\\Program Files\\Wireshark\\tshark.exe";

            // コマンド引数
            string args = "-D";

            // プロセスを起動
            processTsInterface = new Process();
            ProcessStartInfo processStartInfo = new ProcessStartInfo(executeCommand, args);
            processStartInfo.CreateNoWindow = true;
            processStartInfo.UseShellExecute = false;
            processStartInfo.RedirectStandardOutput = true;
            processStartInfo.RedirectStandardError = true;

            // 文字化けするので、文字コードを指定する
            processStartInfo.StandardErrorEncoding = Encoding.UTF8;
            processStartInfo.StandardOutputEncoding = Encoding.UTF8;

            // 起動
            processTsInterface = Process.Start(processStartInfo);

            // 受信イベント
            processTsInterface.OutputDataReceived += dataReceivedTsInterface;
            processTsInterface.ErrorDataReceived += errReceivedTsInterface;

            processTsInterface.BeginErrorReadLine();
            processTsInterface.BeginOutputReadLine();
        }

        /// <summary>
        /// tshrak インターフェース一覧を表示 出力
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void dataReceivedTsInterface(object sender, DataReceivedEventArgs e)
        {
            string packetText = e.Data;
            if (packetText != null && packetText.Length > 0)
            {
                PrintTextBoxByThread(packetText, textBox1);
            }
        }

        /// <summary>
        /// tshrak インターフェース一覧を表示 エラー
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void errReceivedTsInterface(object sender, DataReceivedEventArgs e)
        {
            string packetText = e.Data;
            if (packetText != null && packetText.Length > 0)
            {
                PrintTextBoxByThread("ERR:" + packetText, textBox1);
            }
        }

        /// <summary>
        /// テキストボックスに出力
        /// </summary>
        /// <param name="msg"></param>
        /// <param name="tb"></param>
        private void PrintTextBox(string msg, TextBox tb)
        {
            if (string.IsNullOrEmpty(tb.Text))
            {
                tb.Text = msg;
            }
            else
            {
                tb.Text = tb.Text + "\r\n" + msg;
            }

            // キャレット位置を末尾に移動
            tb.SelectionStart = tb.Text.Length;
            tb.Focus();
            tb.ScrollToCaret();
        }

        /// <summary>
        /// 別スレッドからのテキストボックス更新
        /// </summary>
        /// <param name="msg"></param>
        /// <param name="tb"></param>
        private void PrintTextBoxByThread(string msg, TextBox tb)
        {
            // 別スレッドからテキストの更新
            this.Invoke((MethodInvoker)(() => PrintTextBox(msg, tb)));
        }

        /// <summary>
        /// パケットキャプチャ開始 ボタンクリック
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button2_Click(object sender, EventArgs e)
        {
            // 必須チェック
            if (string.IsNullOrEmpty(textBox2.Text))
            {
                MessageBox.Show("インターフェース番号を指定してください");
                return;
            }

            // コマンド
            string executeCommand = "C:\\Program Files\\Wireshark\\tshark.exe";

            // コマンド引数
            // TODO: 適当に条件変えて下さい
            string args = String.Format("-i {0} -l -e text -Tfields " +
                "-f \"net 183.90.240.49\"",
                textBox2.Text
                );

            // プロセスを起動
            processTsCap = new Process();
            ProcessStartInfo processStartInfo = new ProcessStartInfo(executeCommand, args);
            processStartInfo.CreateNoWindow = true;
            processStartInfo.UseShellExecute = false;
            processStartInfo.RedirectStandardOutput = true;
            processStartInfo.RedirectStandardError = true;

            // 文字化けするので、文字コードを指定する
            processStartInfo.StandardErrorEncoding = Encoding.UTF8;
            processStartInfo.StandardOutputEncoding = Encoding.UTF8;

            // 起動
            processTsCap = Process.Start(processStartInfo);

            // 受信イベント
            processTsCap.OutputDataReceived += dataReceivedTsCap;
            processTsCap.ErrorDataReceived += errReceivedTsCap;

            processTsCap.BeginErrorReadLine();
            processTsCap.BeginOutputReadLine();
        }

        /// <summary>
        /// tshrak パケットキャプチャを表示 出力
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void dataReceivedTsCap(object sender, DataReceivedEventArgs e)
        {
            string packetText = e.Data;
            if (packetText != null && packetText.Length > 0)
            {
                PrintTextBoxByThread(packetText, textBox3);
            }
        }

        /// <summary>
        /// tshrak パケットキャプチャを表示 エラー
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void errReceivedTsCap(object sender, DataReceivedEventArgs e)
        {
            string packetText = e.Data;
            if (packetText != null && packetText.Length > 0)
            {
                PrintTextBoxByThread("ERR:" + packetText, textBox3);
            }
        }

        /// <summary>
        /// フォームクローズ
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            if (processTsInterface != null && !processTsInterface.HasExited)
            {
                // プロセス終了
                processTsInterface.Kill();
            }

            if (processTsCap != null && !processTsCap.HasExited)
            {
                // プロセス終了
                processTsCap.Kill();
            }
        }
    }
}

最後に

実装手順は以上になります。お疲れ様でした。
アイディア次第では面白いアプリが作れるかもしれませんね。
それでは。🙂

コメント

タイトルとURLをコピーしました