HITCON CTF 2016 Quals の Writeup

TSGの面々(cookies,dai,hakatashi,moratorium,satos,yamaguchi)で参加してました。(土曜は各々の自宅で、日曜は集まって1徹夜で)
1550点で38位でした。

自分が関わった問題について

Handcrafted pyc

pythonバイトコードが与えられるので、デコンパイルして読め、という問題。
dis というライブラリがあるとのことで、それでデコンパイルする。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import marshal, zlib, base64

bbx = base64.b64decode('eJyNVktv00AQXm/eL0igiaFA01IO4cIVCUGFBBJwqRAckLhEIQmtRfPwI0QIeio/hRO/hJ/CiStH2M/prj07diGRP43Hs9+MZ2fWMxbnP6mux+oK9xVMHPFViLdCTB0xkeKDFEFfTIU4E8KZq8dCvB4UlN3hGEsdddXU9QTLv1eFiGKGM4cKUgsFCNLFH7dFrS9poayFYmIZm1b0gyqxMOwJaU3r6xs9sW1ooakXuRv+un7Q0sIlLVzOCZq/XtsK2oTSYaZlStogXi1HV0iazoN2CV2HZeXqRQ54TlJRb7FUlKyUatISsdzo+P7UU1Gb1POdMruckepGwk9tIXQTftz2yBaT5JQovWvpSa6poJPuqgao+b9l5Aj/R+mLQIP4f6Q8Vb3g/5TB/TJxWGdZr9EQrmn99fwKtTvAZGU7wzS7GNpZpDm2JgCrr8wrmPoo54UqGampFIeS9ojXjc4E2yI06bq/4DRoUAc0nVnng4k6p7Ks0+j/S8z9V+NZ5dhmrJUM/y7JTJeRtnJ2TSYJvsFq3CQt/vnfqmQXt5KlpuRcIvDAmhnn2E0t9BJ3SvB/SfLWhuOWNiNVZ+h28g4wlwUp00w95si43rZ3r6+fUIEdgOZbQAsyFRRvBR6dla8KCzRdslar7WS+a5HFb39peIAmG7uZTHVm17Czxju4m6bayz8e7J40DzqM0jr0bmv9PmPvk6y5z57HU8wdTDHeiUJvBMAM4+0CpoAZ4BPgJeAYEAHmgAUgAHiAj4AVAGORtwd4AVgC3gEmgBBwCPgMWANOAQ8AbwBHgHuAp4D3gLuARwoGmNUizF/j4yDC5BWM1kNvvlxFA8xikRrBxHIUhutFMBlgQoshhPphGAXe/OggKqqb2cibxwuEXjUcQjccxi5eFRL1fDSbKrUhy2CMb2aLyepkegDWsBwPlrVC0/kLHmeCBQ==')
bx = zlib.decompress(bbx)
x = marshal.loads(bx)

print 'valname .. ' ,x.co_varnames
print 'const .. ' ,x.co_consts
print 'const .. ' ,x.co_consts[1].co_varnames
print 'const .. ' ,x.co_consts[1].co_consts

import dis
print dis.disassemble(x)
print dis.dis(x.co_consts[1])

と、中にmainという関数があり、その中で、

  1           0 LOAD_GLOBAL              0 (chr)
              3 LOAD_CONST               1 (108)
              6 CALL_FUNCTION            1
              9 LOAD_GLOBAL              0 (chr)
 
...中略...

            722 LOAD_CONST              18 (33)
            725 CALL_FUNCTION            1
            728 ROT_TWO             
            729 BINARY_ADD          
            730 ROT_TWO             
            731 BINARY_ADD          
            732 BINARY_ADD          
            733 BINARY_ADD          
            734 BINARY_ADD          
            735 BINARY_ADD          
            736 BINARY_ADD          
            737 LOAD_CONST               0 (None)
            740 NOP                 
            741 JUMP_ABSOLUTE          759
        >>  744 LOAD_GLOBAL              1 (raw_input)
            747 JUMP_ABSOLUTE         1480
        >>  750 LOAD_FAST                0 (password)
            753 COMPARE_OP               2 (==)
            756 JUMP_ABSOLUTE          767
        >>  759 ROT_TWO             
            760 STORE_FAST               0 (password)
            763 POP_TOP             
            764 JUMP_ABSOLUTE          744
        >>  767 POP_JUMP_IF_FALSE     1591
            770 LOAD_GLOBAL              0 (chr)
            773 LOAD_CONST              17 (99)
            776 CALL_FUNCTION            1
            779 LOAD_GLOBAL              0 (chr)
            782 LOAD_CONST              10 (116)

みたいになっている。
スタックに物を積んだり連結したりしてるようなので、適当にエミュレータもどきを書いてやる。

with open('da.txt','r') as fp:
	s = fp.readlines()

sta = []

for d in s:
	if 'CONST' in d:
		x = d.split()[3][1:-1]
		if x == 'None':
			break
		sta.append(chr(int(x)))
	elif 'ROT' in d:
		x = sta.pop()
		y = sta.pop()
		sta.append(x)
		sta.append(y)
	elif 'ADD' in d:
		x = sta.pop()
		y = sta.pop()
		sta.append(x+y)

print sta
print sta[0][::-1]

#Call me a Python virtual machine! I can interpret Python bytecodes!!!

"""
password: Call me a Python virtual machine! I can interpret Python bytecodes!!!
hitcon{Now you can compile and run Python bytecode in your brain!}
"""

(どうやら、767のPOP_JUMP_IF_FALSEをnopにして実行するという手もあったらしい)

Hackpad

でかい.pcapファイルが渡される。中を見るとhttp500の中にちょこっとhttp200が紛れている、という感じ。

これ(【CODEGATE2011 - Quals】 Crypto400 - 人素(とーしろー)の物思い)の存在を思い出す。どうやら同じくpadding oracle attackのキャプチャが与えられるので、元の文字列を復号する問題。こないだやったなー、(csaw ctf 2016 quals のwriteup - 忖度のNeo)と思いつつやる。

当初scapyでやろうかとおもっていたのだが、データが膨大で無限に時間がかかっていたので適当に抽出もどきを書く。C++pythonよりずっとはやい。

#include<cstdio>
#include<cstring>

using namespace std;
#define rep(i,n) for(int i=0;i<((int)(n));i++)

FILE* fp;
void uget(char* s){
	int ls = strlen(s);
	int i=0;
	for(;i<ls;){
		char c;
		fread(&c, sizeof(char),1,fp);
		if(s[i]==c)i++;
		else{
			if(c==s[0])i=1;
			else i=0;
		}
	}
}

int main(void){
	fp = fopen("hackpad.pcap","rb");
	rep(i,100000){
		uget("msg");
		char s[100];
		int de = 65;
		if(!fread(s, sizeof(char),de,fp)){
			break;
		}
		s[de]='\0';
		printf("%s\n",s);
	}
	return 0;
}

padding oracle attackなので、

...前略...
=67acd06f7f7b28762310ce1213fccb11997d9369c74c82abba4cc3b1bfc65f02
=000000000000000000000000000000ff6c957ff0feef61b161cfe3373c2d9b90
...後略...

こんな感じで、先頭文字が一気に000000になってるとこを探して、その直前のみを抽出する。

with open('extattack.txt','rb') as fp:
	s = fp.read()

ms = '=ff'
s = s.split('\n')
for d in s:
	d = d[d.find('='):]
	if len(d)<2 or d[1]==' ':
		continue
	if d[1:3]=='00':
		continue
	
	if int(ms[1:3],16) <= int(d[1:3],16):
		print ms
	ms = d

これによって、

=67acd06f7f7b28762310ce1213fccb11997d9369c74c82abba4cc3b1bfc65f02
=a90ca309b638f6d2c43bf3ceddb72c7e6c957ff0feef61b161cfe3373c2d9b90
=19a50e949a9e12ca51b680074d53abe15639aa3688659566d9acc93bb72080f7
=325ddb45f355f21ea0dfb10bce43b097e5ebd643808a0e50e1fc3d16246afcf6
=9089a03ce2f77b24d1995e6f5a1dcc9288dfedf02ad4ae84fd92c5c53bbd98f0
=f0aadd905ba0dafd83e5f5ba4d8de9c08b21d838a3261874c4ee3ce8fbcb9662
=f843b158c7596f16b58e449188fbeb178d5706499dd985ec0c13573eeee03766
=ee34773ee8e79f9475772441908b6f42f7010a867edfed92c33233b17a9730eb
=847a7ff24ead84a2b20247c45bee43924a82a6db51fa6124bfc48ef99d669e21
=6ae7c5eb319f1550c6baf9c9aa45a94c740d12656f597e691bbcbaa67abe1a09
=6e546c551c3b17097fc3cdc40bde6260f02afc37140b167533c7536ab2ecd4ed
=cc1a9a46767267074fb26e16c792a38937572fc9154d23aa7d8c92b84b774702
=4f674fb564345dce08e4f68836022461632ed2737a569e4dfbe01338fcbb2a77
=1259a7104a29e8298e9e23408ddd5f47ddd6990ce169bb4f48e1ca96d30eced2
=b9a9a97e9459db3e3c95bfe2e336bbba3b6fe5b875ca6481056848be0fbc26bc
=5b1e9bcc00be5db1611778cc7a8c55c3bffdfe966da4221103408f459ec1ef12
=c29d8ff214d65e643327f621e6f18b6ac72068bc1b96df045d3fa12cc2a9dcd1
=b2100dc26fe3bd783446df5bf2dabeb862ffdf876b3bc3a3ed2373559bcbe3f4
=029ba0f0094aa3db94504335f9b29e8d70a8c695bf54796bfe471cd34b463e98
=14d1a0e0814e6371e45d06c9515c248276212df912deef882b657954d7dada47

という、正式にdecodeできる(であろう)長さ2ブロック分の文字列が20個見つかる。

あと、ほかにパケットの先頭を漁ると、

3ed2e01c1d1248125c67ac637384a22d997d9369c74c82abba4cc3b1bfc65f026c957ff0feef61b161cfe3373c2d9b905639aa3688659566d9acc93bb72080f7e5ebd643808a0e50e1fc3d16246afcf688dfedf02ad4ae84fd92c5c53bbd98f08b21d838a3261874c4ee3ce8fbcb96628d5706499dd985ec0c13573eeee03766f7010a867edfed92c33233b17a9730eb4a82a6db51fa6124bfc48ef99d669e21740d12656f597e691bbcbaa67abe1a09f02afc37140b167533c7536ab2ecd4ed37572fc9154d23aa7d8c92b84b774702632ed2737a569e4dfbe01338fcbb2a77ddd6990ce169bb4f48e1ca96d30eced23b6fe5b875ca6481056848be0fbc26bcbffdfe966da4221103408f459ec1ef12c72068bc1b96df045d3fa12cc2a9dcd162ffdf876b3bc3a3ed2373559bcbe3f470a8c695bf54796bfe471cd34b463e9876212df912deef882b657954d7dada47

という、21ブロック(336byte)の文字列が見つかる。
これが攻撃対象の文字列と考えられるので、これを復号してやる。
やり方はNEOのときと同様に。

qs = "3ed2e01c1d1248125c67ac637384a22d997d9369c74c82abba4cc3b1bfc65f026c957ff0feef61b161cfe3373c2d9b905639aa3688659566d9acc93bb72080f7e5ebd643808a0e50e1fc3d16246afcf688dfedf02ad4ae84fd92c5c53bbd98f08b21d838a3261874c4ee3ce8fbcb96628d5706499dd985ec0c13573eeee03766f7010a867edfed92c33233b17a9730eb4a82a6db51fa6124bfc48ef99d669e21740d12656f597e691bbcbaa67abe1a09f02afc37140b167533c7536ab2ecd4ed37572fc9154d23aa7d8c92b84b774702632ed2737a569e4dfbe01338fcbb2a77ddd6990ce169bb4f48e1ca96d30eced23b6fe5b875ca6481056848be0fbc26bcbffdfe966da4221103408f459ec1ef12c72068bc1b96df045d3fa12cc2a9dcd162ffdf876b3bc3a3ed2373559bcbe3f470a8c695bf54796bfe471cd34b463e9876212df912deef882b657954d7dada47"

xs = """
=67acd06f7f7b28762310ce1213fccb11997d9369c74c82abba4cc3b1bfc65f02
=a90ca309b638f6d2c43bf3ceddb72c7e6c957ff0feef61b161cfe3373c2d9b90
=19a50e949a9e12ca51b680074d53abe15639aa3688659566d9acc93bb72080f7
=325ddb45f355f21ea0dfb10bce43b097e5ebd643808a0e50e1fc3d16246afcf6
=9089a03ce2f77b24d1995e6f5a1dcc9288dfedf02ad4ae84fd92c5c53bbd98f0
=f0aadd905ba0dafd83e5f5ba4d8de9c08b21d838a3261874c4ee3ce8fbcb9662
=f843b158c7596f16b58e449188fbeb178d5706499dd985ec0c13573eeee03766
=ee34773ee8e79f9475772441908b6f42f7010a867edfed92c33233b17a9730eb
=847a7ff24ead84a2b20247c45bee43924a82a6db51fa6124bfc48ef99d669e21
=6ae7c5eb319f1550c6baf9c9aa45a94c740d12656f597e691bbcbaa67abe1a09
=6e546c551c3b17097fc3cdc40bde6260f02afc37140b167533c7536ab2ecd4ed
=cc1a9a46767267074fb26e16c792a38937572fc9154d23aa7d8c92b84b774702
=4f674fb564345dce08e4f68836022461632ed2737a569e4dfbe01338fcbb2a77
=1259a7104a29e8298e9e23408ddd5f47ddd6990ce169bb4f48e1ca96d30eced2
=b9a9a97e9459db3e3c95bfe2e336bbba3b6fe5b875ca6481056848be0fbc26bc
=5b1e9bcc00be5db1611778cc7a8c55c3bffdfe966da4221103408f459ec1ef12
=c29d8ff214d65e643327f621e6f18b6ac72068bc1b96df045d3fa12cc2a9dcd1
=b2100dc26fe3bd783446df5bf2dabeb862ffdf876b3bc3a3ed2373559bcbe3f4
=029ba0f0094aa3db94504335f9b29e8d70a8c695bf54796bfe471cd34b463e98
=14d1a0e0814e6371e45d06c9515c248276212df912deef882b657954d7dada47
""".split()
xs = map(lambda d: d[1:],xs)

ts = ""

for s in xs:
	s = s[:32]
	ta = qs[:32]
	qs = qs[32:]
	be = ""
	for i in xrange(16):
		a = int(s[i*2:i*2+2],16)
		b = int(ta[i*2:i*2+2],16)
		be += chr(a^b^16)
	
	ts += be

print ts
"""
In cryptography, a padding oracle attack is an attack which is performed using the padding of a cryptographic message.
hitcon{H4cked by a de1ici0us pudding '3'}
In cryptography, variable-length plaintext messages often have to be padded (expanded) to be compatible with the underlying cryptographic primitive.
"""

Let's Decrypt

CBCモード理解してますか、という問題。

こっちから暗号化された値を送ると向こうでAES-CBCで復号してくれるサービスが与えられる。また、文字列 'The quick brown fox jumps over the lazy dog' を暗号化した値も返してくれる。鍵と初期化ベクトルとしてどちらもflagを使われているので、それを当ててください、という問題。

CBCについては、暗号利用モード - Wikipedia が分かりやすい。
CBCは16byteのブロックごとに処理しているので、まず、'The quick brown fox jumps over the lazy dog' にパディングを足して48byteにする。(各ブロックをC[0],C[1],C[2]とする) これを暗号化すると48byteの文字列が返ってくる。(各ブロックをD[0],D[1],D[2]とする)
ここで、初期化ベクトルをIVとすると、各ブロックに対して、

C[0] = dec(D[0]) ^ IV, C[1] = dec(D[1]) ^ D[0], C[2] = dec(D[2]) ^ D[1] 

の関係がある。
さて、たとえば復号文字列として、D[1]+D[0]+D[1]+D[2] を送ってみると、64byteの復号化された文字列が返ってくる。(A[0],A[1],A[2],A[3]とする)
このとき、

A[0] = dec(D[1]) ^ IV, A[1] = dec(D[0]) ^ D[1], A[2] = dec(D[1]) ^ D[0], A[3] = dec(D[2]) ^ D[1] = C[2]

となっている。C[2]は正しくパディングされているのでA[2]も正しくパディングされていて、向こうでdecrypt-errorになることはない。
さて、ここで式をよく見てみると、

IV = A[0] ^ dec(D[1]) = A[0] ^ C[1] ^ D[0] 

となっているが、A[0],C[1],D[1]は既知なのでこれらをXORすればフラグが得られる。

#coding: utf-8

import struct, socket, sys, telnetlib, os,time

	
def hex2s(s):
	ls = len(s)
	res = ""
	for i in xrange(ls/2):
		res += chr(int(s[i*2:i*2+2],16))
	return res


"""
The quick brown The quick brown 
fox jumps over the lazy dog

4a5b8d0034e5469c071b60000ca134d9
e04f07e4dcd6cf096b47ba48b357814e
4a89ef1cfad33e1dd28b892ba7233285

"""

a = '4a5b8d0034e5469c071b60000ca134d9'
b = 'e04f07e4dcd6cf096b47ba48b357814e'
c = '4a89ef1cfad33e1dd28b892ba7233285'


def getunt(c):
	global sock
	res = ""
	while res=='' or res[-len(c):]!=c:
		res += sock.recv(1)
	print res
	return res

def send(s):
	global sock
	sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
	sock.connect(('52.69.125.71', 4443))

	getunt('decrypt\n')
	sock.send('2\n' + s + '\n')
	s = sock.recv(1024)
	s = hex2s(s)
	print s
	return s

ta = hex2s(a)
tb = hex2s(b)
tc = hex2s(c)

tx = send(b + a + b + c)

gx = tx[:16]
ty = 'fox jumps over t'

def zxor(p,q):
	res = ""
	for c,d in zip(p,q):
		res += chr(ord(c)^ord(d))
	return res

s = zxor(zxor(ty,gx),ta)

print s
|	
#hitcon{R4nd0m IV plz XD}

Sharingan

AIが天元に打ったあとマネ碁をしてくるので、相手より石を多くとれ、という問題。
ただし、相手の石が取られそうになると、自分の石が相手の石に置換されてしまう、という仕様。
ヒカルの碁、の3巻あたりにあったマネ碁破りの方法を利用する。

+++++++++
+++○○○○++
++○●●●*○+
++●○●●○++
+●+○○○●++
++●●●●+++
+++++++++

対称に打っていくと、上の盤面のようになる。
ここで白が*に打つと、AIは取られると判断してそれを黒石に置換するが、それでも囲われているので取られる、という仕組み。

Flame

powerpcの64bitバイナリをreversingする問題。

powerpcバイナリを動かせるgdbとかのデバッガがあればあっという間に解決していた筈なのだが、それを入れるのに(僕とかyamaguchi氏とか)めちゃ苦労していて、ずっとほったらかしにされていた。yamacuchi氏からobjdump結果を貰ってそれをもとに解析した。

powerpc語になれていなかったのでわりと勘で補ったところがあるが、主要部分は

srand(7777);
unsigned int data[35] = {どうやら初期化されてるみたい};
for(int i=0;i<35;i++){
  data[i] ^= rand();
}
char s[36];
scanf("%s",s);
if(strlen(s)==35){
  for(int i=0;i<35;i++){
    if(s[i]!=data[i]){
      //たぶんWrongとか表示してexitするやつ
    }
  }
  //たぶんCongratsとかがこのへんで表示されそう
}

だいたいこんなかんじ。デバッガがあれば適当にメモリを覗くだけで済むのだがそうは問屋が卸さない。

まず、"qemu-ppc -d in_asm flame"で実行時のトレース結果が一緒に出るので、それを元にして解こうとしてみる。具体的には、先頭の一致文字数が長いほどトレース結果が増えるはずなので、まず先頭の1文字を全通り試して1文字目を確定させる->2文字目...みたいな感じでやっていこうかと思ったのだが、なぜかqemuの挙動がおかしくてうまいこと動かない(i=1のときにi<35がfalseと判定されているっぽい)。のでこの案は失敗。

さて、readelfの結果を参考にしつつ、ELF中の初期化データが置かれてそうなとこを探ってみる。と、

...前略...
0078970: 7420 796f 7572 2066 6c61 6720 3a29 0000  t your flag :)..
0078980: 596f 7572 2066 6c61 6720 6973 2069 6e63  Your flag is inc
0078990: 6f72 7265 6374 203a 2800 0000 0000 0cfe  orrect :(.......
00789a0: 0000 0859 0000 095d 0000 0871 0000 040d  ...Y...]...q....
00789b0: 0000 0006 0000 0ade 0000 0fa8 0000 0561  ...............a
00789c0: 0000 09da 0000 0878 0000 0682 0000 0fa9  .......x........
00789d0: 0000 0f5f 0000 025e 0000 0db0 0000 0fbf  ..._...^........
00789e0: 0000 0bc6 0000 0d38 0000 095d 0000 0d09  .......8...]....
00789f0: 0000 07ed 0000 0307 0000 01c0 0000 0399  ................
0078a00: 0000 0956 0000 0a45 0000 0292 0000 0c8a  ...V...E........
0078a10: 0000 092f 0000 004a 0000 0964 0000 0194  .../...J...d....
0078a20: 0000 09da 0000 011f 2e2e 2f63 7375 2f6c  ........../csu/l
0078a30: 6962 632d 7374 6172 742e 6300 5f5f 6568  ibc-start.c.__eh
0078a40: 6472 5f73 7461 7274 2e65 5f70 6865 6e74  dr_start.e_phent
0078a50: 7369 7a65 203d 3d20 7369 7a65 6f66 202a  size == sizeof *
0078a60: 474c 2864 6c5f 7068 6472 2900 4641 5441  GL(dl_phdr).FATA
0078a70: 4c3a 206b 6572 6e65 6c20 746f 6f20 6f6c  L: kernel too ol
...後略...

のようなものがある。0x789cから0x78a24に謎データが載っていて、これの長さがint配列で35なので、これで試しにデコードしてみる。と、フラッグが復元された。

x = """
00789a0: 0000 0cfe  orrect :(.......
00789a0: 0000 0859 0000 095d 0000 0871 0000 040d  ...Y...]...q....
00789b0: 0000 0006 0000 0ade 0000 0fa8 0000 0561  ...............a
00789c0: 0000 09da 0000 0878 0000 0682 0000 0fa9  .......x........
00789d0: 0000 0f5f 0000 025e 0000 0db0 0000 0fbf  ..._...^........
00789e0: 0000 0bc6 0000 0d38 0000 095d 0000 0d09  .......8...]....
00789f0: 0000 07ed 0000 0307 0000 01c0 0000 0399  ................
0078a00: 0000 0956 0000 0a45 0000 0292 0000 0c8a  ...V...E........
0078a10: 0000 092f 0000 004a 0000 0964 0000 0194  .../...J...d....
0078a20: 0000 09da 0000 011f
"""

y = """
205757590
998377520
1430092073
2047191058
1959523426
1763442792
1345239717
793358328
658433361
1016199565
1294268491
1322964720
2137247737
1405325116
642114049
1392987601
988381069
740379636
1285459211
1904974096
943865137
605738913
1559909246
926847391
1442292648
425531748
1307965978
1673880289
860617914
1317232
831869049
1066375504
999694753
114477475
966082914
"""

x = x.split('\n')
x = x[1:-1]
tx = []

td = []
for i,d in enumerate(x):
	d = d.split(' ')
	d = d[1:]
	p = 4
	if i==0:
		p = 1
	elif i == 9:
		p = 2
	for j in xrange(0,p):
		td.append(d[j*2+1])

y = y[1:-1]
y = y.split('\n')


s = ""
for p,q in zip(td,y):
	q = hex(int(q))
	p = int(p,16)
	q = int(q[2:],16)
	
	s += chr( (p ^ q) & 0xff)

print s

#hitcon{P0W3rPc_a223M8Ly_12_s0_345y}

I'm here

yamaguchiプロとcookiesプロのペアがやってた。解説は彼らに任せる。
僕も手元でいじったりしていて、pythonでサーバとの通信部を書いてそれを渡したりしていたのだが、自分が出力部を適当に書いていたせいで、なぜかコアダンプが途切れてしまってflagが得られない、みたいなバグを生み出してしまっていた。申し訳ねえ。(自戒の意味をこめてメモしておく)

angry boy

申し訳ねえシリーズ第二弾。自分の書いたスクリプトをhakatashi氏に並列化とかいじってもらったりしていたのだが、書き方がひどかった(自分の書くコードはたいてい目が腐るといわれてしまう)ためにいろいろと面倒くさいことになってしまった模様。

RegExpert

moRE

今回のesolang部門。与えられた要件を満たす正規表現を作って送りつける問題。人々の間でonigurumaとか田中哲スペシャルとかの用語が通じるようになっていたのは面白かった。

「正規表現」に無限のパワーを与える"田中哲スペシャル" - Atzy->getLog() とか、回文や XML にマッチする鬼車の正規表現 - まめめも とかを参考にした。Rubular: a Ruby regular expression editor and tester ここがテストとしてよいそう。

自分の関わった部分についてだけ。

^(?<p>(a\g<p>b|ab))$

a^nb^nにマッチする正規表現。(反復補題とはなんだったのか...)
\gを使えば再帰ができるので実質文脈自由文法と同じ能力になる。
あと、これを12文字以下にする必要があったが、そのへんはmoratorium氏にまかせた。

^(0|[1-9]\d*0000|(4|8|(([1-9]\d*|)([13579][26]|[2468][048])|[1-9]\d*0[48]))(00|))$

うるう年のみにマッチする正規表現。つまり x%4==0 && (x%100!=0 || x%400==0) ですね。
x%4==0 || x%400==0をまとめることができるので縮まる。

^(?!(xx+)\1+$)xx+$

xが素数個ならんだ文字列(xx|xxx|xxxxx|.....) に対応する正規表現
文脈自由でさえないので、どないすればええねんという感じだったが、ぐぐるとこれ (java - How to determine if a number is a prime with regex? - Stack Overflow)
が出てくる。?!内の(xx+)\1+が合成数にマッチするので、これを用いれば動く。

^(?=[01]*,[01]*$)(?=((?<u>(.,.|.\g<u>.)))$)(?<w>(|(?<s>(0|1))\g<w>)),(?<t>(|(?!\k<s+0>).\g<t>))$

001,110とか、010100,101011とか、前半と後半がビット反転の関係にあるもののみにだけマッチするやつ。
本質は

^(?<w>(|(?<s>(0|1))\g<w>)),(?<t>(|(?!\k<s+0>).\g<t>))$

の部分で、左側にある?<s>でキャプチャされた部分と同じ再帰の深さにあるとこで、右で\k<s+0>がキャプチャされる、みたいな?(自分でも原理はよく分かっていなくて、適当に動かしていたらうまく動いた、という感じ)これだけだとコンマの左右での文字列長が違っていてもマッチしてしまっていたので、前半をとりあえず書いてみたが、これでは長いと言われてしまい、あとはmoratorium氏のショートコーディング力におねがいした。

moREは、4問解いて終いかと思ったら、flagがregexp形式で出力されてきて焦る。残り20分強くらあったが似非solverを書ききれなかった...(終了30分後くらいにフラグが得られた)

bef = r"""
^([[dKSbqJWjnuhQ}iyfspkUPY HBFxtRE{GCLOXwoNcv]&&[^dqEpDvkz\-}BPoIrXFW ]&&[^Oo zE}PvdBl\-:DkcUXifjnLMueryg]&&[^EmlpD}TYbiaNonudjHAwzCUcQkxf{vSWFrXgIOBV]&&[f:VLxUeGFYMEoaWyPchBbvA]&&[^XsZjFxQ:wAgqiE{er\-fODTd]&&[^EtZlYUze\-G{JgyVcfknDSXCRo:jx]])([[srkgOMniDpXEujfdVwBYUKJmWAQ{Sqh\-oRa b]&&[IVyWRAJBxiGkDoefLQt]&&[^Y:PsuMK br{mjZkgG]&&[^KVcqnRYdojLQXsrBlzeyDI gbStGZ]&&[ ZEMnjAbkH{iQKmzgDtTsFpxXI:uCvfUVdqSlBwYP]&&[^qsbGCEn:NFDhyU{]&&[^}\-zWEAOTJSMrIaPHChvN{Y]&&[tvYkLJrTiAxeVnHmjQ I{FpRqESyzONlKCWg\-cX]])([[^Ssk}\-mqKUOARXIuZLgDpHeM]&&[OFxcVeqJHbtYvmuza]&&[jKHYwgPpsntbkCdl\-UxBTZFaVGzSOrvem]&&[^gKyqRLDMcA kWmlHEJGPXSdxeZ\-F:sobz}rC]&&[pRwAFqeKc{jnzU ClmNOhiYWPxytVbufX:GTg]&&[^zmUMVNk}SWCiDGLFTP:Xsea\-]&&[\-QVjC:woRJtFAvkIbEKGYi{DLaxf]&&[PVcx wFZNuklmqbzshpedGf}tWSaTXo\-OiL]])([[^xNFB:TzpysbwA ]&&[^wyNXgCKLTMIbAtRU]&&[^eGIdM SroOjLpDVNatWA\-JKqCwU]&&[xUEnXRGLmCHkdTMuBZeSqc}yh:IpwlQA]&&[TC\-pUB{xIlH:sPaKvkoDQeAXiMWnJNGVrLy hFR}cS]&&[BFVabZicAu sEqmLxdTP]])([[td\-{kaP iEFWqCZlOomnSVeG}]&&[mAzYtkBZjEH oWligRncIhNs}]&&[vYeb\-VAg:oQSRwjMkd]&&[IPgS}kr DiazyxudVHneNsEcmoQRBtbMGKhWq]&&[QNkdSTWKF vBoqPXJrItayA]&&[aPL{AQ:RJ\-fFrETKel}unxpYBmVq vwoSOdUIbCjtH]])([[^Xm fQlHLDeGJtwqgCTxAB]&&[^Odhsi{lPbyotmRFkzHcAf XEIuGVKYaeUWJwgjZpM]&&[^FgG}sPyXEdTwWxvcrCUjN:\-M]&&[^abJd\-IFyWeEixGM{V:ATrOqLposcvNU ]&&[ukRGnoW VBK}yq]])([[nzK{aRQwqYIsZvuMxyO:GUFWb\-lrSc]&&[DULOQ{IxBgRAhymMJe]&&[^tuEybwanKZhJOVlUQ\-ioRM XfvqP:NrdcIDzAxeH]])([[PujVDm:fOxEygG{sIbJkRnrC\-}HSaUlXdMBZLYio]&&[^MiWlhQtrfDodTHGS]&&[RzwgjWbda}SDUcXHfBAos{mI\-lQOtYJ]&&[^vZQBsXYo{VxjCpFyD]&&[\-emjNSv:LPkVta}IoTzdYGKCDq{fQnJhRybUZAOF]&&[iHPGNJkaScdY:BtIRVmXOw}M]&&[Z RkohALFygUut}NYdmGQprEeIWwxqs]&&[nDUCkRQLlqTPHstyhxoA\-Guap]])([[^T\-DkmjSx{zGO CLB:FZEiIVKUfqH]&&[JbR qf:ey{jQOrVBM]&&[BHQcfZr:iyovVLPJRptgM}FGX{neSulwT]&&[^Pag}fD:vu LHdRoYshziXTQFmpZCtlwG]&&[vL fI{iYMHbj:NVDck\-ZeRUQ}PqBTpAuhFCGKJsO]&&[^SpOVGYWwKZHsravMnIkDci:uogPjAXxBCUfbqRQEy]&&[^VIEGU:jvyhQw{xsioSgWTdPDcZrJRB\-HLKOA}lmzM]])([[YOepxMTni{sFR UdlkhtJrKo:ZDHXEzASgGVf]&&[zXJlpWqfhswme{vUVd\-ZL}KQoItyYSbaR:OGE]&&[^EpAWqdDONwLTtCkm ZMsy]&&[^kpd\-nASNDaLvWhmMyEeYJoHjGOTziX{IF l]&&[^bse{rtKxY\-kyOd]&&[^QKMdVsRvaq}AyCHneglrXDTcSo{UIbZFNOj]&&[ivfpnNSbIROhCj\-MDuwygazmqxoPL:KGAc]&&[GnCOM}DsfxoBSUzHItXFQiPEpVbdkq:hRcTJLeg]&&[^hDtfnY\-KZgRLSFz]])([[JYOmFnZCMyLoQGNXz}kUgDdW A{wscevhbIRqSE]&&[^aJdQXmlVHroTfw]&&[^vzeKuJiFGST\-cDm jVqCdNy:]&&[^kDLtoJsfixvMXpVE SBRd\-AU:lQuqP}cYn]&&[^jeAMXfFpBlYO dWgShJRqv}TUNCbkzP]&&[qwGYZmefjtJNbL:gyuiKPsMn}BprTDIkRFocvx]&&[^tMI:U}uzkK\-Ogad]])\9([[^tAjm\-WcNe{STqiwlEfPhQ}:nXoCkxFu]&&[gmvMKIUSAFXxaEd{\-N bwo:RGCkeyVQrPOqlcD}u]&&[gJx u:YONUlbM{CKrocsk]&&[gJHCARb\-Z WnfTGzjp:MhYaFseVwSrli]&&[^}GUxR{yKl\-AfMXiFOYNHkw]&&[^xAUOIVozPv\-gQJsjLSe]&&[^U}QipGex{quvHDZ JA]&&[^YRFsayHbzjQ:CL\-tDxlpN]])\5([[S\-kYJaogmdW{cVK}AFCsnf Irqile:X]&&[fBQFUpGkInrVHS{JCujcwM ZiL}\-RWvdy]&&[u\-PnYjdrtB colfQHOIDKSbZUiJ{]&&[^E}uHVzplmiIvaFCZksy:AnWMeo]&&[^gmu}\-eJXlTqIZL]&&[zKVJgdFDEvcLksjYBHmGRWTiPlwMOSXC :bt}Z]&&[URKEWj\-qx{ekAglQ:DozcaMtHsB hPFCYZu]&&[^ARlmN}TuUZ{fFhsX:iJ]&&[^LVdTwsPjaRQGpJYcXeMHuxEBZ{q\-kKlFWmtg]])([[^TeW{vc}rJCqVgyHOPZpwM]&&[^xnevtzrilpJXHISOTdGCumAwqEsU]&&[hTFAEIaNMuyXHe:QiSr\-fLm}vbYUD{RkJBgKq x]&&[XCLNRxtPivVnhBKu\-olwpYsEWzkUMqaeGT{]&&[kCVXKEPwQWDh{qYmBOgZUlIaRHN:jJv\-bTxMncs ]&&[^dQqWLhNBmjCtoOYyGTSMFzVRli}rvHgkA ]&&[^{dUaK}t:gLFlfVEeTvmCNIxqZYijnJokspWcAHbSX]])([[^wEaDvpNlILVQAXGu]&&[^rDnApNtPmeVUuLoJQ}FRBKwqy O]&&[^\-eE:YlaJk}yRoLWugzdVZPT MDvrqHUGn]&&[^n}zejcgb{Gsfk\-J NaiolwEYWDHdLTM:VmyXAvpQq]&&[nckSa}mzWJU:BxPwltY{TVMsvXpoGeiOI]&&[akS\-Iy}mvL{lWU:GNDHQKYATCsErdgpzOM wX]])\3([[^}KWdlCNqGHBjJhsDIMrAEp]&&[^mFnOYG iXrjVdT]&&[^E B\-AzxKQX{SyvJb:gIRqkdUhtNG]&&[u{noSO}Kg\-WDYp:javyV]&&[^eHBZVpkxUFCw:f]&&[ WPRGxOuBimLkYc:wsadqf}lyJzvDUnNAQKbVIj\-e]&&[JNwKD{aUoie qEyWrpPkZBFTh]])\12\3\2\6([[aHcCmUkIfQtVgEGl hxYXFiuKeSLZ]&&[xefFIqAgSaYMuGs]&&[^uGLnsoDclFqhIQbfvOWix{ezHJV aZ:ytUpmYTw]&&[ITtQmBnJSUgNDV}hAWXGMsureERFL]&&[^ldLBEXwC{tP H:zyIKODU]&&[^OEVYTbFq hHed\-KcXw]&&[^SxtJGvPc\-XDFKRsYZanur]])\13([[wLPkKCduRy\-aMOlmbiFEZWXvIGJjoDct]&&[EDrC}I{xBT\-hcabNusAPFneqJOX:ki j]&&[xiFIYQMDLGHlmV{a\-XyZP]&&[^uG s}XCzqnTKBvH]&&[nzLdajUB{CmwySsAROk\-QKFPMxftv}olDi]&&[^DjqEMzyRhuSZJxbWQsLvONUCG Vfk:KmdYaw\-TnitH]&&[Qd{PUywXOpxzurml]])\12\5\17\12\16([[PBr{mqMZFhUazHpOxKSfondtV}TkICRbLGg:yD]&&[joDyrlszYQNUcSfhmVnPuEHb]&&[A:DBcWzNsnmX JCLTaxOVSH}fYZqro]&&[x:nR{DMCNGKlAqSvemziYufHFw dc}ZsLkQbWryh]&&[WrHQ\-EezlgLwNPT}pUCFicMoa:jtDfmXbyRB]&&[rsIwzLy{HVCKtkAndxOlRF TXmNZQafjBUPGJ:pMc}]&&[^CUtQDsIjhNF \-nlpiAVHuBRYEoKXdwS{yLaGf]&&[kYphKuSL}emn{wxOVTrCNJHoIfUavXAD]&&[{oIxVNXZHiM\-AwcjsdCOtmFzB]])\19\2\6\17\13\2\6\13([[LC Awgo:OBe\-hNTtuKaSbqzlcG]&&[^FxTbDHLga}UWjyOtXh\-RqeMzdflPVJnImBYkCiN]&&[mkOlsoBhb:{TiuNPeHqLnY}MgZ\-UfAvQz]&&[^r\-dOsbDVPcyiSwUXRh FoCLWIvQuza]&&[^bVJUOwlNjPdzBCeZa{GkHEgWchr]&&[REg{W oUv:VnmwAXa]&&[^rnqgfEcmetLih}JKUbTZW ON]&&[^NxDRsYGZyjatCoqXPWr]&&[^WkXcKVYSjTf{ydnupPUbwqtMGJhONQRv}DHg:ei L]])\6\5\3\1\9\12\13([[Ay MDBhsCnWTGfrcoqF}N\-ZUQixmRkzbpYud]&&[^GENTDZQe\-PnVxIdJAMBjOcCz:Xp FyHbgRraf]&&[znksUoR:JjEDBpSNChQWZ f}OYiwugXA\-y]&&[^YQZPzcVluyn}qe:SDBbtIUNd iO{r]&&[^UEGPoLdriVzNglYmyObTnv :]&&[^UYdtg{PbaZHOjlFi:rQycEIvVBx\-sADCLS}R]&&[^hON{U\-lbyEJASYLoGD]&&[osuJSiHrN{htdUDGaZWPImM:FBQ bYOA]])\5\12([[^EVFSeGNrbDCaM{KmLh\- YHdopOsInfXW}]&&[^XGSh}Ha:eADCPpcEZrjidzOMBUImwQWon sL]&&[v{dOApXUfG}haeqS\-DJyW:KHNoTzElkgYiImVFBucC]&&[^MmPebn:Q KhsDz]&&[^Kuog {OPQXivtTWzhDC}GSfcM\-k:wLAY]&&[FQiLqbSJwesUhf DuARlZdaIGg]&&[^FOLsfqRUIcCV{jJYmodivN}xAHPpy]])([[^CX:OBWnHboDhzi}F]&&[nrpgbcqI lTCQAowdZfmEGKyij:tPHhDU]&&[^sgLvMFGSR{quCTpBQKcProyA]&&[^mF:ZAoskhfgKwH]&&[MqiJYyR:SWDQpd v}rKwsmEcXHTzZtgakf{Gju]&&[^Ko\-psGakrVg:nYAiuFSxEXwbmehOM qNHURT]&&[cRydhm{iQDKaTvFeIAnBqWlwLt:GOs]&&[^lpgFNLIDZJKuzXT:Pf]&&[TzUePJX{\-ausnEicyWwdA}rIVRgMHNLQ:kKbDxSh]])\14([[^eSwrLVByhniCQvDsAcIlxfmNUWuMzToHKpGd]&&[}vNOcwzmipquLHtJah:rTfyZXl KBGCMSbUYe]&&[^kWsTvGqrV\-mfMac]&&[^ZLmIfqdnjCzRXrQYsbBWv\-UJ:plu]&&[jOV}CaWBkMKuFvcESLhsilz{YNydmfQRUXDxJ:ZtP]&&[jLzqHRo{:kabdrK}ZUsQCGiuA\-mecExvgShwMFJOyt]&&[EtRYlumkyHDUTz:Ve}i\-nBJdcOqKZpWXNFbxShfs]&&[^IxRqKplz:fYoerTn uCMDiJVyNUAQE]&&[^z{AqXkswgMfaJdOIN:PjytRebU]])$
"""

inde = 0

def ins(s):
	global inde
	return inde * ' ' + s 

def spka(s):
	s = s[1:]
	d = 1
	r = ""
	res = []
	for c in s:
		r += c
		if c=='[':
			d += 1
		elif c==']':
			d -= 1
			if d==0:
				res.append(r)
				r = 0
	return res


def toa(ss):
	res = ""
	for s in ss:
		s = s[:-1]
		a = [1] * 256
		p = s.split('&&')
		#print p
		for d in p:
			d = d[1:-1]
			#print d
			if d[0]=='^':
				d = d[1:]
				for c in d:
					q = ord(c)
					a[q] = 0
			else:
				ta = [0] * 256
				for c in d:
					q = ord(c)
					ta[q] = 1
				for c in xrange(256):
					a[c] *= ta[c]
		
		for d in xrange(256):
			if a[d]==1:
				res += chr(d)
				break
	#print res
	return res

rs = [""]
i = 0
ps = []

ans = []

for c in bef:
	if c=='(':
		print ins('(')
		inde += 3
		if len(rs)>0:
			po = rs.pop()
			print 'naka ..',po
			ans.append(po)

		rs.append("")
	elif c==')':
		g = rs.pop()
		#print 'g .. ',g
		#print ins(g)
		g = spka(g)
		g = toa(g)
		ps.append(g)
		ans.append(g)
		print ins(g)
		print g
		inde -= 3
		print ins(')')
	else:
		if len(rs)>0:
			rs = rs[:-1] + [rs[-1] + c]
		else:
			rs += [c]
	i += 1
print ps
ans = ans[1:]
print ans

ta = ""
for d in ans:
	if d[0]=='\\':
		d = d.split('\\')
		d = d[1:]
		for x in d:
			p = int(x)
			ta += ps[p-1]
	else:
		ta += d
print ta

#hitcon{Re:Zero -Starting Programming in Another World-}

時間内に通したい人生だった。

感想

集まってやると、知見がその場で共有できるし問題を解くやる気が大いに向上する(個人比)のでとてもよかった(ただし徹夜は判断能力を奪う)。
終わってみるとチームでいろいろ解いたように思えるが、実際は日曜の晩とか、6時間ほどだれも得点しない、みたいな状況が生えていたりしたのでもうちょっとすぱすぱ進められるようになりたい。あと、結局最後、angry boy も moREも1時間ほど間に合わなくて得点できていない(650点が得られなかった)みたいな感じだったのでずいぶん心残りである。
自分はpwn力をわりとつけたと思っていたのに全く解けなかったのでなんだかなあ、と。64bitELFを読む根性をつけるか、読みやすくするツールを導入するべき。あとヒープがてんでだめなのでどうにかしたい。
SECCONに向けてもっと力をつける必要を感じた。