まずはこちらをごらんください。
すごすぎる……。恐ろしいですね。
なぜこんなことになるのか、解説していきましょう。まずはPerlの気持ちになりましょう。
Perlの気持ち編
ポイントその1 barewordを数値コンテキストで評価するとどうなるのかということ
件のプログラムは、base64 っぽい文字列が書かれていますが、これを前からPerlコードとして読んでいくと、大きく2つのパートに分かれることに気づきます。というのも、前から一文字ずつ読んでいくと、「+」という演算子にぶつかるわけですね。
それに気づくと、このコードは前半部分
dXNlIE1JTUU6OkJhc2U2NDtwcmludCBlbmNvZGVfYmFzZTY0IGpvaW4nJyw8PjsKX19FTkRfXwo
と、
s//v62/e+s//v60/e+s//v44/e+s//v39/e+s//v39/e+s//join/+s//v32/e+s//base64/ss+
s//v95/e+s//decode/+s//v32/e+s//print/+s//v59/e+s//Base64/+s//v58/e+s//v58/e
+s//MIME/+s//v32/e+s//use/s/eval
に分けることができる、と気づくでしょう。
まずは前半部分からやっつけていきましょう。Perlコードにおいて、""
などで囲まれていない上にsigilを持たない文字列は、「bare word」として解釈され、use strict
をしていない環境では、同名の解決可能な関数などが見つからない場合、文字列扱いになります。
my $bare = nyan; print $bare; # => nyan
というわけで、前半部分はdXNlIE1JTUU6OkJhc2U2NDtwcmludCBlbmNvZGVfYmFzZTY0IGpvaW4nJyw8PjsKX19FTkRfXwo
というbarewordとして解釈され、この部分は"dXNlIE1JTUU6OkJhc2U2NDtwcmludCBlbmNvZGVfYmFzZTY0IGpvaW4nJyw8PjsKX19FTkRfXwo"
という文字列として解釈されます。
一旦文字列として解釈された"dXNlIE1JTUU6OkJhc2U2NDtwcmludCBlbmNvZGVfYmFzZTY0IGpvaW4nJyw8PjsKX19FTkRfXwo"
は、 +
オペレータに渡されます。このとき、+演算は渡されたものを数値コンテキストとして解釈します。文字列"dXNlIE1JTUU6OkJhc2U2NDtwcmludCBlbmNvZGVfYmFzZTY0IGpvaW4nJyw8PjsKX19FTkRfXwo"を数値コンテキストとして解釈するとどうなるでしょうか。やってみましょう。
✔ perl -e 'print "dXNlIE1JTUU6OkJhc2U2NDtwcmludCBlbmNvZGVfYmFzZTY0IGpvaW4nJyw8PjsKX19FTkRfXwo" + 0' 0
parseできないので、0として解釈されているのがわかるでしょう。
これで、前半をPerlコードとして見たときには 単に '0 + 後半'というコードとして解釈されることがわかりました。では後半にいきましょう。
後半戦
さて、同じように読み進めていくと、'+'という記号がキモになっていることに気づくでしょう。前から順に + で様々な項が前から順に評価されています。
後半を仔細に見ていくと、基本的に s//vなんか/e
という項と、 s//なんか/sが0からn個
という項が+
で連結されていて、最後にs//use/s/eval
が続いていますね。ではそれぞれの項がどう評価されるか見てみましょう。
ポイントその2 s//vなんか/e という項
s/なんか/べつのなんか/option
というのは、正規表現の置換リテラルですね。ではeというオプションはなにかというと、s/なんか/べつのなんか/e
の、「べつのなんか」の部分をPerlコードとして評価する、というオプションです。では「vなんか」というbarewordはPerlにとってどういう意味を持っているでしょうか。これは「ヴァージョン文字列」と呼ばれるものです。(see perldata - Perl のデータ型 - perldoc.jp )。
というわけで、v62
の評価結果は、10進数における62を16進数に変換して、"\x3e"となります。これは">"です。
ところで、s//v62d/e
の部分、=~
でマッチしていませんね。この場合、Perlは$_
というデフォルト変数に対する置換として解釈します。今$_は空ですから、空文字列に対して、「空文字列にマッチしたら最初の空文字列を">"として置き換えてそれを$_に代入する」という動きをします。つまりこういうことです。
s//v62/e; print $_; # => ">"
要するに、$_の先頭にv62dの評価結果を文字列コンテキストで挿入してるわけですね。そして、これを繰り返すことで、$_
に文字列を貯めていきます。
ポイントその3 s//なんか/0からn文字のs という項について
これは「0からn文字のs」が置換正規表現のオプションとして扱われますね。ではsというオプションは何を意味するでしょうか。これは「ワイルドカードのドット( . )が改行にもマッチするようにする」というオプションです。今回は改行使ってないので、この「0からn文字のs」については無視して良いことになりますね。
さて、そうなると、s//なんか/0からn文字のs
の評価結果は、$_ の先頭に「なんか」の部分のbarewordを文字列として解釈したものを貯めていくことになるわけです。
ポイントその4 最後の項
さて、最後はs//use/s/eval
という項です。
これはちょっとトリッキーですが、Perlの気持ちになって読むと、(今まで解釈してきた部分 + s//use/s) / (eval)
という割り算として解釈できます。 /
の優先順位は +
より高いですからね。 つまり、今までは単純に前から読んできたけど、評価順としては、(今まで読んできた部分 + s//use/s) を評価して、そのあと(eval) を評価して、最後に / で割り算する、という形ですね。でも割り算の結果は捨てられてるので、結果的には前から順に評価されてるのと同じことです。
では最後の部分をみてみましょう。s//use/s
についてはさきほど見たとおりですね。 $_ の先頭に "use" をappendするように解釈されます。そして、そのあと(eval) が評価されるわけですが、evalは引数が省略された場合、$_を暗黙の引数として取ります。これで、「最後の項を除く部分を評価した結果得られた $_ をevalで評価する」というプログラムの完成です。
では、今まで見てきたものをまとめましょう。
Perlの気持ち解決編
dXNlIE1JTUU6OkJhc2U2NDtwcmludCBlbmNvZGVfYmFzZTY0IGpvaW4nJyw8PjsKX19FTkRfXwo+ s//v62/e+s//v60/e+s//v44/e+s//v39/e+s//v39/e+s//join/+s//v32/e+s//base64/ss+ s//v95/e+s//decode/+s//v32/e+s//print/+s//v59/e+s//Base64/+s//v58/e+s//v58/e +s//MIME/+s//v32/e+s//use/s/eval
(dXNlIE1JTUU6OkJhc2U2NDtwcmludCBlbmNvZGVfYmFzZTY0IGpvaW4nJyw8PjsKX19FTkRfXwo+ s//v62/e+s//v60/e+s//v44/e+s//v39/e+s//v39/e+s//join/+s//v32/e+s//base64/ss+ s//v95/e+s//decode/+s//v32/e+s//print/+s//v59/e+s//Base64/+s//v58/e+s//v58/e +s//MIME/+s//v32/e+s//use/s) / (eval)
を評価した結果、
$_ = "use MIME::Base64;print decode_base64 join'',<>" eval $_;
という計算が得られることになります。おお!!!base64デコーダだ!!!
base64の気持ち編
では、Perlの気持ちになったときに無視されていた前半のbarewordをbase64として解釈してみましょう。これはbase64エンコードするPerlコードになっています。
echo "dXNlIE1JTUU6OkJhc2U2NDtwcmludCBlbmNvZGVfYmFzZTY0IGpvaW4nJyw8PjsKX19FTkRfXwo+" | base64 -D use MIME::Base64;print encode_base64 join'',<>; __END__
この出力をPerlコードとして解釈すると、__END__以下は無視されるから、そのあとは(base64としてvalidならば)自由になんでも書くことが可能!!!!!!結果、base64エンコードするPerlコードが得られるわけですね。かしこーーーーーーーい!!!!
まとめ
Perlの黒魔術を説明しつつ、黒魔術コードを読み解いてみました。読む方としては「なるほどなー」って感じだけど、これを書くの変態すぎるしすごすぎる……。これ、base64が'+'と'/'を使えることと、Perlの'$_'と、ヴァージョン文字列とかいう変態仕様を最大限悪用してるんですよ。すごすぎる!!!という思いを得ることになりましたね!!!!世の中どんだけ天才がいるんだよ……。