2014-06-15

rm -rfしちゃったけどどうする

rm -rf remains

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

結果

なんともマクガイバー的だ。

3 comments:

Anonymous said...

root が "/bin/rm -rf /" をしたらどうなるのか?
http://katsu.watanabe.name/doc/rmrf/

こんな記事もあるよ。

Anonymous said...

事故でやったことありますけど
/binが真っ先に消えて
以後rmができなくなるので
ぶっちゃけ思ったほどの害は出ないですね

Softql said...

very useful blog very nice information provide NPO network planning and optimization