Perlの黒魔術を解説するよ〜〜〜〜

まずはこちらをごらんください。

shinh.hatenablog.com

すごすぎる……。恐ろしいですね。

なぜこんなことになるのか、解説していきましょう。まずは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 の評価結果は、$_ の先頭に「なんか」の部分の文字列を貯めていくことになるわけです。

ポイントその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の'$_'と、ヴァージョン文字列とかいう変態仕様を最大限悪用してるんですよ。すごすぎる!!!という思いを得ることになりましたね!!!!世の中どんだけ天才がいるんだよ……。