Square CTF 2019 Writeup

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

Decode me (snake oil 150)

pycと、pngの暗号化ファイルが添付されている。まずEasy Python Decompilerでpycをデコンパイルする。

# Embedded file name: ./encoder.py
import base64
import string
import sys
from random import shuffle

def encode(f, inp):
    s = string.printable
    init = lambda : (list(s), [])
    bag, buf = init()
    for x in inp:
        if x not in s:
            continue
        while True:
            r = bag[0]
            bag.remove(r)
            diff = (ord(x) - ord(r) + len(s)) % len(s)
            if diff == 0 or len(bag) == 0:
                shuffle(buf)
                f.write(''.join(buf))
                f.write('\x00')
                bag, buf = init()
                shuffle(bag)
            else:
                break

        buf.extend(r * (diff - 1))
        f.write(r)

    shuffle(buf)
    f.write(''.join(buf))


if __name__ == '__main__':
    with open(sys.argv[1], 'rb') as r:
        w = open(sys.argv[1] + '.enc', 'wb')
        b64 = base64.b64encode(r.read())
        encode(w, b64)

スクリプト処理の概要は以下の通り。

・base64エンコードした文字列をencodeする。
・encode処理
 ・bagはprintableの各文字の配列(初期化)
 ・bufはから配列(初期化)
 ・base64エンコードした各文字に対して、以下を実行
  ・r = bag[0]
  ・bagからrを削除
  ・diff = (ord(x) - ord(r) + len(s)) % len(s)
  ・diffが0 またはbagの長さが0の場合
   ・bufをシャッフル
   ・bufの結合文字列をファイル書き込み
   ・\x00をファイル書き込み
   ・bag, bufを初期化
   ・bagをシャッフル
  ・diffが0でなくbagの長さが0でない場合
   ループから抜ける
  ・r * (diff - 1)をbufに追加
  ・rをファイル書き込み
 ・bufをシャッフル
 ・bufの結合文字列を書き込み

復号はまず\x00区切りで考える。bagはシャッフルされるが、そのリストで使われた文字は後ろで複数の文字として現れる。そして使われた文字は削除されるので、どこまでがbugでどこからがbufかは区別できる。
あとは逆算すれば復号できる。

import string

s = string.printable
b64 = string.letters + string.digits + '+/='

def split_list_and_data(block):
    for i in range(len(s)):
        lst = block[:i]
        data = block[i:]
        err = False
        for c in s:
            if c in data and c not in lst:
                err = True
                break
        if err == False:
            return lst, data

def decrypt(lst, data):
    dec = ''
    for c in lst:
        diff = data.count(c) + 1
        code = diff + ord(c)
        if chr(code) not in b64 and code - len(s) > 0:
            code -= len(s)
        elif chr(code) not in b64 and code - len(s) <= 0:
            code += len(s)
        dec += chr(code)
    return dec

with open('decodeme.png.enc', 'rb') as f:
    enc = f.read()

blocks = enc.split('\x00')

flag = ''
for block in blocks:
    lst, data = split_list_and_data(block)
    flag += decrypt(lst, data)

with open('decodeme.png', 'wb') as f:
   f.write(flag.decode('base64'))

復号したPNG画像にフラグが書いてある。
f:id:satou-y:20191022073414p:plain

flag-6f38426c5963729d

Go cipher (go, not web 1000)

鍵は24バイトで、そのmd5が暗号化データの先頭32バイトに記載される。story4の暗号の先頭32バイトがflagの暗号の先頭32バイトと同じなので、同じ鍵になっている。xのシフトから1バイトごとの可能性のある鍵を絞る。またyとzのXORの組み合わせまでは考慮する必要がないので、yとzのXORを一つのパラメータとして、ブルートフォースして鍵を絞る。

def get_join_info(list1, list2, idx1, idx2):
    for l1 in list1:
        bin_l1 = bin(l1[0])[2:].zfill(8)
        for l2 in list2:
            bin_l2 = bin(l2[0])[2:].zfill(8)
            if bin_l1[:7] == bin_l2[1:]:
                print '%d: %s -> %d: %s' % (idx1, str(l1), idx2, str(l2))

with open('story4.txt', 'rb') as f:
    pt = f.read()

with open('story4.txt.enc', 'rb') as f:
    ct = f.read()

h = ct[:32]
ct = ct[32:].decode('hex')

key_list = []
for j in range(64):
    if j < 12:
       round = 4
    else:
       round = 3
    keys = []
    for i in range(round):
        key = []
        for x in range(256):
            for yz in range(256):
                code = ((ord(pt[i * 64 + j]) - x) % 256) ^ yz
                if code == ord(ct[i * 64 + j]):
                    key.append((x, yz))
        keys.append(key)

    if j < 12:
        key = set(keys[0]) & set(keys[1]) & set(keys[2]) & set(keys[3])
    else:
        key = set(keys[0]) & set(keys[1]) & set(keys[2])
    key_list.append(list(key))

for i in range(63):
    get_join_info(key_list[i], key_list[i+1], i, i+1)

実行結果は以下の通り。

0: (52, 176) -> 1: (26, 97)
0: (52, 176) -> 1: (154, 225)
1: (26, 97) -> 2: (141, 195)
1: (26, 97) -> 2: (13, 67)
2: (141, 195) -> 3: (198, 134)
2: (141, 195) -> 3: (70, 6)
3: (198, 134) -> 4: (99, 12)
3: (198, 134) -> 4: (227, 140)
4: (99, 12) -> 5: (49, 153)
4: (99, 12) -> 5: (177, 25)
4: (227, 140) -> 5: (113, 89)
4: (227, 140) -> 5: (241, 217)
5: (113, 89) -> 6: (56, 19)
5: (113, 89) -> 6: (184, 147)
5: (177, 25) -> 6: (216, 179)
5: (177, 25) -> 6: (88, 51)
6: (88, 51) -> 7: (44, 103)
6: (88, 51) -> 7: (172, 231)
7: (44, 103) -> 8: (150, 207)
7: (44, 103) -> 8: (22, 79)
8: (150, 207) -> 9: (203, 30)
8: (150, 207) -> 9: (75, 158)
9: (75, 158) -> 10: (165, 61)
9: (75, 158) -> 10: (37, 189)
10: (165, 61) -> 11: (82, 251)
10: (165, 61) -> 11: (210, 123)
10: (37, 189) -> 11: (146, 59)
10: (37, 189) -> 11: (18, 187)
11: (210, 123) -> 12: (105, 118)
11: (210, 123) -> 12: (233, 246)
12: (233, 246) -> 13: (244, 108)
12: (233, 246) -> 13: (116, 236)
13: (116, 236) -> 14: (58, 217)
13: (116, 236) -> 14: (186, 89)
13: (117, 237) -> 14: (58, 217)
13: (117, 237) -> 14: (186, 89)
14: (58, 217) -> 15: (157, 51)
14: (58, 217) -> 15: (29, 179)
14: (186, 89) -> 15: (221, 115)
14: (186, 89) -> 15: (93, 243)
15: (157, 51) -> 16: (206, 38)
15: (157, 51) -> 16: (78, 166)
15: (29, 179) -> 16: (14, 230)
15: (29, 179) -> 16: (142, 102)
16: (14, 230) -> 17: (7, 140)
16: (14, 230) -> 17: (135, 12)
16: (142, 102) -> 17: (199, 76)
16: (142, 102) -> 17: (71, 204)
17: (199, 76) -> 18: (99, 217)
17: (199, 76) -> 18: (227, 89)
17: (71, 204) -> 18: (35, 153)
17: (71, 204) -> 18: (163, 25)
17: (7, 140) -> 18: (3, 185)
17: (7, 140) -> 18: (131, 57)
17: (135, 12) -> 18: (67, 249)
17: (135, 12) -> 18: (195, 121)
18: (35, 153) -> 19: (145, 179)
18: (35, 153) -> 19: (17, 51)
19: (17, 51) -> 20: (136, 102)
19: (17, 51) -> 20: (8, 230)
20: (136, 102) -> 21: (196, 204)
20: (136, 102) -> 21: (68, 76)
21: (196, 204) -> 22: (226, 152)
21: (196, 204) -> 22: (98, 24)
22: (226, 152) -> 23: (241, 177)
22: (226, 152) -> 23: (113, 49)
23: (113, 49) -> 24: (56, 98)
23: (113, 49) -> 24: (184, 226)
23: (114, 48) -> 24: (185, 227)
23: (114, 48) -> 24: (57, 99)
24: (56, 98) -> 25: (28, 197)
24: (56, 98) -> 25: (156, 69)
24: (57, 99) -> 25: (28, 197)
24: (57, 99) -> 25: (156, 69)
25: (28, 197) -> 26: (14, 138)
25: (28, 197) -> 26: (142, 10)
26: (16, 136) -> 27: (136, 20)
26: (16, 136) -> 27: (8, 148)
26: (144, 8) -> 27: (200, 84)
26: (144, 8) -> 27: (72, 212)
26: (141, 11) -> 27: (198, 86)
26: (141, 11) -> 27: (70, 214)
26: (15, 137) -> 27: (135, 21)
26: (15, 137) -> 27: (7, 149)
26: (14, 138) -> 27: (135, 21)
26: (14, 138) -> 27: (7, 149)
26: (142, 10) -> 27: (71, 213)
26: (142, 10) -> 27: (199, 85)
26: (143, 9) -> 27: (71, 213)
26: (143, 9) -> 27: (199, 85)
26: (13, 139) -> 27: (6, 150)
26: (13, 139) -> 27: (134, 22)
27: (71, 213) -> 28: (35, 203)
27: (71, 213) -> 28: (163, 75)
27: (22, 166) -> 28: (139, 83)
27: (22, 166) -> 28: (11, 211)
27: (87, 229) -> 28: (43, 179)
27: (87, 229) -> 28: (171, 51)
27: (230, 118) -> 28: (243, 251)
27: (230, 118) -> 28: (115, 123)
27: (23, 165) -> 28: (139, 83)
27: (23, 165) -> 28: (11, 211)
27: (118, 6) -> 28: (59, 163)
27: (118, 6) -> 28: (187, 35)
27: (199, 85) -> 28: (227, 11)
27: (199, 85) -> 28: (99, 139)
27: (6, 150) -> 28: (3, 235)
27: (6, 150) -> 28: (131, 107)
27: (198, 86) -> 28: (227, 11)
27: (198, 86) -> 28: (99, 139)
27: (151, 37) -> 28: (203, 19)
27: (151, 37) -> 28: (75, 147)
27: (183, 69) -> 28: (219, 3)
27: (183, 69) -> 28: (91, 131)
27: (70, 214) -> 28: (35, 203)
27: (70, 214) -> 28: (163, 75)
27: (102, 246) -> 28: (179, 59)
27: (102, 246) -> 28: (51, 187)
27: (247, 133) -> 28: (251, 227)
27: (247, 133) -> 28: (123, 99)
27: (39, 181) -> 28: (147, 91)
27: (39, 181) -> 28: (19, 219)
27: (215, 101) -> 28: (107, 115)
27: (215, 101) -> 28: (235, 243)
27: (38, 182) -> 28: (147, 91)
27: (38, 182) -> 28: (19, 219)
27: (135, 21) -> 28: (195, 43)
27: (135, 21) -> 28: (67, 171)
27: (150, 38) -> 28: (203, 19)
27: (150, 38) -> 28: (75, 147)
27: (167, 53) -> 28: (83, 155)
27: (167, 53) -> 28: (211, 27)
27: (214, 102) -> 28: (107, 115)
27: (214, 102) -> 28: (235, 243)
27: (231, 117) -> 28: (243, 251)
27: (231, 117) -> 28: (115, 123)
27: (182, 70) -> 28: (219, 3)
27: (182, 70) -> 28: (91, 131)
27: (55, 197) -> 28: (27, 195)
27: (55, 197) -> 28: (155, 67)
27: (7, 149) -> 28: (3, 235)
27: (7, 149) -> 28: (131, 107)
27: (246, 134) -> 28: (251, 227)
27: (246, 134) -> 28: (123, 99)
27: (134, 22) -> 28: (195, 43)
27: (134, 22) -> 28: (67, 171)
27: (119, 5) -> 28: (59, 163)
27: (119, 5) -> 28: (187, 35)
27: (86, 230) -> 28: (43, 179)
27: (86, 230) -> 28: (171, 51)
27: (166, 54) -> 28: (83, 155)
27: (166, 54) -> 28: (211, 27)
27: (103, 245) -> 28: (179, 59)
27: (103, 245) -> 28: (51, 187)
27: (54, 198) -> 28: (27, 195)
27: (54, 198) -> 28: (155, 67)
28: (195, 43) -> 29: (225, 215)
28: (195, 43) -> 29: (97, 87)
28: (67, 171) -> 29: (161, 151)
28: (67, 171) -> 29: (33, 23)
29: (161, 151) -> 30: (208, 142)
29: (161, 151) -> 30: (80, 14)
29: (97, 87) -> 30: (176, 174)
29: (97, 87) -> 30: (48, 46)
30: (176, 174) -> 31: (216, 92)
30: (176, 174) -> 31: (88, 220)
31: (216, 92) -> 32: (108, 56)
31: (216, 92) -> 32: (236, 184)
32: (108, 56) -> 33: (182, 49)
32: (108, 56) -> 33: (54, 177)
32: (236, 184) -> 33: (246, 241)
32: (236, 184) -> 33: (118, 113)
33: (118, 113) -> 34: (59, 227)
33: (118, 113) -> 34: (187, 99)
33: (182, 49) -> 34: (219, 67)
33: (182, 49) -> 34: (91, 195)
34: (59, 227) -> 35: (157, 199)
34: (59, 227) -> 35: (29, 71)
35: (157, 199) -> 36: (78, 142)
35: (157, 199) -> 36: (206, 14)
35: (29, 71) -> 36: (142, 78)
35: (29, 71) -> 36: (14, 206)
36: (78, 142) -> 37: (167, 157)
36: (78, 142) -> 37: (39, 29)
36: (206, 14) -> 37: (231, 221)
36: (206, 14) -> 37: (103, 93)
37: (231, 221) -> 38: (115, 27)
37: (231, 221) -> 38: (243, 155)
37: (103, 93) -> 38: (51, 219)
37: (103, 93) -> 38: (179, 91)
37: (167, 157) -> 38: (211, 123)
37: (167, 157) -> 38: (83, 251)
37: (39, 29) -> 38: (19, 187)
37: (39, 29) -> 38: (147, 59)
38: (19, 187) -> 39: (137, 183)
38: (19, 187) -> 39: (9, 55)
38: (115, 27) -> 39: (57, 103)
38: (115, 27) -> 39: (185, 231)
38: (147, 59) -> 39: (201, 247)
38: (147, 59) -> 39: (73, 119)
38: (51, 219) -> 39: (153, 135)
38: (51, 219) -> 39: (25, 7)
38: (211, 123) -> 39: (233, 215)
38: (211, 123) -> 39: (105, 87)
38: (179, 91) -> 39: (89, 71)
38: (179, 91) -> 39: (217, 199)
38: (243, 155) -> 39: (121, 167)
38: (243, 155) -> 39: (249, 39)
38: (83, 251) -> 39: (169, 151)
38: (83, 251) -> 39: (41, 23)
39: (201, 247) -> 40: (100, 47)
39: (201, 247) -> 40: (228, 175)
39: (233, 215) -> 40: (244, 95)
39: (233, 215) -> 40: (116, 223)
39: (105, 87) -> 40: (180, 159)
39: (105, 87) -> 40: (52, 31)
39: (169, 151) -> 40: (84, 63)
39: (169, 151) -> 40: (212, 191)
39: (73, 119) -> 40: (36, 111)
39: (73, 119) -> 40: (164, 239)
39: (137, 183) -> 40: (196, 143)
39: (137, 183) -> 40: (68, 15)
39: (9, 55) -> 40: (132, 207)
39: (9, 55) -> 40: (4, 79)
39: (41, 23) -> 40: (148, 255)
39: (41, 23) -> 40: (20, 127)
40: (196, 143) -> 41: (98, 207)
40: (196, 143) -> 41: (226, 79)
40: (164, 239) -> 41: (210, 95)
40: (164, 239) -> 41: (82, 223)
41: (210, 95) -> 42: (233, 254)
41: (210, 95) -> 42: (105, 126)
41: (82, 223) -> 42: (41, 62)
41: (82, 223) -> 42: (169, 190)
42: (105, 126) -> 43: (52, 221)
42: (105, 126) -> 43: (180, 93)
42: (169, 190) -> 43: (84, 253)
42: (169, 190) -> 43: (212, 125)
43: (84, 253) -> 44: (42, 58)
43: (84, 253) -> 44: (170, 186)
43: (212, 125) -> 44: (234, 250)
43: (212, 125) -> 44: (106, 122)
44: (234, 250) -> 45: (245, 245)
44: (234, 250) -> 45: (117, 117)
45: (245, 245) -> 46: (122, 235)
45: (245, 245) -> 46: (250, 107)
45: (117, 117) -> 46: (58, 43)
45: (117, 117) -> 46: (186, 171)
46: (122, 235) -> 47: (189, 214)
46: (122, 235) -> 47: (61, 86)
46: (58, 43) -> 47: (29, 54)
46: (58, 43) -> 47: (157, 182)
46: (250, 107) -> 47: (253, 22)
46: (250, 107) -> 47: (125, 150)
46: (186, 171) -> 47: (93, 118)
46: (186, 171) -> 47: (221, 246)
47: (189, 214) -> 48: (94, 45)
47: (189, 214) -> 48: (222, 173)
47: (125, 150) -> 48: (62, 13)
47: (125, 150) -> 48: (190, 141)
48: (222, 173) -> 49: (239, 91)
48: (222, 173) -> 49: (111, 219)
49: (239, 91) -> 50: (247, 54)
49: (239, 91) -> 50: (119, 182)
49: (111, 219) -> 50: (55, 118)
49: (111, 219) -> 50: (183, 246)
50: (247, 54) -> 51: (123, 172)
50: (247, 54) -> 51: (251, 44)
50: (119, 182) -> 51: (59, 108)
50: (119, 182) -> 51: (187, 236)
50: (55, 118) -> 51: (155, 140)
50: (55, 118) -> 51: (27, 12)
50: (183, 246) -> 51: (91, 76)
50: (183, 246) -> 51: (219, 204)
51: (59, 108) -> 52: (157, 89)
51: (59, 108) -> 52: (29, 217)
51: (251, 44) -> 52: (253, 249)
51: (251, 44) -> 52: (125, 121)
52: (30, 216) -> 53: (15, 179)
52: (30, 216) -> 53: (143, 51)
52: (157, 89) -> 53: (78, 242)
52: (157, 89) -> 53: (206, 114)
52: (29, 217) -> 53: (14, 178)
52: (29, 217) -> 53: (142, 50)
52: (159, 91) -> 53: (79, 243)
52: (159, 91) -> 53: (207, 115)
52: (158, 88) -> 53: (79, 243)
52: (158, 88) -> 53: (207, 115)
52: (31, 219) -> 53: (15, 179)
52: (31, 219) -> 53: (143, 51)
53: (14, 178) -> 54: (135, 229)
53: (14, 178) -> 54: (7, 101)
53: (142, 50) -> 54: (71, 37)
53: (142, 50) -> 54: (199, 165)
53: (15, 179) -> 54: (135, 229)
53: (15, 179) -> 54: (7, 101)
53: (143, 51) -> 54: (71, 37)
53: (143, 51) -> 54: (199, 165)
54: (71, 37) -> 55: (163, 107)
54: (71, 37) -> 55: (35, 235)
54: (7, 101) -> 55: (131, 75)
54: (7, 101) -> 55: (3, 203)
55: (3, 203) -> 56: (129, 151)
55: (3, 203) -> 56: (1, 23)
56: (129, 151) -> 57: (64, 47)
56: (129, 151) -> 57: (192, 175)
56: (125, 147) -> 57: (62, 45)
56: (125, 147) -> 57: (190, 173)
57: (64, 47) -> 58: (160, 222)
57: (64, 47) -> 58: (32, 94)
57: (65, 46) -> 58: (160, 222)
57: (65, 46) -> 58: (32, 94)
58: (32, 94) -> 59: (16, 61)
58: (32, 94) -> 59: (144, 189)
59: (144, 189) -> 60: (72, 123)
59: (144, 189) -> 60: (200, 251)
59: (145, 188) -> 60: (72, 123)
59: (145, 188) -> 60: (200, 251)
60: (72, 123) -> 61: (36, 118)
60: (72, 123) -> 61: (164, 246)
61: (169, 243) -> 62: (212, 238)
61: (169, 243) -> 62: (84, 110)
61: (164, 246) -> 62: (210, 236)
61: (164, 246) -> 62: (82, 108)
61: (165, 247) -> 62: (210, 236)
61: (165, 247) -> 62: (82, 108)
61: (168, 242) -> 62: (212, 238)
61: (168, 242) -> 62: (84, 110)
62: (210, 236) -> 63: (233, 88)
62: (210, 236) -> 63: (105, 216)
62: (178, 204) -> 63: (217, 72)
62: (178, 204) -> 63: (89, 200)

このことから0~63まで一貫してつながるものを探す。

0: (52, 176) -> 1: (26, 97)
1: (26, 97) -> 2: (141, 195)
2: (141, 195) -> 3: (198, 134)
3: (198, 134) -> 4: (99, 12)
4: (99, 12) -> 5: (177, 25)
5: (177, 25) -> 6: (88, 51)
6: (88, 51) -> 7: (44, 103)
7: (44, 103) -> 8: (150, 207)
8: (150, 207) -> 9: (75, 158)
9: (75, 158) -> 10: (165, 61)
10: (165, 61) -> 11: (210, 123)
11: (210, 123) -> 12: (233, 246)
12: (233, 246) -> 13: (116, 236)
13: (116, 236) -> 14: (58, 217)
14: (58, 217) -> 15: (29, 179)
15: (29, 179) -> 16: (142, 102)
16: (142, 102) -> 17: (71, 204)
17: (71, 204) -> 18: (35, 153)
18: (35, 153) -> 19: (17, 51)
19: (17, 51) -> 20: (136, 102)
20: (136, 102) -> 21: (196, 204)
21: (196, 204) -> 22: (226, 152)
22: (226, 152) -> 23: (113, 49)
23: (113, 49) -> 24: (56, 98)
24: (56, 98) -> 25: (28, 197)
25: (28, 197) -> 26: (14, 138)
26: (14, 138) -> 27: (135, 21)
27: (135, 21) -> 28: (195, 43)
28: (195, 43) -> 29: (97, 87)
29: (97, 87) -> 30: (176, 174)
30: (176, 174) -> 31: (216, 92)
31: (216, 92) -> 32: (236, 184)
32: (236, 184) -> 33: (118, 113)
33: (118, 113) -> 34: (59, 227)
34: (59, 227) -> 35: (157, 199)
35: (157, 199) -> 36: (78, 142)
36: (78, 142) -> 37: (39, 29)
37: (39, 29) -> 38: (147, 59)
38: (147, 59) -> 39: (73, 119)
39: (73, 119) -> 40: (164, 239)
40: (164, 239) -> 41: (82, 223)
41: (82, 223) -> 42: (169, 190)
42: (169, 190) -> 43: (212, 125)
43: (212, 125) -> 44: (234, 250)
44: (234, 250) -> 45: (245, 245)
45: (245, 245) -> 46: (122, 235)
46: (122, 235) -> 47: (189, 214)
47: (189, 214) -> 48: (222, 173)
48: (222, 173) -> 49: (239, 91)
49: (239, 91) -> 50: (119, 182)
50: (119, 182) -> 51: (59, 108)
51: (59, 108) -> 52: (29, 217)
52: (29, 217) -> 53: (14, 178)
53: (14, 178) -> 54: (7, 101)
54: (7, 101) -> 55: (3, 203)
55: (3, 203) -> 56: (129, 151)
56: (129, 151) -> 57: (64, 47)
57: (64, 47) -> 58: (32, 94)
58: (32, 94) -> 59: (144, 189)
59: (144, 189) -> 60: (72, 123)
60: (72, 123) -> 61: (164, 246)
61: (164, 246) -> 62: (210, 236)

結果は上記の通りだが、62バイト目から63バイト目の関係を1つに絞れなく、以下のどちらかである。

62: (210, 236) -> 63: (233, 88)
62: (210, 236) -> 63: (105, 216)

右シフトなので、56バイト目までで全貌が分かり、割り出すことが可能である。そのことを考慮して、flagの方を復号する。

with open('flag.txt.enc', 'rb') as f:
    ct = f.read()

h = ct[:32]
ct = ct[32:].decode('hex')

xs = [52, 26, 141, 198, 99, 177, 88, 44, 150, 75, 165, 210, 233, 116, 58, 29, 
    142, 71, 35, 17, 136, 196, 226, 113, 56, 28, 14, 135, 195, 97, 176, 216, 
    236, 118, 59, 157, 78, 39, 147, 73, 164, 82, 169, 212, 234, 245, 122, 189, 
    222, 239, 119, 59, 29, 14, 7, 3, 129, 64, 32, 144, 72, 164, 210]

yzs = [176, 97, 195, 134, 12, 25, 51, 103, 207, 158, 61, 123, 246, 236, 217, 
    179, 102, 204, 153, 51, 102, 204, 152, 49, 98, 197, 138, 21, 43, 87, 174, 
    92, 184, 113, 227, 199, 142, 29, 59, 119, 239, 223, 190, 125, 250, 245, 
    235, 214, 173, 91, 182, 108, 217, 178, 101, 203, 151, 47, 94, 189, 123, 
    246]

last_xyz = {233: 88, 105: 216}

bin_all_x = ''
init = True
for x in xs:
    bin_x = bin(x)[2:].zfill(8)
    if init:
        init = False
        bin_all_x += bin_x
    else:
        bin_all_x = bin_x[0] + bin_all_x
    if len(bin_all_x) == 64:
        break

bin_all_x2 = bin_all_x * 2
for i in range(64):
    val = int(bin_all_x2[120-i:128-i], 2)
    if i != 63:
        assert val == xs[i]
    else:
        xs.append(val)
        yzs.append(last_xyz[val])

flag = ''
for i in range(len(ct)):
    code = ((ord(ct[i]) ^ yzs[i]) + xs[i]) % 256
    flag += chr(code)

print flag

復号結果は以下の通り。

Yes, you did it! flag-742CF8ED6A2BF55807B14719
flag-742CF8ED6A2BF55807B14719