LoginSignup
1
0

More than 5 years have passed since last update.

Google CTF 2018 write up (JS SAFE 2.0)

Posted at

Google CTF 2018に参加しました。
相変わらずの難易度だったが、今回はBeginners Questがあったりしてカジュアル勢にも楽しめるコンテストになっていたと思う。
一応1問(JS SAFE 2.0)だけ解けたのでwrite upを書いておく。

問題

You stumbled upon someone's "JS Safe" on the web. It's a simple HTML file that can store secrets in the browser's localStorage. This means that you won't be able to extract any secret from it (the secrets are on the computer of the owner), but it looks like it was hand-crafted to work only with the password of the owner...

function x(х) {
    ord = Function.prototype.call.bind(''.charCodeAt);
    chr = String.fromCharCode;
    str = String;
  console.log(ord('х'), ord('x'));

    function h(s) {
        for (i = 0; i != s.length; i++) {
          a = ((typeof a == 'undefined' ? 1 : a) + ord(str(s[i]))) % 65521;  // 0xfff1
          b = ((typeof b == 'undefined' ? 0 : b) + a) % 65521;
        }
        // bとaの上下8bitずつで文字列生成
        return chr(b >> 8) + chr(b & 0xFF) + chr(a >> 8) + chr(a & 0xFF)
    }

    function c(a, b, c) {
        for (i = 0; i != a.length; i++) c = (c || '') + chr(ord(str(a[i])) ^ ord(str(b[i % b.length])));
        return c
    }
    // for (a = 0; a != 1000; a++) debugger; // 邪魔
    x = h(str(x));                                                                        // 1.
    console.log(x, x.length, '\n');  // 8bit 4文字
    source = /Ӈ#7ùª9¨M¤�À.áÔ¥6¦¨¹.ÿÓÂ.Ö�£JºÓ¹WþÊ�mãÖÚG¤�¢dÈ9&òªћ#³­1᧨/;
    source.toString = function() {
        return c(source, x)
    };
    console.log('source', source.source.length);
    try {
      // console.log('debug', source);
      // with(source)return eval('eval(c(source,x))')
      with(source) {
        var s = c(source,x);                                                               // 2.
        console.log('c', s);
        var s2 = eval(s);                                                                  // 3.
        console.log('eval2', s2);
        return s2;
      }
    } catch (e) {
      console.error(e);
    }
} 

求めるべきフラッグはパスワード。
問題としてはjavascriptのコードを読み解くだけなのだが、色々と引っ掛けがあってだいぶ時間を溶かしてしまった。

解法

関数xがtrueを返すような入力を求めればよい。関数xがやっていることは

  1. xを関数hで加工して32bitに(正確には8bit文字*4)
  2. 上を数字とsource文字列でxorを取って文字列の生成
  3. 生成した文字列をevalしてreturn

この時、注意しないと行けないのは、関数xの引数がх(ギリシャ文字のカイ)であること。
なので、str(x)は関数xを文字列化したものになる。(javascriptは関数を文字列化出来るらしい)
なので2までは入力に関係なく毎回同じ処理になる。

関数hを詳しくみる。

function h(s) {
    for (i = 0; i != s.length; i++) {
      a = ((typeof a == 'undefined' ? 1 : a) + ord(str(s[i]))) % 65521;  // 0xfff1
      b = ((typeof b == 'undefined' ? 0 : b) + a) % 65521;
    }
    // bとaの上下8bitずつで文字列生成
    return chr(b >> 8) + chr(b & 0xFF) + chr(a >> 8) + chr(a & 0xFF)
}

注意しないといけないのは、abがグローバル変数であるということ。なのでaの状態によって結果が変わってしまう。
さらに関数xの途中でfor (a = 0; a != 1000; a++) debugger;が挟まっているので、処理1を行う時はa=1000になっている。

a=1000h(x)を計算して、source文字列を関数cで復元したら、以下の文字列が得られた。

х==c('¢×&Ê´cʯ¬$¶³´}ÍÈ´T—©Ð8ͳÍ|Ԝ÷aÈÐÝ&›¨þJ',h(х))//᧢

ここのхはカイ。なので、上の処理が真になるようなカイ(=パスワード)を求めればよい。
h(х)の結果は32bitなので総当りしてもよいのだが、入力хが許されている文字が、[0-9a-zA-Z_@!?-]しかないのことを利用する。

function c(a, b, c) {
    for (var i = 0; i != a.length; i++) c = (c || '') + chr(ord(str(a[i])) ^ ord(str(b[i % b.length])));
    return c;
}

関数ca[i]h(х)[i % 4]をxorした結果を返すので、これが全て[0-9a-zA-Z_@!?-]になるようなh(х)[i]だけを残す。
これなら256 * len(a)の探索で済む。
その結果2通りのh(х)が見つかって、その片方でフラッグが求まった。

CTF{_N3x7-v3R51ON-h45-AnTI-4NTi-ant1-D3bUg_}

感想

問題としてはそこまで複雑ではない(というか専門的な知識を特に必要としない)
が、хをカイと気づかなかったり、sourceに非unicode文字が混ざっているので下手にコピペを行うとsourceの文字列が変わってしまったりとハマりやすいところが多かったと思う。

(しかし今回はOCR is COOLにフラッグがなかったり、shall we play a gameで1,000,000回勝ったのにフラッグが文字化けしたりとツライ問題が多かった)

なんか最近miscみたいな問題しか解いていないので、暗号とかpwnとか解けるようになりたいね。

最初にダウンロードしたファイルと
2018-06-29-235433_568x63_scrot.png
後でダウンロードしたファイル
2018-06-29-235504_736x69_scrot.png

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0