From 9d5ec5e3bc01c6174dea048e118edee579c36565 Mon Sep 17 00:00:00 2001 From: nsfisis Date: Sat, 7 Feb 2026 23:06:23 +0900 Subject: fix(style): fix codeblock style for rouge --- .../phperkaigi-2023-tokens-q1/index.html | 256 ++++++++++----------- 1 file changed, 124 insertions(+), 132 deletions(-) (limited to 'services/nuldoc/public/blog/posts/2025-01-08/phperkaigi-2023-tokens-q1/index.html') diff --git a/services/nuldoc/public/blog/posts/2025-01-08/phperkaigi-2023-tokens-q1/index.html b/services/nuldoc/public/blog/posts/2025-01-08/phperkaigi-2023-tokens-q1/index.html index 510f3062..25859d88 100644 --- a/services/nuldoc/public/blog/posts/2025-01-08/phperkaigi-2023-tokens-q1/index.html +++ b/services/nuldoc/public/blog/posts/2025-01-08/phperkaigi-2023-tokens-q1/index.html @@ -15,7 +15,7 @@ PHPerKaigi 2023 トークン問題解説 (1/5)|REPL: Rest-Eat-Program Loop - +
@@ -167,8 +167,7 @@ まずはトークンを得る方法を解説抜きで説明する。次のように実行する。

-
$ echo "#iwillblog" | php Q1.png >/dev/null
-
+
$ echo "#iwillblog" | php Q1.png >/dev/null

無事に実行できていれば「#ModernPHPisStaticallyTypedLanguage」というトークンが得られる。 @@ -182,8 +181,7 @@ まずは素直に画像として見てみよう。全体は QR コードになっている。適当な QR コードリーダで読み込むと、次のようなテキストが表示されるはずだ。

-
Guess password. $ echo "password" | php Q1.png >/dev/null
-
+
Guess password. $ echo "password" | php Q1.png >/dev/null

メッセージは、この画像の実行方法とこの問題でやるべきこと (パスワードの推測) を示している。 @@ -198,9 +196,8 @@ 不正なパスワードを使って実行してみると、次のようなエラーメッセージが表示される。

-
$ echo "foo" | php Q1.png >/dev/null
-401 Unauthorized
-
+
$ echo "foo" | php Q1.png >/dev/null +
401 Unauthorized

すでに 「解き方」の節 で示したように、パスワードである PHPer トークンは「#iwillblog」である。これを与えて実行すると正解のトークンが得られる。 @@ -270,25 +267,23 @@ strings コマンドを使うと、隠されたデータを簡単に閲覧できる。

-
IHDR
--HHc
-<PLTE
-IDATx
-IEND
-<?php
-error_reporting(-1);
-$b = unpack('C*', file_get_contents(__FILE__));
-$w = $b[20]+2;
-$h = $b[24]+2;
-// (以下略)
-
+
IHDR +
-HHc +
<PLTE +
IDATx +
IEND +
<?php +
error_reporting(-1); +
$b = unpack('C*', file_get_contents(__FILE__)); +
$w = $b[20]+2; +
$h = $b[24]+2; +
// (以下略)

IHDRIEND が PNG 画像の一部で、<?php からが実際のプログラムになっている。もちろんこれを PHP プログラムとして動かすと、PHP タグより前にある PNG 画像としてのデータはそのまま標準出力へと出力されてしまう。それを防ぐため、QR コードを読み込んだときの実行方法

-
Guess password. $ echo "password" | php Q1.png >/dev/null
-
+
Guess password. $ echo "password" | php Q1.png >/dev/null

には標準出力を捨てるよう >/dev/null と指定されている。 @@ -303,108 +298,107 @@ $h = $b[24]+2; 画像の正体がわかったところで、画像に隠されていた PHP プログラムについて見ていこう。先ほどは一部しか記載しなかったので、全体を載せる。なお、ある程度ゴルフしながら書いたので、空白こそ残しているものの可読性は非常に低いことと思う。

-
<?php
-error_reporting(-1);
-$b = unpack('C*', file_get_contents(__FILE__));
-$w = $b[20]+2;
-$h = $b[24]+2;
-$cs = [];
-for ($y = 0; $y < $h; $y++)
-  for ($x = 0; $x < $w; $x++)
-    $cs[$y*$w + $x] = ($x*$y === 0 || $x === $w-1 || $y === $h-1)
-                        ? 0
-                        : $b[122+($y-1)*($w-1)+$x-1];
-$i = stream_isatty(STDIN)
-    ? []
-    : array_map(ord(...), str_split(trim((string) fgets(STDIN))));
-$m = [];
-$pc = 1*$w+1;
-$dp = 0;
-$cc = 1;
-$c0 = 1;
-$b = 0;
-$ns = 0;
-$o = '';
-while (true) {
-  $ns++;
-  if ($ns > 1e5) {
-    echo "infinite loop detected\n";
-    break;
-  $c1 = $cs[$pc];
-  $y = (6 + intdiv($c1-2, 3) - intdiv($c0-2, 3)) % 6;
-  $x = (3 + $c1%3 - $c0%3) % 3;
-  match (($c0 !== 1) * ($c1 !== 1) * ($y*3 + $x)) {
-    1 => $m[] = $b,
-    2 => array_pop($m),
-    3 => $m[] = array_pop($m) + array_pop($m),
-    4 => $m[] = (fn($x, $y) => $y - $x)(array_pop($m), array_pop($m)),
-    5 => $m[] = array_pop($m) * array_pop($m),
-    8 => $m[] = array_pop($m) === 0 ? 1 : 0,
-    11 => $cc *= pow(-1, array_pop($m)),
-    12 => $m[] = $m[count($m)-1],
-    13 => $m = (fn($n, $d, $m, $l) => [
-            ...array_slice($m, 0, $l-$d),
-            ...array_reverse([
-              ...array_reverse(array_slice($m, $l-$d, $d-$n)),
-              ...array_reverse(array_slice($m, $l-$n)),
-            ]),
-          ])(array_pop($m), array_pop($m), $m, count($m)),
-    15 => !empty($i) and $m[] = array_shift($i),
-    16 => $o .= sprintf('%d', array_pop($m)),
-    17 => $o .= sprintf('%c', array_pop($m)),
-    default => 'nop',
-  };
-  $c0 = $c1;
-  for ($j = 0; $j < 8; $j++) {
-    $v = [];
-    if ($c1 === 1) {
-      $x = $pc % $w;
-      $y = intdiv($pc, $w);
-      $e = [($y+1)*$w-1, ($h-1)*$w+$x, $y*$w, $x][$dp];
-      $z = [1, $w, -1, -$w][$dp];
-      for ($ep = $pc; $ep !== $e; $ep += $z)
-        if ($cs[$ep] !== 1) break;
-      $ep -= $z;
-      $pc = $ep;
-    } else {
-      $q = [$pc];
-      $ep = $pc;
-      while (!empty($q)) {
-        $qq = array_pop($q);
-        $v[$qq] = true;
-        foreach ([$qq+1, $qq+$w, $qq-1, $qq-$w] as $qp) {
-          if ($cs[$qp] !== $c1) continue;
-          if (isset($v[$qp])) continue;
-          $q[] = $qp;
-          $qx = $qp % $w;
-          $qy = intdiv($qp, $w);
-          $x = $ep % $w;
-          $y = intdiv($ep, $w);
-          if (
-            ($dp === 0 && ($x < $qx || ($x === $qx && ($y<=>$qy) === $cc)))
-            || ($dp === 1 && ($y < $qy || ($y === $qy && ($qx<=>$x) === $cc)))
-            || ($dp === 2 && ($qx < $x || ($qx === $x && ($qy<=>$y) === $cc)))
-            || ($dp === 3 && ($qy < $y || ($qy === $y && ($x<=>$qx) === $cc)))
-          )
-            $ep = $qp;
-        }
-      }
-    }
-    $np = $ep + [1, $w, -1, -$w][$dp];
-    if ($cs[$np] !== 0) {
-      $b = count(array_keys($v));
-      $pc = $np;
-      break;
-    }
-    if ($j === 7) break 2;
-    if ($j % 2 === 0) $cc = -$cc;
-    if ($j % 2 === 1) $dp = ($dp+1) % 4;
-// The original Piet image is wrong: it outputs 403 error for invalid passwords.
-// Failure of authentication should be notified by 401, not 403.
-// I noticed that one month before PHPerKaigi, but I could not read or write (paint)
-// Piet any longer at that time.
-fwrite(STDERR, str_replace('403 Forbidden', '401 Unauthorized', $o));
-
+
<?php +
error_reporting(-1); +
$b = unpack('C*', file_get_contents(__FILE__)); +
$w = $b[20]+2; +
$h = $b[24]+2; +
$cs = []; +
for ($y = 0; $y < $h; $y++) +
for ($x = 0; $x < $w; $x++) +
$cs[$y*$w + $x] = ($x*$y === 0 || $x === $w-1 || $y === $h-1) +
? 0 +
: $b[122+($y-1)*($w-1)+$x-1]; +
$i = stream_isatty(STDIN) +
? [] +
: array_map(ord(...), str_split(trim((string) fgets(STDIN)))); +
$m = []; +
$pc = 1*$w+1; +
$dp = 0; +
$cc = 1; +
$c0 = 1; +
$b = 0; +
$ns = 0; +
$o = ''; +
while (true) { +
$ns++; +
if ($ns > 1e5) { +
echo "infinite loop detected\n"; +
break; +
$c1 = $cs[$pc]; +
$y = (6 + intdiv($c1-2, 3) - intdiv($c0-2, 3)) % 6; +
$x = (3 + $c1%3 - $c0%3) % 3; +
match (($c0 !== 1) * ($c1 !== 1) * ($y*3 + $x)) { +
1 => $m[] = $b, +
2 => array_pop($m), +
3 => $m[] = array_pop($m) + array_pop($m), +
4 => $m[] = (fn($x, $y) => $y - $x)(array_pop($m), array_pop($m)), +
5 => $m[] = array_pop($m) * array_pop($m), +
8 => $m[] = array_pop($m) === 0 ? 1 : 0, +
11 => $cc *= pow(-1, array_pop($m)), +
12 => $m[] = $m[count($m)-1], +
13 => $m = (fn($n, $d, $m, $l) => [ +
...array_slice($m, 0, $l-$d), +
...array_reverse([ +
...array_reverse(array_slice($m, $l-$d, $d-$n)), +
...array_reverse(array_slice($m, $l-$n)), +
]), +
])(array_pop($m), array_pop($m), $m, count($m)), +
15 => !empty($i) and $m[] = array_shift($i), +
16 => $o .= sprintf('%d', array_pop($m)), +
17 => $o .= sprintf('%c', array_pop($m)), +
default => 'nop', +
}; +
$c0 = $c1; +
for ($j = 0; $j < 8; $j++) { +
$v = []; +
if ($c1 === 1) { +
$x = $pc % $w; +
$y = intdiv($pc, $w); +
$e = [($y+1)*$w-1, ($h-1)*$w+$x, $y*$w, $x][$dp]; +
$z = [1, $w, -1, -$w][$dp]; +
for ($ep = $pc; $ep !== $e; $ep += $z) +
if ($cs[$ep] !== 1) break; +
$ep -= $z; +
$pc = $ep; +
} else { +
$q = [$pc]; +
$ep = $pc; +
while (!empty($q)) { +
$qq = array_pop($q); +
$v[$qq] = true; +
foreach ([$qq+1, $qq+$w, $qq-1, $qq-$w] as $qp) { +
if ($cs[$qp] !== $c1) continue; +
if (isset($v[$qp])) continue; +
$q[] = $qp; +
$qx = $qp % $w; +
$qy = intdiv($qp, $w); +
$x = $ep % $w; +
$y = intdiv($ep, $w); +
if ( +
($dp === 0 && ($x < $qx || ($x === $qx && ($y<=>$qy) === $cc))) +
|| ($dp === 1 && ($y < $qy || ($y === $qy && ($qx<=>$x) === $cc))) +
|| ($dp === 2 && ($qx < $x || ($qx === $x && ($qy<=>$y) === $cc))) +
|| ($dp === 3 && ($qy < $y || ($qy === $y && ($x<=>$qx) === $cc))) +
) +
$ep = $qp; +
} +
} +
} +
$np = $ep + [1, $w, -1, -$w][$dp]; +
if ($cs[$np] !== 0) { +
$b = count(array_keys($v)); +
$pc = $np; +
break; +
} +
if ($j === 7) break 2; +
if ($j % 2 === 0) $cc = -$cc; +
if ($j % 2 === 1) $dp = ($dp+1) % 4; +
// The original Piet image is wrong: it outputs 403 error for invalid passwords. +
// Failure of authentication should be notified by 401, not 403. +
// I noticed that one month before PHPerKaigi, but I could not read or write (paint) +
// Piet any longer at that time. +
fwrite(STDERR, str_replace('403 Forbidden', '401 Unauthorized', $o));

これは一体なんなのか。ずばり、難解プログラミング言語の一つ Piet のインタプリタである。Piet はピエト・モンドリアン (『赤・青・黄のコンポジション』などで知られる抽象画家) の作品にインスピレーションを受けて作られた、画像をソースコードとするプログラミング言語である。インタプリタは画像の各ピクセルの上を進みながら、色等に応じて特定の処理をおこなっていく。ここでは詳しい言語仕様については解説しないので、気になる方は Wikipedia の記事「Piet」 などを参照してほしい。 @@ -413,8 +407,7 @@ $h = $b[24]+2; プログラムの冒頭にあるこの箇所

-
$b = unpack('C*', file_get_contents(__FILE__));
-
+
$b = unpack('C*', file_get_contents(__FILE__));

__FILE__ つまりこの画像ファイルを読み込んでいる。先ほど Piet は画像をソースコードにしていると説明した。そう、今回の問題の画像ファイル Q1.png は、PHP 製 Piet インタプリタであると同時に、Piet のソースコード画像でもあるのだ。QR コード中央のカラフルな部分が Piet の命令になっている。 @@ -466,12 +459,11 @@ $h = $b[24]+2; ところで、先ほど掲載した Piet のインタプリタのソースコード末尾には次のような箇所がある。

-
// The original Piet image is wrong: it outputs 403 error for invalid passwords.
-// Failure of authentication should be notified by 401, not 403.
-// I noticed that one month before PHPerKaigi, but I could not read or write (paint)
-// Piet any longer at that time.
-fwrite(STDERR, str_replace('403 Forbidden', '401 Unauthorized', $o));
-
+
// The original Piet image is wrong: it outputs 403 error for invalid passwords. +
// Failure of authentication should be notified by 401, not 403. +
// I noticed that one month before PHPerKaigi, but I could not read or write (paint) +
// Piet any longer at that time. +
fwrite(STDERR, str_replace('403 Forbidden', '401 Unauthorized', $o));

コメントにも書かれているが、この Piet のソースコード画像には誤りがあった。本来 HTTP のステータスコードを真似るのなら、認証の失敗には 401 を返さなければならない。しかし、Piet のソースは 403 を返すように書いてしまっていた。そのことに私が気付いたのは PHPerKaigi 2023 が開催されるひと月前で、その時点で私はこの Piet のソースコードを (ちょうどこの記事でそうなっているのと同じように) 読解できなくなっていた。さらに悪いことに、正しいメッセージ「401 Unauthorized」は元の「403 Forbidden」よりも3文字長い。3文字出力が長くなるということは、それだけ Piet で塗るべきピクセルが増えることを意味する。もはや3文字追加で出力するだけの余白はこの画像に残されていなかった (と思う。腕ききの Piet プログラマならできるかもしれないので挑戦してみてほしい)。 -- cgit v1.3-1-g0d28