トンネルプロトコル(ICMPTunnel)ツールとしてのStreamRelay.NET.exe

プラグイン「stdioICMPTunnel.dll」は、「ポート=0」(-LocalPort 0 または -RemotePort 0)の時の標準入出力のリダイレクト先を指定する

リダイレクト先として、ICMPTunnel など

一応、簡単な説明を[free119.htm](http://rocketeer.dip.jp/sanaki/free/free119.htm#icmptunnel)にも書いたけど、もう少し具体的に書く。

---
### はじめに

「Local」と呼称される側なり、「Remote」と呼称される側なりが、「ポート=0」で標準入出力の場合が、このプラグインの前提条件

例えば、「Local」側で入力をトンネルしたい場合は、以下のオプション

- -LocalInputIcmpTunnel ≪オプション文字列≫

を使う。

前提の「ポート=0」も含ませると

- -LocalPort 0 -LocalInputIcmpTunnel ≪オプション文字列≫

な感じ

同様に、「Local」側で出力をトンネルしたい場合は、

- -LocalOutputIcmpTunnel ≪オプション文字列≫

を使う。

「Remote」側でそうしたい場合も、同様に

- -RemoteInputIcmpTunnel ≪オプション文字列≫ // 「Remote」と呼称される側の入力
- -RemoteOutputIcmpTunnel ≪オプション文字列≫ // 「Remote」と呼称される側の出力

を使う。

---
### タイプ

トンネルのタイプは、以下

<dl>
 <dt><b>TunnelType=ICMPorg</b>
  <dd>独自の ICMP トンネルで、ICMP のペイロード部にそのままデータを埋め込む<br><br>
 <dt><b>TunnelType=Ping</b>
  <dd>TunnelType=ICMPorgと同じ方式だが、<a href="https://docs.microsoft.com/ja-jp/dotnet/api/system.net.networkinformation.ping?view=netframework-4.8">System.Net.NetworkInformation.Ping</a>クラスを使う事で UACな管理権限が不要。ただしICMP Echo Requestのみで送信専用<br><br>
 <dt><b>TunnelType=IPv4OpRRUdp</b> または <b>TunnelType=IPv4OpRRIcmp</b>
  <dd>IPv4 オプションのレコードルートオプションに見せかけて埋め込む。それぞれ上位プロトコル(フェイク)がICMPなのかUDPかで2つのオプションがある
</dl>

---
#### ICMPorg または Ping

データをそのままICMPペイロード部に埋め込む

Ping の場合は、[System.Net.NetworkInformation.Ping](https://docs.microsoft.com/ja-jp/dotnet/api/system.net.networkinformation.ping?view=netframework-4.8)クラスを使う事で ICMP Echo Request 限定ではあるが UACな管理権限を不要としたが、送信のみ。

---
#### IPv4OpRRUdp または IPv4OpRRIcmp

データを IPv4 オプションのレコードルート(RR)に見せかけて埋め込む

IPv4 オプション内部の書式は以下の通り

<table border="1" align="center">
 <tr><td>0x07(レコードルートのフラグ値は0x07)</td></tr>
 <tr><td>IPv4オプションのサイズ(RRは4の倍数なので{IPv4オプションが4の倍数}、4の倍数)</td></tr>
 <tr><td>ポインタ(=IPv4オプションのサイズ(※))</td></tr>
 <tr><td>実際のデータのサイズ</td></tr>
 <tr><td>実際のデータ(上のデータサイズを加えて4の倍数にならない場合は、NULLパディング)</td></tr>
 <tr><td>0x00</td></tr>
</table>

この次に上位プロトコルがくるが、上位プロトコルとして「ICMP」「UDP」が選択できる

(※) : 同じ値にする事で、経路上のルータに上書きさせない

---
### 権限

- 受信は全て RawSocket を使っているので、UACな管理権限が必要
- 送信については、ICMP に関してのみ RawSocket を使っているので、UACな管理権限が必要。ただし、Ping は Pingクラスを使っているので UAC な管理権限は不要
- UDP の送信については、[System.Net.Sockets.UdpClient](https://docs.microsoft.com/ja-jp/dotnet/api/system.net.sockets.udpclient?view=netframework-4.8) を使っているで、UACな管理権限は不要

---
### オプション

<table border="1" align="center">
<tr><th>Src</th><td>送信元のアドレス。送受信で指定可。受信時にはフィルタ条件となる</td></tr>
<tr><th>Dist</th><td>送信先のアドレス。送信時には必須。受信時にはフィルタ条件となる</td></tr>
<tr><th>Bind</th><td>受信時にパイントするNICのアドレス。基本的に受信時は「Dist」で指定したアドレスにバインドするが、受信フィルタにしたくない場合にはこちら側を使用</td></tr>
<tr><th>Type</th><td>ICMPのタイプ。Ping以外のICMPの送信時には必須。受信時にはフィルタ条件となる</td></tr>
<tr><th>Code</th><td>ICMPのコード。Ping以外のICMPの送信時には必須。受信時にはフィルタ条件となる</td></tr>
<tr><th>srcPort</th><td>送信元のポート。UDPの送信時には指定可。受信時にはフィルタ条件となる</td></tr>
<tr><th>distPort</th><td>送信先のポート。UDPの送信時には必須。受信時にはフィルタ条件となる</td></tr>
<tr><th>headersize=4 or 8]</th><td>ICMPヘッダサイズ。既定は8</td></tr>
<tr><th>DataSize</th><td>ICMPorgの送信データの最大値(32Bit単位)</td></tr>
<tr><th>TTL</th><td>送受信時の TTL。受信時にはフィルタ条件となる。送信時は、min-max%2cランダムアルゴリズム名で、その範囲の乱数をTTLにセットする</td></tr>
<tr><th>ToS</th><td>送受信時の ToS(後述)</td></tr>
<tr><th>DF</th><td>DontFragmentビットの指定。送信時には指定可。受信時にはフィルタ条件となる</td></tr>
<tr><th>FakeDataFile</th><td>IPv4OpRRUdp または IPv4OpRRIcmpのフェイクデータをファイルで指定。ICMPの場合はICMPヘッダから。UDPのデータはUDPペイロードだけ</td></tr>
<tr><th>FakeData</th><td>IPv4OpRRUdp または IPv4OpRRIcmpのフェイクデータをURLエンコードな文字列で指定。ICMPの場合はICMPヘッダから。UDPのデータはUDPペイロードだけ</td></tr>
<tr><th>FakeDataHex</th><td>IPv4OpRRUdp または IPv4OpRRIcmpのフェイクデータを16進数な文字列で指定。ICMPの場合はICMPヘッダから。UDPのデータはUDPペイロードだけ</td></tr>
<tr><th>LSRまたはSSR</th><td>ルーズソースルーティング、またはストリクトソースルーティングを指定。送信時のみ指定可。IPv4OpRRUdp または IPv4OpRRIcmpはRRなのでダメ。IPv4でICMPorgの時のみ使用できる</td></tr>
</table>

---
#### TTL

送信パケットの TTL の値をセットする、また受信時は、受信パケットのTTL値の範囲が指定の範囲の場合だけ受信するようなフィルタリングの役割を持つ

送信時は、

`TTL=≪最小値≫-≪最大値≫%2c≪アルゴリズム名≫`

で、≪最小値≫～≪最大値≫間のランダムな値にする事ができる。

ランダムな値にするアルゴリズムは、以下のコマンドで出力された名前が使用できる

`StreamRelay.NET.exe -ListRandomize`

StreamRelay.NET.exe とプラグイン乱数については「[乱数アルゴリズムの試用ツールとしてのStreamRelay.NET.exe](https://qiita.com/tomoki0sanaki/items/36df6463cafec0f93313)」を参照

---
#### ToS

ToSの書式は**「Hex-Hex」**

最初のHexが、上書きするビットを指定。
次のHexが上書きする値を指定。

例えば、下位、3-5ビットを対象にする場合、00011100=0x1C。
この3ビット(3-5ビット)を011にしたい、つまりxxx011xx(xは0or1)なので、00001100=0x0C。
まとめて、

> TOS=1C-0C

となる

どうも、レジストリの変更が必要なようだが、
しかしながら、自分の Windows環境では、ToS を書き換えられないので、テストはしていない。

---
### 送信または受信の具体例

以下は「Remote」と呼称される側から送信、「Local」と呼称される側で受信とする

---
#### ICMPorg の送信

![icmptunnel1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/182822/b0d1ff32-84ee-1965-c4ed-5c2d3137a1bf.png)

`StreamRelay.NET.exe -LocalPort 0 -RemotePort 0 -RemoteOutputIcmpTunnel TunnelType=ICMPorg,Src=192.0.2.1,Dist=192.0.2.2,Type=8,Code=0`

標準入力(→StreamRelay.NET.exe→)192.0.2.1(→IcmpType,Code=8,0→)192.0.2.2

という感じ。

![icmptunnel9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/182822/a4e77dbf-8f57-2e3b-0294-834737411558.png)

送信データは「abcdefg(Cr)(Lf)」。ICMP のペイロード部に収まっている

TunnelType=ICMPorgの場合、4バイト以上は、4の倍数バイトでパケットを作っているので、2つの ICMP データグラムに分かれている

---
#### ICMPorg の受信

![icmptunnel1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/182822/b0d1ff32-84ee-1965-c4ed-5c2d3137a1bf.png)

`StreamRelay.NET.exe -LocalPort 0 -RemotePort 0 -LocalInputIcmpTunnel TunnelType=ICMPorg,Src=192.0.2.1,Dist=192.0.2.2,Type=8,Code=0`

192.0.2.1(→IcmpType,Code=8,0→)192.0.2.2(→StreamRelay.NET.exe→)標準出力

という感じ。

---
#### Ping の送信

![icmptunnel2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/182822/54074cf4-5553-f9fc-ab5f-9ba0cb1594f7.png)

Ping は送信のみ

`StreamRelay.NET.exe -LocalPort 0 -RemotePort 0 -RemoteOutputIcmpTunnel TunnelType=Ping,Dist=192.0.2.2`

標準入力(→StreamRelay.NET.exe→)x.x.x.x(→IcmpEchoRequest→)192.0.2.2

という感じ。

![icmptunnel10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/182822/11667b40-26bc-797c-cd14-f14c0387c7f0.png)

送信データは「abcdefghijk(Cr)(Lf)」。ICMP のペイロード部に収まっている

---
#### IPv4OpRRUdp の送信

![icmptunnel4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/182822/037f68a5-bf51-8456-743c-3c01308cdbcc.png)

DNS問い合わせのUDPペイロード部を「dns.dat」として用意しておく

「-FakeDataHex」「-FakeData」「-FakeDataFile」オプションの範囲は、UDP の場合は UDPペイロードだけ用意すればいい

`StreamRelay.NET.exe -LocalPort 0 -RemotePort 0 -RemoteOutputIcmpTunnel TunnelType=IPv4OpRRUdp,Src=192.0.2.1,Dist=192.0.2.2,srcPort=52,distPort=53,FakeDataFile=dns.dat`

標準入力(→StreamRelay.NET.exe→)192.0.2.1:52(→DNS問い合わせのレコードルートの中→)192.0.2.2:53

という感じ。

![icmptunnel11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/182822/1c8e1e54-d8a6-1310-6ecd-f474c27e7c4d.png)

送信データは「abcdefghijk(Cr)(Lf)」。IPv4 オプションの RR(レコードルート) の中に収まっている

---
#### IPv4OpRRUdp の受信

![icmptunnel4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/182822/037f68a5-bf51-8456-743c-3c01308cdbcc.png)

`StreamRelay.NET.exe -LocalPort 0 -RemotePort 0 -LocalOutputIcmpTunnel TunnelType=IPv4OpRRUdp,Src=192.0.2.1,Dist=192.0.2.2,srcPort=52,distPort=53`

192.0.2.1:52(→UDPのレコードルートの中→)192.0.2.2:53(→StreamRelay.NET.exe→)標準出力

という感じ。

---
#### IPv4OpRRIcmp の送信

![icmptunnel3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/182822/1ca10b2f-5b11-f8ab-2248-d4aac89cb42e.png)

`StreamRelay.NET.exe -LocalPort 0 -RemotePort 0 -RemoteOutputIcmpTunnel TunnelType=IPv4OpRRIcmp,Src=192.0.2.1,Dist=192.0.2.2,FakeDataHex=0800000000000000`

「-FakeDataHex」「-FakeData」「-FakeDataFile」オプションの範囲は、ICMP の場合は ICMPヘッダから指定する

標準入力(→StreamRelay.NET.exe→)192.0.2.1(→IcmpType,Code=8,0のレコードルートの中→)192.0.2.2

という感じ。

![icmptunnel12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/182822/54a674c1-6680-709c-86d6-a49c7968f4c7.png)

送信データは「abcdefghijk(Cr)(Lf)」。IPv4 オプションの RR(レコードルート) の中に収まっている

---
#### IPv4OpRRIcmp の受信

![icmptunnel3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/182822/1ca10b2f-5b11-f8ab-2248-d4aac89cb42e.png)

`StreamRelay.NET.exe -LocalPort 0 -RemotePort 0 -LocalInputIcmpTunnel TunnelType=IPv4OpRRIcmp,Src=192.0.2.1,Dist=192.0.2.2,Type=8,Code=0`

192.0.2.1(→IcmpType,Code=8,0のレコードルートの中→)192.0.2.2(→StreamRelay.NET.exe→)標準出力

という感じ。

---
### トンネルプロトコルを使用するうえでの注意点

![icmptunnel5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/182822/8c165c0d-3a93-39ca-8e36-6e665faa9ad6.png)

ICMP Echo Request などに偽装すると、受信側OSで自動的に ICMP Echo Reply が発生するため、それをどうやってフィルタする(取り除く)かという点がなかなかの難題。

まぁ、2台の StreamRelay.NET.exe 間の往路と復路の両方とも ICMP Echo Request にするとか、両方とも UDP にするとか、復路は別のプロトコル(例えば ICMP Host Unreachableとか)にするとか、などの使用方法もある。
この場合は、上記の OS が自動的に変身するパケットは無関係となるので、上記の課題はそもそも存在しないことになるが、しかしこの方法では隠蔽率が下がるような気がする。

やはり、**往路は ICMP Echo Request で、復路は ICMP Echo Reply** とか、**往路は UDP レコードルートで、復路は ICMP Port Unreachable のレコードルート**というような使用方法の方が隠蔽率が高いのではないだろうか。

当然だが、この場合でも往路のパケットと復路のバケットの個数の違いや、パケットの順番などで検知できるとは思う。
(往路に対して復路のみ{リクエスト-レスポンス方式}という通信の場合は少し隠蔽率は高いかもしれない)
そして、このような使用方法を採用する際には、受信側OSで自動的に送信されるパケットをどうやって取り除いていくかというのが設定のしどころとなると思うので、そういう事例をいくつか考えてみたい

肝は ICMP Echo Reply なんだよなぁ～

---
#### トンネルプロトコルを使用するうえでの注意点、一つの解決策

受信側OSがパケット応答しないようにすればいいというのが、一つの解決策となるであろう。

・OSに割り当てられたアドレス以外も受信できれば、そういうアドレスでトンネリング通信をする

直上のハブが、リピータハブなら可能かもしれないが、現在のスイッチングハブ全盛の時代には難しいかもしれない。

ArpPoisoning という手もあるかもしれない・・・

---
#### フィルタで使えるオプション

受信時のフィルタで使えるものとして以下のオプションを用意してみた

- Src : 送信元アドレスチェックする
- Dist : 送信先アドレスをチェックする(受信はプロミスキャスモードなので)
- SrcPort : 送信元ポート番号 - UDPの際に使える
- DistSrcPort : 送信先ポート番号 - UDPの際に使える
- Type : ICMP のタイプ - ICMP の際に使える
- Code : ICMP のコード - ICMP の際に使える
- ToS : IPv4 の ToS ヘッダが指定した値かどうかでチェックする(相手OSの自動返信パケットと異なるようにすればいい)
- TTL : TTL値が指定した値かどうかでチェックする(相手OSの自動返信パケットと異なるようにすればいい)
- DF : IPv4 の DontFragment ビットの値(True/False)でチェックする

---
### 送受信の具体例

2台のStreamRelay.NET.exe 間のうち、「Remote」と呼称される側から「Local」と呼称される側への通信を**「往路」**。その逆の通信を**「復路」**とする

---
#### 往路を ICMP Echo Request、復路をICMP Echo Reply (ICMP ペイロードに保持)

![icmptunnel6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/182822/62b37657-5e55-fdd5-9943-abeefe78ad9d.png)

ホストA 側

`StreamRelay.NET.exe -LocalPort 0 -RemotePort 0 -RemoteOutputIcmpTunnel TunnelType=ICMPorg,Src=192.0.2.1,Dist=192.0.2.2,Type=8,Code=0 -RemoteInputIcmpTunnel TunnelType=ICMPorg,Src=192.0.2.2,Dist=192.0.2.1,Type=0,Code=0,TTL=200-250`

- 192.0.2.1→192.0.2.2 へ ICMP Echo Request のペイロードに埋め込んで送信
- 192.0.2.2からの受信は、ICMP Echo Reply のペイロードに埋め込んだデータを抽出。その際の TTL は200-250の間のパケット以外は無視。

ホストB 側

`StreamRelay.NET.exe -LocalPort 0 -RemotePort 0 -LocalInputIcmpTunnel TunnelType=ICMPorg,Src=192.0.2.1,Dist=192.0.2.2,Type=8,Code=0 -LocalOutputIcmpTunnel TunnelType=ICMPorg,Src=192.0.2.2,Dist=192.0.2.1,Type=0,Code=0,TTL=230-250%2cRandom`

- 192.0.2.1からの受信は、ICMP Echo Request のペイロードに埋め込んだデータを抽出
- 192.0.2.2→192.0.2.1 へ ICMP Echo Reply のペイロードに埋め込んで送信。その際のTTLはSystem.Randomで230-250の範囲の乱数値

**復路**の送信と受信に着目すると、ホップ数が30未満程度の経路という前提がある

Windows の通常の TTL は 128からなので、OSが自動的に送信したパケットかどうかはTTL値で判断している。

---
#### 往路を ICMP Echo Request、復路をICMP Echo Reply (ICMP ペイロードに保持) その2

トンネルタイプを Ping に置き換えてもよい

ホストA 側

`StreamRelay.NET.exe -LocalPort 0 -RemotePort 0 -RemoteOutputIcmpTunnel TunnelType=Ping,Dist=192.0.2.2 -RemoteInputIcmpTunnel TunnelType=ICMPorg,Src=192.0.2.2,Dist=192.0.2.1,Type=0,Code=0,TTL=200-250`

---
#### 往路を ICMP Echo Request、復路をICMP Echo Reply (レコードルート内部に保持)

![icmptunnel7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/182822/9bab6a00-2fc4-39d3-3309-c9cea47c663b.png)

ホストA 側

`StreamRelay.NET.exe -LocalPort 0 -RemotePort 0 -RemoteOutputIcmpTunnel TunnelType=IPv4OpRRIcmp,Src=192.0.2.1,Dist=192.0.2.2,Type=8,Code=0,FakeData=abcdefghijklmn -RemoteInputIcmpTunnel TunnelType=IPv4OpRRIcmp,Src=192.0.2.2,Dist=192.0.2.1,Type=0,Code=0,TTL=200-250,DF=true`

- 192.0.2.1→192.0.2.2 へ ICMP Echo Request の IPv4 RR に埋め込んで送信
- 192.0.2.2からの受信は、ICMP Echo Reply の IPv4 RR に埋め込んだデータを抽出。その際の TTL は200-250の間のパケット以外は無視。

ホストB 側

`StreamRelay.NET.exe -LocalPort 0 -RemotePort 0 -LocalInputIcmpTunnel TunnelType=IPv4OpRRIcmp,Src=192.0.2.1,Dist=192.0.2.2,Type=8,Code=0 -LocalOutputIcmpTunnel TunnelType=IPv4OpRRIcmp,Src=192.0.2.2,Dist=192.0.2.1,Type=0,Code=0,TTL=230-250%2cRandom,DF=true,FakeData=abcdefghijklmn`

- 192.0.2.1からの受信は、ICMP Echo Request の IPv4 RR に埋め込んだデータを抽出
- 192.0.2.2→192.0.2.1 へ ICMP Echo Reply の IPv4 RR に埋め込んで送信。その際のTTLはSystem.Randomで230-250の範囲の乱数値

Windows の通常の TTL は 128からなので、OSが自動的に送信したパケットかどうかはTTL値で判断している。
および、Windows は通常 DF=false なので、DF=True とする事で、OSが自動的に送信したパケットかどうかを DF ビットの値で判断している

---
#### 往路を UDPのレコードルート、復路をICMP Port Unreachable のレコードルート

![icmptunnel8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/182822/4f490395-d136-c6de-e453-de22d8d8428a.png)

DNS問い合わせのUDP部分のデータを「dns.dat」として用意しておく

事前にホストB 側で、以下を実行し、53/udpを開けておく

`nc.exe -nvv -L -u -p 53`

これで、ホストBから ICMP Port Unreachableは出なくなる。


ホストA 側

`StreamRelay.NET.exe -LocalPort 0 -RemotePort 0 -RemoteOutputIcmpTunnel TunnelType=IPv4OpRRUdp,Src=192.0.2.1,Dist=192.0.2.2,srcPort=52,distPort=53,FakeDataFile=dns.dat -RemoteInputIcmpTunnel TunnelType=IPv4OpRRIcmp,Src=192.0.2.2,Dist=192.0.2.1,Type=3,Code=0`

OSが自動的に ICMP Port Unreachable(Type=3)を出さないので、TTL や DF などでのフィルタリングはしなくてもよいだろう

ホストB 側

`StreamRelay.NET.exe -LocalPort 0 -RemotePort 0 -LocalOutputIcmpTunnel TunnelType=IPv4OpRRUdp,Src=192.0.2.1,Dist=192.0.2.2,srcPort=52,distPort=53 -LocalOutputIcmpTunnel TunnelType=IPv4OpRRIcmp,Src=192.0.2.2,Dist=192.0.2.1,Type=3,Code=0,FakeData=abcdefghijklmn`

OSが自動的に ICMP Port Unreachable(Type=3)を出さないので、TTL や DF などでのフィルタリングの際に役立つ変更はしなくてもよいだろう

---
### IPv4ソースルーティング

IPv4ソースルーティングは「[ソースルーティング試験ツールとしてのStreamRelay.NET.exe](https://qiita.com/tomoki0sanaki/items/8da7f82c09197428d64b)」


---
### 目次へ戻る

[目次というか最初の一歩](http://qiita.com/tomoki0sanaki/items/85f20cbe18378e33cd6f)