Subscribed unsubscribe Subscribe Subscribe

記号解読にチャレンジ

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文の中で起こっていることは

  1. 記号列を8文字ずつ切り出す
  2. それぞれtrで変換して0x00〜0x1fの領域へ
  3. bit列に変換し下位5bitずつを取り出す
  4. 5bitずつ繋げたbit列から8bitずつ切り出す
  5. 1byteずつ復元する
  6. (゚Д゚)ウマー

こういう流れで「記号列」から「任意の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