Perlで記号プログラミング - TAKESAKOのはてな出張所
に対しての挑戦が。
id:TAKESAKO に挑戦してみる - yujiorama の日記
面白そうなので自分もやってみる。
#!/usr/bin/perl $^='-^\\\\%+"<&]\\$=/**:.](%_,//;<#)\\^_%$=]."\\&=\'?/:$?({/!_})(([=##!-^/\'%_#"]{"?~<]$\':$;#"&]`<#:?', $^_='_~`||`]@^_`"./,))<%?%<"(?<_}+^.;#^^@@{`:,|[{_``__[]_;/@-<~<;^.[^~::<&""[_`}*.[?]+[?!|-_`@[*);//(]?",=_+?]{{#`,@@<*@[{|/`.@#@[""[+_}[?__(?.+[_}<.?_"{>|!{}@@^_,{<', $^_^='{>][[[;/,{>/]_@@]((,^!_&+-{#%%@^[*~)&_>_][|@{>]!+)!~);_?|%.[%<%""/(`>>^`{?@_@+^>@|}.[<{>{(>:(<";&%_"){=+:@_}]@%.[^(`_<<]^!@+|`-|:{#}#`,]]]_){"-;.{|]@`>_"=%(>@_|', $^_=~('(?{$^_=~'.('_^""'^'+,|/'."-".('$%'^';{'.'(-:^;'.('[)]@'^'>_<,').'$^_})')))
これを実行すると、
$ ./takesako.pl 1: 円周率は 約 3.14 です。 2: 円周率は 約 3.14 です。 3: 円周率は 約 3.14 です。 ... 97: 円周率は 約 3.14 です。 98: 円周率は 約 3.14 です。 99: 円周率は 約 3.14 です。
と出力される。
何故!?
まずは常套手段、'-MO=Deparse'してみる。
$ perl -MO=Deparse takesako.pl $^ = '-^\\\\%+"<&]\\$=/**:.](%_,//;<#)\\^_%$=]."\\&=\'?/:$?({/!_})(([=##!-^/\'%_#"]{"?~<]$\':$;#"&]`<#:?', $^_ = '_~`||`]@^_`"./,))<%?%<"(?<_}+^.;#^^@@{`:,|[{_``__[]_;/@-<~<;^.[^~::<&""[_`}*.[?]+[?!|-_`@[*);//(]?",=_+?]{{#`,@@<*@[{|/`.@#@[""[+_}[?__(?.+[_}<.?_"{>|!{}@@^_,{<', $^_ ^= '{>][[[;/,{>/]_@@]((,^!_&+-{#%%@^[*~)&_>_][|@{>]!+)!~);_?|%.[%<%""/(`>>^`{?@_@+^>@|}.[<{>{(>:(<";&%_"){=+:@_}]@%.[^(`_<<]^!@+|`-|:{#}#`,]]]_){"-;.{|]@`>_"=%(>@_|', $^_ =~ /(?{\$^_=~tr^\r-\037^(-:^;eval\$^_})/; takesako.pl syntax OK
…大して変わらない orz いや、余計見にくくなってるw
最後に"eval"の文字が見えるだけだ。しかもこれをそのまま実行しようとしてもエラーになるという罠。
$ perl -MO=Deparse takesako.pl | perl takesako.pl syntax OK Can't modify single ref constructor in transliteration (tr///) at (re_eval 1) line 1, at EOF Compilation failed in regexp at - line 1.
謎だらけです。
1、2行目
ところで元のプログラムをよくよく見てみると、
- 1行目:変数"$^"にクォートされている文字列を代入しているだけ
- 2行目:変数"$^_"にクォートされている文字列を代入しているだけ
なのがわかる。
変数"$^"はデフォルトで”STDOUT_TOP"という値を持つ特殊変数だけど、変更可能なのでどうにでも使える。
$ perl -le 'print $^' STDOUT_TOP $ perl -le '$^="hoge"; print $^' hoge
変数"$^_"は特にデフォルト値は定義されていない変数のようだ。
というわけで2行目までの段階では、ただ変数に文字列を代入しているだけ。
printfデバッグ(笑)で確認してみる。
#!/usr/bin/perl $^='-^\\\\%+"<&]\\$=/**:.](%_,//;<#)\\^_%$=]."\\&=\'?/:$?({/!_})(([=##!-^/\'%_#"]{"?~<]$\':$;#"&]`<#:?', $^_='_~`||`]@^_`"./,))<%?%<"(?<_}+^.;#^^@@{`:,|[{_``__[]_;/@-<~<;^.[^~::<&""[_`}*.[?]+[?!|-_`@[*);//(]?",=_+?]{{#`,@@<*@[{|/`.@#@[""[+_}[?__(?.+[_}<.?_"{>|!{}@@^_,{<', # $^_^='{>][[[;/,{>/]_@@]((,^!_&+-{#%%@^[*~)&_>_][|@{>]!+)!~);_?|%.[%<%""/(`>>^`{?@_@+^>@|}.[<{>{(>:(<";&%_"){=+:@_}]@%.[^(`_<<]^!@+|`-|:{#}#`,]]]_){"-;.{|]@`>_"=%(>@_|', # $^_=~('(?{$^_=~'.('_^""'^'+,|/'."-".('$%'^';{'.'(-:^;'.('[)]@'^'>_<,').'$^_})'))) ; printf '$^ : %s', $^; printf "\n"; printf '$^_ : %s', $^_; printf "\n";
$ ./debug.pl $^ : -^\\%+"<&]\$=/**:.](%_,//;<#)\^_%$=]."\&='?/:$?({/!_})(([=##!-^/'%_#"]{"?~<]$':$;#"&]`<#:? $^_ : _~`||`]@^_`"./,))<%?%<"(?<_}+^.;#^^@@{`:,|[{_``__[]_;/@-<~<;^.[^~::<&""[_`}*.[?]+[?!|-_`@[*);//(]?",=_+?]{{#`,@@<*@[{|/`.@#@[""[+_}[?__(?.+[_}<.?_"{>|!{}@@^_,{<
なにもおかしなところはない。
3行目
では次に3行目。ここでは何をしているのか?"$^_^='...'" と、可愛い顔文字に隠された意味とは?
まぁ2行目で代入した変数"$^_"に対して、右辺の文字列との排他的論理和を代入する演算子"^="を適用させてるわけですね。
書き換えると
$^_ = "$^_" ^ '<文字列>'
つまり、2、3行目をあわせると
$^_ = '_~`||`]@^_`"./,))<%?%<"(?<_}+^.;#^^@@{`:,|[{_``__[]_;/@-<~<;^.[^~::<&""[_`}*.[?]+[?!|-_`@[*);//(]?",=_+?]{{#`,@@<*@[{|/`.@#@[""[+_}[?__(?.+[_}<.?_"{>|!{}@@^_,{<' ^ '{>][[[;/,{>/]_@@]((,^!_&+-{#%%@^[*~)&_>_][|@{>]!+)!~);_?|%.[%<%""/(`>>^`{?@_@+^>@|}.[<{>{(>:(<";&%_"){=+:@_}]@%.[^(`_<<]^!@+|`-|:{#}#`,]]]_){"-;.{|]@`>_"=%(>@_|',
という、2つの文字列の排他的論理和を変数"$^_"に代入する処理ということになる。
せっかくなのでここの段階の$^_をprintfデバッグしてみよう。
#!/usr/bin/perl $^='-^\\\\%+"<&]\\$=/**:.](%_,//;<#)\\^_%$=]."\\&=\'?/:$?({/!_})(([=##!-^/\'%_#"]{"?~<]$\':$;#"&]`<#:?', $^_ = '_~`||`]@^_`"./,))<%?%<"(?<_}+^.;#^^@@{`:,|[{_``__[]_;/@-<~<;^.[^~::<&""[_`}*.[?]+[?!|-_`@[*);//(]?",=_+?]{{#`,@@<*@[{|/`.@#@[""[+_}[?__(?.+[_}<.?_"{>|!{}@@^_,{<' ^ '{>][[[;/,{>/]_@@]((,^!_&+-{#%%@^[*~)&_>_][|@{>]!+)!~);_?|%.[%<%""/(`>>^`{?@_@+^>@|}.[<{>{(>:(<";&%_"){=+:@_}]@%.[^(`_<<]^!@+|`-|:{#}#`,]]]_){"-;.{|]@`>_"=%(>@_|', # $^_=~('(?{$^_=~'.('_^""'^'+,|/'."-".('$%'^';{'.'(-:^;'.('[)]@'^'>_<,').'$^_})'))) ; printf '$^_ : %s', $^_; printf "\n";
$ ./debug.pl {}$g;$^=length;$@=pack'B'$^&?substr$_$^&~$_}eval$@^;s
ターミナル上で普通に実行するとこんな出力が得られるけど、実はこの文字列にはコンソールに表示されていないものが含まれている。
hexdumpで出力を確かめてみよう。
$ ./debug.pl | hexdump -C 00000000 24 5e 5f 20 3a 20 24 40 3d 27 27 3b 66 6f 72 24 |$^_ : $@='';for$| 00000010 5e 0d 73 70 6c 69 74 14 0d 13 7b 1d 7d 0e 14 11 |^.split...{.}...| 00000020 24 5e 0e 7b 6e 65 78 74 20 69 66 24 5e 65 71 27 |$^.{next if$^eq'| 00000030 27 3b 24 5e 3d 7e 74 72 7c 21 12 14 1f 12 40 5b |';$^=~tr|!....@[| 00000040 12 60 7b 12 7e 7c 5c 15 12 5c 18 1c 7c 3b 24 5f |.`{.~|\..\..|;$_| 00000050 3d 75 6e 70 61 63 6b 27 42 0f 27 11 24 5e 3b 73 |=unpack'B.'.$^;s| 00000060 14 13 13 13 0d 13 7b 1a 7d 0e 14 24 16 14 67 3b |......{.}..$..g;| 00000070 24 5e 3d 6c 65 6e 67 74 68 3b 24 40 13 3d 70 61 |$^=length;$@.=pa| 00000080 63 6b 27 42 0f 27 11 24 5e 26 1c 3f 73 75 62 73 |ck'B.'.$^&.?subs| 00000090 74 72 24 5f 11 15 11 24 5e 26 7e 1c 1f 24 5f 7d |tr$_...$^&~..$_}| 000000a0 65 76 61 6c 24 40 0a |eval$@.| 000000a7
ところどころに0x0d(CR)が入っているため、コンソールへの表示が上書きされていることが分かる。
ともあれ、少しはコードっぽいものが見えてきた。
4行目
最後の呪文。ここは代入文ではなく、"=~"演算子を用いた正規表現によるマッチングを行っている。
左辺は3行目までに登場している"$^_"なので気にしないとして、右辺は何を表しているのか?
まずは右辺だけをprintfデバッグしてみよう。
#!/usr/bin/perl $^='-^\\\\%+"<&]\\$=/**:.](%_,//;<#)\\^_%$=]."\\&=\'?/:$?({/!_})(([=##!-^/\'%_#"]{"?~<]$\':$;#"&]`<#:?', $^_ = '_~`||`]@^_`"./,))<%?%<"(?<_}+^.;#^^@@{`:,|[{_``__[]_;/@-<~<;^.[^~::<&""[_`}*.[?]+[?!|-_`@[*);//(]?",=_+?]{{#`,@@<*@[{|/`.@#@[""[+_}[?__(?.+[_}<.?_"{>|!{}@@^_,{<' ^ '{>][[[;/,{>/]_@@]((,^!_&+-{#%%@^[*~)&_>_][|@{>]!+)!~);_?|%.[%<%""/(`>>^`{?@_@+^>@|}.[<{>{(>:(<";&%_"){=+:@_}]@%.[^(`_<<]^!@+|`-|:{#}#`,]]]_){"-;.{|]@`>_"=%(>@_|', # $^_=~('(?{$^_=~'.('_^""'^'+,|/'."-".('$%'^';{'.'(-:^;'.('[)]@'^'>_<,').'$^_})'))) ; printf ('(?{$^_=~'.('_^""'^'+,|/'."-".('$%'^';{'.'(-:^;'.('[)]@'^'>_<,').'$^_})')));
これも文字列の排他的論理和と連結が混じっているもので、制御文字が含まれていたりする。
$ ./debug.pl | hexdump -C 00000000 28 3f 7b 24 5e 5f 3d 7e 74 72 5e 0d 2d 1f 5e 28 |(?{$^_=~tr^.-.^(| 00000010 2d 3a 5e 3b 65 76 61 6c 24 5e 5f 7d 29 |-:^;eval$^_})| 0000001d
拡張正規表現の機能で、
'...' =~ '(?{ code })'
という書き方で"code"の部分が実行される。このあたりについては以下の記事が詳しいです。
2006-11-07 - 兼雑記
ちゃんと"perldoc perlre"で調べても載ってます。
"(?{ code })" WARNING: This extended regular expression feature is considered highly experimental, and may be changed or deleted without notice. This zero‐width assertion evaluates any embedded Perl code. It always succeeds, and its "code" is not interpolated. Currently, the rules to determine where the "code" ends are somewhat convoluted. ...
いつまで使えるかわからないけど。。
ともかく、この4行目の右辺の文字列はまさにそれを使っているわけで、先程のprintfデバッグ結果から"code"に該当する部分を抜き出す。
$ ./debug.pl | hexdump -C 00000000 28 3f 7b 24 5e 5f 3d 7e 74 72 5e 0d 2d 1f 5e 28 |(?{$^_=~tr^.-.^(| 00000010 2d 3a 5e 3b 65 76 61 6c 24 5e 5f 7d 29 |-:^;eval$^_})| $ ./debug.pl | hexdump -c 0000000 ( ? { $ ^ _ = ~ t r ^ \r - 037 ^ ( 0000010 - : ^ ; e v a l $ ^ _ } ) 000001d
どちらが分かりやすいだろうか? 要するに、"code"の部分はこうなる。
$^_=~tr^\r-\037^(-:^;eval$^_
"tr"でデリミタに"^"が使われていることに気をつけて、文字コードをシフトするように変換していると解釈すれば、こう書ける。
$^_ =~ tr/\x0d-\x1f/\x28-\x3a/; eval $^_;
ということで、上記のcode部分が実行されることになるので、最初のプログラムは以下のものと等価ということになる。
#!/usr/bin/perl $^ = '-^\\\\%+"<&]\\$=/**:.](%_,//;<#)\\^_%$=]."\\&=\'?/:$?({/!_})(([=##!-^/\'%_#"]{"?~<]$\':$;#"&]`<#:?'; $^_ = '_~`||`]@^_`"./,))<%?%<"(?<_}+^.;#^^@@{`:,|[{_``__[]_;/@-<~<;^.[^~::<&""[_`}*.[?]+[?!|-_`@[*);//(]?",=_+?]{{#`,@@<*@[{|/`.@#@[""[+_}[?__(?.+[_}<.?_"{>|!{}@@^_,{<' ^ '{>][[[;/,{>/]_@@]((,^!_&+-{#%%@^[*~)&_>_][|@{>]!+)!~);_?|%.[%<%""/(`>>^`{?@_@+^>@|}.[<{>{(>:(<";&%_"){=+:@_}]@%.[^(`_<<]^!@+|`-|:{#}#`,]]]_){"-;.{|]@`>_"=%(>@_|'; $^_ =~ tr/\x0d-\x1f/\x28-\x3a/; eval $^_;
evalを辿る
では最後にevalされる文字列は何か? 3行目までで作成された"$^_"に対してtrで一部の文字コードをシフトさせたものになる。
最後のevalの代わりにprintfデバッグしてみよう。
#!/usr/bin/perl $^ = '-^\\\\%+"<&]\\$=/**:.](%_,//;<#)\\^_%$=]."\\&=\'?/:$?({/!_})(([=##!-^/\'%_#"]{"?~<]$\':$;#"&]`<#:?'; $^_ = '_~`||`]@^_`"./,))<%?%<"(?<_}+^.;#^^@@{`:,|[{_``__[]_;/@-<~<;^.[^~::<&""[_`}*.[?]+[?!|-_`@[*);//(]?",=_+?]{{#`,@@<*@[{|/`.@#@[""[+_}[?__(?.+[_}<.?_"{>|!{}@@^_,{<' ^ '{>][[[;/,{>/]_@@]((,^!_&+-{#%%@^[*~)&_>_][|@{>]!+)!~);_?|%.[%<%""/(`>>^`{?@_@+^>@|}.[<{>{(>:(<";&%_"){=+:@_}]@%.[^(`_<<]^!@+|`-|:{#}#`,]]]_){"-;.{|]@`>_"=%(>@_|'; $^_ =~ tr/\x0d-\x1f/\x28-\x3a/; printf $^_;
$ ./debug.pl $@='';for$^(split/(.{8})/,$^){next if$^eq'';$^=~tr|!-/:-@[-`{-~|\0-\37|;$_=unpack'B*',$^;s/...(.{5})/$1/g;$^=length;$@.=pack'B*',$^&7?substr$_,0,$^&~7:$_}eval$@
まさしくデバッグ実行したときに出てくるコード!
これはもはや普通に実行可能なコードなので、"-MO=Deparse"してしまおう。
$ ./debug.pl | perl -MO=Deparse $@ = ''; foreach $^ (split(/(.{8})/, $^, 0)) { next if $^ eq ''; $^ =~ tr(!-/:-@[-`{-~)[\000-\037]; $_ = unpack('B*', $^); s/...(.{5})/$1/g; $^ = length $_; $@ .= pack('B*', $^ & 7 ? substr($_, 0, $^ & 4294967288) : $_); } eval $@;
まだ謎の部分は多いけど、かなり読めるコードになってきた。
ここまでの段階で、最初のコードと等価なコードが
#!/usr/bin/perl $^ = '-^\\\\%+"<&]\\$=/**:.](%_,//;<#)\\^_%$=]."\\&=\'?/:$?({/!_})(([=##!-^/\'%_#"]{"?~<]$\':$;#"&]`<#:?'; $@ = ''; foreach $^ (split(/(.{8})/, $^, 0)) { next if $^ eq ''; $^ =~ tr(!-/:-@[-`{-~)[\000-\037]; $_ = unpack('B*', $^); s/...(.{5})/$1/g; $^ = length $_; $@ .= pack('B*', $^ & 7 ? substr($_, 0, $^ & 4294967288) : $_); } eval $@;
と表現できる。
つまり最初のプログラムは、evalを含むコードをevalする文字列、を拡張正規表現でeval実行する、ということか?もう何が何だか…w
今のこのコードで最後にevalされる"$@"をprintfデバッグしてみると
#!/usr/bin/perl $^ = '-^\\\\%+"<&]\\$=/**:.](%_,//;<#)\\^_%$=]."\\&=\'?/:$?({/!_})(([=##!-^/\'%_#"]{"?~<]$\':$;#"&]`<#:?'; $@ = ''; foreach $^ (split(/(.{8})/, $^, 0)) { next if $^ eq ''; $^ =~ tr(!-/:-@[-`{-~)[\000-\037]; $_ = unpack('B*', $^); s/...(.{5})/$1/g; $^ = length $_; $@ .= pack('B*', $^ & 7 ? substr($_, 0, $^ & 4294967288) : $_); } printf $@;
$ ./debug.pl for(1..99){print"$_: 円周率は 約 3.14 です。\n"}
というのが見事に出てくる。
さらなる解読
では最後の"$@"を作り出しているコードはどうなっているのか?最初は空文字列で初期化して、ループ内で連結されていっているのは一目瞭然。ということはこのループ内で何が起こっているのかを知る必要がある。
そもそもループ変数は何だ?ということでまたprintfデバッグ。
#!/usr/bin/perl $^ = '-^\\\\%+"<&]\\$=/**:.](%_,//;<#)\\^_%$=]."\\&=\'?/:$?({/!_})(([=##!-^/\'%_#"]{"?~<]$\':$;#"&]`<#:?'; $@ = ''; foreach $^ (split(/(.{8})/, $^, 0)) { printf "%s\n", $^; # next if $^ eq ''; # $^ =~ tr(!-/:-@[-`{-~)[\000-\037]; # $_ = unpack('B*', $^); # s/...(.{5})/$1/g; # $^ = length $_; # $@ .= pack('B*', $^ & 7 ? substr($_, 0, $^ & 4294967288) : $_); } # printf $@;
$ ./debug.pl -^\\%+"< &]\$=/** :.](%_,/ /;<#)\^_ %$=]."\& ='?/:$?( {/!_})(( [=##!-^/ '%_#"]{" ?~<]$':$ ;#"&]`<# :?
まぁ /.{8}/ でsplitしているので、8文字ずつ切り出されることになる、か。
このままだと空文字も検出されるのでループ内1行目で無視する、と。
#!/usr/bin/perl $^ = '-^\\\\%+"<&]\\$=/**:.](%_,//;<#)\\^_%$=]."\\&=\'?/:$?({/!_})(([=##!-^/\'%_#"]{"?~<]$\':$;#"&]`<#:?'; $@ = ''; foreach $^ (split(/(.{8})/, $^, 0)) { next if $^ eq ''; printf "%s\n", $^; # $^ =~ tr(!-/:-@[-`{-~)[\000-\037]; # $_ = unpack('B*', $^); # s/...(.{5})/$1/g; # $^ = length $_; # $@ .= pack('B*', $^ & 7 ? substr($_, 0, $^ & 4294967288) : $_); } # printf $@;
$ ./debug.pl -^\\%+"< &]\$=/** :.](%_,/ /;<#)\^_ %$=]."\& ='?/:$?( {/!_})(( [=##!-^/ '%_#"]{" ?~<]$':$ ;#"&]`<# :?
で、次の行。
ここのtr変換した後の出力は、なんとすべて0x00〜0x1fの領域になる。
#!/usr/bin/perl $^ = '-^\\\\%+"<&]\\$=/**:.](%_,//;<#)\\^_%$=]."\\&=\'?/:$?({/!_})(([=##!-^/\'%_#"]{"?~<]$\':$;#"&]`<#:?'; $@ = ''; foreach $^ (split(/(.{8})/, $^, 0)) { next if $^ eq ''; $^ =~ tr(!-/:-@[-`{-~)[\000-\037]; printf "%s\n", $^; # $_ = unpack('B*', $^); # s/...(.{5})/$1/g; # $^ = length $_; # $@ .= pack('B*', $^ & 7 ? substr($_, 0, $^ & 4294967288) : $_); } # printf $@;
% ./debug.pl | hexdump -C 00000000 0c 19 17 17 04 0a 01 11 0a 05 18 17 03 12 0e 09 |................| 00000010 09 0a 0f 0d 18 07 04 1a 0b 0e 0a 0e 10 11 02 08 |................| 00000020 17 19 1a 0a 04 03 12 18 0d 01 17 05 0a 12 06 14 |................| 00000030 0e 0f 03 14 07 0a 1c 0e 00 1a 1e 08 07 07 0a 16 |................| 00000040 12 02 02 00 0c 19 0e 0a 06 04 1a 02 01 18 1c 01 |................| 00000050 0a 14 1f 11 18 03 06 0f 03 0a 10 02 01 05 18 1b |................| 00000060 11 02 0a 0f 14 0a |......| 00000066
…ここについては後でまた考えよう。
で、そうして得られたものを今度はunpackでbit stringに変換している。
#!/usr/bin/perl $^ = '-^\\\\%+"<&]\\$=/**:.](%_,//;<#)\\^_%$=]."\\&=\'?/:$?({/!_})(([=##!-^/\'%_#"]{"?~<]$\':$;#"&]`<#:?'; $@ = ''; foreach $^ (split(/(.{8})/, $^, 0)) { next if $^ eq ''; $^ =~ tr(!-/:-@[-`{-~)[\000-\037]; $_ = unpack('B*', $^); printf "%s\n", $_; # s/...(.{5})/$1/g; # $^ = length $_; # $@ .= pack('B*', $^ & 7 ? substr($_, 0, $^ & 4294967288) : $_); } # printf $@;
$ ./debug.pl 0000110000011001000101110001011100000100000010100000000100010001 0000010100011000000101110000001100010010000011100000100100001001 0000111100001101000110000000011100000100000110100000101100001110 0000111000010000000100010000001000001000000101110001100100011010 0000010000000011000100100001100000001101000000010001011100000101 0001001000000110000101000000111000001111000000110001010000000111 0001110000001110000000000001101000011110000010000000011100000111 0001011000010010000000100000001000000000000011000001100100001110 0000011000000100000110100000001000000001000110000001110000000001 0001010000011111000100010001100000000011000001100000111100000011 0001000000000010000000010000010100011000000110110001000100000010 0000111100010100
で、こんどは8bitの0と1で出てきたそれらのうち、それぞれ上位3bitを切り捨てて下位5bitずつだけを得る。
もともと0x00〜0x1fの範囲のデータなので上位3bitはすべて0になっている。
そうして得られた文字列の長さを"$^"に代入する。最後の部分以外は8文字ずつとったうちの5bitずつなので当然40になる。
#!/usr/bin/perl $^ = '-^\\\\%+"<&]\\$=/**:.](%_,//;<#)\\^_%$=]."\\&=\'?/:$?({/!_})(([=##!-^/\'%_#"]{"?~<]$\':$;#"&]`<#:?'; $@ = ''; foreach $^ (split(/(.{8})/, $^, 0)) { next if $^ eq ''; $^ =~ tr(!-/:-@[-`{-~)[\000-\037]; $_ = unpack('B*', $^); s/...(.{5})/$1/g; $^ = length $_; printf "%d: %s\n", $^, $_; # $@ .= pack('B*', $^ & 7 ? substr($_, 0, $^ & 4294967288) : $_); } # printf $@;
$ ./debug.pl 40: 0110011001101111011100100010100000110001 40: 0010111000101110001110010011100100101001 40: 0111101101110000011100100110100101101110 40: 0111010000100010001001000101111100111010 40: 0010000011100101100001101000011011100101 40: 1001000110101000111001111000111010000111 40: 1110001110000001101011110010000011100111 40: 1011010010000100001000000011001100101110 40: 0011000100110100001000001110001110000001 40: 1010011111100011100000011001100111100011 40: 1000000010000010010111000110111000100010 10: 0111110100
ではループ内最後の行で"$@"に連結している文字列は何なのか?またもやprintfデバッグしてみよう。
#!/usr/bin/perl $^ = '-^\\\\%+"<&]\\$=/**:.](%_,//;<#)\\^_%$=]."\\&=\'?/:$?({/!_})(([=##!-^/\'%_#"]{"?~<]$\':$;#"&]`<#:?'; $@ = ''; foreach $^ (split(/(.{8})/, $^, 0)) { next if $^ eq ''; $^ =~ tr(!-/:-@[-`{-~)[\000-\037]; $_ = unpack('B*', $^); s/...(.{5})/$1/g; $^ = length $_; printf "%s\n", pack('B*', $^ & 7 ? substr($_, 0, $^ & 4294967288) : $_); # $@ .= pack('B*', $^ & 7 ? substr($_, 0, $^ & 4294967288) : $_); } # printf $@;
$ ./debug.pl for(1 ..99) {prin t"$_: 円? ??率 は ? ?? 3. 14 ? ?す? ??\n" }
切れてるけど、なんとなくそれっぽい文字列になっているのがわかる。
ところでこのループ内最終行、packに第2引数で渡っているのは何?
$^ & 7 ? substr($_, 0, $^ & 4294967288) : $_
"$^"はbit列の長さ。末尾の行以外は、5bit * 8文字で40になっている。末尾の行だと5, 10, 15, 20, 25, 30, 35のいずれかになる。
7と論理積を取っているということは、"$^"の下位3bitがどれか1つでもtrueになっているかどうか。逆に考えると、"$^"が8の倍数の値であれば、下位3bitはすべて0になるので7との論理積も0になる。
従って、"$^ & 7"は"$^ == 40"のときだけfalse, それ以外の場合はtrueになる。
では"$^ != 40"のとき、substrで"$_"から切り出されてる長さは?
4294967288とは何か。Deparseする前の文字列を見てみるとわかるけど、これは7のbit反転。
$ perl -le 'print ~7' 4294967288
なので、4294967288というのは下から下位3bitをすべて0にするマスクであると考えられる。"$^ & 4294967288"は、「"$^"より小さい、最大の8の倍数」を取り出すことになる。最終行から最長のbyte列を取り出すための仕組みですね。
整理する
ということで、ここまで見てきた通りに整理すると。あのfor文の中で起こっていることは
- 記号列を8文字ずつ切り出す
- それぞれtrで変換して0x00〜0x1fの領域へ
- bit列に変換し下位5bitずつを取り出す
- 5bitずつ繋げたbit列から8bitずつ切り出す
- 1byteずつ復元する
- (゚Д゚)ウマー
こういう流れで「記号列」から「任意のbyte列」を作成できるというわけだ。
あとはこれを作成するコードを文字列にし、evalしてもらう。
それをただevalにするのではなくニコニコ顔文字を含むトリックを挟む。
最後のevalは拡張正規表現で。
何がすごいって
ポイントは
$^ =~ tr(!-/:-@[-`{-~)[\000-\037];
ここ。
ASCIIコード表を照らし合わせながらみてみると分かりやすいかも。
"!"〜"/" →0x21から0x2fまでの15文字 ":"〜"@" →0x3aから0x40までの7文字 "["〜"`" →0x5bから0x60までの6文字 "{"〜"~" →0x7bから0x7eまでの4文字
印字可能な文字から英数字を除いた全32文字を、うまく変換して、0x00〜0x1fの領域に収めている、ということ。
この領域がすべて5bitで表現できる。
これによってすべての記号を組み合わせることで任意のbit列、byte列を生成できるようにしている!
いやはや、しかしこんなのどうやって思いついたんだろう。。すごすぎる。。。w