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であれば、そのkey
とi
の組み合わせがPNGヘッダを復元する際に有用である可能性が高いと考えられる