rm -rfしちゃったけどどうする
rm -rfの後に残りしもの
遊びのために、筆者は新しいLinuxサーバーを立ち上げて、rootでrm -rf /を実行して、何が残るかをみてみた。どうやら、今のrmというのは筆者のようなアホを相手にしなければならない未来に生きているようなので、実際に実行するには、--no-preserve-rootをつける必要があった。
# rm -rf --no-preserve-root /
かかるおろかなる行為の後では、
- /bin/ls
- /bin/cat
- /bin/chmod
- /usr/bin/file
のような、偉大なるツールのたぐいはみな消え失せてしまった。まだ、ssh接続とbashセッションは生きているはずだ。つまり、bashの組み込みコマンドであるechoとかは残っているということだ。
Bashマクガイバーたれ
root@rmrf:/# ls -bash: /bin/ls: No such file or directory
lsは存在しないが、echoとfileglobは残っている。どう使えばいいものか。
root@rmrf:/# echo * dev proc run sys # echo /dev/pts/* /dev/pts/0 /dev/pts/3 /dev/pts/ptmx
おや、/dev, /proc, /run, /sysは残ってたぞ。さて、我々はlsを手に入れた。もう少し読みやすくしてみよう。
root@rmrf:/# for file in /dev/pts/*; do echo $file; done /dev/pts/0 /dev/pts/3 /dev/pts/ptmx
何人かのRedditorが、printfも生きているとレスしてくれた。
root@rmrf:/# ls() { printf '%s\n' ${1:+${1%/}/}*; }
「printfは引数がなくなるまでフォーマット文字列を適用しようとする」 -camh-
bashでは関数を定義できるため、いささか制限的ではあるが、lsを作り出すこともできる。
root@rmrf:/# ls() { printf '%s\n' ${1:+${1%/}/}*; } -bash: syntax error near unexpected token `('
ハァ? 間違いなんてないはずだろ。lsがハッシュされてたりエイリアスされてたりすんのか?
root@rmrf:/# type ls ls is aliased to `ls --color=auto'
なるほど。つまり、ls--color=auto () { printf '%s\n' ${1:+${1%/}/}*; }と展開されるわけか。やれやれ、まあ、unaliasするか。
root@rmrf:/# unalias ls root@rmrf:/# ls() { printf '%s\n' ${1:+${1%/}/}*; } root@rmrf:/# ls /dev /proc /run /sys root@rmrf:/# ls /dev /dev/pts
そしてセーブっと。
root@rmrf:/# echo 'ls() { printf '%s\n' ${1:+${1%/}/}*; }' >> utils.sh root@rmrf:/# source utils.sh
catはどうだ。組み込みコマンドのreadにパイプとリダイレクトを組み合わせれば、ぎこちないもののcatになる。
root@rmrf:/# (while read line; do echo "$line"; done) < utils.sh ls() { printf '%s\n' ${1:+${1%/}/}*; }
ここまで揃えれば、echoで任意のバイト列を書き込めることにより、環境を再構築して、curlとかwgetとかで必要なバイナリを引っ張ってこれる。筆者の最初の選択は、他でも言われているように、BusyBoxをゲットすることだ。Busyboxは組み込みLinuxにおけるスイスアーミーナイフであり、組み込みのwgetやddやtarや、多くの機能がある。Eusebeîa君がこのことについてはよく書いているので、ここでは書かない。
問題はある。
望みのバイト列をechoしてバイナリを作ったとしても、そのファイルには実行権限がない。busyboxを始動させることはできない。この問題に対する最も簡単な対処法としては、なにか実行できるファイルをみつけて、echoで上書きするということだ。しかし、現時点で、/usrと/binはぜんぶ破壊してしまった。そういうわけで、結構めんどくさい。
シェルglobとbashを使って、実行ビットが立っているファイルを探すことができる。ディレクトリはもちろん無視する。
executable () { if [[ ( ! -d $1 ) && -x $1 ]] ; then echo "$1"; fi }
実行可能ファイルを探せ!
root@rmrf:/# for file in /*; do executable $file; done root@rmrf:/# for file in /*/*; do executable $file; done root@rmrf:/# for file in /*/*/*; do executable $file; done /proc/1107/exe /proc/1136/exe /proc/1149/exe /proc/1179/exe /proc/1215/exe /proc/1217/exe /proc/1220/exe /proc/1221/exe /proc/1223/exe /proc/1248/exe /proc/1277/exe /proc/1468/exe /proc/1478/exe /proc/1625/exe /proc/1644/exe /proc/1/exe /proc/374/exe /proc/378/exe /proc/471/exe /proc/616/exe /proc/657/exe /proc/self/exe
やったね。いや、でもまてよ。こいつぁ、もうディスク上には存在しない実行ファイルへのシンボリックリンクじゃねーか。executable()を書き換えて、シンボリックリンクは無視するようにしよう。
root@rmrf:/# executable () { if [[ ( ! -d $1 ) && ( ! -h $1 ) && -x $1 ]] ; then echo "$1"; fi } root@rmrf:/# for file in /*/*/*; do executable $file; done root@rmrf:/# for file in /*/*/*/*; do executable $file; done root@rmrf:/# for file in /*/*/*/*/*; do executable $file; done root@rmrf:/# for file in /*/*/*/*/*/*; do executable $file; done
さてと、やれやれ。何かカーネルに使えるものがあるかも知れないな。まあ少なくとも、sysrqマジックで再起動はできるわけだ。
root@rmrf:/# echo 1 > /proc/sys/kernel/sysrq root@rmrf:/# echo "b" > /proc/sysrq-trigger
さて、マシンから完全に締め出しを食らったので、この金曜日は何かもっと別の有意義なことをするとするかな。ご愛読ありがとうございました。筆者のさらなるご活躍にご期待下さい。あと、実行ビットを立てる方法があったら知らせてくれよな。
追記、Redditorのthrow_away5046が、詳細な、美しい、解決方法をレスしてくれた。
同じアーキテクチャのぶっ壊してないマシン上で
$ mkdir $(xxd -p -l 16 /dev/urandom) $ cd $_ $ apt-get download busybox-static $ dpkg -x *.deb . $ alias encode='{ tr -d \\n | sed "s#\\(..\\)#\\\\x\\1#g"; echo; }' $ alias upload='{ xxd -p | encode | nc -q0 -lp 5050; }' $ upload < bin/busybox
訳注: ncでTCPの5050ポートで待ち受けて、接続してきたリモートホストにbusyboxのバイナリを送りつける
rmrfしたマシン上で
# cd / # alias decode='while read -ru9 line; do printf "$line"; done' # alias download='( exec 9<>/dev/tcp/{IP OF NON HOSED BOX}/5050; decode )' # download > busybox
訳注:pseudo file経由でぶっ壊してないマシンのIPのTCPの5050ポートに接続して、返ってきたbusyboxのバイナリをファイルに書き込む。
busyboxのパーミッションを変更するshared objectを作る
$ cat > setx.c <<EOF extern int chmod(const char *pathname, unsigned int mode); int entry(void) { return !! chmod("busybox", 0700); } char *desc[] = {0}; struct quick_hack { char *name; int (*fn)(void); int on; char **long_doc, *short_doc, *other; } setx_struct = { "setx", entry, 1, desc, "chmod 0700 busybox", 0 }; EOF $ gcc -Wall -Wextra -pedantic -nostdlib -Os -fpic -shared setx.c -o setx $ upload < setx
訳注:busyboxというファイルの実行ビットを立てるコード(setx)をshared objectとしてコンパイルして、busyboxを送信したのと同様の方法で送信。
setxをenableして、setxを組み込み関数とし、busyboxを実行可能にする
# ( download > setx; enable -f ./setx setx; setx; ) # /busybox mkdir .bin # /busybox --install -s .bin # PATH=/.bin
訳注:GNU bashの組み込みコマンド、enableの-fオプションは、shared objectを読み込んで、ファイル名と同じ組み込みコマンドとして実行できる。
Bash Reference Manual: Bash Builtins結果
なんともマクガイバー的だ。