過程が大事

学んだことを適当にアウトプットします

picoCTF Writeup (Web問)その7

Java Script Kiddie(400pts)

Description The image link appears broken... https://jupiter.challenges.picoctf.org/problem/58112 or http://jupiter.challenges.picoctf.org:58112

アクセスフォームが一つあり、適当に入力すると見れない画像が表示される
ソースを確認

<html>
    <head>    
       <script src="jquery-3.3.1.min.js"></script>
       <script>
           var bytes = [];
           $.get("bytes", function(resp) {
               bytes = Array.from(resp.split(" "), x => Number(x));
           });

           function assemble_png(u_in){
               var LEN = 16;
               var key = "0000000000000000";
               var shifter;
               if(u_in.length == LEN){
                   key = u_in;
               }
               var result = [];
               for(var i = 0; i < LEN; i++){
                   shifter = key.charCodeAt(i) - 48;
                   for(var j = 0; j < (bytes.length / LEN); j ++){
                       result[(j * LEN) + i] = bytes[(((j + shifter) * LEN) % bytes.length) + i]
                   }
               }
               while(result[result.length-1] == 0){
                   result = result.slice(0,result.length-1);
               }
               document.getElementById("Area").src = "data:image/png;base64," + btoa(String.fromCharCode.apply(null, new Uint8Array(result)));
               return false;
           }
       </script>
   </head>
    <body>

        <center>
            <form action="#" onsubmit="assemble_png(document.getElementById('user_in').value)">
                <input type="text" id="user_in">
                <input type="submit" value="Submit">
            </form>
            <img id="Area" src=""/>
        </center>

    </body>
</html>

assemble_png関数でフォームで入力したkeyが16文字ならそれを用いてbytes配列を操作してPNG画像を構築しているみたい
つまり、これは正しいkeyを入力することができればPNG画像になり、画面に出力されるプログラムになっている
bytes配列はdevtoolsのネットワークタブで確認できるためそれを使って正しいkeyの値を推測する

まず初めにkeyの値の範囲を考える
プログラム中でkeyの各文字は、key.charCodeAt(i) - 48として処理されており、この- 48はASCIIコードで数字'0'から'9'が48から57までの値にマッピングされているため、keyは数値列であることがわかる
pngファイルには、先頭に8バイトのマジックバイト(89 50 4E 47 0D 0A 1A 0A)がある
そのため、これを利用してkeyを推測できる

単純に全探索でkeyを推測しようとすると0~9の16文字で1016になるため動的計画法を用いる
以下のプログラムを実行して得られた値をフォームに入力するとQRコードが出現し、読み込むとflagが得られる

def find_matching_keys(bytes_array, png_magic_bytes):
    // 動的計画法用の配列を初期化
    dp = [[] for _ in range(len(png_magic_bytes) + 1)]
    dp[0].append("")

    // 各PNGマジックバイトについて調査
    for i, magic_byte in enumerate(png_magic_bytes):
        // キーの候補は0から9までの整数
        for key in range(10):

            if magic_byte == bytes_array[key * len(png_magic_bytes) + i]:
                for d in dp[i]:
                    // 新たなkeyの組み合わせを生成
                    dp[i + 1].append(d + str(key))

    return dp

if __name__ == '__main__':
    // devtoolsから得られるbytesを配列に変換
    bytes_array = list(map(int, input().split()))

    // PNGのマジックバイト列
    png_magic_bytes = [
        0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,
        0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52,
    ]

    // key候補を探す
    dp = find_matching_keys(bytes_array, png_magic_bytes)

    // 結果を出力
    for d_list in dp:
      for d in d_list:
        if len(d) == len(png_magic_bytes):
          print(d)

if magic_byte == bytes_array[key * len(png_magic_bytes) + i]: の解説

Javascriptの下記の部分に対応

result[(j * LEN) + i] = bytes[(((j + shifter) * LEN) % bytes.length) + i]

JavaScript側の処理
JavaScriptのプログラムでは、assemble_png関数が、入力されたkeyに基づいてbytesを変換している

LENは配列の長さ(このケースでは16) key.charCodeAt(i) - 48でキーの各文字(数字)をASCIIコードから数値に変換
shifterはこの数値で、これがbytes配列をどれだけシフトするかを決定する
それぞれのkeyの文字(0〜9)に対して、bytes配列がどのように変更されるかを計算して、最終的なresult配列を構築

Python側の処理
Pythonプログラムでは、逆の操作を行う
すなわち、与えられたbytes_arrayからオリジナルのPNGマジックバイトを復元する適切なkeyを見つけようとする

このif magic_byte == bytes_array[key * len(png_magic_bytes) + i]:行は、PNGマジックバイトの各バイトが、特定のkeyでシフトした後のbytes_array内の対応する位置にあるかどうかを確認している

keyは0から9までの値で、これがbytes_arrayの各セグメント(長さ16)をどれだけシフトするかを表す
bytes_array[key * len(png_magic_bytes) + i]:は、keyで指定されたシフト量に基づいて、bytes_array内の特定のバイトを参照する
この確認がTrueであれば、そのkeyiの組み合わせがPNGヘッダを復元する際に有用である可能性が高いと考えられる