Pragyan CTF 2020 Writeup

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

Welcome!!! (miscellaneous 10)

Telegramのbotと会話する。
f:id:satou-y:20200229114417p:plain
/helpを確認すると、/movieなるものがある。
/movieを試すと、以下の一覧が表示された。

Pulp Fiction
Casablanca
Terminator
Final Destination
Batman Begins
Once Upon A Time In Hollywood
Tomb Raider
Se7en
Aquaman
Ratatouille
Ender's Game
Casino Royale
Oblivion
Oceans 11
Little Women
Aladdin
Finding Nemo

各行の先頭の文字をつなげると、フラグになりそう。

PCTFBOTSARECOOLAF
p_ctf{bots_are_cool_af}

Up can be Down (Forensics 100)

$ exiftool mrRobot.jpg 
ExifTool Version Number         : 10.10
File Name                       : mrRobot.jpg
Directory                       : .
File Size                       : 857 kB
File Modification Date/Time     : 2020:02:22 21:59:50+09:00
File Access Date/Time           : 2020:02:22 22:01:10+09:00
File Inode Change Date/Time     : 2020:02:22 21:59:50+09:00
File Permissions                : rwxrwxrwx
File Type                       : JPEG
File Type Extension             : jpg
MIME Type                       : image/jpeg
JFIF Version                    : 1.01
Exif Byte Order                 : Big-endian (Motorola, MM)
X Resolution                    : 28
Y Resolution                    : 28
Resolution Unit                 : cm
Artist                          : 8f068b017cd807fd3b8c684dea2f8156
Y Cb Cr Positioning             : Centered
XMP Toolkit                     : Image::ExifTool 11.70
Format                          : U29tZSBTSEEgbWF5YmUhISEh
Comment                         : c82358dfb202ce9cfddc34e13d403fa3
Image Width                     : 2560
Image Height                    : 1920
Encoding Process                : Baseline DCT, Huffman coding
Bits Per Sample                 : 8
Color Components                : 3
Y Cb Cr Sub Sampling            : YCbCr4:4:4 (1 1)
Image Size                      : 2560x1920
Megapixels                      : 4.9

Format の値をBase64デコードしてみる。

$ echo U29tZSBTSEEgbWF5YmUhISEh | base64 -d
Some SHA maybe!!!!

Comment の値を CrackStation でクラックすると、c82358dfb202ce9cfddc34e13d403fa3 は sha256の部分一致でaviumが見つかった。

$ echo -n avium | sha256sum
c82358dfb202ce9cfddc34e13d403fa38f068b017cd807fd3b8c684dea2f8156  -

aviumのsha256の後半はArtistの値と同じ。どうやらaviumがパスフレーズになりそうだ。
steghideでこれをパスフレーズにして隠蔽ファイルを取り出す。

$ steghide extract -sf mrRobot.jpg 
Enter passphrase: 
wrote extracted data to "flag.txt".
$ cat flag.txt
Congrats! This was way too wasy :P

This is the key:
p_ctf{s0rry_6ut_1_@m_n0t_@_r060t}

ASCII Sentence (Crypto 100)

10進数のASCIIコードが並んでいるとみて、デコードする。

Y3t1cnRfYXNsa29fZV9mb2FfZWVlX25nZHlwdHlfZWh3a3RpX29ifQ==

Base64文字列になるので、さらにデコードする。

c{urt_aslko_e_foa_eee_ngdypty_ehwkti_ob}

転置式暗号の何かの暗号と推測できる。フラグはp_ctfから始まるはずなので、そのインデックスを見てみる。

          111111111122222222223333333333
0123456789012345678901234567890123456789
c{urt_aslko_e_foa_eee_ngdypty_ehwkti_ob}

p    26
_    ?
c    0(40)
t    27
f    14(54)

27飛びで次の文字にすれば復号できそう。つまりスキュタレー暗号になっていると推測できる。

enc = '8951116499911082102898878115975057102908657109985070102908786108885053110907210811910072108102908710451975182112885057105102816161'

code = ''
dec = ''
for i in range(len(enc)):
    code += enc[i]
    if int(code) >= 32 and int(code) < 127:
        dec += chr(int(code))
        code = ''
print dec

dec = dec.decode('base64')
print dec

index = dec.index('p')
flag = ''
while True:
    if len(flag) == len(dec):
        break
    flag += dec[index]
    index += 27
    if index > len(dec) - 1:
        index -= len(dec)

print flag
p_ctf{you_are_the_weakest_link_good_bye}

AskTheOracle (Crypto 150)

$ nc ctf.pragyan.org 8500
Enter in format '<Ciphertext>|<Initialisation Vector>'
aaa|aaa
Cipher not in base64
IV not in base64
VGhpcyBpcyBhbiBJVjQ1Ng==
name 'cipher' is not defined
Padding Error!

$ echo VGhpcyBpcyBhbiBJVjQ1Ng== | base64 -d
This is an IV456

これが暗号化した時のIVと推測できる。
いろいろ試したところ、パディングエラーになるときに以下のメッセージが出ているようだ。

'Not happening padding error!'

これを利用してCBC Padding Oracleで復号する。

import socket
from base64 import b64encode, b64decode

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

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

def unpad(s):
    return s[:-ord(s[-1])]

def is_valid(ct):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect(('ctf.pragyan.org', 8500))
    data = recvuntil(s, '\n').rstrip()
    #print data

    print ct
    s.sendall(ct + '\n')

    data = recvuntil(s, '\n').rstrip()
    #print data
    valid_flag = recvuntil(s, '\n').rstrip()
    #print valid_flag
    if valid_flag == 'Not happening padding error!':
        data = recvuntil(s, '\n').rstrip()
        #print data
        data = recvuntil(s, '\n').rstrip()
        #print data
        return False
    else:
        return True

enc = 'TIe8CkeWpqPFBmFcIqZG0JoGqBIWZ9dHbDqqfdx2hPlqHvwH/+tbAXDSyzyrn1Wf'
enc = b64decode(enc)
enc_blocks = []
enc_blocks.append(b64decode('VGhpcyBpcyBhbiBJVjQ1Ng=='))
for i in range(0, len(enc), 16):
    enc_blocks.append(enc[i:i+16])

xor_blocks = []
for i in range(len(enc_blocks)-1, 0, -1):
    xor_block = ''
    for j in range(16):
        for code in range(256):
            print '%d - %d - %d: %s' % (i, j, code, xor_block.encode('hex'))
            print '****', str_xor(xor_block, enc_blocks[i-1][-j:]), '****'
            try_pre_block = '\x00' * (16 - j - 1) + chr(code) + str_xor(xor_block, chr(j+1)*j)
            try_cipher = b64encode(enc_blocks[i]) + '|' + b64encode(try_pre_block)
            if is_valid(try_cipher):
                xor_code = (j+1) ^ code
                xor_block = chr(xor_code) + xor_block
                break

    xor_blocks.append(xor_block)

xor_blocks = xor_blocks[::-1]

flag = ''
for i in range(len(xor_blocks)):
    flag += str_xor(enc_blocks[i], xor_blocks[i])

flag = unpad(flag)
print flag
pctf{b@d_p@nd@s_@r3_3v3rywh3r3_c@tch}