はまやんはまやんはまやん

hamayanhamayan's blog

Java Script Kiddie [picoCTF 2019 Web 400]

https://ctftime.org/task/9502

前提知識

  • バイナリデータに関する導入知識

解説

ソースをチラッと見ると、与えられたキーを元にバイト列を生成している。
形式はimage/pngになっているので、先頭がPNGのヘッダーとなるように調整すれば良さそう。
バイナリデータの先頭は形式ごとに統一されていて、マジックナンバーが配置してある。

var bytes = []; // ここにエンコードされたデータが入る
$.get("bytes", function(resp) { // jqueryの機能で、bytesファイルを取ってくる
    bytes = Array.from(resp.split(" "), x => Number(x)); // 空白で数が羅列されてるっぽい
});

function assemble_png(u_in){ // u_inに入力データが入る
    var LEN = 16;
    var key = "0000000000000000"; // これがkeyらしい
    var shifter;
    if(u_in.length == LEN){ // 入力データの長さがLENならkeyとして採用しているので、16桁の数値が入力として必要
        key = u_in;
    }
    var result = []; // resultデータ
    for(var i = 0; i < LEN; i++){
        shifter = key.charCodeAt(i) - 48; // 48 -> '0'なので数に変換している
        for(var j = 0; j < (bytes.length / LEN); j ++){
            result[(j * LEN) + i] = bytes[(((j + shifter) * LEN) % bytes.length) + i] // mod i毎に独立でshifter分だけシフトしている
        }
    }
    while(result[result.length-1] == 0){ // 末尾の0を消す処理(必要?)
        result = result.slice(0,result.length-1);
    }
    // base64に変換して入れる
    document.getElementById("Area").src = "data:image/png;base64," + btoa(String.fromCharCode.apply(null, new Uint8Array(result)));
    return false;
}

こんな感じなので、とりあえずkeyが全部0のshiftしないやつを取ってきて、"bytes"の中身を得る。

 0 | cc b6 5b c6 4a 42 3c fb d9 a3 00 00 00 ff b7 5b 
 1 | 52 b5 27 3f ad 0a ff 66 00 fb 00 0d 00 de 83 7f
 2 | f7 9b 7f 2f 4b 00 5b f7 00 b6 9c 00 49 00 40 e3
 3 | 49 e3 4e 75 e5 49 43 76 01 78 e8 ed 00 00 cd 00
 4 | 89 45 4e b9 81 0c 54 82 54 2b b2 a3 9a 48 ff 00
 5 | 00 50 01 b1 ae 01 d9 0a 21 00 2d 90 c8 c0 78 52
 6 | a4 00 00 af 0d 54 ed 72 60 00 37 1e 64 4d 78 6c
 7 | 30 00 85 02 00 9f 60 41 b6 00 80 de 14 37 00 dc
 8 | 33 10 28 44 8f 90 1a 94 e3 78 20 91 78 bb 00 72
 9 | 0c e9 3c 47 8b 99 01 d9 8c 07 8c 68 e4 55 44 99

こう出てくる。

ここを参考にPNGのフォーマットを確認する。
以下は全て16進数で書いてある。

  • 先頭8バイト、ファイルシグネチャ89 50 4E 47 0D 0A 1A 0A
  • 次の4バイト、IHDRチャンクの長さ00 00 00 0d
  • 次の4バイト、IHDRチャンクの種類49 48 44 52
  • 次の4バイト、画像の幅
  • 次の4バイト、画像の高さ
  • 次の1バイト、ビット深度 01 or 02 or 04 or 08 or 10
  • 次の1バイト、カラータイプ 00 or 02 or 03 or 04 or 06
  • 次の1バイト、圧縮手法
  • 次の1バイト、フィルター手法
  • 次の1バイト、インターレース手法

まず先頭16バイトで複数の可能性を考えると、以下のようなスライドでないとダメ。

45396185 15012495
  4      261
          7

さて、画像の幅を考えると、3番目は4だろう。
カラータイプを考えると、10番目で7はありえない。

45496185 15012495
         261

ここまで来れば8通りなので、全探索して答えを探す。
結局でseleniumで自動化してスクリーンショット撮ったから別に絞らなくても良かった。
「4549618526012495」が答え。QRコードが出てくるので、参照するとフラグ。