Attack All Around

今やCTFと競プロばかりだが、トビタテ生のアメリカ留学やTOEFL奮闘記

COMPFEST13 CTF crypto writeup

書くのが遅れ、Resultなどは分からなくなりました…


Writeup



Snab? Yes, Snab


Snab? Yes, Snab
Snab likes to give you a challenge. It is a simple challenge of RSA encrypted messages, and you only have to find out what those messages really are! Be careful though, Snab has some trick up his sleeve.
ファイル:Snab.py, output.txt

# Snab.py

from Cryptodome.Util.number import*

e = 0x10001
s = pow(p + q, 2)
n = p*q
a = pow(s, 3, r)
b = (s - q*(2*p + q))*r

m_list = [findme]

c_list = []
for i in range(len(m_list)):
    m = bytes_to_long(m_list[i])
    c = pow(m*r, e, n)

    c_list.append(c)

output = open("output.txt", "w")
output.writelines([str(i) + "\n" for i in [e, s, n, a, b, c_list]])
output.close()

""" output.txt
65537
518521484172073259043145502034694599512935443283076107003494840643504150663248402410462928864122818999731280619095755616648044687630285476363367234348295346345048643618601901838740100
121789376487960809489253386587170686658768726657045553214623415992384832614485249137256874454267032401365173859563210814953487893574413409932117585950570225259024509903129746392143101
14910
1443061772954701335732136128869248910288995712185482317126411260349775148932784597588115548780067761993841192961205698418501468762734686695975550561598140253475710348439640002745286347562
[95844532553991737600355244654272099305361975575150371319709729091243030203575898742071987199800250922501746626433985253038713853151746857514762678605619742310839669559545627531098676, 42098262117872607180245376226279234844537189667792611290978137770131205295202393318329675438677406769928295941768074280915365884838027414974072838410934952571392616562898636004189303, 8604504123043858588289398284978073629384165878986588408956445422750740896636700840713408309772547146776823067482307495576552057400894861616123713400577813256614795674220942022738198, 66896916235028791010554130879834163456721897024453929564151545727202320039792487273512943832159287883050106923587075192390665897004465138382234040927275478139131450371794658563343368, 88176130128782413821390318550151008388570132120182664342566671328546119423517817326934034720909238554168653863093116429325532932401977519369212892117707167802400008407395125896733332, 42250039274640778630603717605163827961176577828564055370588929192401015587247485151024369147022833032549004175634147831360114651662490704138925606397505368573040950634048151235675964, 106267843822546752528780879737401351948170741446817769684516569656816005147897267321452764634553751488085440938706773625287154372645991244141121226180609731226228509942129690482744498, 7344462713592491879813960159075800353984094813742489003735150623847056840460595091048879286634691169764793649426176975158414555454778075430233699780146900520609629142406422725693811, 68155732896092345896827379516624133280166986984023541993085330906321960888421556683672078055376548346464764100036149614632795220030187229733989823788323988946361921828069707823065198, 2456638129741631242062051214133833843357605035108383884677777076160879939756985403557604264648903511528401478876871578775440101482814072714355366084122429853207060638683606389504551, 99671982271645788903414016384550975165361965345980177928115018027271173062935625698434769263846972984813377601618481025600240081090732166957299336765744471217496851539810214590361856]

"""

未知数はfindmeと書いてあるようです。



1) p, q を求める


output.txtからsを持ってきます。

s = pow(p + q, 2)

とあるので、gmpyなどで2乗根を求めます。p+qとpq (=n) が分かっているので解と係数の関係でp, qを求めます。



2) r を求める。

b = (s - q*(2*p + q))*r

とありここで未知数はrだけなので、output.txtからb,sを持ってきて 1)の結果使えばrは求まります。うまくいかない場合は p, q を入れ替えましょう。



3) m_list を求める。

1) で求めたp, qから秘密鍵dを導出し、c_listの各値を復号すると、m×rとなるので、inverse(r,n)をかけてmを求めます。

b"#Snab says good job! But you're not done yet\n"
b'flag = findme\n'
b"halfa = ''.join([flag[i] for i in range (0, len(flag), 2)])\n"
b"halfb = ''.join([flag[i] for i in range (1, len(flag), 2)]\n"
b"p = bytes_to_long(bytes(halfa, encoding = 'utf-8'))\n"
b"q = bytes_to_long(bytes(halfb, encoding = 'utf-8'))\n"
b'r = 0\n'
b'while (not(isPrime(p) and isPrime(q))):\n'
b'    p += 1\n'
b'    q += 1\n'
b'    r += 1\n'

復号するとこのようになりました。



4) flag を求める。

p, qを1ずつ足していって共に素数になったら終わるプログラムのようです。元のp, qはflagの偶数、奇数文字目をbytes_to_longしたものみたいです。

p-r, q-rをlong_to_bytesして、1文字毎組み合わせればOKです。

import gmpy2
from Crypto.Util.number import *
import math
e = 65537
s = 
n = 
a = 14910
B = 
c_list = 
b ,ch =gmpy2.iroot(s,2)
b = int(b)
c = n
rt = b*b-4*c
rt2, ch = gmpy2.iroot(rt,2)
p = (int(rt2)+b)//2
q = n//p
r = math.gcd(s**3-a,B)
phi = (p-1)*(q-1)
d = inverse(e,phi)
assert math.gcd(e,phi) == 1

for c in c_list:
    m = pow(c,d,n)
    print(long_to_bytes((m*inverse(r,n))%n))

m2 = long_to_bytes(p-r)
m1 = long_to_bytes(q-r)
for i in range(len(m1)):
    x1 = m1[i]
    x2 = m2[i]
    print(chr(x1),end="")
    print(chr(x2),end="")
print()



COMPFEST13{y0U_d1DnT_3xpEcT_t0_FinD_pQ_4s_a_fl4g_DiD_y0u_7e1877a801}



You AES Me Up


So I can stand on scoreboard~
nc 103.152.242.242 10016
ファイル:chall.py

#chall.py

#!/usr/bin/env python3
import sys
import os
import random
import binascii
from Crypto.Cipher import AES
from Crypto.Util.number import long_to_bytes, bytes_to_long
from secret import FLAG

IV = os.urandom(AES.block_size)
KEY = os.urandom(AES.block_size)

class Unbuffered(object):
    def __init__(self, stream):
        self.stream = stream
    def write(self, data):
        self.stream.write(data)
        self.stream.flush()
    def writelines(self, datas):
        self.stream.writelines(datas)
        self.stream.flush()
    def __getattr__(self, attr):
        return getattr(self.stream, attr)

sys.stdout = Unbuffered(sys.stdout)

def pad(msg):
    return msg + (chr(16 - len(msg) % 16) * (16 - len(msg) % 16)).encode()

def get_flag():
    flag = pad(FLAG)
    cipher = AES.new(IV, AES.MODE_ECB)
    flag = cipher.encrypt(flag)

    enc = b''
    flag = pad(flag)
    iv = IV
    for i in range(0, len(flag), 16):
        cipher = AES.new(KEY, AES.MODE_CBC, iv)
        enc += cipher.encrypt(flag[i:i+16])
        iv = long_to_bytes(bytes_to_long(enc[i:i+16]) ^ bytes_to_long(flag[i:i+16]))
    print('flag (in hex) =', binascii.hexlify(enc).decode())

def encrypt():
    msg = input('msg (in hex) = ')
    if (len(msg) % 2 != 0):
        print('Invalid input!')
        return
    msg = binascii.unhexlify(msg.encode())
    cipher = AES.new(KEY, AES.MODE_CBC, IV)
    enc = cipher.encrypt(pad(msg))
    print('enc (in hex) =', binascii.hexlify(enc).decode())

def decrypt():
    enc = input('enc (in hex) = ')
    if (len(enc) % 32 != 0):
        print('Invalid input!')
        return
    enc = binascii.unhexlify(enc.encode())
    cipher = AES.new(KEY, AES.MODE_CBC, IV)
    msg = cipher.decrypt(enc)
    print('msg (in hex) =', binascii.hexlify(msg).decode())

def menu():
    print('1. Get encrypted flag')
    print('2. Encrypt a message')
    print('3. Decrypt a message')
    print('4. Exit')

if __name__ == '__main__':
    while True:
        try:
            menu()
            choice = input('> ')
            if choice == '1':
                get_flag()
            elif (choice == '2'):
                encrypt()
            elif (choice == '3'):
                decrypt()
            elif (choice == '4'):
                print('Bye.')
                break
            else:
                print('Invalid input!')
        except:
            print('Something went wrong.')
            break

encrypt, decryptは所定のKEY, IVで行うのに対し、flagの暗号化だけはIVを変えながら、そしてflagを16文字毎暗号化して、暗号文の後ろに繋げています。



1) IV を求める
求められるのか、求められない前提で解くのか予想つけられない中、ググったらドンピシャのサイト発見。

Finding the IV of AES CBC (CTF) - Cryptography Stack Exchange

言われてみたら、そりゃそうだって感じなんですけどね。



2) ECBモードで復号後のflag を求める

KEYは不明なので、サーバ上で復号させます。flag[0:16]はenc[0:16]を投げればOKです。

以降のflagは少し特殊です。暗号化時にはiv (=long_to_bytes(bytes_to_long(enc[i-16:i]) xor bytes_to_long(flag[i-16:i]) ) )を使っていますが、サーバ上ではIVでしか復号できません。ivは求められるので、enc[i:i+16]を復号させてflag[i:i+16] xor IV を得ます。IVとivをxorさせればflag[i:i+16]が分かります。



3) flag を求める

IVをkeyとしたECBモードで暗号化したのが2)で求めたflagです。あとは今のように復号するだけです。

from Crypto.Util.number import *
from functools import reduce
from operator import mul
from itertools import combinations
import sys
import socket, struct, telnetlib
from tqdm import tqdm
import base64
from Crypto.Util.Padding import pad,unpad
from Crypto.Cipher import AES
import binascii

# --- common funcs ---
def sock(remoteip, remoteport):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((remoteip, remoteport))
    return s, s.makefile('rw')

def read_until(f, delim='\n'):
    data = ''
    while not data.endswith(delim):
        data += f.read(1)
    return data

def reading(f):
    for _ in range(4): read_until(f)
    read_until(f,"> ")
    return 
    
#HOSTはIPアドレスでも可
HOST, PORT = "103.152.242.242", 10016
s, f = sock(HOST, PORT)
reading(f)
# 暗号文取得
s.send(b"1\n")
enc_flag = read_until(f).split()[-1]
enc = []
for i in range(0,len(enc_flag),32):
    enc.append(enc_flag[i:i+32])
print(enc)

#IVを求める
c0 = "00"*16
c1 = "00"*16
reading(f)
s.send(b"3\n")
read_until(f,"= ")
s.send(c1.encode()+b"\n")
p1 = read_until(f).split()[-1]

reading(f)
s.send(b"3\n")
read_until(f,"= ")
s.send((c0+c1).encode()+b"\n")
p2 = read_until(f).split()[-1]
IV = int(p1,16)^int(p2[32:],16)
print("IV =",long_to_bytes(IV))
print("len IV =",len(long_to_bytes(IV)))

#flagを求める
flag = []
reading(f)
s.send(b"3\n")
read_until(f,"= ")
s.send(enc[0].encode()+b"\n")
recv_m = read_until(f).split()
flag0 = recv_m[-1] #flag0 = flag[0:16]
print("flag0 =",flag0)
flag.append(flag0)
ft = flag0
for i in range(1,len(enc)):
    iv = int(enc[i-1],16)^int(flag[-1],16)
    reading(f)
    s.send(b"3\n")
    read_until(f,"= ")
    s.send(enc[i].encode()+b"\n")
    recv_m = read_until(f).split()
    ret = recv_m[-1]
    ft = int(ret,16)^iv^IV
    flag.append(hex(ft)[2:])
    print(flag)
enc = ""
for cf in flag:
    t = "0"*(32-len(cf))+cf
    enc += t
print("enc =",enc)
enc = binascii.unhexlify(enc.encode())
enc = unpad(enc,16)
print("enc =",enc)
print("len IV =",len(long_to_bytes(IV)))
cipher = AES.new(long_to_bytes(IV), AES.MODE_ECB)
flag = cipher.decrypt(enc)
print(flag)


COMPFEST13{y0u_aes_me_UpPpppPp_____t0_c0d3_on_st0rmy_Sea4aA5____e0212d1a34}



Secure Channel


You are able to watch the encrypted chat of Alice and Bob. They are talking about the flag right now. Can you get the flag from them?
P.S.: Alice and Bob once have said that they will communicate with some trivial encoding, so people will not notice their message at first glance. Although, people will know it if they search it on the internet! Sure, all of it is printable characters.
Watch the conversation: nc 103.152.242.56 8231
Talk with Alice: nc 103.152.242.56 8230
Talk with Bob: nc 103.152.242.56 8232
ファイル:alice-bob.py, secrets.py, talk-with-alice.py, talk-with-bob.py

# alice-bob.py

#!/usr/bin/env python3
import sys
from base64 import b64encode, b64decode
from Crypto.Util.number import getPrime, bytes_to_long  as bl, long_to_bytes as lb
from secrets import Alice, Bob
from chats import alice_dialogue, bob_dialogue
import time

class Unbuffered(object):
    def __init__(self, stream):
        self.stream = stream
    def write(self, data):
        self.stream.write(data)
        self.stream.flush()
    def writelines(self, datas):
        self.stream.writelines(datas)
        self.stream.flush()
    def __getattr__(self, attr):
        return getattr(self.stream, attr)

sys.stdout = Unbuffered(sys.stdout)

try:
    g = bl(b64decode(input('g: ')))
    assert g > 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
    p = getPrime(512)

    alice = Alice()
    bob = Bob()

    alice_public_part = alice.make_public_part(g, p)
    bob_public_part = bob.make_public_part(g, p)

    alice.make_private_part(bob_public_part, p)
    bob.make_private_part(alice_public_part, p)

    print('p:', p)
    print('Alice\'s public part:', b64encode(lb(alice_public_part)).decode())
    #print('Bob\'s public part:', b64encode(lb(bob_public_part)).decode()) # Bob doesn't want to share it to you :(
    print()

    assert len(alice_dialogue) == len(bob_dialogue)
    while True:        
        for i in range(len(alice_dialogue)):
            print('Messages from Alice:')
            msg = alice.send_message(alice_dialogue[i])
            print(b64encode(msg).decode())
            print(bob.receive_message(msg))
            print()
            time.sleep(0.5)

            print('Messages from Bob:')    
            msg = bob.send_message(bob_dialogue[i])
            print(b64encode(msg).decode())
            print(alice.receive_message(msg))
            print()
            time.sleep(0.5)
except:
    print('Something is wrong.')
    exit(0)
# secrets.py

#!/usr/bin/env python3
from Crypto.Util.number import bytes_to_long as bl, long_to_bytes as lb
from Crypto.Cipher import AES
import os
import random
import string

sp = list(map(ord, list(string.printable)))

def pad(msg):
    pad_length = random.randint(20, 100)
    for i in range(pad_length):
        c = random.randint(0, 255)
        while c in sp:
            c = random.randint(0, 255)
        pos = random.randint(0, len(msg) - 1)
        msg = msg[:pos] + chr(c).encode() + msg[pos:]
    while True:
        c = random.randint(0, 255)
        while c in sp:
            c = random.randint(0, 255)
        pos = random.randint(0, len(msg) - 1)
        msg = msg[:pos] + chr(c).encode() + msg[pos:]
        if (len(msg) % 16 == 0):
            break
    return msg

class Person:
    def make_public_part(self, g, p):
        return pow(g, self.secret, p)

    def make_private_part(self, gx, p):
        self.key = pow(gx, self.secret, p) % 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
        self.key = lb(self.key)
        while (len(self.key) != 16):
            self.key += b'\x01'
        return self.key

    def send_message(self, msg):
        iv = os.urandom(16)
        cipher = AES.new(self.key, AES.MODE_CBC, iv)
        enc = iv + cipher.encrypt(pad(msg))
        return enc
    
    def receive_message(self, enc_message):
        try:
            iv = enc_message[:16]
            enc = enc_message[16:]
            cipher = AES.new(self.key, AES.MODE_CBC, iv)
            msg = cipher.decrypt(enc)
            return 'Message received!'
        except:
            return 'Message not received!'

class Alice(Person):
    def __init__(self):
        self.secret = 0 # REDACTED

class Bob(Person):
    def __init__(self):
        self.secret = 0 # REDACTED
        assert 2 < self.secret < 100

class You(Person):
    def __init__(self, secret):
        self.secret = secret
# talk-with-alice.py

#!/usr/bin/env python3
import sys
from base64 import b64encode, b64decode
from Crypto.Util.number import getPrime, bytes_to_long  as bl, long_to_bytes as lb
from secrets import Alice, You

class Unbuffered(object):
    def __init__(self, stream):
        self.stream = stream
    def write(self, data):
        self.stream.write(data)
        self.stream.flush()
    def writelines(self, datas):
        self.stream.writelines(datas)
        self.stream.flush()
    def __getattr__(self, attr):
        return getattr(self.stream, attr)

sys.stdout = Unbuffered(sys.stdout)

try:
    your_secret = bl(b64decode(input('Your secret: ')))
    g = bl(b64decode(input('g: ')))
    assert g > 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
    p = getPrime(512)

    alice = Alice()
    you = You(your_secret)

    alice_public_part = alice.make_public_part(g, p)
    your_public_part = you.make_public_part(g, p)

    alice.make_private_part(your_public_part, p)
    your_private_part = you.make_private_part(alice_public_part, p)

    print('p:', p)
    print('Alice\'s public part:', b64encode(lb(alice_public_part)).decode())
    print('Your public part:', b64encode(lb(your_public_part)).decode())
    print('Your private part:', b64encode(your_private_part).decode())
    print()

    while True:
        msg = b64decode(input('Message to Alice: '))
        print(alice.receive_message(msg))
        print()

except:
    print('Something is wrong.')
    exit(0)
# talk-with-bob.py

#!/usr/bin/env python3
import sys
from base64 import b64encode, b64decode
from Crypto.Util.number import getPrime, bytes_to_long  as bl, long_to_bytes as lb
from secrets import Bob, You

class Unbuffered(object):
    def __init__(self, stream):
        self.stream = stream
    def write(self, data):
        self.stream.write(data)
        self.stream.flush()
    def writelines(self, datas):
        self.stream.writelines(datas)
        self.stream.flush()
    def __getattr__(self, attr):
        return getattr(self.stream, attr)

sys.stdout = Unbuffered(sys.stdout)

try:
    your_secret = bl(b64decode(input('Your secret: ')))
    g = bl(b64decode(input('g: ')))
    assert g > 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
    p = getPrime(512)

    bob = Bob()
    you = You(your_secret)

    bob_public_part = bob.make_public_part(g, p)
    your_public_part = you.make_public_part(g, p)

    bob.make_private_part(your_public_part, p)
    your_private_part = you.make_private_part(bob_public_part, p)

    print('p:', p)
    print('Bob\'s public part:', b64encode(lb(bob_public_part)).decode())
    print('Your public part:', b64encode(lb(your_public_part)).decode())
    print('Your private part:', b64encode(your_private_part).decode())
    print()

    while True:
        msg = b64decode(input('Message to Bob: '))
        print(bob.receive_message(msg))
        print()

except:
    print('Something is wrong.')
    exit(0)

色んなpythonプログラムがありますが、要約するとAlice, Bobはそれぞれ秘密鍵を持っていてDiffie-Hellman鍵共有で鍵を生成し、その鍵をAESのCBCモードで暗号化する際に使って会話しているという感じです。



1) Bobの秘密鍵を求める

Bobの秘密鍵はsecrets.pyにあるように2から100までの間と簡単にbrute forceできる値となっています。gはassert文に引っかからないようにしてtalk-with-bob.pyと通信すると、 "Bob's public part:" から計算して、Bobの秘密鍵が73ということが分かります。



2) 二人の共有している鍵を求める

alice-bob.pyと通信すると、"Alice's public part:"を教えてくれます。二人の秘密鍵をa,bとすると、これはga mod pです。実際に暗号化に使う鍵はgab mod pでありbは1)で73と求めたので、("Alice's public part:")b mod pで鍵は求まりました。



3) 二人の会話の内容を求める

二人の暗号化された会話文を復号してみると、こんな感じです。

b'\x1c\xc2\x05\xc3\x04\x1f\xc3\xc2\xbe\x13\x1d\xc3\x18\x84\xc3\xbb\xc3\xc3\x9a\xc3\xb7\xc3\x83\x87\xc2\x9c\xc2\x98\xc3\x85\xb4\x99\xb4\xc2\x9c\xc2\x99\xc3\xc3\xc2\x88\xa0\x15\xc3\xac\x0f\xc2\x96\x16\x1d\x84\x15\xc2\x9a\xc3\xbf\xc2\xc2\x8c\x94\xc2\xc2\xc2\x97\xc2\xa0\x9f\xc2\x89\xbb\x10\xc3\xa1\xc2\xbf\xc2\xc3\x91\xad\x05\xc2\x998\xc3\x817\xc3\x94d\xc3\xc3\x8f\xc2\xc2\x9e\x95\x9f\xc2\xc3\x93\xc3\x83\xb5\xc2\xc3\x9c\xb9\xc2\xc3\x00\xc3\xbb\xa2\xc2\x9d\xc3\x9f\xc3\xa4\xc3\x91\x05\x08\xc2\xc3\x1e\x82\xc2\xb5\xa7\xb4\xc2\x95\xc2\xa0\xc3\xa6\x0f\xc2\x12\xb1\xc2\xb5&'
b"8\xc3\x88\xc3\xbc\xc3\xbc\x1c\x04\xc2\x02\xb6\xc2\x8b\x07\xc3\xc2\xb1\x9d\xc3\xc3\xc2\x83\x92\xc3\xbf\xb87\xc2\x8d\x1b\xc3\x90\xc3\xc2\xbd\xb9\x00\xc2\xc2\x83\x84\x7f\x12d\x15'2"
b'\xc2\x90\x0f\xc3\xa1\xc3\xc2\x90\x17\xc3\xaf\x07\xc3\xab\x88\x19\xc3\xc3\x0f\xbf\xad\xc2\xc2\x12\x88\xc2\x9a\x8c\xc3\xa5\xc3\x80\xc3\x9d\xc2\xc3\xb7\x1a\xbd6\xc3\xc3\xac\x87\xc3\xbc\xc2\x8d\xc3\x80\xc3\x96\xc2\xc3\xab\x9a\xc2\x8e\xc2\xbc>\xc2\xc3\x8d\x9a\xc2\xc2\xad\xab\xc3\xc3\x9a\x1b\xc2\x11\xc2\x85\x15\x0f\xc3\x95\xa8\x80\xc3\xc2\x86\x82\xc2\x11\xb8\x02\xc2\x98\xc2\xbd\xc2\xc2\x8a\x84\xc3\xb1\xc2\x86\xc3\xb0\xc3\x99\xc2\xc3\xc3\xc2\x9b\x02\x17\x9f\xc2\xc3\x87\xc3\x87\xc3\x91\xc2\xb9\x80\x7f\x9e\xc2\xaa\xc3\xc2\xc2\x9f\xba\xc3\xc2\x92\xc3\xc3\xae\xaf\xc3\x81\x80\xc2\xa4\xc2\xc3\xaa\xa5\xae\xc2\xc3\xc3\xbd\x8f\x93\x0f\xc2\xb1\x1a\xc2\x91\xbd\xc2\xa6p<\x16\xc3\x93\x1ac/\xc2\xbb\xc2\x02\x9d\x1c\xc3\xc2\xbc\xc3\x8b\x83\xc2\xc3\x99\x83\x1fc'

復号うまくいってないように見えますが、これうまくいってます。\xc2がやけに多く見られます。

これ、pad関数がこんな風な変な平文にしているのです。pad(msg)とあると、いつものパディングかと見過ごしてしまいました。結局、pad関数はprintableではない文字を付け足しているだけです。なので非ASCII文字を除外すればOKです。

87d&
87d'2
6>p<c/c
=(l#a
6uO2nDfm1=Bkq9&@3BW&@rc.&56
=(lLpA8c%#DC9NKCh[Zr56
:2+3L/g*_.BOQ'q+EV:2F!,(2@:q1
=(l#a56
6VgEQ7R^6T0f+/5An3YW1c@1!
88W2r+A-ctF<G[=AKYT!EcZ=F1,'h\BOPpi@ru:&F$B
8LJ?tE,oN3FEo!MF`M%9H#IgJBOQ'q+EV:.+ED%7F8
=_2#T/0JP@@:re"0KM*G>l
8K_\TG%De<BOr;uCggs\2D@0t+EVO?/hSa
:MVL(8K`4kCht58ASu$$BlkJ+AoqU)+F.mJ/g*Z&+E)-M
1GE8q0f:XC5tP'F:N^>XAiOC2@qI_]An?A
1,r\s@P^#%2*#8*An*\P0Ocjn@l.XL2e?JY@:V;R2)-jB3Ab/(
:2+3L/0K.J+D>2,AKZ).AKYT$@:p^#Dg*?
8K_\bE+L/*@:O(aEcW@5@;]t$F<GX9AKZ).Blbm
<+oue+Cf(nDJj$%+DGm>F(Jj(Eb-A6BkM+$56
=_2#T/c
8K_\bE+L/;DfmFJAKZ#-B4uB>
8K_\bE+L/5D_;
:MV(pBOu&
6@!,p
6@!,
@X2M

取り除いてみるとこんな感じになりました。問題文からも何かしら暗号化を施しているのでしょう。base64では使えない文字があるのでbase85だろうと思ったが復号できず…。ここでタイムオーバーとなりました。



答えはASCII85でした。

base85とASCII85の違いは知りませんでした…。符号化の時の使う文字の順番が異なるようですね(ASCII85はASCIIコードでの昇順)。

Base85(85進数表記)に関する一考察 - Qiita

pythonではbase64.685decode([byte列]) で復号できます。

b'Hey'
b'Hey?'
b'Bob..'
b'What'
b'Do you like a secret?'
b"Who doesn't like?"
b'Nice. I have this flag.'
b'What?'
b'COMPFEST13{4fd29464a'
b'Hmm. Just the first 20 characters?'
b"I'm pretty sure you have the rest"
b'Yeah, flag[20:60]'
b'I have the flag[60:] too...'
b'Ok, I will send it after you. Go on.'
b'30b51506AIUEOuh_f8facf99fe}'
b'28a1b39559f4fc500b41c4b17ec8ad74512394a8'
b'Nice, we have the flag now!'
b'I hope hackers cannot see this.'
b'This channel is secure right?'
b'Yeah.'
b"I hope you're right."
b'I hope so.'
b'Ok then'
b'Bye.'
b'Bye'
b'bye'


COMPFEST13{4fd29464a28a1b39559f4fc500b41c4b17ec8ad74512394a830b51506AIUEOuh_f8facf99fe}