ポートスキャンをシェルで実装する【ワンライナー】

タイトルの通り、ポートスキャンをシェルで実装します(bashとzshで動作確認済み)。

※ご自身で管理しているホスト以外にはスキャンを実行しないでください

Contents

結論

結論から言うと、ワンライナーでそれっぽいのが出来ました(hpingコマンドを使用しています)。

# ポート範囲とかhpingのオプションなどは適宜変更(bashとzshで動作確認済み)
# IP指定です
seq 0 100 |while read port ;do (sudo hping -S -p $port -c 1 192.168.0.1 2>/dev/null )&done |grep "sport=" |grep "rtt="

ついでにPing(ICMPタイプ8)を用いたホストスキャンも作ってみました。

# /24のホストスキャン(自身の環境でのみ使用してください)
seq 254 |while read host ;do (sudo hping -C 8 -c 1  192.168.0.${host} 2>/dev/null )&done |grep "ip=" |grep "rtt="

# /16のホストスキャン(自身の環境でのみ使用してください)
# 結構な負荷がかかるため、sleepの値で調整してください。(EC2のt2.microではsleep 5くらいが良さそうでした)
seq 254 |while read octet3 ;do (seq 254 |while read octet4 ;do (sudo hping -C 8 -c 1  172.20.${octet3}.${octet4} 2>/dev/null )&done );sleep 1 ;done |grep "ip=" |grep "rtt="

参考までに、/16のホストスキャン実行時の負荷状況です(htopコマンド)。

CPUの使用率が60〜70%前後で、ロードアベレージは30くらいまで上がっています(sleep 1で実行)。

最初はsleepを挟まずに実行してしまい、CPUやメモリの使用率がカンストしていました。(ご注意を)

hpingで作ってみた

最初はこんな感じで作りました。

  • 第一引数にスキャンタイプ(SYNスキャン or FINスキャン)
  • 第二引数にスキャンホスト
  • 第三、第四引数にポート範囲
#!/bin/bash

# 定数
SCAN_TYPE=$1
DST_SERVER=$2
PORT_START=$3
PORT_END=$4

# 前処理
PORT_RANGE=$(seq $PORT_START $PORT_END)
REPORT_FILE="${DST_SERVER}_report.txt"
cat /dev/null > ${REPORT_FILE}
trap "final; exit 1" 2

# 主処理
case ${SCAN_TYPE} in
        "S")
                for port in ${PORT_RANGE}
                do
                        printf '\r%1s' "scanning....(${port}/${PORT_END})"
                        hping3 -S -p ${port} -c 1 ${DST_SERVER} >>/dev/null 2>/dev/null
                        if [ $? -eq 0 ]; then echo " <== This port is open" ; fi
                done;;
        "F")
                for port in ${PORT_RANGE}
                do
                        printf '\r%1s' "scanning....(${port}/${PORT_END})"
                        hping3 -F -p ${port} -c 1 ${DST_SERVER} >>/dev/null 2>/dev/null
                        if [ $? -eq 0 ]; then echo " <== This port is open" ; fi
                done;;
        *)
                echo 'first argument is "S" or "F"';;
esac

# お飾り
seq 30 100 | while read count
do
        printf '\r%1s' "${count}% ${var}"
        sleep 0.02
        var+="#"
done
echo ""

exit 0

hpingコマンドを使ってSYNパケットやFINパケットを投げています。

その終了コードが0の場合にはポートが開いていると判断しています。

tcpdumpでパケットを見ながらやると面白いです(こちらのサイトを参考にしました)。

しかし、このスクリプトでは、ポートを一つ一つ丁寧に見に行っているので、処理速度が遅すぎました。

処理速度を改良

そこで、for文の中身を無理やりバックグラウンド処理に回してみました。

(そのままだとうまくいかなかったので、他にも若干いじっています。)

#!/bin/bash

# 定数
SCAN_TYPE=$1
DST_SERVER=$2
PORT_START=$3
PORT_END=$4

# 前処理
PORT_RANGE=$(seq $PORT_START $PORT_END)
REPORT_FILE="${DST_SERVER}_report.txt"
cat /dev/null > ${REPORT_FILE}
trap "final; exit 1" 2

# 主処理
case ${SCAN_TYPE} in
        "S")
                for port in ${PORT_RANGE}
                do
                (
                        hping3 -S -p ${port} -c 1 ${DST_SERVER} 2>/dev/null |grep sport
                ) &
                done;;
        "F")
                for port in ${PORT_RANGE}
                do
                (
                        hping3 -F -p ${port} -c 1 ${DST_SERVER} 2>/dev/null |grep sport
                ) &
                done;;
        *)
                echo 'first argument is "S" or "F"';;
esac

sleep 1
exit 0

ワンライナーで良くない?

改良したものを見ていると、「ワンライナーで良くない?」という結論に達しました。

# ポート範囲とかhpingのオプションなどは適宜変更(bashとzshで動作確認済み)
# IP指定です
seq 0 100 |while read port ;do (sudo hping -S -p $port -c 1 192.168.0.1 2>/dev/null )&done |grep sport