Upgrade to Pro — share decks privately, control downloads, hide ads and more …

PHPerチャレンジ 解説LT / PHPerKaigi2023challenge

Cybozu
March 25, 2023

PHPerチャレンジ 解説LT / PHPerKaigi2023challenge

PHPerKaigi 2023 の PHPer チャレンジ用にサイボウズから問題を出題しました。
そちらの解説 LT で仕様したスライドです。

PHPerKaigi 2023: https://phperkaigi.jp/2023/
問題: https://blog.cybozu.io/entry/phperkaigi2023-sponser
LT: https://fortee.jp/phperkaigi-2023/proposal/07411094-2fc1-4abd-904f-9470454531e6

Cybozu

March 25, 2023
Tweet

More Decks by Cybozu

Other Decks in Technology

Transcript

  1. PHPerチャレンジ 解説LT
    サイボウズ株式会社 そが

    View full-size slide

  2. PHPer チャレンジ、みなさんやっていますか︖
    ▌「#{⽂字列}」な形式の PHPer トークンをたくさん⾒つけて、
    スコアを稼ぐ企画
    ▌今年はサイボウズから PHPer チャレンジ⽤の問題を出題しています
    https://blog.cybozu.io/entry/phperkaigi2023-sponser
    その問題の解説を⾏う LT です︕

    View full-size slide

  3. 出題&解説の⼈
    ▌なまえ︓そが
    ▌会社︓サイボウズ株式会社
    ▌仕事︓弊社製品 Garoon の開発チームで、
    テクニカルサポート系
    ▌PHP︓仕事柄、書くのは稀で読むことが多い

    View full-size slide

  4. Step1
    ▌まず、2種類のプログラムがあることを確認
    n getPHPerToken.php
    n getKey.php
    ▌欲しいのは PHPer トークンなので、
    まずは getPHPerToken.php を読んでみる

    View full-size slide

  5. getPHPerToken.php /**
    * This program can display a PHPer token.
    * But to get the correct PHPer token,
    * there are a few things you need to do before running this program.
    *
    * 1. You should get key1, key2 and key3 from getKey.php,
    * and fill , and of this program.
    * 2. The decrypt function does not work as expected for some reason.
    * This should be fixed.
    **/
    $key1=;
    $key2=;
    $key3=;
    function decrypt(array $ciphertext, int $public_key, int $secret_key) {
    $hex = "";
    foreach($ciphertext as $value) {
    $hex .= dechex($value ** $secret_key % $public_key);
    }
    $cleartext=hex2bin($hex);
    return $cleartext;
    }
    $ciphertext = [3181896, 6283063, 4748177, 3723679, 5707941];
    $public_key = 8555851;
    $secret_key = ($key1 + $key2 * 2 + $key3 * 3) ** 2 * 2 ** 3 + $key2 * $key3 + $key2 + $key3
    + 3;
    $cleartext=decrypt($ciphertext, $public_key, $secret_key);
    if(substr(md5($cleartext),0,30) === "97097d30ceb203d46ab08edf0308ba") {
    echo "PHPerToken is #" . $cleartext;
    } else {
    echo "Failed to get PHPerToken...";
    }

    View full-size slide

  6. getPHPerToken.php(冒頭)
    /**
    * This program can display a PHPer token.
    * But to get the correct PHPer token,
    * there are a few things you need to do before running this program.
    *
    * 1. You should get key1, key2 and key3 from getKey.php,
    * and fill , and of this program.
    * 2. The decrypt function does not work as expected for some reason.
    * This should be fixed.
    **/
    $key1=;
    $key2=;
    $key3=;
    PHPerToken を表⽰できるプログラムだ
    けど、事前に以下2つをやってね。
    1.getKey.php からキーを3つ取得
    2.decrypt 関数の修正

    View full-size slide

  7. getPHPerToken.php(冒頭)
    /**
    * This program can display a PHPer token.
    * But to get the correct PHPer token,
    * there are a few things you need to do before running this program.
    *
    * 1. You should get key1, key2 and key3 from getKey.php,
    * and fill , and of this program.
    * 2. The decrypt function does not work as expected for some reason.
    * This should be fixed.
    **/
    $key1=;
    $key2=;
    $key3=;
    PHPerToken を表⽰できるプログラムだ
    けど、事前に以下2つをやってね。
    1.getKey.phpからキーを3つ取得
    2.decrypt 関数の修正
    じゃあ、getKey.php を⾒てみよう

    View full-size slide

  8. getKey.php
    /**
    * This program can display key1, key2 and key3.
    **/
    class A {
    public static function counter() {
    static $counter = 0;
    $counter += (2023 != "2023PHP");
    return $counter;
    }
    }
    class B extends A {}
    A::counter();
    $key = B::counter() . 3 << 2;
    $version = substr(phpversion(), 0, 3);
    if ($version === "7.4"){
    echo "key1: " . $key;
    } elseif ($version === "8.0") {
    echo "key2: " . $key;
    } elseif ($version === "8.1") {
    echo "key3: " . $key;
    } else {
    echo "Get no key!";
    }

    View full-size slide

  9. getKey.php
    /**
    * This program can display key1, key2 and key3.
    **/
    class A {
    public static function counter() {
    static $counter = 0;
    $counter += (2023 != "2023PHP");
    return $counter;
    }
    }
    class B extends A {}
    A::counter();
    $key = B::counter() . 3 << 2;
    $version = substr(phpversion(), 0, 3);
    if ($version === "7.4"){
    echo "key1: " . $key;
    } elseif ($version === "8.0") {
    echo "key2: " . $key;
    } elseif ($version === "8.1") {
    echo "key3: " . $key;
    } else {
    echo "Get no key!";
    }
    PHP のバージョンごとに
    異なるキーを出⼒しそう

    View full-size slide

  10. getKey.php
    /**
    * This program can display key1, key2 and key3.
    **/
    class A {
    public static function counter() {
    static $counter = 0;
    $counter += (2023 != "2023PHP");
    return $counter;
    }
    }
    class B extends A {}
    A::counter();
    $key = B::counter() . 3 << 2;
    $version = substr(phpversion(), 0, 3);
    if ($version === "7.4"){
    echo "key1: " . $key;
    } elseif ($version === "8.0") {
    echo "key2: " . $key;
    } elseif ($version === "8.1") {
    echo "key3: " . $key;
    } else {
    echo "Get no key!";
    }
    PHP 7.4, 8.0, 8.1 で挙動が変化
    するコードなので、$key が全部異なる

    View full-size slide

  11. 挙動変化の元ネタ
    ▌⽂字列と数値の⽐較
    n https://www.php.net/manual/ja/migration80.incompatible.php#migration80.incompatible.core.
    string-number-comparision
    ▌ビットシフトや加算、減算に対する連結演算⼦の優先順位が変更
    n https://www.php.net/manual/ja/migration80.incompatible.php#migration80.incompatible.core.
    other
    ▌継承したメソッド内で static 変数を使う
    n https://www.php.net/manual/ja/migration81.incompatible.php#migration81.incompatible.core.
    static-variable-inheritance

    View full-size slide

  12. getKey.php の実⾏ /**
    * This program can display key1, key2 and key3.
    **/
    class A {
    public static function counter() {
    static $counter = 0;
    $counter += (2023 != "2023PHP");
    return $counter;
    }
    }
    class B extends A {}
    A::counter();
    $key = B::counter() . 3 << 2;
    $version = substr(phpversion(), 0, 3);
    if ($version === "7.4"){
    echo "key1: " . $key;
    } elseif ($version === "8.0") {
    echo "key2: " . $key;
    } elseif ($version === "8.1") {
    echo "key3: " . $key;
    } else {
    echo "Get no key!";
    }
    ▌各 PHP バージョンでの実⾏⽅法は、お好みでOK
    n ⾃前でそれぞれ実⾏環境⽤意するもよし
    n Web 上で済ますもよし
    n https://3v4l.org/ など
    n 暗算できたらとてもつよし
    ▌key1=12, key2=112, key3=212

    View full-size slide

  13. getPHPerToken.php(冒頭)
    /**
    * This program can display a PHPer token.
    * But to get the correct PHPer token,
    * there are a few things you need to do before running this program.
    *
    * 1. You should get key1, key2 and key3 from getKey.php,
    * and fill , and of this program.
    * 2. The decrypt function does not work as expected for some reason.
    * This should be fixed.
    **/
    $key1=12;
    $key2=112;
    $key3=212;
    取得した key1, key2, key3 を⼊れる

    View full-size slide

  14. getPHPerToken.php(冒頭)
    PHPerToken を表⽰できるプログラムだ
    けど、事前に以下2つをやってね。
    1.getKey.php からキーを3つ取得
    2.decrypt 関数の修正
    /**
    * This program can display a PHPer token.
    * But to get the correct PHPer token,
    * there are a few things you need to do before running this program.
    *
    * 1. You should get key1, key2 and key3 from getKey.php,
    * and fill , and of this program.
    * 2. The decrypt function does not work as expected for some reason.
    * This should be fixed.
    **/
    $key1=12;
    $key2=112;
    $key3=212;

    View full-size slide

  15. getPHPerToken.php(decrypt 関数)
    function decrypt(array $ciphertext, int $public_key, int $secret_key) {
    $hex = "";
    foreach($ciphertext as $value) {
    $hex .= dechex($value ** $secret_key % $public_key);
    }
    $cleartext=hex2bin($hex);
    return $cleartext;
    }
    $ciphertext = [3181896, 6283063, 4748177, 3723679, 5707941];
    $public_key = 8555851;
    $secret_key = ($key1 + $key2 * 2 + $key3 * 3) ** 2 * 2 ** 3 +
    $key2 * $key3 + $key2 + $key3 + 3;
    $cleartext=decrypt($ciphertext, $public_key, $secret_key);
    どこを直す必要が
    あるかな︖

    View full-size slide

  16. getPHPerToken.php(decrypt 関数)
    function decrypt(array $ciphertext, int $public_key, int $secret_key) {
    $hex = "";
    foreach($ciphertext as $value) {
    $hex .= dechex($value ** $secret_key % $public_key);
    }
    $cleartext=hex2bin($hex);
    return $cleartext;
    }
    $ciphertext = [3181896, 6283063, 4748177, 3723679, 5707941];
    $public_key = 8555851;
    $secret_key = ($key1 + $key2 * 2 + $key3 * 3) ** 2 * 2 ** 3 +
    $key2 * $key3 + $key2 + $key3 + 3;
    $cleartext=decrypt($ciphertext, $public_key, $secret_key);
    実際の処理を
    イメージしてみよう

    View full-size slide

  17. getPHPerToken.php(decrypt 関数)
    function decrypt(array $ciphertext, int $public_key, int $secret_key) {
    $hex = "";
    foreach($ciphertext as $value) {
    $hex .= dechex($value ** $secret_key % $public_key);
    }
    $cleartext=hex2bin($hex);
    return $cleartext;
    }
    $ciphertext = [3181896, 6283063, 4748177, 3723679, 5707941];
    $public_key = 8555851;
    $secret_key = ($key1 + $key2 * 2 + $key3 * 3) ** 2 * 2 ** 3 +
    $key2 * $key3 + $key2 + $key3 + 3;
    $cleartext=decrypt($ciphertext, $public_key, $secret_key);
    $secret_key は
    なんだかデカい数が
    ⼊りそう

    View full-size slide

  18. getPHPerToken.php(decrypt 関数)
    function decrypt(array $ciphertext, int $public_key, int $secret_key) {
    $hex = "";
    foreach($ciphertext as $value) {
    $hex .= dechex($value ** $secret_key % $public_key);
    }
    $cleartext=hex2bin($hex);
    return $cleartext;
    }
    $ciphertext = [3181896, 6283063, 4748177, 3723679, 5707941];
    $public_key = 8555851;
    $secret_key = ($key1 + $key2 * 2 + $key3 * 3) ** 2 * 2 ** 3 +
    $key2 * $key3 + $key2 + $key3 + 3;
    $cleartext=decrypt($ciphertext, $public_key, $secret_key);
    なんだかデカい数を
    使った累乗計算
    ※7桁の7桁乗

    View full-size slide

  19. getPHPerToken.php(decrypt 関数)
    function decrypt(array $ciphertext, int $public_key, int $secret_key) {
    $hex = "";
    foreach($ciphertext as $value) {
    $hex .= dechex($value ** $secret_key % $public_key);
    }
    $cleartext=hex2bin($hex);
    return $cleartext;
    }
    $ciphertext = [3181896, 6283063, 4748177, 3723679, 5707941];
    $public_key = 8555851;
    $secret_key = ($key1 + $key2 * 2 + $key3 * 3) ** 2 * 2 ** 3 +
    $key2 * $key3 + $key2 + $key3 + 3;
    $cleartext=decrypt($ciphertext, $public_key, $secret_key);
    64bit の範囲では、
    正確な計算が
    無理そう…
    ※7桁の7桁乗

    View full-size slide

  20. decrypt 関数の修正⽅針
    ▌最終的に出したいのは $public_key で割った余りなので、
    合同式の計算の性質が利⽤できる︕
    n 𝑎 ≡ b, c ≡ 𝑑 のとき 𝑎b ≡ 𝑐𝑑
    $value ** $secret_key % $public_key

    View full-size slide

  21. decrypt 関数の修正⽅針
    ▌𝑎! ≡ 𝑥 (mod 𝑛) を次のように計算できる
    n 𝑎" ≡ 𝑚"
    n 𝑎# ≡ 𝑎𝑚"
    ≡ 𝑚#
    n 𝑎$ ≡ 𝑎𝑚#
    ≡ 𝑚$
    n ・・・
    n 𝑎! ≡ 𝑎𝑚!%&
    ≡ 𝑚!
    ≡ 𝑥

    View full-size slide

  22. decrypt 関数の修正⽅針
    ▌𝑎! ≡ 𝑥 (mod 𝑛) を次のように計算できる
    n 𝑎" ≡ 𝑚"
    n 𝑎# ≡ 𝑎𝑚"
    ≡ 𝑚#
    n 𝑎$ ≡ 𝑎𝑚#
    ≡ 𝑚$
    n ・・・
    n 𝑎! ≡ 𝑎𝑚!%&
    ≡ 𝑚!
    ≡ 𝑥
    𝑎を1回かけるごとに逐⼀𝑛で割った余りを求めて
    いけば、⼤きすぎる数(𝑎!)の計算を回避できる︕

    View full-size slide

  23. decrypt 関数の修正⼀案
    foreach($ciphertext as $value) {
    $hex .= dechex($value ** $secret_key % $public_key);
    }
    for($i = 0; $i < count($ciphertext); $i++){
    $value = 1;
    for ($count = 0; $count < $secret_key; $count++){
    $value = $value * $ciphertext[$i] % $public_key;
    }
    $hex .= dechex($value);
    }

    View full-size slide

  24. decrypt 関数の修正⼀案
    foreach($ciphertext as $value) {
    $hex .= dechex($value ** $secret_key % $public_key);
    }
    for($i = 0; $i < count($ciphertext); $i++){
    $value = 1;
    for ($count = 0; $count < $secret_key; $count++){
    $value = $value * $ciphertext[$i] % $public_key;
    }
    $hex .= dechex($value);
    }
    1回かけるたびに余りを
    求める、を繰り返す

    View full-size slide

  25. getPHPerToken.php(さいご)
    if(substr(md5($cleartext),0,30) === "97097d30ceb203d46ab08edf0308ba") {
    echo "PHPerToken is #" . $cleartext;
    } else {
    echo "Failed to get PHPerToken...";
    }
    あとは実⾏するだけなので、
    ぜひ⾃分の⼿で答えを確認してみてください︕

    View full-size slide

  26. getPHPerToken.php の元ネタ
    ▌RSA 暗号です︕
    ▌秘密鍵を計算するための⼿がかりを取得した後、
    それを⽤いて RSA 暗号を解くというストーリーになっていました

    View full-size slide

  27. こたえ✐
    さっさと答え教えて︕という⽅向け

    View full-size slide