NetAgent Security Contest 2010

http://www.netagent-blog.jp/archives/51504789.html
参加していました。この手のイベントは初めてでバイナリ解析なぞやったことないという程度の経験なのですが、まあ楽しみつつ学びつつ。
8問あるうちの前半4問が解けました。このあたりだったらまだそこまで事前知識が無くてもその場で調べて何とかなるようなくらいで、初心者にも優しい設計でした。
ただ仕事でWindows開発やってる身としては6問目のようなのはできるようになっておきたい…。解説が来たら復習しよう。
取り組んでいたのは約15時間ですが、それだけの時間のこととは思えないくらいものすごく勉強になりました。これは学習効果高いです。

1. Activation

マシンの日付を変えるだけでOK。
拍子抜けしたけどLevel1だからこんなものか…

2. Hidden file

とりあえずZIPでWeb検索するとWikipediaにフォーマットの概略があるのを発見。
先頭4バイトが固有のシグネチャだそうなのでそれでデータファイル内を検索すると探したいZIPファイルの先頭位置が見つかった。
末尾の位置も、ZIPフォーマットの最後に付くCentralDirectoryのシグネチャで検索して見つかった。あとはそのCentralDirectory中のフィールドを見ていくと長さがわかる。
最初見たときはもっと苦労するかと思っていたのだけど、ZIPのフォーマットが意外と簡潔だったのでそこまで時間かからなかった。

3. Crypto

暗号化で整数でnとeといったらRSAでしょー。
そのままではブロックサイズがわからないので数バイトの小さなファイルで暗号化を試してみると、1バイト→4バイトで暗号化されているようだと把握。
あとは画像に示されているn、eの値から鍵を探索して(値は大きくないので全探索で大丈夫)RSAの手順通りにデコードすると答えが出てきた。


問題文にはexeやdllを解析して…と書いてあるけどそんなのいらなかったですハイ。


コード

#include <fstream>
#include <iostream>
#include <vector>
using namespace std;

int n,e,p,q,d;

int main(){
  cin >> n >> e;
  for(int i = 3; i <= n; i+=2){
    if(n % i == 0) {
      p = i;
      q = n / i;
      break;
    }
  }
  int m = (p-1)*(q-1);
  for(d = 2; d < m; ++d) {
    if(d *e % m == 1) {
      break;
    }
  }
  fstream input;
  input.open("password", ios_base::binary|ios_base::in);
  fstream output;
  output.open("output", ios_base::binary|ios_base::out);
  while(!input.eof()){
    long long v = 0;
    for(int i = 0; i < 4; ++i){
      v += (input.get() << (i*8));
    }
    long long res = 1;
    for(int i = 0; i < d; ++i){
      res *= v;
      res %= n;
    }
    output.put(res);
  }
}

4. Character codes

メールのヘッダに "charset=utf-7" と書いてあるのでまあUTF-7なんでしょう。


デコーダを自分で書くのは面倒すぎる…。検索してみたらJavaのライブラリが出てきたので使わせてもらう。→ jutf7
で、デコードしてみようとしたら失敗してしまう。何故だ。
問題のバイト列をちゃんとUTF-7の仕様に則って検討してみると、+-の対応がおかしいし、8bit目が立っているバイトもたくさんある。
そういえば、UTF-7ではアプリケーションが8bit目を独自の情報を載っけるのに使う場合がある、というのをどこかで聞いたことがあるような…。
そこで8bit目を全部クリアしてデコードしてやるとうまくいった。


しかしまだ化けている。
デコード結果のcharの値を見てみると、Shift_JISのひらがなの領域に相当する値が多いよう。
そこで今度はShift_JISとしてデコードしてやると、見事Javaの文字列になって日本語の文章として表示できました。


コード

import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;

public class Main {

  static String data = "+〓...中略...N-";

  public static void main(String[] args) throws Exception {
    Charset utf7 = Charset.forName("UTF-7");
    ByteBuffer buf = ByteBuffer.allocate(data.length());
    for (int i = 0; i < data.length(); ++i) {
      buf.put(i, (byte) (data.charAt(i) & 127));
    }
    int[] ai = new int[data.length()];
    byte[] bi = buf.array();
    for (int i = 0; i < ai.length; ++i) {
      ai[i] = (bi[i] & 255);
    }
    CharBuffer str = utf7.decode(buf);

    Charset sjis = Charset.forName("Shift_JIS");
    ByteBuffer buf2 = ByteBuffer.allocate(str.length() * 2);
    for (int i = 0; i < str.length(); ++i) {
      char c = str.get(i);
      buf2.put(i * 2, (byte) (c));
      buf2.put(i * 2 + 1, (byte) (c >> 8));
    }
    str = sjis.decode(buf2);
    System.out.println(str);
  }
}

5. Crack ZIP files

問題文にあるヒントで検索すると、Known-Plaintext Attackという脆弱性があるのを知る。
zipファイルを古い形式で暗号化している場合、中に入っているファイルのバイト列が一部わかっている場合はパスワードが破れる、と。なるほど。
http://www.woodmann.com/fravia/mike_zipattacks.htm


しかしxyz.zipの中にあるファイルの情報がわからないのでこれがわかってもどうにも…。
houkoku.zipはzipファイルなのだからシグネチャ部分の先頭4バイトはわかるけどそれだけでは無理だろうし。


一応適当にツール拾ってきてブルートフォースもかけてみたけど、さすがにそんなのでは見つからないですね。

6. UnPack EXE

.kdファイルがアセンブラ風のスクリプトになっていて、exeから読み込まれて実行されているみたい。
その中に「グローバル変数に状態を保存」というところがあるので、その書き込み先アドレスをいじってみて変なことが起きないかやってみたけどどうにもならない。


やっぱりこのレベルになるとちゃんと解析しないといけないんだなあと思いつつも、ツールや手法はよく知らないのでとりあえず手元にあるVC2010でアタッチして逆アセンブル結果を見る。
アセンブリコードからリテラルの10000を検索してみたり、ステップ実行させながら勝利数が入っていそうなメモリを探したりしたけど成果無し。挫折

7. JavaScript

ひとまずセミコロンで改行を入れて地道ーに読んでいく。
しばらく読んで触ってしていると、最初のあたりで変数$をいろいろ弄っているところは、そのあとで$のプロパティを全部出力させてやるとどういう結果になるか簡単にわかることに気づいた。
JavaScriptは素人なのでここまでで1時間ほど…)


その結果を使って、次に何か関数を実行しているところを復号すると、変数 __ にとある文字列が入っていることと、変数 _$ にRegExpを返す関数が入っていることがわかる。
んでその正規表現を使って長ーーーーい文字列を置換して、一番最後の部分はcreateCRMFRequestという関数にその文字列を渡してある。


CRMFというのが何なのかよくわからないのだけど、実はその関数の中身はあまり関係なくて、結局パラメタに渡した長い文字列がevalされているだけみたい?
実際、そのcreateCRMFRequestを呼ばずに、自分で文字列をevalしても同じダイアログが出てくる。
ならばその長い文字列を解析すればゴールか…と思ったが長すぎて(200万文字くらいだったか)手作業ではだいぶ無理がある。
一応後ろに ";alert(Answer);" とかくっつけてevalさせてみるのもやったけどもちろんundefined。どこかの関数内のスコープで定義されてるんでしょうね。


デバッガをうまく使ったら見えるのかなぁ…といったあたりで時間終了になりました。

8. x86_64

手つかず。