PowerShellでnc(netcat)を書いてみる
多くのLinuxディストリビューションでは標準でncコマンドが入っており、リモートホストへの接続あるいはローカルポートでの待ち受けを行い、端末からネットワークソケットに対し読み書きすることができた。 一方Windowsの場合、前者はtelnetコマンドで代用することができるが、後者はNetcat for WindowsやCygwin版のnc、何らかのスクリプトなどが必要となる。 PowerShellスクリプトによるnetcat実装としてはpowercatがあるが、あらかじめ環境にスクリプトをロードしておく使い方が想定されており、コマンドプロンプトからスクリプトとして実行するにはやや使いづらい。 そこで、ここではpowercatの実装を参考にncのように使えるPowerShellスクリプトを書き、コマンドプロンプトからスクリプトとして実行してみる。
環境
Windows 8.1 Pro 64 bit版、PowerShell 4.0
>systeminfo OS 名: Microsoft Windows 8.1 Pro OS バージョン: 6.3.9600 N/A ビルド 9600 OS ビルドの種類: Multiprocessor Free システムの種類: x64-based PC プロセッサ: 1 プロセッサインストール済みです。 [01]: Intel64 Family 6 Model 69 Stepping 1 GenuineIntel ~1596 Mhz >powershell -c "$PSVERSIONTABLE" Name Value ---- ----- PSVersion 4.0 WSManStackVersion 3.0 SerializationVersion 1.1.0.1 CLRVersion 4.0.30319.34014 BuildVersion 6.3.9600.17090 PSCompatibleVersions {1.0, 2.0, 3.0, 4.0} PSRemotingProtocolVersion 2.2
nc相当のPowerShellスクリプト
実際にコードを書くと次のようになる。
# nc.ps1 Param( [string]$addr, [int]$port, [alias("l")][int]$lport, [alias("v")][switch]$verbose ) $ErrorActionPreference = "Stop" if ($verbose) { $VerbosePreference = "continue" } function interact($client) { $stream = $client.GetStream() $buffer = New-Object System.Byte[] $client.ReceiveBufferSize $enc = New-Object System.Text.AsciiEncoding try { $ar = $stream.BeginRead($buffer, 0, $buffer.length, $NULL, $NULL) while ($TRUE) { if ($ar.IsCompleted) { $bytes = $stream.EndRead($ar) if ($bytes -eq 0) { break } Write-Host -n $enc.GetString($buffer, 0, $bytes) $ar = $stream.BeginRead($buffer, 0, $buffer.length, $NULL, $NULL) } if ($Host.UI.RawUI.KeyAvailable) { $data = $enc.GetBytes((Read-Host) + "`n") $stream.Write($data, 0, $data.length) } } } catch [System.IO.IOException] { # ignore exception at $stream.BeginRead() } finally { $stream.Close() } } if ($lport) { $endpoint = New-Object System.Net.IPEndPoint ([System.Net.IPAddress]::Any, $lport) $listener = New-Object System.Net.Sockets.TcpListener $endpoint $listener.Start() Write-Verbose "Listening on [0.0.0.0] (family 0, port $($lport))" $handle = $listener.BeginAcceptTcpClient($null, $null) while (!$handle.IsCompleted) { Start-Sleep -m 100 } $client = $listener.EndAcceptTcpClient($handle) $remote = $client.Client.RemoteEndPoint Write-Verbose "Connection from [$($remote.Address)] port $($lport) [tcp/*] accepted (family 2, sport $($remote.Port))" interact $client $client.Close() $listener.Stop() } elseif ($addr -and $port) { $client = New-Object System.Net.Sockets.TcpClient ($addr, $port) Write-Verbose "Connection to $($addr) $($port) port [tcp/*] succeeded!" interact $client $client.Close() }
PowerShellでのソケット通信は、System.Net.Sockets.TcpClient/TcpListener
によって行うことができる。
また、受信したデータを適宜処理する方法には、$stream.DataAvailable
をチェックして読めるだけ読む同期処理による方法と、上のように$stream.BeginRead/EndRead
を利用して非同期処理を行う方法がある。
後者の方法ではリモートからの切断を$stream.EndRead
の戻り値として返される受信バイト数が0かどうかで判定できるため、上のスクリプトではpowercatにならい後者の方法で行っている。
接続してくるコネクションの待ち受け方法も同期版のAcceptTcpClient
と非同期版のBeginAcceptTcpClient/EndAcceptTcpClient
があるが、前者の場合待ち受け中にCtrl+Cで終了することができなくなってしまうため、これについても後者の非同期版で行っている。
PowerShellはシェルであることもあり、コマンド実行で例外エラーが発生した場合も後続のコマンドは続けて実行されていく。
bashにおけるset -e
のように初回の例外エラーで終了させるには、$ErrorActionPreference = "Stop"
とすればよい。
また、文字列出力コマンドとしてWrite-Host
の代わりにWrite-Verbose
を用いると、$VerbosePreference
の値によって出力の有無を切り替えることができる。
bashのgetopts
などに対応するコマンドライン引数処理の補助機能として、PowerShellにもParam
がある。
Param
は指定した変数に実際の値がセットされるようにすることができ、エイリアス作成や種々のバリデーションも行うことができる。
PowerShellにおいて1行コメントは#
、複数行コメントは<# #>
である。
また、エスケープ文字にはバックスラッシュ(\n
)ではなくチルダ(`n
)を用いる。
コマンドプロンプトを二つ開き、それぞれで待ち受け、接続を行うと次のようになる。
>powershell -ex remotesigned -c ".\nc -v -l 4444" 詳細: Listening on [0.0.0.0] (family 0, port 4444) 詳細: Connection from [127.0.0.1] port 4444 [tcp/*] accepted (family 2, sport 50355) server => client client => server
>powershell -ex remotesigned -c ".\nc -v localhost 4444" 詳細: Connection to localhost 4444 port [tcp/*] succeeded! server => client client => server
前のエントリの正規表現フィルタでは-f
オプションを用いてスクリプトを直接実行したが、ここでは-c
オプションでコマンドとして指定することでスクリプトに引数を与えて実行している。
また、同一ディレクトリにあるスクリプトを実行するため、スクリプト名の前に.\
をつける必要がある。
関連リンク
- PowerShell Magazine » Building Netcat with PowerShell
- Building a TCP Server Using PowerShell | Learn Powershell | Achieve More
- pastebin - powershell netcat - post number 1715493
- PowerShell Code Repository - TCP Client/Server
- Ugly Gizmo: A Telnet Client written in PowerShell
- PowerShell Understanding Asynchronous Sockets