Google CTF 2018 write up (JS SAFE 2.0)

Google CTF 2018に参加しました。
相変わらずの難易度だったが、今回はBeginners Questがあったりしてカジュアル勢にも楽しめるコンテストになっていたと思う。
一応１問（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...

```js
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`を詳しくみる。

```js
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)
}
```

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

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

```
х==c('¢×&Ê´cÊ¯¬$¶³´}ÍÈ´T©Ð8Í³Í|Ô÷aÈÐÝ&¨þJ',h(х))//᧢
```

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

```javascript
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;
}
```

関数`c`は`a[i]`と`h(х)[i % 4]`をxorした結果を返すので、これが全て`[0-9a-zA-Z_@!?-]`になるような`h(х)[i]`だけを残す。
これなら`256 * len(a)`の探索で済む。
その結果２通りの`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](https://qiita-image-store.s3.amazonaws.com/0/34727/f3a73085-1ddb-6a6f-cbbd-33b24e767e1d.png)
後でダウンロードしたファイル
![2018-06-29-235504_736x69_scrot.png](https://qiita-image-store.s3.amazonaws.com/0/34727/84445b51-067e-9134-cef7-9f1551d5ae55.png)