RITSEC CTF 2019 Writeup

この大会は2019/11/16 2:00(JST)~2019/11/18 14:00(JST)に開催されました。
今回もチームで参戦。結果は1890点で900チーム中105位でした。
自分で解けた問題をWriteupとして書いておきます。

Free points! (Misc)

問題にフラグが書いてあった。

RITSEC{fr33_p0int5}

What's Discord? (Misc)

Discordに入ると、#announcementsチャネルのメッセージにフラグがあった。

RITSEC{its_like_irc-but_with_2_much_javascript}

Resume Bank! (Misc)

必須の項目を入力して、適当にPDFをアップロードして送信すると、フラグが表示された。

RITSEC{g3t_th3_r3sum3s}

Onion Layer Encoding (Misc)

何重にもbase64, base32, base16でエンコードしている。エンコードの種類を判定してデコードする。

import base64
import string

def get_info(s):
    alp = False
    for c in s:
        if c.islower():
            return 0
        if c not in string.hexdigits:
            alp = True
    if alp:
        return 1
    else:
        return 2

with open('onionlayerencoding.txt', 'r') as f:
    data = f.read()

while True:
    if data.startswith('RITSEC{'):
        break
    if get_info(data) == 0:
        data = base64.b64decode(data)
    elif get_info(data) == 1:
        data = base64.b32decode(data)
    else:
        data = base64.b16decode(data)

print data
RITSEC{0n1On_L4y3R}

WTF is this number? (Misc)

問題の暗号はこうなっている。

1111111111111111111111111111111111111111111111111111111111111111111111111111111111:1001001:10010:1010011:234:151:234:1100010:1221:115:47:311:74:6D:57:1100101:5A:310:2E:100:27:4B:45:300:340:2G:11100:72:1M:3P:41

:区切りでフラグの1文字ずつをエンコードしている。フラグの先頭がRITSECになると推測していろいろ試していたら、最初のみ文字列の長さで、あとは何番目か(1から開始)でbaseを変更してエンコードしているとわかった。

素数の種類の積がbase
例)2番目:2進数
  6番目:2×3で、種類は2,3なので6進数
  12番目:2^2×3で、種類は2,3なので6進数
  24番目:2^3×3で、種類は2,3なので6進数

以上を元にデコードする。

from sympy import factorint

enc = '1111111111111111111111111111111111111111111111111111111111111111111111111111111111:1001001:10010:1010011:234:151:234:1100010:1221:115:47:311:74:6D:57:1100101:5A:310:2E:100:27:4B:45:300:340:2G:11100:72:1M:3P:41'
codes = enc.split(':')

powers = {}
for i in range(1, len(codes) + 1):
    f = factorint(i)
    primes = 1
    for k, v in f.iteritems():
        primes *= k
    powers[i] = primes

flag = ''
for i in range(len(codes)):
    if i == 0:
        code = len(codes[i])
    else:
        base = i + 1
        if base in powers:
            base = powers[base]
        code = int(codes[i], base)
    flag += chr(code)

print flag
RITSEC{b4s3s_aRe_r4d1cal_Dud3s}

misdirection (Web)

ブラウザで接続すると、リダイレクトしているのがわかる。リダイレクトするURLを追ってみる。

$ curl -s -I -L http://ctfchallenges.ritsec.club:5000/ | grep ^Location
Location: http://ctfchallenges.ritsec.club:5000/R
Location: http://ctfchallenges.ritsec.club:5000/S
Location: http://ctfchallenges.ritsec.club:5000/{
Location: http://ctfchallenges.ritsec.club:5000/4
Location: http://ctfchallenges.ritsec.club:5000/!
Location: http://ctfchallenges.ritsec.club:5000/w
Location: http://ctfchallenges.ritsec.club:5000/a
Location: http://ctfchallenges.ritsec.club:5000/y
Location: http://ctfchallenges.ritsec.club:5000/5
Location: http://ctfchallenges.ritsec.club:5000/_
Location: http://ctfchallenges.ritsec.club:5000/K
Location: http://ctfchallenges.ritsec.club:5000/e
Location: http://ctfchallenges.ritsec.club:5000/3
Location: http://ctfchallenges.ritsec.club:5000/p
Location: http://ctfchallenges.ritsec.club:5000/-
Location: http://ctfchallenges.ritsec.club:5000/m
Location: http://ctfchallenges.ritsec.club:5000/0
Location: http://ctfchallenges.ritsec.club:5000/v
Location: http://ctfchallenges.ritsec.club:5000/1
Location: http://ctfchallenges.ritsec.club:5000/n
Location: http://ctfchallenges.ritsec.club:5000/g
Location: http://ctfchallenges.ritsec.club:5000/}
Location: http://ctfchallenges.ritsec.club:5000/R
Location: http://ctfchallenges.ritsec.club:5000/S
Location: http://ctfchallenges.ritsec.club:5000/{
Location: http://ctfchallenges.ritsec.club:5000/4
Location: http://ctfchallenges.ritsec.club:5000/!
Location: http://ctfchallenges.ritsec.club:5000/w
Location: http://ctfchallenges.ritsec.club:5000/a
Location: http://ctfchallenges.ritsec.club:5000/y
Location: http://ctfchallenges.ritsec.club:5000/5
Location: http://ctfchallenges.ritsec.club:5000/_
Location: http://ctfchallenges.ritsec.club:5000/K
Location: http://ctfchallenges.ritsec.club:5000/e
Location: http://ctfchallenges.ritsec.club:5000/3
Location: http://ctfchallenges.ritsec.club:5000/p
Location: http://ctfchallenges.ritsec.club:5000/-
Location: http://ctfchallenges.ritsec.club:5000/m
Location: http://ctfchallenges.ritsec.club:5000/0
Location: http://ctfchallenges.ritsec.club:5000/v
Location: http://ctfchallenges.ritsec.club:5000/1
Location: http://ctfchallenges.ritsec.club:5000/n
Location: http://ctfchallenges.ritsec.club:5000/g
Location: http://ctfchallenges.ritsec.club:5000/}
Location: http://ctfchallenges.ritsec.club:5000/R
Location: http://ctfchallenges.ritsec.club:5000/S
Location: http://ctfchallenges.ritsec.club:5000/{
Location: http://ctfchallenges.ritsec.club:5000/4
Location: http://ctfchallenges.ritsec.club:5000/!
Location: http://ctfchallenges.ritsec.club:5000/w
Location: http://ctfchallenges.ritsec.club:5000/a

URLの末尾をつなげるとフラグになる。

RS{4!way5_Ke3p-m0v1ng}

Take it to the Cleaners (Forensics)

pngファイルが添付されている。EXIFを見てみる。

$ exiftool ritsec_logo2.png 
ExifTool Version Number         : 10.10
File Name                       : ritsec_logo2.png
Directory                       : .
File Size                       : 4.3 kB
File Modification Date/Time     : 2019:11:16 06:02:36+09:00
File Access Date/Time           : 2019:11:16 06:06:38+09:00
File Inode Change Date/Time     : 2019:11:16 06:02:36+09:00
File Permissions                : rwxrwxrwx
File Type                       : PNG
File Type Extension             : png
MIME Type                       : image/png
Image Width                     : 328
Image Height                    : 154
Bit Depth                       : 8
Color Type                      : Palette
Compression                     : Deflate/Inflate
Filter                          : Adaptive
Interlace                       : Noninterlaced
Palette                         : (Binary data 129 bytes, use -b option to extract)
Exif Byte Order                 : Big-endian (Motorola, MM)
Image Description               : Hi there! Looks like youre trying to solve the forensic_fails challenge! Good luck!
Resolution Unit                 : inches
Artist                          : Impos73r
Y Cb Cr Positioning             : Centered
Copyright                       : RITSEC 2018
Exif Version                    : 0231
Components Configuration        : Y, Cb, Cr, -
User Comment                    : RVZHRlJQe1NCRVJBRlZQRl9TTlZZRl9KQkFHX1VSWUNfTEJIX1VSRVJ9
Flashpix Version                : 0100
GPS Latitude Ref                : North
GPS Longitude Ref               : West
Image Size                      : 328x154
Megapixels                      : 0.051

User Commentにbase64文字列があるので、デコードしてみる。

$ echo RVZHRlJQe1NCRVJBRlZQRl9TTlZZRl9KQkFHX1VSWUNfTEJIX1VSRVJ9 | base64 -d
EVGFRP{SBERAFVPF_SNVYF_JBAG_URYC_LBH_URER}

さらにROT13デコードする。

$ echo RVZHRlJQe1NCRVJBRlZQRl9TTlZZRl9KQkFHX1VSWUNfTEJIX1VSRVJ9 | base64 -d | tr '[A-Za-z]' '[N-ZA-Mn-za-m]'
RITSEC{FORENSICS_FAILS_WONT_HELP_YOU_HERE}
RITSEC{FORENSICS_FAILS_WONT_HELP_YOU_HERE}

the_doge (Stego)

jpgファイルが添付されている。steghideでパスフレーズtreatを指定してみる。

$ steghide extract -sf the_doge.jpg 
Enter passphrase: 
wrote extracted data to "doge_ctf.txt".
$ cat doge_ctf.txt 
RITSEC{hAppY_l1L_doG3}
RITSEC{hAppY_l1L_doG3}

Shiny (Crypto)

黄金虫暗号。https://www.dcode.fr/gold-bug-poeで復号する。

POEWASTHEGOAT
RITSEC{poewasthegoat}

ASIS CTF Finals 2019 Writeup

この大会は2019/11/16 2:00(JST)~2019/11/18 2:00(JST)に開催されました。
今回もチームで参戦。結果は956点で357チーム中29位でした。
暗号問題に取り組んだのですが、1問も解けず残念!
自分で解けた問題をWriteupとして書いておきます。

Welcome (Trivia, Warm-up)

問題にフラグが書いてあった。

ASIS{ASIS_CTF_is_a_banquet_absentees_are_starving_to_death!}

Survey (Cool-down)

アンケートに答えたら、フラグが表示された。

ASIS{Th<3nks_f0r_part!c!pat!ng_ASISCTF20190_0U@Finals!}

Pwn2Win CTF 2019 Writeup

この大会は2019/11/9 1:37(JST)~2019/11/11 1:37(JST)に開催されました。
今回もチームで参戦。結果は903点で220チーム中29位でした。
自分で解けた問題をWriteupとして書いておきます。

~ The Last Resort [Read First] (Bonus)

問題にフラグが書いてあった。

CTF-BR{br0adc4s7_#01_succe55fully_r3c31v3d}

g00d b0y (Bonus)

Rulesのページの最下部にフラグがあった。

CTF-BR{RTFM_1s_4_g00d_3xpr3ss10n_v5.0}

Newbie CTF(N-CTF) 2019 Writeup

この大会は2019/11/2 0:00(JST)~2019/11/3 0:00(JST)に開催されました。
今回もチームで参戦。結果は12854点で566チーム中9位でした。
自分で解けた問題をWriteupとして書いておきます。

Discord (Misc)

Discordに入ると、#rulesチャネルにフラグが書いてあった。

KorNewbie{W31C0m3_t0_0ffiC14l_D1$C05d}

NC_MIC (Misc)

$ nc prob.vulnerable.kr 20000

特に何も表示されない。-vを指定してみる。

$ nc -v prob.vulnerable.kr 20000
Connection to prob.vulnerable.kr 20000 port [tcp/*] succeeded!
KorNewbie{W3lC0m3_T0_K0RN3wB13_W4RG4M3!!!!!}
KorNewbie{W3lC0m3_T0_K0RN3wB13_W4RG4M3!!!!!}

Catch Me (Misc)

アニメーションGIFになっていて、縦4~12、横1~9の表で、各フレームでどこか1箇所に黒丸がある。
縦と横の数字を結合して、順に並べてみる。

119, 48, 119, 95, 101, 52, 103, 49, 101, 95, 51, 121, 51

ASCIIコードとして文字にしてみる。

codes = [119, 48, 119, 95, 101, 52, 103, 49, 101, 95, 51, 121, 51]

flag = ''
for code in codes:
    flag += chr(code)
print flag

実行結果は以下の通り。

w0w_e4g1e_3y3
KorNewbie{w0w_e4g1e_3y3}

BiMilCode (Misc)

$ nc prob.vulnerable.kr 20034
==================================================
This is BiMilCode
I'll give you a chance to type 3 times.
Good Luck
# if you got it how to solve this problem type 0 .
==================================================
You can encode me? :  cc 7d 74 6b b9 61 4e 72 
Input : 11111111
8b 50 52 3f 6e 5d 3e 6b 
you have 2 chance left.
Input : 22222222
8c 51 53 40 6f 5e 3f 6c 
you have 1 chance left.
Input : 00000000
8a 4f 51 3e 6d 5c 3d 6a 
you have 0 chance left.
Think more about it.

入力した文字列とエンコード結果の差が一定になっている。一回何か文字列を8バイト入力すれば、その差がわかるので、逆算して文字列を算出して答える。

import socket

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(('prob.vulnerable.kr', 20034))

data = recvuntil(s, 'Input : ')
send_data = '1' * 8
print data + send_data

code_str = data.split('\n')[6].split(':  ')[1]
codes = code_str.split(' ')

s.sendall(send_data + '\n')
data = recvuntil(s, 'Input : ')
print data + '0'

code_str2 = data.split('\n')[0]
codes2 = code_str2.split(' ')[:-1]

key = []
for i in range(8):
    k = int(codes2[i], 16) - ord('1')
    key.append(k)

pt = ''
for i in range(8):
    code = int(codes[i], 16) - key[i]
    pt += chr(code)

s.sendall('0\n')
data = recvuntil(s, ': ')
print data + pt
s.sendall(pt + '\n')

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

実行結果は以下の通り。

==================================================
This is BiMilCode
I'll give you a chance to type 3 times.
Good Luck
# if you got it how to solve this problem type 0 .
==================================================
You can encode me? :  4c 81 c8 8a 8a b7 70 cc
Input : 11111111
55 6b 85 78 8d 6d 36 8d
you have 2 chance left.
Input : 0
Oh really? come on ! : (GtC.{kp
You did it!
KorNewbie{Nace_I_believed_it}
KorNewbie{Nace_I_believed_it}

Top Secret (Forensic)

$ strings Windows\ 7\ Enterprise\ K-b94208dd.vmem | grep KorNewbie
KorNewbie{OH..You_Know_B4sic_0F_M3mory_Forensics!}
KorNewbie{OH..You_Know_B4sic_0F_M3mory_Forensics!}
KorNewbie{OH..You_Know_B4sic_0F_M3mory_Forensics!}
KorNewbie{OH..You_Know_B4sic_0F_M3mory_Forensics!}
KorNewbie{OH..You_Know_B4sic_0F_M3mory_Forensics!}
KorNewbie{OH..You_Know_B4sic_0F_M3mory_Forensics!}
KorNewbie{OH..You_Know_B4sic_0F_M3mory_Forensics!}
KorNewbie{OH..You_Know_B4sic_0F_M3mory_Fore
KorNewbie{OH..You_Know_B4sic_0F_M3mory_Forensics!}
KorNewbie{OH..You_Know_B4sic_0F_M3mory_Forensics!}
KorNewbie{OH..You_Know_B4sic_0F_M3mory_Forensics!}

Contact point (Forensic)

abファイルが添付されている。Android Backup Extractorでjarを取り出す。

>java -jar abe.jar unpack backup.ab backup.jar

jarを展開し、ファイルを見ていく。いくつかdbファイルが入っている。
apps\com.android.browser\db\browser2.dbの中をDB Browser for SQLiteで見ていく。
f:id:satou-y:20191104210113p:plain
Jeju_international_airportについて調べていることがわかる。

KorNewbie{Jeju_international_airport}

REC (Forensic)

exeのヘッダが欠落しているので補完する。
実行しようとすると、次のエラーメッセージが表示される。

libgcc_s_dw2-1.dll が見つからないため、コードの実行を続行できません。プログラムを再インストールすると、この問題が解決する可能性があります。 

libgcc_s_dw2-1.dllをダウンロードしてexeと同じパスに置き、実行すると、フラグが表示された。

KorNewbie{Recover_Signature}

Chat (Forensic)

9GB近くあるzipを解凍すると、vmのファイル構成が展開される。NewbieCTF2019_KakaoTalk.vmdkをさらに解凍する。その中にBasic data partition.imgがあるので、Autopsyで開いて解析してみる。
f:id:satou-y:20191104211111p:plain
最近開いたドキュメントに "last_pc_login.dat.lnk"がある。このパスのあたりを調べてみる。
f:id:satou-y:20191104211209p:plain
/Users/NewbieCTF2019/AppData/Local/Kakao/KakaoTalk/users/login_list.datを見ると、メールアドレスが書いてある。

renek@it-simple.net
KorNewbie{renek@it-simple.net}

really vulnerable rsa (Crypto)

$ nc prob.vulnerable.kr 20020
I got public keys
N = 3512807632561
e = 65537
but... I don't know Ciphertext
Input your key to Decrypt >>> 11
failed to decrypt... here is results
51107770278
2127466376600
474970932836
3174514850353
1890649196132
1976463879009
2034826053429
2522129914975

Nと同じ値を入力してみる。

$ nc prob.vulnerable.kr 20020
I got public keys
N = 4125955954907
e = 65537
but... I don't know Ciphertext
Input your key to Decrypt >>> 4125955954907
failed to decrypt... here is results
0
0
0
0
0
0
0
0
$ nc prob.vulnerable.kr 20020
I got public keys
N = 2497289593217
e = 65537
but... I don't know Ciphertext
Input your key to Decrypt >>> 4994579186434
failed to decrypt... here is results
0
0
0
0
0
0
0
0

何回実行してもNと同じ値を入力すると、0になる。秘密鍵を求める問題ではないと言っていることから、これは平文である可能性が高い。以上のことから以下のような計算をし、m*[input]が複数行並んでいると推測する。

pow(m*[input], e, N) = C

例えばinputに11を指定すれば、結果の表示にinverse(11, N)を掛け算すればフラグになるはず。

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(('prob.vulnerable.kr', 20020))

coeff = 11
data = recvuntil(s, '>>> ')
print data + str(coeff)
N = int(data.split('\n')[1].split('= ')[1])
s.sendall(str(coeff) + '\n')

data = recvuntil(s, '\n').rstrip()
print data
pt_nums = []
for i in range(8):
    data = recvuntil(s, '\n').rstrip()
    print data
    pt_nums.append(int(data))

flag = ''
for pt_num in pt_nums:
    m = (pt_num * inverse(coeff, N)) % N
    flag += long_to_bytes(m)

print flag

実行結果は以下の通り。

I got public keys
N = 3491294480189
e = 65537
but... I don't know Ciphertext
Input your key to Decrypt >>> 11
failed to decrypt... here is results
72620922650
2148979528972
496484085208
3174514850353
1912162348504
1997977031381
2056339205801
2522129914975
KorNewbie{Th1s_C1ph3r_1s_t00_vuln3r5ble}
KorNewbie{Th1s_C1ph3r_1s_t00_vuln3r5ble}

BackdoorCTF 2019 Writeup

この大会は2019/10/27 0:30(JST)~2019/10/28 0:30(JST)に開催されました。
今回もチームで参戦。結果は1336点で128チーム中7位でした。
自分で解けた問題をWriteupとして書いておきます。

minecraft (crypto)

$ cat mine.py
def revmine(block):
    bl = block
    i = 0
    while True:
        i+=1
        sid = hexhash(long_to_bytes(block['nonce'])+long_to_bytes(i)+block['prev'].encode('utf-8'))
        if sid[:3] == '000':
            bl['data'] = i
            bl['self'] = sid
            bl['nonce'] = (s1*i+s2)%s3
            return bl

flag.encの内容は以下の形式で37個のデータが入っている。

[{"self": <sid>, "hashalg": "sha256", "prev": <sha256>, "data": <i>, "nonce": <(s1*i+s2)%s3>}, ...]

nonceは書き換えられている。一つのデータでハッシュの計算からブルートフォースで試してみると元のnonceは100前後の数値になった。ASCIIコードとして文字にできるかもしれない。
また、prevの値は他のデータのselfで設定されているものばかりで、prevが0000000000000000のデータがあるのでそのデータから始まると推測できる。順番に並び替え、それぞれ元のnonceから文字にしていく。

from Crypto.Util.number import *
from hashlib import sha256

def get_nonce(data, prev, self):
    nonce = 0
    while True:
        text = long_to_bytes(nonce) + long_to_bytes(data) + prev
        if sha256(text).hexdigest() == self:
            return nonce
        nonce += 1


with open('flag.enc', 'r') as f:
    enc = eval(f.read())

hashes = ['0000000000000000']
for i in range(len(enc)):
    for e in enc:
        if e['prev'] == hashes[i]:
            hashes.append(e['self'])
            break

flag = ''
for h in hashes[1:]:
    for e in enc:
        if e['self'] == h:
            nonce = get_nonce(e['data'], e['prev'], e['self'])
            flag += chr(nonce)

print flag

実行結果は以下の通り。

flag{1337_cr33p3r_kn0ws_4b0ut_b10cks}
$ echo -n flag{1337_cr33p3r_kn0ws_4b0ut_b10cks} | sha256sum
70b2803c59f0259737834fdb664d1c6bc7fdf997d3e4815a63f5b485b42142c9  -
70b2803c59f0259737834fdb664d1c6bc7fdf997d3e4815a63f5b485b42142c9

feedback

アンケートに答えたら、フラグが表示された。

flag{thanks_please_rate_on_ctftime}
$ echo -n flag{thanks_please_rate_on_ctftime} | sha256sum
c228a4f59201e3e15b1041845418f2ac745ddc0f1958cc701c5934893925dd80  -
c228a4f59201e3e15b1041845418f2ac745ddc0f1958cc701c5934893925dd80

TastelessCTF 2019 Writeup

この大会は2019/10/26 21:00(JST)~2019/10/27 21:00(JST)に開催されました。
今回もチームで参戦。結果は155点で157チーム中44位でした。
参加表明の問題しか解けていませんが、
自分で解けた問題をWriteupとして書いておきます。

sanity (misc)

PoWを解けばよいだけ。

$ nc hitme.tasteless.eu 10001
sha1(5781029c37adfa30, input) prefix = 00000...

ここで別ターミナルで以下を実行する。

$ go run pow.go 5781029c37adfa30 00000
1388809

先ほどのターミナルで続きに1388809を入力する。

$ nc hitme.tasteless.eu 10001
sha1(5781029c37adfa30, input) prefix = 00000...
1388809
welcome! tctf{are_y0u_readdddyyyy}
tctf{are_y0u_readdddyyyy}

Hack.lu CTF 2019 Writeup

この大会は2019/10/22 19:00(JST)~2019/10/24 19:00(JST)に開催されました。
今回もチームで参戦。結果は692点で277チーム中68位でした。
自分で解けた問題をWriteupとして書いておきます。

COBOL OTP (crypto)

COBOLのコードを読み解くと、以下のようになる。

ws-flag: 英数字1バイト
ws-key: 英数字50バイト
ws-parse-data: 符号付き9桁数字
ws-xor-len: 数字1桁、初期値1
ws-ctr: 数字1桁

・key.txt読み込み→ws_key
・ws_ctrに1をセット
・以下50回繰り返し
 ・getcharコール
 ・1文字ずつ入力文字をws-parseにセット
 ・ws_parseをws-flagにセット
 ・ws_keyのws_ctr番目の文字とXOR→結果を表示
 ・ws_ctrを1プラス

簡単に言うと、keyとフラグをXORして暗号化しているのだが、よく見るとws-ctrは1桁のためkeyの先頭10バイトだけが繰り返し、XORに使われている。XORの鍵は10バイトになっていることと同様になる。そこで先頭がflag{になることから少しずつフラグを割り出す。

with open('out', 'rb') as f:
    enc = f.readlines()[1].rstrip()

pt_parts = []

head_flag = 'flag{'
tail_flag = '}'

key1 = []
for i in range(len(head_flag)):
    code = ord(enc[i]) ^ ord(head_flag[i])
    key1.append(code)

pt_part1 = []
for j in range(50//10):
    pt = ''
    for i in range(len(key1)):
        code = ord(enc[i+j*10]) ^ key1[i]
        pt += chr(code)
    pt_part1.append(pt)

print pt_part1
pt_parts.append(pt_part1)

## predict from here ##
pt2_flag = '_'
key2 = ord(enc[35]) ^ ord(pt2_flag)

pt_part2 = []
for j in range(50//10):
    pt = chr(ord(enc[5+j*10]) ^ key2)
    pt_part2.append(pt)

print pt_part2
pt_parts.append(pt_part2)

pt3_flag = 't'
key3 = ord(enc[36]) ^ ord(pt3_flag)

pt_part3 = []
for j in range(50//10):
    pt = chr(ord(enc[6+j*10]) ^ key3)
    pt_part3.append(pt)

print pt_part3
pt_parts.append(pt_part3)

pt4_flag = 'h'
key4 = ord(enc[37]) ^ ord(pt4_flag)

pt_part4 = []
for j in range(50//10):
    pt = chr(ord(enc[7+j*10]) ^ key4)
    pt_part4.append(pt)

print pt_part4
pt_parts.append(pt_part4)

pt5_flag = '3'
key5 = ord(enc[38]) ^ ord(pt5_flag)

pt_part5 = []
for j in range(50//10):
    pt = chr(ord(enc[8+j*10]) ^ key5)
    pt_part5.append(pt)

print pt_part5
pt_parts.append(pt_part5)

pt6_flag = '_'
key6 = ord(enc[39]) ^ ord(pt6_flag)

pt_part6 = []
for j in range(50//10 - 1):
    pt = chr(ord(enc[9+j*10]) ^ key6)
    pt_part6.append(pt)
pt_part6.append('')

print pt_part6
pt_parts.append(pt_part6)

flag = ''
for i in range(50//10):
    for j in range(6):
        flag += pt_parts[j][i]

print flag

実行結果は以下の通り。

['flag{', '_c4n_', 'O2_c3', '_s4v3', 'fUtUr']
['N', 'b', 'r', '_', 'E']
['0', 'u', 't', 't', '1']
['w', 'y', 's', 'h', '!']
['_', '_', '_', '3', '}']
['u', 'C', '&', '_', '']
flag{N0w_u_c4n_buy_CO2_c3rts_&_s4v3_th3_fUtUrE1!}
flag{N0w_u_c4n_buy_CO2_c3rts_&_s4v3_th3_fUtUrE1!}