0CTF/TCTF 2022 Writeup

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

welcome (Misc)

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

flag{wish_you_have_fun_in_2022}

real magic dlog (Crypto)

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

・LEN = 17
・PoW
・magic: ランダム17バイト文字列
・magic_num: magicの数値化
・magicを16進数で表示
・P: 数値入力
・E: 数値入力
・data: 16進文字列入力
・num1: dataを10進数にした数値
・P >> (384 - LEN * 8) == magic_numで、Pが素数の場合、
 ・data2: dataのsha384ダイジェスト(hex)
 ・num2: data2を10進数にした数値
 ・pow(num1, E, P) == num2 % Pの場合、フラグを表示

num1, num2は依存関係にあるので、独立して指定できない。また、Pは条件があるので、P - 1の素因数が小さい値になるよう調整した上で、Eを解く問題となる。
(384 - LEN * 8)ビット分は小さいビットの素数で構成し、残りのビットを条件を満たすよう割り算して算出する。P - 1の素因数がすべて51ビット未満にできるまで、サーバに接続しなおして、条件を満たすまで繰り返す。さらに離散対数問題の解が存在するnum1, num2の組を探す。離散対数問題を解くことができれば、あとはパラメータを入力していけばよい。

#!/usr/bin/env sage
import socket
import string
import re
import itertools
import sympy
from Crypto.Util.number import *
from hashlib import sha256, sha384

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

LEN = 17

while True:
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect(('202.120.7.219', 15555))

    data = recvuntil(s, b'\n').rstrip()
    print(data)
    pattern = '\+ (.+)\) == (.+)'
    m = re.search(pattern, data)
    tail_text = m.group(1)
    h = m.group(2)

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

    chars = string.ascii_letters + string.digits + '!#$%&*-?'
    for c in itertools.product(chars, repeat=4):
        head_text = ''.join(c)
        text = head_text + tail_text
        if sha256(text.encode()).hexdigest() == h:
            print(head_text)
            s.sendall(head_text.encode() + b'\n')
            break

    data = recvuntil(s, b'\n').rstrip()
    print(data)
    magic = bytes.fromhex(data)
    magic_num = bytes_to_long(magic)

    P_base = magic_num << (384 - LEN * 8)

    prod = 2
    for _ in range(9):
        p = getPrime(27)
        prod *= p

    found = False
    for i in range(1, 257):
        P_parts = P_base // prod + i
        P = prod * P_parts + 1
        if isPrime(P) and P >> (384 - LEN * 8) == magic_num:
            if factor(P - 1)[-1][0].nbits() < 51:
                found = True
                break

    if found:
        break
    else:
        s.close()

print('[+] P - 1 =', factor(P - 1))

F = GF(P)
g = F.multiplicative_generator()

while True:
    num1 = ZZ(g ** randint(1, p - 1))
    send_data = hex(num1)[2:]
    data2 = sha384(send_data.encode()).hexdigest()
    num2 = int(data2, 16)

    print('[+] discrete_log() start')
    try:
        E = ZZ(discrete_log(F(num2), F(num1)))
        assert pow(num1, E, P) == num2 % P
        break
    except:
        print('[+] Exception occurred')
        continue

data = recvuntil(s, b'\n').rstrip()
print(data)
print(hex(P)[2:])
s.sendall(hex(P)[2:].encode() + b'\n')
data = recvuntil(s, b'\n').rstrip()
print(data)
print(hex(E)[2:])
s.sendall(hex(E)[2:].encode() + b'\n')
data = recvuntil(s, b'\n').rstrip()
print(data)
print(send_data)
s.sendall(send_data.encode() + b'\n')
data = recvuntil(s, b'\n').rstrip()
print(data)

実行結果は以下の通り。

        :
        :
sha256(XXXX + ns0-1OlPpG&FWMat) == 23909a09dfb9a4efd8b264bae436f05f829bd37339d645cdea6c924abb8f0372
Give me XXXX:
z8Xy
584dfa5be19766efe6f0fa3f353214e838
sha256(XXXX + 6Q0vwpiE*5dQhe4R) == 63ba546bb91bda036778db99804144bf54927a28cd4157219d447af13942b6a2
Give me XXXX:
nHX#
099cb53f1e49f4ea3e2df88a02d11becb1
sha256(XXXX + FmcSWSPNz7Yp5pVT) == c7a799a3ee9a66f48a8caa5774f57259c764a4be132479b4afdb821bcd049247
Give me XXXX:
D&3N
ebc82af9cdb736593a0a3c397192255a0e
[+] P - 1 = 2 * 3^2 * 5 * 11 * 13 * 271 * 541 * 5903 * 40829 * 1644637 * 68905957 * 76659659 * 86409703 * 90452987 * 92508137 * 93260641 * 96174671 * 98107447 * 105566633 * 3452271289 * 39614162513
[+] discrete_log() start
P:>
ebc82af9cdb736593a0a3c397192255a0e49f1aefef7a68ed267ea2a0d2d2220b86d41e92f536519e4c75ab9ba0f8c8b
E:>
856fb0a054d4f6c2ca56e6ec8897dabdbb602c7a869a92201b14a935428ca955f1f9ff7462fdfac60db8c6bfa5ed171e
data:>
2f2488891e2ad3a456fc2d9879083bc7b9077f8105b6e744fe26cef7d93a59ed0460702886b88695a85db694e6a68078
flag{Hope_you_can_solve_by_smoothness_this_time}
flag{Hope_you_can_solve_by_smoothness_this_time}

SURVEY (Misc)

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

flag{covid19_steals_the_finals_and_we_hope_to_meet_you_onsite_someday}

CSAW CTF Qualification Round 2022 Writeup

この大会は2022/9/10 1:00(JST)~2022/9/12 1:00(JST)に開催されました。
今回もチームで参戦。結果は1787点で884チーム中128位でした。
自分で解けた問題をWriteupとして書いておきます。

Welcome (misc)

Discordに入り、#rulesチャネルでリアクションすると、新しいチャネルが現れる。#announcementsチャネルのトピックを見ると、フラグが書いてあった。

flag{c54w_f1n4l5_15_1n_p3r50n_y4y}

Quantum Leap (misc)

フラグは"flag{"から始まり、"}"で終わるので、該当する文字の2進数を比較する。

>>> bin(ord('f'))
'0b1100110'
>>> bin(ord('w'))
'0b1110111'

下0, 4bit反転
下1, 3, 5bit -> 1, 0, 1

>>> bin(ord('l'))
'0b1101100'
>>> bin(ord('x'))
'0b1111000'

下2, 4bit反転
下1, 3, 5bit -> 0, 1, 1

>>> bin(ord('a'))
'0b1100001'
>>> bin(ord('q'))
'0b1110001'

下4bit反転
下1, 3, 5bit -> 0, 0, 1

>>> bin(ord('g'))
'0b1100111'
>>> bin(ord('v'))
'0b1110110'

下0, 4bit反転
下1, 3, 5bit -> 1, 0, 1

>>> bin(ord('{'))
'0b1111011'
>>> bin(ord('n'))
'0b1101110'

下0, 2, 4bit反転
下1, 3, 5bit -> 1, 1, 1

>>> bin(ord('}'))
'0b1111101'
>>> bin(ord('i'))
'0b1101001'

下2, 4bit反転
下1, 3, 5bit -> 0, 1, 1

以下の法則で変換されていると推測できる。

・下1bitが1の場合、下0ビットを反転
・下3bitが1の場合、下2ビットを反転
・下5bitが1の場合、下4ビットを反転

これを元に復元する。

#!/usr/bin/env python3
with open('output', 'r') as f:
    data = f.read().rstrip()

flag = ''
for d in data:
    b = bin(ord(d))[2:].zfill(8)
    b = list(b)
    if b[2] == '1':
        b[3] = str(int(b[3]) ^ 1)
    if b[4] == '1':
        b[5] = str(int(b[5]) ^ 1)
    if b[6] == '1':
        b[7] = str(int(b[7]) ^ 1)
    b = ''.join(b)
    flag += chr(int(b, 2))
print(flag)
flag{4_qu4ntum_g4t3}

DockREleakage (rev)

docker imageのtar.gzを展開し、確認する。repositoriesを見る。

{"dockre-chal":{"latest":"928ab519cd995aeae5eced3dbe4b7e86c8bc7f7662ef0f73e59c2f30b2b3b8e4"}}

928ab519cd995aeae5eced3dbe4b7e86c8bc7f7662ef0f73e59c2f30b2b3b8e4のlayer.tarを展開し、chal/flag.txtを見る。

73c73d_w17h1n_7h3_d0ck3rf1l3}
Find the rest of the flag by yourself!

フラグの後半が見つかった。次にacbb216b17482071caca135101282177f6ffed7b8ee0bfc5323aae103c216d74.jsonを見る。この中のデータの一つに以下のデータがある。

{"created":"2022-09-03T07:46:12.680399343Z","created_by":"/bin/sh -c echo \"ZmxhZ3tuM3Yzcl9sMzR2M181M241MTcxdjNfMW5mMHJtNDcxMG5fdW5wcjA=\" \u003e /dev/null","empty_layer":true}

base64文字列をデコードする。

$ echo ZmxhZ3tuM3Yzcl9sMzR2M181M241MTcxdjNfMW5mMHJtNDcxMG5fdW5wcjA= | base64 -d
flag{n3v3r_l34v3_53n5171v3_1nf0rm4710n_unpr0

先ほどのフラグの後半部と結合すると、フラグになる。

flag{n3v3r_l34v3_53n5171v3_1nf0rm4710n_unpr073c73d_w17h1n_7h3_d0ck3rf1l3}

Word Wide Web (web)

http://web.chal.csaw.io:5010/stuffにアクセスすると、たくさんの単語が表示される。その中に一つの単語だけリンクがあるので、アクセスしてみる。すると、またたくさんの単語が表示され、その中に一つの単語だけリンクがある。繰り返し、リンクをたどる処理をスクリプトにし、実行する。

#!/usr/bin/env python3
import requests
import re

s = requests.Session()

url = 'http://web.chal.csaw.io:5010'
path = ''

while True:
    r = s.get(url + path)
    body = r.text

    pattern = '\<a href="(\/.+)"\>'
    m = re.search(pattern, body)
    if m is None:
        print(body)
        break
    path = m.group(1)

行きついたところにフラグがあった。

CTF{w0rdS_4R3_4mAz1nG_r1ght}

Gotta Crack Them All (crypto)

keyとXORを取っている。パスワードの一つが"Cacturne-Grass-Dark"とわかっている。

$ nc crypto.chal.csaw.io 5002
You can encrypt a pre-approved password using this service.

What is the password you would like to encrypt?

>> Cacturne-Grass-Dark
The encrypted password is: b'kz\xc6\xb9\xd9Du\xcb\x8a\x9e\xe0\x9d\xbeo\xee\x03\xcf\xddd'
Would you like to go again? (Y/N)

暗号化リストの6行目が該当の暗号だとわかるので、XORでキーを割り出し、復号する。鍵の長さが不足しているので、推測して復号する。37行目から以下のように推測し、XORでキーを割り出し、再度復号する。

b'Hakamo-o-Dragon-Fig\xd5\xe3wj0' → b'Hakamo-o-Dragon-Fighting'
#!/usr/bin/env python3
from Crypto.Util.strxor import strxor
from string import *

with open('encrypted_passwords.txt', 'rb') as f:
    words = f.read().splitlines()

with open('leaked_password.txt', 'r') as f:
    leaked = f.read()

key = strxor(words[5], leaked.encode())

for word in words:
    password = strxor((key * 2)[:len(word)], word)
    print('[-]', password)

# guess
key += strxor(words[36][len(key):], b'hting')

for word in words:
    password = strxor((key * 2)[:len(word)], word)
    print('[+]', password)

実行結果は以下の通り。

[-] b'Kingler-Water'
[-] b'Darkrai-Dark'
[-] b'Chingling-Psychic'
[-] b'Happiny-Normal'
[-] b'Clawitzer-Water'
[-] b'Cacturne-Grass-Dark'
[-] b'Slowking-Poison-Psy\xde\xffwg'
[-] b'Sneasel-Dark-Ice'
[-] b'Hoopa-Psychic-Ghost'
[-] b'Rhyperior-Ground-Ro\xde\xfc'
[-] b'Seedot-Grass'
[-] b'Chinchou-Water-Elec\xc9\xe5wg'
[-] b'Tsareena-Grass'
[-] b'Excadrill-Ground-St\xd8\xf2r'
[-] b'Gumshoos-Normal'
[-] b'Kricketune-Bug'
[-] b'Dartrix-Grass-Flyin\xda'
[-] b'Pikipek-Normal-Flyi\xd3\xf0'
[-] b'Dugtrio-Ground-Stee\xd1'
[-] b'Basculin-Water'
[-] b'Hippowdon-Ground'
[-] b'Togetic-Fairy-Flyin\xda'
[-] b'Finneon-Water'
[-] b'Riolu-Fighting'
[-] b'Entei-Fire'
[-] b'Spritzee-Fairy'
[-] b'Mantine-Water-Flyin\xda'
[-] b'Silvally-Normal'
[-] b'Bellsprout-Grass-Po\xd4\xe4qj'
[-] b'Wyrdeer-Normal-Psyc\xd5\xfe}'
[-] b'Marill-Water-Fairy'
[-] b'Herdier-Normal'
[-] b'Altaria-Dragon-Flyi\xd3\xf0'
[-] b'Thwackey-Grass'
[-] b'Spewpa-Bug'
[-] b'Bronzong-Steel-Psyc\xd5\xfe}'
[-] b'Hakamo-o-Dragon-Fig\xd5\xe3wj0'
[-] b'Chespin-Grass'
[-] b'Mr. Mime-Psychic-Fa\xd4\xe5g'
[-] b'Tornadus-Flying'
[-] b'Pupitar-Rock-Ground'
[-] b'Combusken-Fire-Figh\xc9\xfepc'
[-] b'Guzzlord-Dark-Drago\xd3'
[-] b'Carnivine-Grass'
[-] b'Growlithe-Fire'
[-] b'Grubbin-Bug'
[-] b'Gastrodon-Water-Gro\xc8\xf9z'
[-] b'Goomy-Dragon'
[-] b'Thievul-Dark'
[-] b'1n53cu2357234mc1ph3\x8f'
[-] b'Seadra-Water'
[+] b'Kingler-Water'
[+] b'Darkrai-Dark'
[+] b'Chingling-Psychic'
[+] b'Happiny-Normal'
[+] b'Clawitzer-Water'
[+] b'Cacturne-Grass-Dark'
[+] b'Slowking-Poison-Psychic'
[+] b'Sneasel-Dark-Ice'
[+] b'Hoopa-Psychic-Ghost'
[+] b'Rhyperior-Ground-Rock'
[+] b'Seedot-Grass'
[+] b'Chinchou-Water-Electric'
[+] b'Tsareena-Grass'
[+] b'Excadrill-Ground-Steel'
[+] b'Gumshoos-Normal'
[+] b'Kricketune-Bug'
[+] b'Dartrix-Grass-Flying'
[+] b'Pikipek-Normal-Flying'
[+] b'Dugtrio-Ground-Steel'
[+] b'Basculin-Water'
[+] b'Hippowdon-Ground'
[+] b'Togetic-Fairy-Flying'
[+] b'Finneon-Water'
[+] b'Riolu-Fighting'
[+] b'Entei-Fire'
[+] b'Spritzee-Fairy'
[+] b'Mantine-Water-Flying'
[+] b'Silvally-Normal'
[+] b'Bellsprout-Grass-Poison'
[+] b'Wyrdeer-Normal-Psychic'
[+] b'Marill-Water-Fairy'
[+] b'Herdier-Normal'
[+] b'Altaria-Dragon-Flying'
[+] b'Thwackey-Grass'
[+] b'Spewpa-Bug'
[+] b'Bronzong-Steel-Psychic'
[+] b'Hakamo-o-Dragon-Fighting'
[+] b'Chespin-Grass'
[+] b'Mr. Mime-Psychic-Fairy'
[+] b'Tornadus-Flying'
[+] b'Pupitar-Rock-Ground'
[+] b'Combusken-Fire-Fighting'
[+] b'Guzzlord-Dark-Dragon'
[+] b'Carnivine-Grass'
[+] b'Growlithe-Fire'
[+] b'Grubbin-Bug'
[+] b'Gastrodon-Water-Ground'
[+] b'Goomy-Dragon'
[+] b'Thievul-Dark'
[+] b'1n53cu2357234mc1ph32'
[+] b'Seadra-Water'

この中からadminのパスワードと思われる、ポケモンのモンスター名でないものがフラグ。

1n53cu2357234mc1ph32

Phi Too Much In Common (crypto)

$ nc crypto.chal.csaw.io 5000
**********   TOO MUCH IN COMMON      **********

   Have at it!

/------------------------------\
|           COMMANDS              |
|                                 |
|   1) ciphertext_info            |
|   2) solve_challenge <password> |
|   3) exit                       |
\------------------------------/

> 1
N = 66230379529365020257079274492974065493126330971013187062230550667484536336498921190934189244362957312249586769969174687366278892047021464836842536529323843984917711253546999262427337059964746457287003004612989077687496386808318868155443955433495669933416861831960406309846051654977485404679018836950591207073
e = 3203033219058218846809870595889303749356759660802874921757919347285050689023
c = 16602634789029238804962653840385596228672793718393061759190379436477434589539439401344810124749337507914027486814367920958698864422367836842000865838169143446244202199945258912479805482529460471614475452210103481314126501905118652422131330354981855680471366776223771244856426005553912820240798175126871245542
> /------------------------------\
|           COMMANDS              |
|                                 |
|   1) ciphertext_info            |
|   2) solve_challenge <password> |
|   3) exit                       |
\------------------------------/

> 1
N = 97158515552243305887166842257349497196761944723849796333314570210192465467248058977973817520811466238434579242618470413011446370885671769783517122178886737136267378478851110781637707375501871752419723352747812903232025026662684439394479235396634760931569159089432041476376506570454407306279469567609447874133
e = 18408112354007983870269760119469358714794425138855862698209283717095203251527
c = 40597559051967819648988812970676624339815299554173974340185756834851649772498697096416499057158638069790189014561709885506503658509085168258788719816632907655273832559720389170846504255470221971178328064025401908633312901398161846240306537659732243916190526957713561623469784228736357566604931785270598213207
> /------------------------------\
|           COMMANDS              |
|                                 |
|   1) ciphertext_info            |
|   2) solve_challenge <password> |
|   3) exit                       |
\------------------------------/

> 1
N = 77552624443207359646089700308292520363775651928919510316405587244412029459793814355599651363969705936241997624014034446410130409040520904704222288741418451869075450281604530862820189902262119380780789529627644683074820698307434013253176331127640530460518445472446585030342275865032619556106417427541045657889
e = 8290673712481114196337956497649210842364305631913472623568114174185580849831
c = 62227187843422899487768810431178945148094010586999335663611835937007997481020063524535949914284336751562242177109249354250522379832825592726182607445470462086490320419744326295588542630205868022550363635863255848462379142157715139645979242744412831701661714996123243796658849718267858779823092830129715228493
> /------------------------------\
|           COMMANDS              |
|                                 |
|   1) ciphertext_info            |
|   2) solve_challenge <password> |
|   3) exit                       |
\------------------------------/

> 1
N = 66230379529365020257079274492974065493126330971013187062230550667484536336498921190934189244362957312249586769969174687366278892047021464836842536529323843984917711253546999262427337059964746457287003004612989077687496386808318868155443955433495669933416861831960406309846051654977485404679018836950591207073
e = 6461914606527340033058068371191892105920231183404124838916730227375848219991
c = 61747655950367328690845311363491999477172913254413763024768230345790552599866557902469338036025727322620488230742095151608347464525375842535363889225896691398096757040997363089905454473662909295617645290221572120790915100723370598403902217780692569984791434842099938496690776449770494327844731248133766400182
> /------------------------------\
|           COMMANDS              |
|                                 |
|   1) ciphertext_info            |
|   2) solve_challenge <password> |
|   3) exit                       |
\------------------------------/

> 2 12345678

Nope!

何回かciphertext_infoを取得すると、同じNで異なるeで暗号化したcが得られる。Common Modulus Attackで復号する。
これをクリアすると、phiを計算する問題が出題される。N, e, dがわかっているので、phiを算出し答える。

#!/usr/bin/env python3
import socket
import gmpy2
from Crypto.Util.number import *
from Crypto.PublicKey import RSA

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

def commom_modulus_attack(c1, c2, e1, e2, n):
    gcd, s1, s2 = gmpy2.gcdext(e1, e2)
    if s1 < 0:
        s1 = -s1
        c1 = gmpy2.invert(c1, n)
    elif s2 < 0:
        s2 = -s2
        c2 = gmpy2.invert(c2, n)

    v = pow(c1, s1, n)
    w = pow(c2, s2, n)
    x = (v*w) % n
    return x

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('crypto.chal.csaw.io', 5000))

ns = []
es = []
cs = []
while True:
    data = recvuntil(s, b'\n> ')
    print(data + '1')
    s.sendall(b'1\n')
    data = recvuntil(s, b'\n').rstrip()
    print(data)
    N = int(data.split(' ')[-1])
    data = recvuntil(s, b'\n').rstrip()
    print(data)
    e = int(data.split(' ')[-1])
    data = recvuntil(s, b'\n').rstrip()
    print(data)
    c = int(data.split(' ')[-1])

    if N in ns:
        index = ns.index(N)
        e1 = es[index]
        e2 = e
        c1 = cs[index]
        c2 = c

        m = commom_modulus_attack(c1, c2, e1, e2, N)
        try:
            password = long_to_bytes(m).decode()
            break
        except:
            ns.append(N)
            es.append(e)
            cs.append(c)
    else:
        ns.append(N)
        es.append(e)
        cs.append(c)

data = recvuntil(s, b'\n> ')
print(data + '2 ' + password)
s.sendall(b'2 '+ password.encode() + b'\n')

data = recvuntil(s, b'\n> ')

N = int(data.splitlines()[6].split(' ')[-1])
e = int(data.splitlines()[8].split(' ')[-1])
d = int(data.splitlines()[10].split(' ')[-1])

rsa = RSA.construct((N, e, d))
p = rsa.p
q = rsa.q
phi = (p - 1) * (q - 1)

print(data + '2 ' + str(phi))
s.sendall(b'2 '+ str(phi).encode() + b'\n')

for _ in range(5):
    data = recvuntil(s, b'\n').rstrip()
    print(data)

実行結果は以下の通り。

**********   TOO MUCH IN COMMON      **********

   Have at it!

/------------------------------\
|           COMMANDS              |
|                                 |
|   1) ciphertext_info            |
|   2) solve_challenge <password> |
|   3) exit                       |
\------------------------------/

> 1
N = 92599299343232292261834178543890237387484921511614229653415119150379025672557158079593766699105621305545121399064580043995019950138120667405208241479131905776463986888676538577986955456126293031500188512827288084619006072487276385597167984228025224038068741864645024709036190735173856986216816635205617371251
e = 16928907533626229902284227783657840508336857921783474343555553116847230865331
c = 79570849854711607139417345642388677273839703713555430342321535812820875952624829829228862056309772593548873488031858181225277251937313263316901165117441026524819040228237484837355605354466786258483584226934759111747348086470492341818531267980922417619965568267394278979803974326580670936121922143960459533798
> /------------------------------\
|           COMMANDS              |
|                                 |
|   1) ciphertext_info            |
|   2) solve_challenge <password> |
|   3) exit                       |
\------------------------------/

> 1
N = 66841426676804688396771769972561843618412039028675927408156721898462403499632272355018096006084265389119375725460220991982054036255312770486606664268573532988575589293219994782714415202818755031344902659461329975751170875314154800578771280759291227371080846206675137271359015081773571314195744455213154427597
e = 20643168474203546933298257670240213719583351519179679768833156342247300184973
c = 34367729465156325930897062256052971848240755538905676844390968630323096992780138935126484933870426892930284766886879143689207515309637107833249240440322560307519059504083894525893593615656325221819307854771539020472177848929350810915040438732916654817910816704684422906316608152236582001686917198854494566951
> /------------------------------\
|           COMMANDS              |
|                                 |
|   1) ciphertext_info            |
|   2) solve_challenge <password> |
|   3) exit                       |
\------------------------------/

> 1
N = 93150654553270961014768164130409121928813076081873161613053090131547851938714893597908885804564067189857086242620569580714858215239915642529188260181294150027598408092318846514660800971605904900252102919761867835829741604253924392570808908720438859080776198561197727644262517765355363081126013877332755639777
e = 14564002562960621555096419172124728966001366362068142277514940102932321715587
c = 26823373791497368615326329325823097570538826785067685635320982007306494352439874098201830966485969176118141362926620739747065514898346909721894614163220613190756377329935483173296178692954012036304274932386801987084207773762448597756225917999340679191286184229577288745713915917134865116686254096088755399915
> /------------------------------\
|           COMMANDS              |
|                                 |
|   1) ciphertext_info            |
|   2) solve_challenge <password> |
|   3) exit                       |
\------------------------------/

> 1
N = 97934790717457816377408047595808017336290557647132874078463273042273480783693965913047913569035873972956920187266096013384149733128634153495383667005783367406343085928898092741457647502175596143302932648245342944432576818196386356562830861689797976228719838904175290216793497280116034968767061763793356427923
e = 17026356965343564383006139344035147997574236973261931055393297574271594721947
c = 36422996639906934280791759292864646158321891131994714018774979799204112699817423912629655615142621165865073844048825501188197026566655735034651136290222437988406549568072738686120589143594127844448881834208636747038354360336434517062474426581430949103364256096272027091751555393826040970441807243209096030970
> /------------------------------\
|           COMMANDS              |
|                                 |
|   1) ciphertext_info            |
|   2) solve_challenge <password> |
|   3) exit                       |
\------------------------------/

> 1
N = 93150654553270961014768164130409121928813076081873161613053090131547851938714893597908885804564067189857086242620569580714858215239915642529188260181294150027598408092318846514660800971605904900252102919761867835829741604253924392570808908720438859080776198561197727644262517765355363081126013877332755639777
e = 3903045633246461963443996627866752963174454790408982275112905147194381029419
c = 63047438524847764130018540464138007337825148721616052463529587222913141611318978605784743617174005749795694486145667033309999978142171791961527324895350873542614181199261564385165826366721405368299699380566756406246665364100155930677775564053779176118898354227653325334993025828331467179165827916940801675049
> /------------------------------\
|           COMMANDS              |
|                                 |
|   1) ciphertext_info            |
|   2) solve_challenge <password> |
|   3) exit                       |
\------------------------------/

> 2 d0nt_reUs3_c0mm0n_m0duLus_iN_RSA

********** What the Phi?  **********

Give me phi and I'll give you a flag


N = 97934790717457816377408047595808017336290557647132874078463273042273480783693965913047913569035873972956920187266096013384149733128634153495383667005783367406343085928898092741457647502175596143302932648245342944432576818196386356562830861689797976228719838904175290216793497280116034968767061763793356427923

e = 24246426518850857132122259750361730417342755259639998613040824532553721920239

d = 38550551838267677901600814308974794800412771782528865753556316459464100087576689630219459038572918207153521275050604895720746906733498267505162038809151851814433919954002172458165034676258879934994139534497931839686512983498911853904726892873467416261172268490783160334649530502171196793008921183132824046351
/------------------------------\
|           COMMANDS              |
|                                 |
|   1) try_again                  |
|   2) phi <phi_value>            |
|   3) exit                       |
\------------------------------/

> 2 97934790717457816377408047595808017336290557647132874078463273042273480783693965913047913569035873972956920187266096013384149733128634153495383667005783347591848586542793419570625418124529900910744439497817365571012710187525969553709932015969835839230374334244769937204684915684471122778808735239720897227128

What?! How did you do that??

flag{aR3nT_U_tH3_RSA_ninJA}
flag{aR3nT_U_tH3_RSA_ninJA}

Not Too Taxing (crypto)

bkcrackで既知平文攻撃を行う。

$ ./bkcrack -C Tax_Ret_Form_Nov_2021.zip -c Tax_Ret_Form_Nov_2021.pdf -p Tax_Ret_Form_Blank.pdf
bkcrack 1.0.0 - 2020-11-11
Generated 4194304 Z values.
[11:07:32] Z reduction using 150176 bytes of known plaintext
22.6 % (33917 / 150176)
255 values remaining.
[11:07:36] Attack on 255 Z values at index 117285
Keys: b2c4a24e f036ff3a 998f6727 
47.5 % (121 / 255)
[11:07:37] Keys
b2c4a24e f036ff3a 998f6727

$ ./bkcrack -C Tax_Ret_Form_Nov_2021.zip -c Tax_Ret_Form_Nov_2021.pdf -k b2c4a24e f036ff3a 998f6727 -d Tax_Ret_Form_Nov_2021.pdf
bkcrack 1.5.0 - 2022-07-07
[13:41:06] Writing deciphered data Tax_Ret_Form_Nov_2021.pdf (maybe compressed)
Wrote deciphered data.

抽出したTax_Ret_Form_Nov_2021.pdfをPDFStreamDumperで開き、各オブジェクトを見ていく。オブジェクト1268にフラグが書いてあった。

flag{1f_y0u_u53_z1pcryp70_4ny0n3_c4n_aud17_y0u}

ADDA CTF Writeup

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

Signals From Space (Hardware 80)

sstvでデコードしてみる。

$ sstv -d signal_from_space.wav -o flag.png
[sstv] Searching for calibration header... Found!    
[sstv] Detected SSTV mode Martin 2
[sstv] Decoding image...   [#############################################] 100%
[sstv] Drawing image data...
[sstv] ...Done!

出力した画像にフラグが書いてあった。

ctf{0ld_5ch00l_73ch}

passWORDLE (Reverse 80)

スクリプト"./js/passwordle.js"を見てみる。

const targetWords = [
    'password',
    '123456',
    '12345678',
    '1234',
    'qwerty',
    '12345',
    :
    :
    'epson',
    'evangeli',
    'eeeee1',
    'eyphed',
    
]
    :
    :
function randomPassword() {

    const offsetFromDate = new Date(2022, 0, 1)
    const msOffset = Date.now() - offsetFromDate
    const dayOffset = msOffset / 1000 / 60 / 60 / 24
    const res = targetWords[Math.floor(dayOffset)]
    return res;
}
    :
    :

年月日によって、パスワードが決まり、2022/1/1と2022/10/10の日数の差から該当するパスワードを求めることができる。

#!/usr/bin/env python3
import datetime

targetWords = [
    'password',
    '123456',
    '12345678',
    '1234',
    'qwerty',
    '12345',
    :
    :
    'epson',
    'evangeli',
    'eeeee1',
    'eyphed',  
]

dt1 = datetime.datetime(2022, 1, 1)
dt2 = datetime.datetime(2022, 10, 10)
td = (dt2 - dt1).days
password = targetWords[td]
flag = 'ctf{%s}' % password
print(flag)
ctf{united}

The searcher (Web 180)

フラグに使われている文字を入力すると、Resultsにその数が表示される。位置もあっていると、regularにその数が表示される。このことから推測していく。
使われている文字は以下の文字であることがわかる。

BRSTacdefghinort{}

あとは先頭から推測していく。最終的には以下を入力すると、本当のフラグが表示された。

ctf{SearchingToBeRedefined}
You rock. Here is the real Flag ctf{SearchIsMakingLifeEasier}
ctf{SearchIsMakingLifeEasier}

Cat Caffee (Network 100)

pcapの通信データは802.11で、WPAで暗号化されてるようなので、クラックする。

$ aircrack-ng -w dict/rockyou.txt traffic.pcap
Reading packets, please wait...
Opening traffic.pcap
Read 41264 packets.

   #  BSSID              ESSID                     Encryption

   1  00:1E:42:12:D6:A2                            Unknown
   2  00:21:29:77:A3:05  CatCaffee                 WPA (1 handshake)
   3  0C:F4:D5:D5:76:43  Recover.Me-157640         Unknown
   4  50:C7:BF:79:E4:4A                            WPA (0 handshake)
   5  BA:2E:61:13:FD:CD                            WEP (0 IVs)
   6  D4:68:4D:65:F3:93  Recover.Me-25F390         Unknown
   7  D8:97:BA:01:04:AE  dbed26                    Unknown
   8  DA:97:BA:01:04:AF  UniFi                     Unknown
   9  F4:17:B8:CD:66:F7  Rumburak                  Unknown
  10  F4:17:B8:CD:66:FB  Rumburak                  WPA (0 handshake)

Index number of target network ? 2

Reading packets, please wait...
Opening traffic.pcap
Read 41264 packets.

1 potential targets


                               Aircrack-ng 1.6 

      [00:35:26] 10532772/14344392 keys tested (4892.84 k/s) 

      Time left: 12 minutes, 59 seconds                         73.43%

                          KEY FOUND! [ THUNDERCATS ]


      Master Key     : D8 FE 43 A0 2E AF 31 BE 52 AC CA ED BE 15 51 96 
                       66 DE EC 25 78 49 2A 1E 72 EE 1D D1 6A B0 CF DB 

      Transient Key  : 02 D1 B6 10 D1 5C 94 8E 5D D2 81 AF 00 D4 75 29 
                       7C 33 CB 12 33 88 CD 6E 10 BB 7B 39 D9 A0 D4 C1 
                       30 32 7F 8B 46 56 8E B0 71 0B CB 6A 15 C0 ED 00 
                       00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 

      EAPOL HMAC     : 73 2E CB C8 7B 0B 20 E3 A6 E6 19 32 FF 42 83 DE

Wiresharkで、[設定]>[Protocols]>[IEEE 802.11]の画面の「編集」から以下の設定を行い、通信の復号を行う。

Key type: wpa-pwd
Key: THUNDERCATS

httpでフィルタリングすると、途中コマンドインジェクションと思われる通信がある。No.16208のパケットを含むHTTPストリームはこうなっている。

GET /setup.cgi?ping_ipaddr1=1&ping_ipaddr2=1&ping_ipaddr3=1&ping_ipaddr4=1&ping_size=60&ping_number=1&ping_interval=1000&ping_timeout=5000&start=Start+Test&todo=ping_test&this_file=Diagnostics.htm&next_file=Diagnostics.htm&c4_ping_ipaddr=1.1.1.1;/bin/ls HTTP/1.1
Host: 192.168.1.1
Cache-Control: max-age=0
Authorization: Basic YWRtaW46YWRtaW4=
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.134 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://192.168.1.1/
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Connection: close

HTTP/1.0 200 OK
sh: cannot create 1: Unknown error 30
killall: pingmultilang: no process killed
killall: 2: no process killed
ARARPTable.htm
AccessRes.htm
Administration.htm
AdvancedWSettings.htm
AppGaming.htm
Backup.htm
DHCPClientTable.htm
DMZ.htm
DSL_status.htm
Diagnostics.htm
EditList.htm
Factorydefaults.htm
FirmwareUpgrade.htm
HNAP1
LANG_BP.js
LANG_DE.js
LANG_EN.js
LANG_FR.js
LANG_GR.js
LANG_IT.js
LANG_NL.js
LANG_PO.js
LANG_RU.js
LANG_TR.js
Language.htm
LocalNetwork.htm
Log.htm
Ping.htm
PortRangeTriggering.htm
QoS.htm
Routercfg.cfg
Routing_Table.htm
Security.htm
Setup.htm
Setup_DDNS.htm
Setup_MAC.htm
Setup_routing.htm
SingleForwarding.htm
Status.htm
Summary.htm
UI_02.gif
UI_03.gif
UI_03_ar.gif
UI_04.gif
UI_04_ar.gif
UI_05.gif
UI_06.gif
UI_07.gif
UI_10.gif
UI_11.gif
UI_12.gif
UI_13.gif
UI_Cisco.gif
UI_Linksys.gif
VPNPassthrough.htm
WClientMACList.htm
WMACFilter.htm
WNetwork.htm
WPS.htm
WSC.htm
WSecurity.htm
Wireless.htm
Wireless_wifi.htm
adsl_driver.htm
ajax.js
cgi_lang.bp
cgi_lang.de
cgi_lang.en
cgi_lang.fr
cgi_lang.gr
cgi_lang.it
cgi_lang.nl
cgi_lang.po
cgi_lang.ru
cgi_lang.tr
cisco_bp.css
cisco_de.css
cisco_en.css
cisco_fr.css
cisco_gr.css
cisco_it.css
cisco_nl.css
cisco_po.css
cisco_ru.css
cisco_tr.css
err_msg
err_msg.bp
err_msg.de
err_msg.en
err_msg.fr
err_msg.gr
err_msg.it
err_msg.nl
err_msg.po
err_msg.ru
err_msg.tr
func.js
fw_version.pat
gen_page
help
help_bp.css
help_bp.js
help_de.css
help_de.js
help_en.css
help_en.js
help_fr.css
help_fr.js
help_gr.css
help_gr.js
help_it.css
help_it.js
help_nl.css
help_nl.js
help_po.css
help_po.js
help_ru.css
help_ru.js
help_tr.css
help_tr.js
index.htm
lh_bg.gif
lh_cisco.gif
linux.js
log_data.htm
others_de.js
others_en.js
others_fr.js
ppp_log
reboot_guage.htm
restore_config.cgi
rh_bg.gif
rh_cisco.gif
set_vpn.js
setup.cgi
upgrade_flash.cgi
upload_lang.cgi
utility.js
wps_mouseover.gif
wps_nonselectable.gif
wps_selectable.gif
Content-type: text/html

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=windows-1252">


<meta name="description" content="LINKSYS WAG54G2 Wireless-G ADSL Home Gateway">

<META http-equiv="Pragma" CONTENT="no-cache">
<META HTTP-EQUIV="Cache-Control" CONTENT="no-cache">
<script language="javascript" type="text/javascript" src="LANG_EN.js"></script>
<script language="javascript" type="text/javascript" src="others_en.js"></script>
<script language="javascript" type="text/javascript" src="func.js"></script>

<script language="javascript" type="text/javascript" src="linux.js"></script>
<script language="javascript" type="text/javascript">
strHtml='<title>'+vdiag+'</title>';
dw(rmTitleBr(strHtml));
strHtml='<LINK REL="stylesheet" TYPE="text/css" HREF="'+vcss_type+'">';
dw(strHtml);

<!-- hide script from old browsers

function checkData2(action)
{
    var cf = document.forms[0];
	var msg = "";
	
	if( action != "save")
	    if( blankIP(cf.ping_ipaddr1, cf.ping_ipaddr2, cf.ping_ipaddr3, cf.ping_ipaddr4) 
	    || badIP(cf.ping_ipaddr1, cf.ping_ipaddr2, cf.ping_ipaddr3, cf.ping_ipaddr4, 255)
	    || isIPMulticast(cf.ping_ipaddr1.value+"."+cf.ping_ipaddr2.value+"."+cf.ping_ipaddr3.value+"."+cf.ping_ipaddr4.value))
		    msg+= msg_invalid_ip;
	msg+= checkInt(cf.ping_size, msg_ping_size, 60, 1514, true);
	msg+= checkInt(cf.ping_number, msg_ping_number, 1, 100, true);
	msg+= checkInt(cf.ping_interval, msg_ping_interval, 100, 9999, true);
	msg+= checkInt(cf.ping_timeout, msg_ping_timeout, 1000, 9999, true);
	if (msg.length > 1)
	{
		alert(msg);
		return false;
	}
    dataToHidden(cf);
    return true;
}

function doPing()
{
    if (checkData2("ping")){
        openDataSubWin('setup.cgi?next_file=Ping.htm', bigsub);
        return true;
    }
    else
        return false; //no submit
}

function checkData()
{
    var cf = document.forms[0];
    if (checkData2("save")){
        cf.todo.value="save";
        return true;
    }
    else
        return false;
}

//-->
</script>
</head>

<body link="#FFFFFF" vlink="#FFFFFF" alink="#FFFFFF" onload="dataToVisible(document.forms[0])">
<form name="diagnostics" method="POST" action="setup.cgi">
<div align="center">

<table border="0" cellpadding="0" cellspacing="0" width="810" bgcolor="#2971b9">
<script language="javascript" type="text/javascript">
showHead('Wireless ADSL2+ Gateway','WAG54G2','V1.00.10',vadmin);
</script>

<script language="javascript" type="text/javascript">
showMenu(vadmin,admin_diag);
</script>
</table>

<!-- data table--> 


<table border="0" cellpadding="0" cellspacing="0" width="810" bgcolor="#ffffff">
<tr>
 <td width="164" height="15" bgColor="#e7e7e7" colspan="2" align="right"><img border="0" src="UI_03.gif" width="8" height="15"></TD>
 <td width="646" height="14" colspan="2"><img border="0" src="UI_02.gif" width="646" height="15"></TD>

</TR>


<tr>
  <td width="164" height="24" colspan="2" class="bwhead"><script language="javascript" type="text/javascript">
dw(va_ping);
</script></TD>
  <td width="454">&nbsp;  </TD>
  <td width="192" valign="bottom" bgcolor="#2971b9" background="rh_bg.gif">
  &nbsp;&nbsp; <A href="setup.cgi?next_file=help/h_Diagnostics.htm" class="submenu" target="_blank"><script language="javascript" type="text/javascript">
dw(vhelp);
</script></a></TD>


</TR>

<tr>
 <td width="156" bgColor="#e7e7e7" valign="top" class="boldhead"><script language="javascript" type="text/javascript">
dw(va_ping_para);
</script></TD>
 <td width="8" background="UI_04.gif"> </TD>
 <td valign="top">
 <table class="std">
 <tr>
<TD width=101 height=25><script language="javascript" type="text/javascript">
dw(va_ping_ip);
</script></TD>
<TD width=296 height=25>
<input type="text" class="ipnum" maxlength="3" size="4" name="ping_ipaddr1" value=""> .
<input type="text" class="ipnum" maxlength="3" size="4" name="ping_ipaddr2" value=""> .
<input type="text" class="ipnum" maxlength="3" size="4" name="ping_ipaddr3" value=""> .
<input type="text" class="ipnum" maxlength="3" size="4" name="ping_ipaddr4" value=""></TD>
           
</TR>
 <tr>
<TD width=101 height=25><script language="javascript" type="text/javascript">
dw(va_ping_size);
</script></TD>
<TD width=296 height=25>
<input type="text" class="num" maxlength="4" size="4" name="ping_size" value="60">&nbsp;<script language="javascript" type="text/javascript">
dw(vBytes);
</script>
</TD>
           
</TR>
 <tr>
<TD width=101 height=25><script language="javascript" type="text/javascript">
dw(va_ping_num);
</script></TD>
<TD width=296 height=25>
<input type="text" class="num" maxlength="3" size="4" name="ping_number" value="1"> <script language="javascript" type="text/javascript">
dw(va_ping_num_value);
</script>
</TD>
           
</TR>
<tr>
<TD width=101 height=25><script language="javascript" type="text/javascript">
dw(va_ping_interval);
</script></TD>
<TD width=296 height=25>
<input type="text" class="num" maxlength="4" size="5" name="ping_interval" value="1000">&nbsp;<script language="javascript" type="text/javascript">
dw(vmseconds);
</script> 
</TD>

</TR>
<tr>
<TD width=101 height=25><script language="javascript" type="text/javascript">
dw(va_ping_time);
</script></TD>
<TD width=296 height=25>
<input type="text" class="num" maxlength="4" size="5" name="ping_timeout" value="5000">&nbsp;<script language="javascript" type="text/javascript">
dw(vmseconds);
</script> </TD>

</TR>
<tr>
<TD width=101 height=25></TD>
<TD width=296 height=25>
 	<script language="javascript" type="text/javascript">
 	dw('<INPUT type="submit" name="start" value="');
 	dw(va_ping_start);dw('" onClick="return doPing();">');</script>
</TD>
</TR>
<tr>
<TD width=101 height=25><script language="javascript" type="text/javascript">
dw(va_ping_result);
</script></TD>
<TD width=296 height=25><B><script language="javascript" type="text/javascript">
dw(va_ping_result1);
</script>1 <script language="javascript" type="text/javascript">
dw(va_ping_result2);
</script>0 <script language="javascript" type="text/javascript">
dw(va_ping_result3);
</script>0ms 
</b></TD>
           
</TR>
 </table><br>&nbsp;
 </TD>
 <td valign="bottom" rowspan="99" bgcolor="#2971b9" background="rh_bg.gif"><img src="rh_cisco.gif" width="192" height="64" alt="Cisco Logo" border="0"></TD>

</TR>

<tr>
  <td colspan="2" bgcolor="#5b5b5b">&nbsp;  </TD>
  <td class="footer" bgcolor="#2971b9" height="33" align="right">
  <script language="javascript" type="text/javascript">
  showSave();
  </script>
  <script language="javascript" type="text/javascript">
  showCancel("Diagnostics.htm");
  </script>	
  &nbsp;  &nbsp; </TD>
</TR>


</table>

</div>

<input type="hidden" name="todo" value="ping_test">
<input type="hidden" name="this_file" value="Diagnostics.htm">
<input type="hidden" name="next_file" value="Diagnostics.htm">
<input type="hidden" name="c4_ping_ipaddr" value="">
<input type="hidden" name="message" value="">

</form>     
</body>

</html>

この後もコマンドが実行され、その結果を取得している。
/bin/whoami の実行結果は以下の通り。

sh: /bin/whoami: not found

/bin/ls /tmp の実行結果は以下の通り。

acl.conf
cmd_agent
dsl_status
etc
flag.txt
gateway.xml
gateways
hnap_devready
htpasswd
lan_uptime
mini_httpd
nvram
ping2file_result
rc_cmd_file
server_cmd_path
sroute
syslog.conf
udhcpd.conf
upgrade_flash.cgi
var
wlan_uptime
wlist
www

/bin/cat /tmp/flag.txt の実行結果は以下の通り。

ctf{1_s33_n0_g0d_up_h3r3_0th3r_th4n_m3}
ctf{1_s33_n0_g0d_up_h3r3_0th3r_th4n_m3}

Desert USB (Forensics 180)

添付のzipファイルはパスワードがかかっているので、クラックする。

$ fcrackzip -u -D -p dict/rockyou.txt USBimage.zip 


PASSWORD FOUND!!!!: pw == bingoboi
$ unzip -P bingoboi USBimage.zip 
Archive:  USBimage.zip
   creating: USBimage/
 extracting: USBimage/encrypted_file001  
  inflating: USBimage/USBimage.zip
$ 7z d USBimage.zip 

7-Zip [64] 16.02 : Copyright (c) 1999-2016 Igor Pavlov : 2016-05-21
p7zip Version 16.02 (locale=ja_JP.UTF-8,Utf16=on,HugeFiles=on,64 bits,2 CPUs Intel(R) Core(TM) i7-10700 CPU @ 2.90GHz (A0655),ASM,AES-NI)

Open archive: USBimage.zip
--
Path = USBimage.zip
Type = zip
Physical Size = 226063

Updating archive: USBimage.zip

Items to compress: 0

    
Files read from disk: 0
Archive size: 22 bytes (1 KiB)
Everything is Ok

USBimage.imgをAutopsyで開く。削除されたファイルの中にf0016568.elfというファイルがある。実行すると、flag.txtがencrypted_file002になり、暗号化される。いろいろ暗号化を試してみる。

空 -> jkslficoveaqwciutzrghbncmegwqlirt
a  -> \x0bkslficoveaqwciutzrghbncmegwqlirt
z  -> \x10kslficoveaqwciutzrghbncmegwqlirt
xb -> \x12\x09slficoveaqwciutzrghbncmegwqlirt
>>> chr(ord('a') ^ 0x0b)
'j'
>>> chr(ord('z') ^ 0x10)
'j'
>>> chr(ord('x') ^ 0x12)
'j'
>>> chr(ord('b') ^ 0x09)
'k'

暗号文と平文のXORがjkslficoveaqwciutzrghbncmegwqlirtになると推測できるので、XORでフラグを復号する。

#!/usr/bin/env python3
from Crypto.Util.strxor import strxor

with open('encrypted_file001', 'rb') as f:
    ct = f.read()

key = b'jkslficoveaqwciutzrghbncmegwqlirt'

flag = strxor(key, ct).decode()
print(flag)
ctf{you_could_have_just_paid_me}

Balsn CTF 2022 Writeup

この大会は2022/9/3 11:00(JST)~2022/9/5 11:00(JST)に開催されました。
今回もチームで参戦。結果は50点で584チーム中265位でした。
参加賞の問題しか解けませんでしたが、
自分で解けた問題をWriteupとして書いておきます。

welcome (misc)

Webコンソールで、"showflag"と入力すると、フラグが画像で流れていく。


BALSN{WELCOME_2_BALSNCTF!}

CakeCTF 2022 Writeup

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

Welcome (welcome)

Discordに入り、#announcementチャネルのメッセージを見ると、フラグが書いてあった。

CakeCTF{p13a53_tast3_0ur_5p3cia1_cak35}

frozen cake (warmup, crypto)

暗号処理は以下の通り。

・m: フラグの数値化
・p, q: ランダム512ビット素数
・n = p * q
・n, pow(m, p, n), pow(m, q, n), pow(m, n, n)を出力
a = pow(m, p, n)
b = pow(m, q, n)
c = pow(m, n, n)

とすると、以下が成り立つ。

pow(a, q, n) = c
pow(b, p, n) = c

さらに以下が成り立つ。

pow(a, q, q) = pow(a, q - 1, q) * pow(a, 1, q) = pow(a, 1, q) = a % q
pow(b, p, p) = pow(b, p - 1, p) * pow(b, 1, p) = pow(b, 1, p) = b % p

以上を踏まえると、以下が成り立つ。

c % p = pow(b, p, p) = b % p
c % q = pow(a, q, q) = a % q

つまり b - c はpの倍数となるため、nとの最大公約数がpとなりnを素因数分解することができる。あとはどれかの式で通常通り復号すればよい。

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

with open('output.txt', 'r') as f:
    params = f.read().splitlines()

n = int(params[0].split(' ')[-1])
a = int(params[1].split(' ')[-1])
b = int(params[2].split(' ')[-1])
c = int(params[3].split(' ')[-1])

p = GCD(b - c, n)
q = n // p
assert n == p * q and isPrime(p) and isPrime(q)

phi = (p - 1) * (q - 1)
d = inverse(p, phi)
m = pow(a, d, n)
flag = long_to_bytes(m).decode()
print(flag)
CakeCTF{oh_you_got_a_tepid_cake_sorry}

brand new crypto (crypto)

暗号処理の概要は以下の通り。

・pubkey, privkey = keygen()
 ・p, q: 512ビット素数
 ・n = p * q
 ・phi = (p-1)*(q-1)
 ・以下、繰り返し
  ・a: phi未満のランダム整数
  ・b = phi + 1 - a
  ・s: phi未満のランダム整数
  ・t = -s*a * inverse(b, phi) % phi
  ・bとphiの最大公約数が1の場合、繰り返しを終了
 ・(s, t, n), (a, b, n)を返却
・c = []
・flagの各文字について、以下の処理を実行
 ・c1, c2 = enc(m, pubkey)
  ・r: n未満ランダム整数
  ・c1 = m * pow(r, s, n) % n
  ・c2 = m * pow(r, t, n) % n
  ・c1, c2を返却
 ・(c1, c2)をcに追加
・pubkey, cを出力

c2, c2について、式を変形していく。

c1 = m * pow(r, s, n) % n
c2 = m * pow(r, t, n) % n
    ↓
C1 = c1 * inverse(m, n) % n
C2 = c2 * inverse(m, n) % n
pow(C1, t, n) = pow(pow(r, s, n), t, n) = pow(r, s * t, n)
              = pow(pow(r, t, n), s, n) = pow(C2, s, n)

これが成り立つmをブルートフォースで求める。

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

with open('output.txt', 'r') as f:
    params = f.read().splitlines()

(s, t, n) = eval(params[0])
c = eval(params[1])

flag = ''
for (c1, c2) in c:
    for m in range(32, 127):
        C1 = c1 * inverse(m, n) % n
        C2 = c2 * inverse(m, n) % n
        if pow(C1, t, n) == pow(C2, s, n):
            flag += chr(m)
            break
print(flag)
CakeCTF{s0_anyway_tak3_car3_0f_0n3_byt3_p1aint3xt}

Survey (survey)

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

CakeCTF{ar3_y0u_5ati5fi3d_with_thi5_y3ar5_cak3?}

MapleCTF 2022 Writeup

この大会は2022/8/27 9:00(JST)~2022/8/29 9:00(JST)に開催されました。
今回もチームで参戦。結果は1411点で618チーム中33位でした。
自分で解けた問題をWriteupとして書いておきます。

Sanity Check (Misc)

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

maple{w3lc0m3_t0_m4pl3_c7f!}

brsaby (Crypto)

RSA暗号。以下のhintの値を使ってNを素因数分解する。

hint = p**4 - q**3
hint * p**3 = p**7 - p**3 * q**3 = p**7 - n**3
    ↓
p**7 - hint * p**3 - N**3 = 0

この7次方程式を解くと、p, qがわかり、通常通り復号することができる。

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

N = 134049493752540418773065530143076126635445393203564220282068096099004424462500237164471467694656029850418188898633676218589793310992660499303428013844428562884017060683631593831476483842609871002334562252352992475614866865974358629573630911411844296034168928705543095499675521713617474013653359243644060206273
e = 65537
enc = 110102068225857249266317472106969433365215711224747391469423595211113736904624336819727052620230568210114877696850912188601083627767033947343144894754967713943008865252845680364312307500261885582194931443807130970738278351511194280306132200450370953028936210150584164591049215506801271155664701637982648648103
hint = 20172108941900018394284473561352944005622395962339433571299361593905788672168045532232800087202397752219344139121724243795336720758440190310585711170413893436453612554118877290447992615675653923905848685604450760355869000618609981902108252359560311702189784994512308860998406787788757988995958832480986292341328962694760728098818022660328680140765730787944534645101122046301434298592063643437213380371824613660631584008711686240103416385845390125711005079231226631612790119628517438076962856020578250598417110996970171029663545716229258911304933901864735285384197017662727621049720992964441567484821110407612560423282

p = var('p')
sol = solve(p**7 - hint * p**3 - N**3, p)
p = int(sol[0].rhs())
q = N // p
assert N == p * q

phi = (p - 1) * (q - 1)
d = inverse(e, phi)
m = int(pow(enc, d, int(N)))
flag = long_to_bytes(m).decode()
print(flag)
maple{s0lving_th3m_p3rf3ct_r000ts_1s_fun}

jwt (Crypto)

app.pyの処理を整理する。

・dbに以下のユーザを登録する。
 username: "admin"
 password: ランダム32バイトの16進数

■/register (GET/POST)
[POST]
・username: POSTデータのusername
・password: POSTデータのpassword
・usernameとpasswordのどちらかががない場合、エラー
・usernameが登録済みである場合、エラー
・username, passwordでユーザ登録する。
[GET]
・登録画面表示

■/login (GET/POST)
[POST]
・username: POSTデータのusername
・password: POSTデータのpassword
・登録済みのユーザとusername, passwordが一致した場合
 ・token = jwt.sign({"user": username})
 ・tokenをクッキーにセットして、/homeにリダイレクト
[GET]
・ログイン画面表示

■/logout (GET/POST)
・クッキーのtokenを空にセット
・ログイン画面にリダイレクト

■/home (GET)
・"admin"だったら、フラグを表示

adminのtokenを算出することができたら、フラグが得られる。次にjwtのsignの仕組みも確認する。

■パラメータ
・G = secp256k1.G
・order = secp256k1.q
・private: 固定値(不明)
・public: G * private

■sign
・header: '{"alg":"ES256","typ":"JWT"}'のURLセーフbase64エンコード
・data: '{"user":"[username]"}'のURLセーフbase64エンコード
・r, s = _sign(header + "." + data)
 ・z: header + "." + dataのsha256ダイジェスト
 ・k: private
 ・z: zの数値化
 ・r: (k * G).x
 ・s: inverse(k, order) * (z + r * private) % order
 ・r, sを返却
・signature = r.to_bytes(32, "little") + s.to_bytes(32, "little")
・header + "." + data + "." + signatureのURLセーフbase64エンコード

signはECDSAを使用しているが、kは固定なため、異なるユーザのtokenを入手すれば、kを算出できる。

z1 = int(sha256(header + "." + data1).hexdigest(), 16)
z2 = int(sha256(header + "." + data2).hexdigest(), 16)
k = (z1 - z2) * inverse(s1 - s2, order) % order

kを算出すれば、どのユーザのsignも算出できる。このため2ユーザのtokenを入手する。
noraユーザを登録すると以下のtokenが設定される。

eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoibm9yYSJ9.75J83TiCMONIDtDLvDQ8FKHa4wx7DNHkauX-Izu11S-5p2wPx1oWQfa4r7s7R8jwRocJgrEr397mhlUZ39x1lw

necoユーザを登録すると以下のtokenが設定される。

eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoibmVjbyJ9.75J83TiCMONIDtDLvDQ8FKHa4wx7DNHkauX-Izu11S-YiA-7ZjLf5wJUld4EO_p-3JM9aryUSeL45R4JqaKLCQ

この情報を使って、adminのtokenを算出する。

#!/usr/bin/env python3
from Crypto.Util.number import bytes_to_long as bl, inverse
from fastecdsa.curve import secp256k1
from base64 import urlsafe_b64decode, urlsafe_b64encode
from hashlib import sha256
from json import loads, dumps

def b64decode(msg: str) -> bytes:
    if len(msg) % 4 != 0:
        msg += "=" * (4 - len(msg) % 4)
    return urlsafe_b64decode(msg.encode())

def b64encode(msg: bytes) -> str:
    return urlsafe_b64encode(msg).decode().rstrip("=")

G = secp256k1.G
order = secp256k1.q

token1 = 'eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoibm9yYSJ9.75J83TiCMONIDtDLvDQ8FKHa4wx7DNHkauX-Izu11S-5p2wPx1oWQfa4r7s7R8jwRocJgrEr397mhlUZ39x1lw'
token2 = 'eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoibmVjbyJ9.75J83TiCMONIDtDLvDQ8FKHa4wx7DNHkauX-Izu11S-YiA-7ZjLf5wJUld4EO_p-3JM9aryUSeL45R4JqaKLCQ'

_header1, _data1, _signature1 = token1.split('.')
_header2, _data2, _signature2 = token2.split('.')
header1 = loads(b64decode(_header1))
header2 = loads(b64decode(_header2))
data1 = loads(b64decode(_data1))
data2 = loads(b64decode(_data2))
signature1 = b64decode(_signature1)
signature2 = b64decode(_signature2)
assert header1['alg'] == 'ES256' and header1['typ'] == 'JWT'
assert header2['alg'] == 'ES256' and header2['typ'] == 'JWT'
assert data1['user'] == 'nora'
assert data2['user'] == 'neco'

r1 = int.from_bytes(signature1[:32], 'little')
r2 = int.from_bytes(signature2[:32], 'little')
s1 = int.from_bytes(signature1[32:], 'little')
s2 = int.from_bytes(signature2[32:], 'little')
assert r1 == r2

z1 = sha256((_header1 + '.' + _data1).encode()).digest()
z2 = sha256((_header2 + '.' + _data2).encode()).digest()
z1 = bl(z1)
z2 = bl(z2)
k = (z1 - z2) * inverse(s1 - s2, order) % order

username = 'admin'
data = {"user": username}
header = b64encode(
    dumps({"alg": "ES256", "typ": "JWT"}).replace(' ', '').encode()
)
data = b64encode(dumps(data).replace(' ', '').encode())
z = sha256((header + '.' + data).encode()).digest()
z = bl(z)
r = (k * G).x
s = inverse(k, order) * (z + r * k) % order
signature = r.to_bytes(32, 'little') + s.to_bytes(32, 'little')
token = header + '.' + data + '.' + b64encode(signature)
print(token)

adminのtokenの算出結果は以下の通り。

eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiYWRtaW4ifQ.75J83TiCMONIDtDLvDQ8FKHa4wx7DNHkauX-Izu11S8IDLHy2P7MGS7FfPJpZagBzl8OHNHGZalfdll8sV55Kg

このtokenをクッキーにセットして、http://jwt.ctf.maplebacon.org/homeにアクセスすると、フラグが表示された。

maple{3ll1pt!c_c2rv3s_f7w!!!}

Spiral-baby (Crypto)

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

・key: ランダム16バイト
・cipher = Spiral(key, rounds=1)
 ・cipher.rounds = 1
 ・self.keys = [bytes2matrix(key)]
 ・self.BLOCK_SIZE = 16
 ・self.keys.append(spiralLeft(self.keys[-1]))
  行列を90°左回転したものを追加
・メニュー表示
・以下繰り返し
 ・option: 入力
 ・optionが1の場合
  ・ciphertext = cipher.encrypt(flag)→16進数表記で表示
 ・optionが2の場合
  ・plaintext: 16進数表記で入力→デコード
  ・ciphertext = cipher.encrypt(plaintext)→16進数表記で表示

■cipher.encrypt(plaintext)
・plaintext: plaintextの長さを16で割り切れない場合はパディング
・16バイトごとにencrypt_block([平文ブロック])して結合
 ・self.state: [平文ブロック]を4x4の行列に整形
 ・self.add_key(0)
  ・self.stateの行列の各要素で(state[i][j] + keys[0][i][j]) % 255を設定
 ・self.substitute()
  ・self.stateの行列の各要素でSBOXの値を設定
 ・self.rotate()
  ・self.stateの行列を右回転
 ・self.add_key(1)
  ・self.stateの行列の各要素で(state[i][j] + keys[1][i][j]) % 255を設定

いくつか試したところ、鍵の各文字を変更すると、暗号が変わる文字が決まり、以下のようになる。

・鍵:0文字目→暗号:3, 12文字目
・鍵:1文字目→暗号:7, 8文字目
・鍵:2文字目→暗号:11, 4文字目
・鍵:3文字目→暗号:15, 0文字目
・鍵:4文字目→暗号:2, 13文字目
・鍵:5文字目→暗号:6, 9文字目
・鍵:6文字目→暗号:10, 5文字目
・鍵:7文字目→暗号:14, 1文字目
・鍵:8文字目→暗号:1, 14文字目
・鍵:9文字目→暗号:5, 10文字目
・鍵:10文字目→暗号:9, 6文字目
・鍵:11文字目→暗号:13, 2文字目
・鍵:12文字目→暗号:0, 15文字目
・鍵:13文字目→暗号:4, 11文字目
・鍵:14文字目→暗号:8, 7文字目
・鍵:15文字目→暗号:12, 3文字目

このことを元に、ブルートフォースで鍵を求めることができる。鍵がわかれば、あとはその鍵で復号すればよい。その際、spiral.pyに復号コードを追加して、以下のコードでspiral_plus.pyとして保存する。

from utils import *


class Spiral:
    def __init__(self, key, rounds=4):
        self.rounds = rounds
        self.keys = [bytes2matrix(key)]
        self.BLOCK_SIZE = 16

        for i in range(rounds):
            self.keys.append(spiralLeft(self.keys[-1]))

    def encrypt(self, plaintext):
        if len(plaintext) % self.BLOCK_SIZE != 0:
            padding = self.BLOCK_SIZE - len(plaintext) % self.BLOCK_SIZE
            plaintext += bytes([padding] * padding)

        ciphertext = b""
        for i in range(0, len(plaintext), 16):
            ciphertext += self.encrypt_block(plaintext[i : i + 16])
        return ciphertext

    def encrypt_block(self, plaintext):
        self.state = bytes2matrix(plaintext)
        self.add_key(0)

        for i in range(1, self.rounds):
            self.substitute()
            self.rotate()
            self.mix()
            self.add_key(i)

        self.substitute()
        self.rotate()
        self.add_key(self.rounds)

        return matrix2bytes(self.state)

    def add_key(self, idx):
        for i in range(4):
            for j in range(4):
                self.state[i][j] = (self.state[i][j] + self.keys[idx][i][j]) % 255

    def substitute(self):
        for i in range(4):
            for j in range(4):
                self.state[i][j] = SBOX[self.state[i][j]]

    def rotate(self):
        self.state = spiralRight(self.state)

    def mix(self):
        out = [[0 for _ in range(4)] for _ in range(4)]
        for i in range(4):
            for j in range(4):
                for k in range(4):
                    out[i][j] += SPIRAL[i][k] * self.state[k][j]
                out[i][j] %= 255

        self.state = out

#### add decrypt functions ####
    def decrypt_round1(self, ciphertext):
        plaintext = b""
        for i in range(0, len(ciphertext), 16):
            plaintext += self.decrypt_block_round1(ciphertext[i : i + 16])

        padding = plaintext[-1]
        if padding < 16:
            plaintext = plaintext[:- padding]

        return plaintext

    def decrypt_block_round1(self, ciphertext):
        self.state = bytes2matrix(ciphertext)

        self.sub_key(self.rounds)
        self.rev_rotate()
        self.rev_substitute()
        self.sub_key(0)

        return matrix2bytes(self.state)

    def sub_key(self, idx):
        for i in range(4):
            for j in range(4):
                self.state[i][j] = (self.state[i][j] - self.keys[idx][i][j]) % 255

    def rev_substitute(self):
        for i in range(4):
            for j in range(4):
                self.state[i][j] = SBOX.index(self.state[i][j])

    def rev_rotate(self):
        self.state = spiralLeft(self.state)
#!/usr/bin/env python3
import socket
from spiral_plus import Spiral

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

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('spiral-baby.ctf.maplebacon.org', 1337))

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

data = recvuntil(s, b'>>> ')
print(data + '1')
s.sendall(b'1\n')
data = recvuntil(s, b'\n').rstrip()
print(data)
flag_ct = bytes.fromhex(data)

try_pt1 = b'a' * 16
data = recvuntil(s, b'>>> ')
print(data + '2')
s.sendall(b'2\n')
print(try_pt1.hex())
s.sendall(try_pt1.hex().encode() + b'\n')
data = recvuntil(s, b'\n').rstrip()
print(data)
try_ct1 = bytes.fromhex(data)

try_pt2 = b'b' * 16
data = recvuntil(s, b'>>> ')
print(data + '2')
s.sendall(b'2\n')
print(try_pt2.hex())
s.sendall(try_pt2.hex().encode() + b'\n')
data = recvuntil(s, b'\n').rstrip()
print(data)
try_ct2 = bytes.fromhex(data)

ct_index = [[3, 12], [7, 8], [11, 4], [15, 0], [2, 13], [6, 9], [10, 5], [14, 1]]

key = [bytes([0])] * 16
for i in range(8):
    found = False
    for k1 in range(256):
        for k2 in range(256):
            try_key = [bytes([0])] * 16
            try_key[i] = bytes([k1])
            try_key[15 - i] = bytes([k2])
            try_key = b''.join(try_key)

            cipher = Spiral(try_key, rounds=1)
            ct = cipher.encrypt(try_pt1)
            index1 = ct_index[i][0]
            index2 = ct_index[i][1]
            if ct[index1] == try_ct1[index1] and ct[index2] == try_ct1[index2]:
                ct = cipher.encrypt(try_pt2)
                if ct[index1] == try_ct2[index1] and ct[index2] == try_ct2[index2]:
                    found = True
                    key[i] = try_key[i]
                    key[15 - i] = try_key[15 - i]
                    break
        if found:
            break

key = b''.join([bytes([k]) for k in key])

cipher = Spiral(key, rounds=1)
flag = cipher.decrypt_round1(flag_ct).decode()
print(flag)

実行結果は以下の通り。

Options:
1. Get encrypted flag
2. Encrypt message
>>> 1
239dc87cec599517111cbd9e48775c9cd0a081b05ee50778271cfbb2cd53e013ee82cab620f70aa1570f95b8170168f0
>>> 2
61616161616161616161616161616161
f0df0c3a34942517eba7992d00560d46
>>> 2
62626262626262626262626262626262
d01b9ab95b86d1a0a4e2424be0c639b6
maple{0nt0_th3_r34l_sp!r4l_0be088}
maple{0nt0_th3_r34l_sp!r4l_0be088}

CTFZone 2022 Writeup

この大会は2022/8/25 0:00(JST)~2022/8/27 0:00(JST)に開催されました。
今回もチームで参戦。結果は1166点で145チーム中51位でした。
自分で解けた問題をWriteupとして書いておきます。

statham (misc)

[Let's go]ボタンをクリックして、開始する。

Task 1から順に解いていく必要がありそう。Task 1はkdbxのパスワードをクラックする。

$ keepass2john Database.kdbx > hash.txt
||<<
hash.txtの先頭の"Database:"を削除して、hashcatでクラックする。
>||
>hashcat -a 0 -m 13400 hash.txt dict/rockyou.txt
hashcat (v6.2.4) starting

OpenCL API (OpenCL 3.0 ) - Platform #1 [Intel(R) Corporation]
=============================================================
* Device #1: Intel(R) UHD Graphics 630, 3200/6484 MB (1621 MB allocatable), 24MCU

Minimum password length supported by kernel: 0
Maximum password length supported by kernel: 256

Hashes: 1 digests; 1 unique digests, 1 unique salts
Bitmaps: 16 bits, 65536 entries, 0x0000ffff mask, 262144 bytes, 5/13 rotates
Rules: 1

Optimizers applied:
* Zero-Byte
* Single-Hash
* Single-Salt

Watchdog: Hardware monitoring interface not found on your system.
Watchdog: Temperature abort trigger disabled.

Host memory required for this attack: 974 MB

Dictionary cache hit:
* Filename..: dict/rockyou.txt
* Passwords.: 14344384
* Bytes.....: 139921497
* Keyspace..: 14344384

[s]tatus [p]ause [b]ypass [c]heckpoint [f]inish [q]uit =>

Session..........: hashcat
Status...........: Running
Hash.Mode........: 13400 (KeePass 1 (AES/Twofish) and KeePass 2 (AES))
Hash.Target......: $keepass$*2*60000*0*9ad1c2e55b2ebabd1f4b6c4e89f0606...43f238
Time.Started.....: Thu Aug 25 23:43:49 2022 (13 hours, 7 mins)
Time.Estimated...: Fri Aug 26 23:48:47 2022 (10 hours, 57 mins)
Kernel.Feature...: Pure Kernel
Guess.Base.......: File (dict/rockyou.txt)
Guess.Queue......: 1/1 (100.00%)
Speed.#1.........:      166 H/s (9.01ms) @ Accel:64 Loops:8 Thr:16 Vec:1
Recovered........: 0/1 (0.00%) Digests
Progress.........: 7815168/14344384 (54.48%)
Rejected.........: 0/7815168 (0.00%)
Restore.Point....: 7815168/14344384 (54.48%)
Restore.Sub.#1...: Salt:0 Amplifier:0-1 Iteration:22600-22608
Candidate.Engine.: Device Generator
Candidates.#1....: goooo3 -> gocubs25

$keepass$*2*60000*0*9ad1c2e55b2ebabd1f4b6c4e89f06062aa0a4c78e21f69a9b13a7267310843df*770a9fee79c3283537a6fc1d452288a0705020a8c8b221d5efd31810be96f59b*eba5d5a0efb17466b63931db6d75ee0b*21a1ac356b03ba2b7485267f0aab1d928bc8e403268ff11f17977d58a93dc436*c95a63c7f541889409d844a73e35b94ee298c5b5a20b782942151f968e43f238:g33kpop

Session..........: hashcat
Status...........: Cracked
Hash.Mode........: 13400 (KeePass 1 (AES/Twofish) and KeePass 2 (AES))
Hash.Target......: $keepass$*2*60000*0*9ad1c2e55b2ebabd1f4b6c4e89f0606...43f238
Time.Started.....: Thu Aug 25 23:43:49 2022 (13 hours, 16 mins)
Time.Estimated...: Fri Aug 26 13:00:04 2022 (0 secs)
Kernel.Feature...: Pure Kernel
Guess.Base.......: File (dict/rockyou.txt)
Guess.Queue......: 1/1 (100.00%)
Speed.#1.........:      168 H/s (9.00ms) @ Accel:64 Loops:8 Thr:16 Vec:1
Recovered........: 1/1 (100.00%) Digests
Progress.........: 8011776/14344384 (55.85%)
Rejected.........: 0/8011776 (0.00%)
Restore.Point....: 7987200/14344384 (55.68%)
Restore.Sub.#1...: Salt:0 Amplifier:0-1 Iteration:59992-60000
Candidate.Engine.: Device Generator
Candidates.#1....: g3ni3ll3 -> fucku7615

Started: Thu Aug 25 23:43:37 2022
Stopped: Fri Aug 26 13:00:05 2022

パスワードは"g33kpop"。KeePassで開き、Master passwordに"g33kpop"を指定すると、ログインできる。中には7つのパスワードが設定されている。Title: Secretの情報は以下の通り。

User Name: Jason_Statham
Password: DefInIte1y_inc0rrect_Pa$$w0rd

[Task 2]ボタンをクリックし、このID/PWを入力しても、認証されない。KeePassのSecretの情報をよく見ると、パスワードは一回変更されていて、前のパスワードは以下の通り。

Y0u_@re_@1m0st_theRe

このパスワードでもダメだった。さらによく見ると、Advancedにreal_password.pngが添付されている。

この画像を見ると、こう書いてある。

6fV6bwYm4b0VUHPp

これをパスワードとして、Jason_Statham / 6fV6bwYm4b0VUHPp でログインする。

ようやくTask 2に進める。Task 2 のIDは"I_L0ve_Pies"と指定されている。パスワードは円周率の部分文字列"1337"の1337番目の位置。Pythonでsympyライブラリを使って算出する。

#!/usr/bin/env python3
import sympy

pi = str(sympy.pi.evalf(100000000))

count = 0
idx = 0
while True:
    idx = pi[1:].find('1337', idx)
    if idx > 0:
        count += 1
    else:
        print('Error! idx=', idx)
        break
    if count == 1337:
        break
    idx += 1

print(idx)

実行結果は以下の通り。

13578220

[Task 3]ボタンをクリックし、I_L0ve_Pies / 13578220 を入力する。

するとログインでき、Task 3のページに入れる。このページの下部に[Flag]ボタンがあるので、これで最後のようだ。何重にもzip圧縮されているので、解凍していく。その際ファイル名に意味がありそうなので、連結していく。

#!/usr/bin/env python3
import zipfile

zips = ['1_Ever.zip']

while True:
    try:
        with zipfile.ZipFile(zips[-1]) as zf:
            name = zf.namelist()[0]
            zips.append(name)
            zf.extractall('')
    except:
        break

msg = ''
i = 1
for zip in zips:
    num = int(zip.split('_')[0])
    if i == num:
        msg += zip.split('_')[1].split('.')[0] + ' '
    else:
        break
    i += 1

msg = msg[:-1]
print(msg)

実行結果は以下の通り。

Ever since the railroad had been officially inaugurated and had begun to arrive with regularity on Wednesdays at eleven oΓÇÖclock and the primitive wooden station with a desk, a telephone, and a ticket window had been built, on the streets of Macondo men and women were seen who had adopted everyday and normal customs and manners but who really looked like people out of a circus; In a town that had chafed under the tricks of the gypsies there was no future for those ambulatory acrobats of commerce who with equal effrontery offered a whistling kettle and a daily regime that would assure the salvation of the soul on the seventh day; but from those who let themselves be convinced out of fatigue and the ones who were always unwary, they reaped stupendous benefits; Among those theatrical creatures, wearing riding breeches and leggings, a pith helmet and steel-rimmed glasses, with topaz eyes and the skin of a thin rooster, there arrived in Macondo on one of so many Wednesdays the chubby and smiling Mr; Herbert, who ate at the house; No one had noticed him at the table until the first bunch of bananas had been eaten; Aureliano Segundo had come across him by chance as he protested In broken Spanish because there were no rooms at the Hotel Jacob, and as he frequently did with strangers, he took him home; Actually, the key you are looking for is in the file number one thousand two hundred fifty two; He was in the captive-balloon business, which had taken him halfway around the world with excellent profits, but he had not succeeded in taking anyone up in Macondo because they considered that invention backward after having seen and tried the gypsiesΓÇÖ flying carpets; He was leaving, therefore, on the next train; When they brought to the table the tiger-striped bunch of bananas that they were accustomed to hang in the dining room during lunch, he picked the first piece of fruit without great enthusiasm; But he kept on eating as he spoke, tasting, chewing, more with the distraction of a wise man than with the delight of a good eater, and when he finished the first bunch he asked them to bring him another; Then he took a small case with optical instruments out of the toolbox that he always carried with him; With the auspicious attention of a diamond merchant he examined the banana meticulously, dissecting it with a special scalpel, weighing the pieces on a pharmacistΓÇÖs scale, and calculating its breadth with a gunsmithΓÇÖs calipers; Then he took a series of instruments out of the chest with which he measured the temperature, the level of humidity in the atmosphere, and the intensity of the light; It was such an intriguing ceremony that no one could eat in peace as everybody waited for Mr; Herbert to pass a final and revealing judgment, but he did not say anything that allowed anyone to guess his intentions; On the days that followed he was seen with a net and a small basket hunting butterflies on the outskirts of town; On Wednesday a group of engineers, agronomists, hydrologists, topographers, and surveyors arrived who for several weeks explored the places where Mr; Herbert had hunted his butterflies; Later on Mr; Jack Brown arrived in an extra coach that had been coupled onto the yellow train and that was silver-plated all over, with seats of episcopal velvet, and a roof of blue glass; Also arriving on the special car, fluttering around Mr; Brown, were the solemn lawyers dressed in black who in different times had followed Colonel Aureliano Buendia everywhere, and that led the people to think that the agronomists, hydrologists, topographers, and surveyors, like Mr; Herbert with his captive balloons and his colored butterflies and Mr; Brown with his mausoleum on wheels and his ferocious German shepherd dogs, had something to do with the war; There was not much time to think about it, however, because the suspicious inhabitants of Macondo barely began to wonder what the devil was going on when the town had already become transformed into an encampment of wooden houses with zinc roofs inhabited by foreigners who arrived on the train from halfway around the world, riding not only on the seats and platforms but even on the roof of the coaches; The gringos, who later on brought their languid wives in muslin dresses and large veiled hats, built a separate town across the railroad tracks with streets lined with palm trees, houses with screened win┬¼ dows, small white tables on the terraces, and fans mounted on the ceilings, and extensive blue lawns with peacocks and quails; The section was surrounded by a metal fence topped with a band of electrified chicken wire which during the cool summer mornings would be black with roasted swallows; No one knew yet what they were after, or whether they were actually nothing but philanthropists, and they had already caused a colossal disturbance, much more than that of the old gypsies, but less transitory and understandable; Endowed with means that had been reserved for Divine Providence in former times, they changed the pattern of the rams, accelerated the cycle of harvest, and moved the river from where it had always been and put it with its white stones and icy currents on the other side of the town, behind the cemetery; It was at that time that they built a fortress of reinforced concrete over the faded tomb of Jose Arcadio, so that the corpses smell of powder would not contaminate the waters; For the foreigners who arrived without love they converted the street of the loving matrons from France into a more extensive village than it had been, and on one glorious Wednesday they brought in a trainload of strange whores, Babylonish women skilled in age-old methods and in possession of all manner of unguents and devices to stimulate the unaroused, to give courage to the timid, to satiate the voracious, to exalt the modest man, to teach a lesson to repeaters, and to correct solitary people; The Street of the Turks, enriched by well-lit stores with products from abroad, displacing the old bazaars with their bright colors, overflowed on Saturday nights with the crowds of adventurers who bumped into each other among gambling tables, shooting galleries, the alley where the future was guessed and dreams interpreted, and tables of fried food and drinks, and on Sunday mornings there were scattered on the ground bodies that were sometimes those of happy dmnkards and more often those of onlookers felled by shots, fists, knives, and bottles during the brawls; It was such a tumultuous and intemperate invasion that during the first days it was impossible to walk through the streets because of the furniture and trunks, and the noise of the carpentry of those who were building their houses in any vacant lot without asking anyoneΓÇÖs permission, and the scandalous behavior of couples who hung their hammocks between the almond trees and made love under the netting in broad daylight and in view of everyone; The only serene corner had been established by peaceful West Indian Negroes, who built a marginal street with wooden houses on piles where they would sit in the doors at dusk singing melancholy hymns in their disordered gabble; So many changes took place in such a short time that eight months after Mr; HerbertΓÇÖs visit the old inhabitants had a hard time recognizing their own town; ΓÇ£Look at the mess weΓÇÖve got ourselves into,ΓÇ\ Colonel Aureliano Buendia said at that time, ΓÇ£just because we invited a gringo to eat some bananas;ΓÇ\ Aureliano Segundo, on the other hand, could not contain his happiness over the avalanche of foreigners; The house was suddenly filled with unknown guests, with invincible and worldly carousers, and it became necessary to add bedrooms off the courtyard, widen the dining room, and exchange the old table for one that held sixteen people, with new china and silver, and even then they had to eat lunch in shifts; Fernanda had to swallow her scmples and their guests of the worst sort like kings as they muddied the porch with their boots, urinated in the garden, laid their mats down anywhere to take their siesta, and spoke without regard for the sensitivities of ladies or the proper behavior of gentlemen; Amaranta, was so scandalized with the plebeian invasion that she went back to eating in the kitchen as in olden days; Colonel Aureliano Buendia, convinced that the majority of those who came into Inis workshop to greet him were not doing it because of sympathy or regard but out of the curiosity to meet a historical relic, a museum fossil, decided to shut himself in by barring the door and he was not seen any more except on very rare occasions when he would sit at the street door; Ursula, on the other hand, even during the days when she was already dragging her feet and walking about groping along the walls, felt a juvenile excitement as the time for the arrival of the train approached; ΓÇ£We have to prepare some meat and fish,ΓÇ\ she would order the four cooks, who hastened to have everything ready under the imperturbable direction of Santa Sofia de la Piedad; ΓÇ£We have to prepare everything,ΓÇ\ she insisted, ΓÇ£because we never know what these strangers like to eat;ΓÇ\ The train arrived during the hottest time of day; At lunchtime the house shook with the bustle of a marketplace, and the perspiring guestsΓÇöwho did not even know who their hosts wereΓÇö trooped in to occupy the best places at the table, while the cooks bumped into each other with enormous kettles of soup, pots of meat, large gourds filled with vegetables, and troughs of rice, and passed around the contents of barrels of lemonade with inexhaustible ladles; The disorder was such that Fernanda was troubled by the idea that many were eating twice and on more than one occasion she was about to burst out with a vegetable hawkerΓÇÖs insults because someone at the table in confusion asked her for the check; More than a year had gone by since Mr; HerbertΓÇÖs visit and the only thing that was known was that the gringos were planning to plant banana trees in the enchanted region that Jose Arcadio Buendia and his men had crossed in search of the route to the great inventions; Two other sons of Colonel Aureliano Buendia, with the cross of ashes on their foreheads, arrived, drawn by that great volcanic belch, and they justified their determination with a phrase that may have explained everybodyΓÇÖs reasons; ΓÇ£We came,ΓÇ\ they said, ΓÇ£because everyone is coming;ΓÇ\ Remedios the Beauty was the only one who was immune to the banana plague; She was becalmed in a magnificent adolescence, more and more impenetrable to formality, more and more indifferent to malice and suspicion, happy in her own world of simple realities; She did not understand why women complicated their lives with corsets and petticoats, so she sewed herself a coarse cassock that she simply put over her and without further difficulties resolved the problem of dress, without taking away the feeling of being naked, which according to her lights was the only decent way to be when at home; They bothered her so much to cut the rain of hair that already reached to her thighs and to make rolls with combs and braids with red ribbons that she simply shaved her head and used the hair to make wigs for the saints; The startling thing about her simplifying instinct was that the more she did away with fashion in a search for comfort and the more she passed over conventions as she obeyed spontaneity, the more disturbing her incredible beauty became and the more provocative she became to men; When the sons of Colonel Aureliano Buendfa were in Macondo for the first time, Ursula remembered that in their veins they bore the same blood as her great- granddaughter and she shuddered with a forgotten fright; ΓÇ£Keep your eyes wide open,ΓÇ\ she warned her; ΓÇ£With any of them your children will come out with the tail of a pig;ΓÇ\ The girl paid such little attention to the warning that she dressed up as a man and rolled around in the sand in order to climb the greased pole, and she was at the point of bringing on a tragedy among the seventeen cousins, who were driven mad by the unbearable

文章中に以下の文がある。

Actually, the key you are looking for is in the file number one thousand two hundred fifty two;

鍵はファイル番号1252のファイルにあるということらしい。

$ zipinfo -z 1252_weΓÇÖve.zip 
Archive:  1252_weΓÇÖve.zip
Impressive! login: "Json_Statham_4evar", password: "JpxCDPs7WswzyqPB"
Zip file size: 224580 bytes, number of entries: 1
-rw-r--r--  3.0 unx   224315 bx defX 22-Jun-10 19:35 1253_got.zip
1 file, 224315 bytes uncompressed, 224337 bytes compressed:  0.0%

ID/PWについて、コメントに書いてある。

login: Json_Statham_4evar
password: JpxCDPs7WswzyqPB

[Flag]ボタンをクリックし、Json_Statham_4evar / JpxCDPs7WswzyqPB を入力すると、フラグが表示された。

CTFZone{57@TH4M_cHAiN_M1sk}

Yet Another Des (reverse, crypto)

$ strings main.exe | grep python
4python38.dll

pythonコードからexeにした可能性が高い。まず、pyinstxtractor.pyでモジュールを抽出する。

$ python3 pyinstxtractor.py main.exe
[+] Processing main.exe
[+] Pyinstaller version: 2.1+
[+] Python version: 3.8
[+] Length of package: 655205 bytes
[+] Found 9 files in CArchive
[+] Beginning extraction...please standby
[+] Possible entry point: pyiboot01_bootstrap.pyc
[+] Possible entry point: pyi_rth_subprocess.pyc
[+] Possible entry point: main.pyc
[+] Found 78 files in PYZ archive
[+] Successfully extracted pyinstaller archive: main.exe

You can now use a python decompiler on the pyc files within the extracted directory

次に生成されたディレクトリ配下にあるmain.pycをデコンパイルする。

$ cd main.exe_extracted/
$ uncompyle6 main.pyc
# uncompyle6 version 3.8.0
# Python bytecode 3.8.0 (3413)
# Decompiled from: Python 3.8.10 (default, Jun 22 2022, 20:18:18) 
# [GCC 9.4.0]
# Embedded file name: main.py
import operator
from textwrap import wrap
from functools import reduce
KEY = '8324465191921420'
INITIAL_PERMUTATION = [
 57, 49, 41, 33, 25, 17, 9, 1,
 59, 51, 43, 35, 27, 19, 11, 3,
 61, 53, 45, 37, 29, 21, 13, 5,
 63, 55, 47, 39, 31, 23, 15, 7,
 56, 48, 40, 32, 24, 16, 8, 0,
 58, 50, 42, 34, 26, 18, 10, 2,
 60, 52, 44, 36, 28, 20, 12, 4,
 62, 54, 46, 38, 30, 22, 14, 6]
INVERSE_PERMUTATION = [
 39, 7, 47, 15, 55, 23, 63, 31,
 38, 6, 46, 14, 54, 22, 62, 30,
 37, 5, 45, 13, 53, 21, 61, 29,
 36, 4, 44, 12, 52, 20, 60, 28,
 35, 3, 43, 11, 51, 19, 59, 27,
 34, 2, 42, 10, 50, 18, 58, 26,
 33, 1, 41, 9, 49, 17, 57, 25,
 32, 0, 40, 8, 48, 16, 56, 24]
SUB_BOXES = [
 [
  14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7,
  0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8,
  4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0,
  15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13],
 [
  15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10,
  3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5,
  0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15,
  13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9],
 [
  10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8,
  13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1,
  13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7,
  1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12],
 [
  7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15,
  13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9,
  10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4,
  3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14],
 [
  2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9,
  14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6,
  4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14,
  11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3],
 [
  12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11,
  10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8,
  9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6,
  4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13],
 [
  4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1,
  13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6,
  1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2,
  6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12],
 [
  13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7,
  1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2,
  7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8,
  2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11]]
EXPANSION = [
 31, 0, 1, 2, 3, 4,
 3, 4, 5, 6, 7, 8,
 7, 8, 9, 10, 11, 12,
 11, 12, 13, 14, 15, 16,
 15, 16, 17, 18, 19, 20,
 19, 20, 21, 22, 23, 24,
 23, 24, 25, 26, 27, 28,
 27, 28, 29, 30, 31, 0]
PERMUTATION = [
 15, 6, 19, 20, 28, 11, 27, 16,
 0, 14, 22, 25, 4, 17, 30, 9,
 1, 7, 23, 13, 31, 26, 2, 8,
 18, 12, 29, 5, 21, 10, 3, 24]
PERMUTED_CHOICE_1 = [
 56, 48, 40, 32, 24, 16, 8,
 0, 57, 49, 41, 33, 25, 17,
 9, 1, 58, 50, 42, 34, 26,
 18, 10, 2, 59, 51, 43, 35,
 62, 54, 46, 38, 30, 22, 14,
 6, 61, 53, 45, 37, 29, 21,
 13, 5, 60, 52, 44, 36, 28,
 20, 12, 4, 27, 19, 11, 3]
PERMUTED_CHOICE_2 = [
 13, 16, 10, 23, 0, 4,
 2, 27, 14, 5, 20, 9,
 22, 18, 11, 3, 25, 7,
 15, 6, 26, 19, 12, 1,
 40, 51, 30, 36, 46, 54,
 29, 39, 50, 44, 32, 47,
 43, 48, 38, 55, 33, 52,
 45, 41, 49, 35, 28, 31]
ROTATES = [
 1, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1]

def slice_string(string):
    return [i.zfill(16) for i in wrap(''.join([hex(ord(i))[2:] for i in string]), 16)]


def to_bin(s):
    return ''.join([bin(int(i, 16))[2:].zfill(4) for i in s])


def permutation(block, box):
    return ''.join([block[i] for i in box])


def xor(arg_1, arg_2):
    return ''.join([str(int(i) ^ int(j)) for i, j in zip(arg_1, arg_2)])


def concatenate(args):
    return reduce(operator.iadd, args, [])


def rotate_left(block, i):
    return bin(int(block, 2) << i & 268435455 | int(block, 2) >> 28 - i)[2:].zfill(28)


def key_gen(block_1, block_2):
    li = []
    for i in ROTATES:
        block_1 = rotate_left(block_1, i)
        block_2 = rotate_left(block_2, i)
        li.append(permutation(block_1 + block_2, PERMUTED_CHOICE_2))
    else:
        return li


def f(block, key):
    final = []
    for j, i in enumerate(wrap(xor(permutation(block, EXPANSION), key), 6)):
        temp_box = [SUB_BOXES[0][0:16],
         SUB_BOXES[0][16:32],
         SUB_BOXES[0][32:48],
         SUB_BOXES[0][48:64]]
        final.append(bin(temp_box[int(i[0] + i[(-1)], 2)][int(i[1:-1], 2)])[2:].zfill(4))
    else:
        return permutation(''.join(final), PERMUTATION)


def des(block, key_array):
    left, right = block[0:len(block) // 2], block[len(block) // 2:]
    for j, i in zip(range(1, 17), key_array):
        right, left = xor(f(right, i), left), right
    else:
        return wrap(permutation(right + left, INVERSE_PERMUTATION), 8)


def main(string):
    encrypted_list = []
    for i in slice_string(string):
        bin_mess, bin_key = to_bin(i), to_bin(KEY)
        permuted_key, permuted_block = permutation(bin_key, PERMUTED_CHOICE_1), permutation(bin_mess, INITIAL_PERMUTATION)
        key_list = key_gen(permuted_key[:len(permuted_key) // 2], permuted_key[len(permuted_key) // 2:])
        encrypted_list.append(''.join([hex(int(i, 2))[2:].zfill(2).lower() for i in des(permuted_block, key_list)]))
    else:
        return ''.join(encrypted_list)


flag = main(input('Input sentence: '))
print(flag)
flag = '7cd245e589aa384ac19dddfafb189650e8c1e6eb13fd52bc'
input()
# okay decompiling main.pyc

KEYは分かっているので、逆算すればよい。

#!/usr/bin/env python3
from textwrap import wrap

KEY = '8324465191921420'
INITIAL_PERMUTATION = [
    57, 49, 41, 33, 25, 17, 9, 1,
    59, 51, 43, 35, 27, 19, 11, 3,
    61, 53, 45, 37, 29, 21, 13, 5,
    63, 55, 47, 39, 31, 23, 15, 7,
    56, 48, 40, 32, 24, 16, 8, 0,
    58, 50, 42, 34, 26, 18, 10, 2,
    60, 52, 44, 36, 28, 20, 12, 4,
    62, 54, 46, 38, 30, 22, 14, 6]

INVERSE_PERMUTATION = [
    39, 7, 47, 15, 55, 23, 63, 31,
    38, 6, 46, 14, 54, 22, 62, 30,
    37, 5, 45, 13, 53, 21, 61, 29,
    36, 4, 44, 12, 52, 20, 60, 28,
    35, 3, 43, 11, 51, 19, 59, 27,
    34, 2, 42, 10, 50, 18, 58, 26,
    33, 1, 41, 9, 49, 17, 57, 25,
    32, 0, 40, 8, 48, 16, 56, 24]

SUB_BOXES = [
    [
        14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7,
        0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8,
        4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0,
        15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13],
    [
        15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10,
        3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5,
        0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15,
        13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9],
    [
        10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8,
        13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1,
        13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7,
        1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12],
    [
        7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15,
        13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9,
        10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4,
        3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14],
    [
        2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9,
        14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6,
        4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14,
        11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3],
    [
        12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11,
        10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8,
        9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6,
        4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13],
    [
        4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1,
        13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6,
        1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2,
        6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12],
    [
        13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7,
        1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2,
        7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8,
        2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11]]

EXPANSION = [
    31, 0, 1, 2, 3, 4,
    3, 4, 5, 6, 7, 8,
    7, 8, 9, 10, 11, 12,
    11, 12, 13, 14, 15, 16,
    15, 16, 17, 18, 19, 20,
    19, 20, 21, 22, 23, 24,
    23, 24, 25, 26, 27, 28,
    27, 28, 29, 30, 31, 0]

PERMUTATION = [
    15, 6, 19, 20, 28, 11, 27, 16,
    0, 14, 22, 25, 4, 17, 30, 9,
    1, 7, 23, 13, 31, 26, 2, 8,
    18, 12, 29, 5, 21, 10, 3, 24]

PERMUTED_CHOICE_1 = [
    56, 48, 40, 32, 24, 16, 8,
    0, 57, 49, 41, 33, 25, 17,
    9, 1, 58, 50, 42, 34, 26,
    18, 10, 2, 59, 51, 43, 35,
    62, 54, 46, 38, 30, 22, 14,
    6, 61, 53, 45, 37, 29, 21,
    13, 5, 60, 52, 44, 36, 28,
    20, 12, 4, 27, 19, 11, 3]

PERMUTED_CHOICE_2 = [
    13, 16, 10, 23, 0, 4,
    2, 27, 14, 5, 20, 9,
    22, 18, 11, 3, 25, 7,
    15, 6, 26, 19, 12, 1,
    40, 51, 30, 36, 46, 54,
    29, 39, 50, 44, 32, 47,
    43, 48, 38, 55, 33, 52,
    45, 41, 49, 35, 28, 31]

ROTATES = [
    1, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1]

def to_bin(s):
    return ''.join([bin(int(i, 16))[2:].zfill(4) for i in s])

def permutation(block, box):
    return ''.join([block[i] for i in box])

def xor(arg_1, arg_2):
    return ''.join([str(int(i) ^ int(j)) for i, j in zip(arg_1, arg_2)])

def rotate_left(block, i):
    return bin(int(block, 2) << i & 268435455 | int(block, 2) >> 28 - i)[2:].zfill(28)

def key_gen(block_1, block_2):
    li = []
    for i in ROTATES:
        block_1 = rotate_left(block_1, i)
        block_2 = rotate_left(block_2, i)
        li.append(permutation(block_1 + block_2, PERMUTED_CHOICE_2))
    else:
        return li

def f(block, key):
    final = []
    for j, i in enumerate(wrap(xor(permutation(block, EXPANSION), key), 6)):
        temp_box = [SUB_BOXES[0][0:16],
         SUB_BOXES[0][16:32],
         SUB_BOXES[0][32:48],
         SUB_BOXES[0][48:64]]
        final.append(bin(temp_box[int(i[0] + i[(-1)], 2)][int(i[1:-1], 2)])[2:].zfill(4))
    else:
        return permutation(''.join(final), PERMUTATION)

def rev_des(block, key_array):
    rl = permutation(block, INITIAL_PERMUTATION)
    right, left = rl[:len(rl) // 2], rl[len(rl) // 2:]
    for j, i in zip(range(16, -1, -1), key_array[::-1]):
        right, left = left, xor(f(left, i), right)
    return left + right

enc_flag = '7cd245e589aa384ac19dddfafb189650e8c1e6eb13fd52bc'
encrypted_list = [enc_flag[i:i+16] for i in range(0, len(enc_flag), 16)]

flag = ''
for encrypted in encrypted_list:
    bin_key = to_bin(KEY)
    permuted_key = permutation(bin_key, PERMUTED_CHOICE_1)
    key_list = key_gen(permuted_key[:len(permuted_key) // 2], permuted_key[len(permuted_key) // 2:])
    rl = ''.join([bin(int(i, 16))[2:].zfill(4) for i in encrypted])

    permuted_block = rev_des(rl, key_list)
    bin_mess = permutation(permuted_block, INVERSE_PERMUTATION)
    s = ''.join([chr(int(bin_mess[i:i+8], 2)) for i in range(0, len(bin_mess), 8)])
    flag += s.lstrip('\x00')

print(flag)
CTFZone{Str0ng_D3S}