
はじめに
こんにちは!動画配信開発部プレミアムグループ所属、23新卒エンジニアの大坂(@yud0uhu)です。
いよいよ3月7日(木)より3日間にわたるカンファレンス、PHPerKaigi 2024が開催されます!
DMM.com(以下、DMM)は、「ゴールド」「T-SHIRT」スポンサーとして協賛しています!
私も当日スタッフとして参加予定です。
記事内に「PHPerチャレンジ」用トークン(#文字列) を掲載していますので、ぜひ最後までお読みください!
PHPerKaigiとは?
PHPerKaigi(ペチパーカイギ)は、PHPer、つまり、現在PHPを使用している方、過去にPHPを使用していた方、これからPHPを使いたいと思っている方、そしてPHPが大好きな方たちが、技術的なノウハウとPHP愛を共有するためのイベントです。
https://phperkaigi.jp/2024/ より
【PHPerKaigi2024 開催概要】
▶︎開催日:2024年3月7日(木)~ 3月9日(土)
▶︎会場:中野セントラルパークカンファレンスおよびオンライン(ニコニコ生放送)
▶︎対象:PHPエンジニアおよびWeb技術のエンジニア
▶︎主催:PHPerKaigi 2024 実行委員会 (実行委員長 長谷川智希 @tomzoh)
▶︎公式サイト:https://phperkaigi.jp/2024/
▶︎ 公式Twitter:https://twitter.com/phperkaigi
申し込みはこちらから
本記事では、参加者向け企画「PHPerチャレンジ」の試みとしてトークンが隠されたQuineコードの実装に挑戦してみたお話をします!
私自身はフロントエンドエンジニアで、普段はReactでTypeScriptを書いていることが多いのですが、今回はせっかくなのでPHPでQuineの実装に挑戦してみました!
【PHPerチャレンジ概要】
PHPerKaigi 2024では、参加者向け企画「PHPerチャレンジ」が実施される予定です。事前に参画する各協賛企業が、ブログ記事やノベルティなどにトークンを記載し、WEBに公開します。参加者は見つけたトークンの数で各種企画の参加権が得られます。(会場参加者のみ特典あり)
Quineとは?
- Quine(クワイン)とは、自分自身のソースコードと完全に同じ文字列を出力するプログラムのことをいいます。
- 「あなたの知らない超絶技巧プログラミングの世界」では、多種多様な書き方で記述されたQuineが紹介されています。
あなたの知らない超絶技巧プログラミングの世界 - 筆者の遠藤さんの作成されたQuineの一つは、カロリーメイトリキッドのサイトでも体験できます。
Quineの記述ルール
Quineの定義についてはWikipediaなどに詳細が記載されていますが、今回はChat GPTと相談し、以下の二つをルールにすることにしました。
- コマンドライン引数など外部からの入力を必要とせず、単体で実行可能であること
- プログラム中に直接ソースコードを含めず、自身の出力をプログラムのロジックだけで生成すること
この二つのルールに従って、トークン文字列が隠されたQuineをPHPで実装してみたいと思います。
<?php
$phper_token = '
]``````~?"WMMMF````4M#atarimaecreatorMF````JM#````4MMF````JMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
] ?M% .MM` .M] MM! MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
] .,. . (F d` -F dMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
] MMF . .MMMMMMMD! JM@^ ?TM#`_"! 7"`-4
] ""` .. , ., , MMMMMMF dMMF .MN. J# d] M; .
] (b .# (b .N dMMMMM] WMMb .MM! (# MF M[ .
] ...d] MM, gM. MM; JM, .Mb..MMa. .(Ma.. ..M# MF .M[ .';
$prog = sprintf(
'echo base64_decode(\'%s\');echo sprintf(file_get_contents(__FILE__), base64_encode(\'%s\'));',
base64_encode($phper_token),
$phper_token
);
echo sprintf("<?php\n\$prog = '%s';\neval(\$prog);\n?>\n", $prog);
これをPHPの実行環境で出力すると、以下のような出力が得られます。
<?php
$prog = 'echo base64_decode('DQpdYGBgYGBgfj8iV01NTUZgYGBgNE0kdG9rZW5NRmBgYGBKTSNgYGBgNE1NRmBgYGBKTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU0NCl0gICAgICAgICAgID9NJSAgICAuTU1gICAgIC5NXSAgICAgTU0hICAgICBNTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTQ0KXSAgICAgLiwuICAgIC4gICAgICAoRiAgICAgIGRgICAgICAtRiAgICAgIGRNTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NDQpdICAgICBNTUYgICAgICAgICAgICAgICAgICAgLiAgICAgICAgICAgICAgLk1NTU1NTU1EISAgSk1AXiAgP1RNI2BfIiEgNyJgLTQNCl0gICAgICIiYCAgICAgICAgLi4gICAgICwgICAgICAgLiwgICAgICwgICAgTU1NTU1NRiAgZE1NRiAuTU4uIEojICBkXSAgTTsgLg0KXSAgICAgICAgICAgICAgICAoYiAgICAuIyAgICAgICAoYiAgICAuTiAgICBkTU1NTU1dICBXTU1iIC5NTSEgKCMgIE1GICBNWyAuDQpdICAgICAgIC4uLmRdICAgIE1NLCAgIGdNLiAgICAgIE1NOyAgIEpNLCAgIC5NYi4uTU1hLiAuKE1hLi4gLi5NIyAgTUYgLk1bIC4=');echo sprintf(file_get_contents(__FILE__), base64_encode('
]``````~?"WMMMF````4M#atarimaecreatorMF````JM#````4MMF````JMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
] ?M% .MM` .M] MM! MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
] .,. . (F d` -F dMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
] MMF . .MMMMMMMD! JM@^ ?TM#`_"! 7"`-4
] ""` .. , ., , MMMMMMF dMMF .MN. J# d] M; .
] (b .# (b .N dMMMMM] WMMb .MM! (# MF M[ .
] ...d] MM, gM. MM; JM, .Mb..MMa. .(Ma.. ..M# MF .M[ .'));';
eval($prog);
?>
ここからは実装を見てみます。
$prog = sprintf( 'echo base64_decode(\\'%s\\');echo sprintf(file_get_contents(__FILE__), base64_encode(\\'%s\\'));', base64_encode($phper_token), $phper_token );
では、$prog に文字列を代入しています。
sprintf() は、フォーマット文字列を通じてプレースホルダを特定の文字列に置き換えて返却するための関数です。
今回の場合、sprintf関数はフォーマット文字列にプレースホルダ%s を持っています。
各プレースホルダは、sprintf関数の次の引数で置換されます。この例では、一つ目のプレースホルダ%sがbase64_encode($phper_token)でBase64にエンコードした$phper_tokenの値に置換され、二つ目のプレースホルダ$s が$phper_token の値に置換されます。
ここでなぜエンコード処理を挟むのかというと、文字列としてのプログラムに特殊文字が含まれていたとき、それがエスケープシーケンスなどと解釈されるのを避けるためです。プログラム実行時にエンコードされた文字列をデコードして出力することで、元の文字列の正確なコピーを出力できます。
そのため、Quiineを実装する一つの手法として、「プログラム全体をある形(Base64など)にエンコードし、実行時にそのエンコードされた形をデコードして出力する」という方法があります。
echo sprintf(file_get_contents(__FILE__), base64_encode('
]``````~?"WMMMF````4M#atarimaecreatorMF````JM#````4MMF````JMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
] ?M% .MM` .M] MM! MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
] .,. . (F d` -F dMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
] MMF . .MMMMMMMD! JM@^ ?TM#`_"! 7"`-4
] ""` .. , ., , MMMMMMF dMMF .MN. J# d] M; .
] (b .# (b .N dMMMMM] WMMb .MM! (# MF M[ .
] ...d] MM, gM. MM; JM, .Mb..MMa. .(Ma.. ..M# MF .M[ .'));';
では、自身のソースコードを取得して(file_get_contents(__FILE__))、はじめの%sに対応する引数としてsprintf関数に渡してから、アスキーアートを再びBase64でエンコードして、それを二つ目の%sに対応する引数として、sprintf関数に渡しています。
ここで、file_get_contents() はファイルの内容を文字列として読み込むためのPHPの組み込み関数です。
PHP: file_get_contents - Manual
__FILE__ はマジック定数の一つで、現在のスクリプトのフルパス(実行中のスクリプトファイル名も含む)を取得します。
結果として、最初に出力したものと同じスクリプトが再生成され、$prog に渡ります。
echo sprintf("<?php\\n\\$prog = '%s';\\neval(\\$prog);\\n?>\\n", $prog);
で、最終的に一つ目の%sが$progの値で置き換えられ、eval関数に命令コードとして渡って実行されます。
これは、最初に定義したQuineのルールに従っています。
- コマンドライン引数など外部からの入力を必要とせず、単体で実行可能であること
- プログラム中に直接ソースコードを含めず、自身の出力をプログラムのロジックだけで生成すること
Quineは、言語によってもいろいろな書き方やテクニックが存在します。みなさんも、ぜひPHPでQuineに挑戦してみてください!
最後に
Quineコードの中に隠されたトークン文字列は
$atarimaecreator
でした!
私たちDMMテックチームは、当たり前を作り続けるため「Tech Vision」を掲げながら、エンタメをはじめさまざまな領域で終わりのない挑戦に挑み続けています。DMMグループでは、一緒に働いてくれる仲間を募集しています。ご興味のある方は、ぜひ募集ページをご確認ください!
https://dmm-corp.com/recruit/