CryptoNite CTF 2026 Writeup

この大会は2026/3/6 14:30(JST)~2026/3/7 22:30(JST)に開催されました。
今回は個人で参戦。結果は2286点で383チーム中79位でした。
自分で解けた問題をWriteupとして書いておきます。

Sanity Check (Misc)

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

Came here looking for something? Here is what you need: TACHYON{hello_there!}
TACHYON{hello_there!}

The Onion (Misc)

$ unzip layer_100.zip               
Archive:  layer_100.zip
  inflating: layer_100.tar
$ tar xvf layer_100.tar       
layer_100.gz
$ gzip -d layer_100.gz  
gzip: layer_100: Value too large for defined data type
$ unzip layer_100    
Archive:  layer_100
  inflating: layer_99.tar
$ tar xvf layer_99.tar 
layer_99.gz
$ gzip -d layer_99.gz 
gzip: layer_99: Value too large for defined data type
$ unzip layer_99 
Archive:  layer_99
  inflating: layer_98.tar

解凍していくと、zip, tar, gzの繰り返しになるので、スクリプトで実行する。ここで最後のlayer_1の内容を表示する。

#!/usr/bin/env python3
import subprocess

cmd_unzip = 'unzip %s'
cmd_untar = 'tar xvf %s'
cmd_ungzip = 'gzip -d %s'

fname = 'layer_100.zip'
cmd = cmd_unzip % fname
subprocess.check_output(cmd.split(' '))

for i in range(100, 0, -1):
    fname_without_ext = 'layer_%d' % i
    cmd = cmd_untar % (fname_without_ext + '.tar')
    subprocess.check_output(cmd.split(' '))

    try:
        cmd = cmd_ungzip % (fname_without_ext + '.gz')
        subprocess.check_output(cmd.split(' '))
    except:
        pass

    if i == 1:
        break

    cmd = cmd_unzip % (fname_without_ext)
    subprocess.check_output(cmd.split(' '))

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

print(data)

実行結果は以下の通り。

gzip: layer_100: Value too large for defined data type
gzip: layer_99: Value too large for defined data type
gzip: layer_98: Value too large for defined data type
        :
        :
gzip: layer_2: Value too large for defined data type
gzip: layer_1: Value too large for defined data type
Q29uZ28gaGVyZSBpcyB5b3VyIGZsYWc6IFRBQ0hZT057MV93MG5kM3Jfd2gwc19iM2hpbmRfdGgzX200c2tfM2hmODRoZnJ9

base64デコードすると、以下の結果となる。

$ echo Q29uZ28gaGVyZSBpcyB5b3VyIGZsYWc6IFRBQ0hZT057MV93MG5kM3Jfd2gwc19iM2hpbmRfdGgzX200c2tfM2hmODRoZnJ9 | base64 -d
Congo here is your flag: TACHYON{1_w0nd3r_wh0s_b3hind_th3_m4sk_3hf84hfr}
TACHYON{1_w0nd3r_wh0s_b3hind_th3_m4sk_3hf84hfr}

Can't catch me (OSINT)

$ exiftool WhatsApp_Image_2026-02-27_at_14.24.21.jpeg                      
ExifTool Version Number         : 13.25
File Name                       : WhatsApp_Image_2026-02-27_at_14.24.21.jpeg
Directory                       : .
File Size                       : 121 kB
File Modification Date/Time     : 2026:03:06 22:51:59+09:00
File Access Date/Time           : 2026:03:07 15:53:48+09:00
File Inode Change Date/Time     : 2026:03:06 22:51:59+09:00
File Permissions                : -rwxrwxrwx
File Type                       : JPEG
File Type Extension             : jpg
MIME Type                       : image/jpeg
JFIF Version                    : 1.01
Resolution Unit                 : None
X Resolution                    : 1
Y Resolution                    : 1
Current IPTC Digest             : 598bbfbfbcb2a16c0fa6c3fa744e66ab
Contact                         : https://github.com/SirLancelot-13/sample-repo/
Application Record Version      : 4
Image Width                     : 1200
Image Height                    : 1600
Encoding Process                : Progressive DCT, Huffman coding
Bits Per Sample                 : 8
Color Components                : 3
Y Cb Cr Sub Sampling            : YCbCr4:2:0 (2 2)
Image Size                      : 1200x1600
Megapixels                      : 1.9

ContactにGithubリポジトリのURLが設定されている。ここにアクセスして、Commitsを見ていく。
「Cleaned sum stuff up」を見ると、コメントにこう書いてある。

#Note: I might not be available for a few days so jus leave me a message at my reddit account: u/11t_tpt_d4_g04t

https://www.reddit.com/user/11t_tpt_d4_g04t/にアクセスしてみると、ユーザ情報にこう書いてある。

Congrats you found me. Here: VEFDSFlPTntTMGMxNGxfbTNkMTRfdHI0aWw1XzRyM19zYzRyeV8yZnczZzR9

base64デコードする。

$ echo VEFDSFlPTntTMGMxNGxfbTNkMTRfdHI0aWw1XzRyM19zYzRyeV8yZnczZzR9 | base64 -d
TACHYON{S0c14l_m3d14_tr4il5_4r3_sc4ry_2fw3g4}
TACHYON{S0c14l_m3d14_tr4il5_4r3_sc4ry_2fw3g4}

Mr-Suave’s First Rookie Mistake (OSINT)

https://github.com/Mr-Suaveが存在する。リポジトリ small_public_project を見ると、READMEにフラグが書いてあった。

TACHYON{4m_I_th3_fl4g_y0u_w4nt?}

これはダミーのようだ。
6個のCommitsがあるので、見ていく。
「Help again please!」を見ると、flag.txtにフラグが書いてあった。

TACHYON{h1dd3n_1n_pl41n_s1ght}

webchal1 (Web Exploitation)

/api/internal/flagにフラグがあるらしい。ただ内部からしかアクセスできない。
Importで以下のURLを指定する。

https://webchal1.vercel.app/api/internal/flag

ノートに以下が表示された。

{"flag":"TACHYON{5SrF_inj3ct10N_c0ol_123wed3}"}
TACHYON{5SrF_inj3ct10N_c0ol_123wed3}

Shark of the Wire I (Forensics)

No.7パケットのTCPのデータ部を見ると、フラグが見つかった。

TACHYON{n3tw0rking_is_4un}

An Image? (Forensics)

pngの先頭4バイトが壊れているので、以下のように修正する。

9a 61 5f 58 -> 89 50 4e 47


復元したpng画像にはQRコードが書かれているので、デコードする。

dGFjaHlvbntjaGFuZ2VfdGhlX2hlYWRlcj99

base64文字列になっているので、デコードする。

$ echo dGFjaHlvbntjaGFuZ2VfdGhlX2hlYWRlcj99 | base64 -d
tachyon{change_the_header?}
tachyon{change_the_header?}

Shark of the Wire II (Forensics)

複数のパケットで、TCPのデータ部にbase64文字列が分散している。

No.7 : VEFDSFlP
No.19: TntmcjRnb
No.31: TNudDNkXz
No.43: FzX24wXzR
No.55: 1bn0=

結合して、base64デコードする。

$ echo VEFDSFlPTntmcjRnbTNudDNkXzFzX24wXzR1bn0= | base64 -d
TACHYON{fr4gm3nt3d_1s_n0_4un}
TACHYON{fr4gm3nt3d_1s_n0_4un}

Capture The Flag (Steganography)

PDF Stream Dumperで開き、オブジェクト1444を見ると、以下の部分がある。

<</Type /Action/S /URI/URI (https://gbhwvro{w3ii0_4qq1o3_h1uw3a})>>

Affine暗号と推測して、https://www.dcode.fr/affine-cipherでブルートフォースで復号する。
この結果、以下となった。

A=3,B=1	tachyon{h3ll0_4ff1n3_c1ph3r}
tachyon{h3ll0_4ff1n3_c1ph3r}

Mustard and Mangoes (Steganography)

6と7のみのデータになっている。6を0、7を1としてデコードしてバイナリファイルにする。

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

b_data = ''
for d in data:
    b_data += str(int(d) % 2)

out = b''
for i in range(0, len(b_data), 8):
    out += bytes([int(b_data[i:i+8], 2)])

with open('flag.png', 'wb') as f:
    f.write(out)

この結果、pngファイルになるが、画像にはフラグは見当たらない。IHDRチャンクのデータを変更して、pngの画像の高さを変更してみる。

下部にフラグが現れた。

TACHYON{67_mu5t4rd_4nd_m4ng03s_246fgtr}

RSA Decipher (Cryptography)

RSA暗号だが、nが極端に大きく、eは小さい。Low Public-Exponent Attackで復号する。

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

with open('challenge.txt', 'r', encoding='utf-8') as f:
    params = f.read().splitlines()

n = int(params[0].split('=')[1])
c = int(params[1].split('=')[1])
e = int(params[2].split('=')[1])

m, success = gmpy2.iroot(c, e)
assert success == True
flag = long_to_bytes(m).decode()
print(flag)
tachyon{R$4_crypt0gr4phy_i$_e4sy!}

Forbidden Communication I (Cryptography)

暗号の画像が添付されている。

https://www.dcode.fr/symbols-ciphersを調べると、Dorabella Cipherであることがわかる。
https://www.dcode.fr/dorabella-cipherでデコードする。

DORABELLAISSOCOOL
TACHYON{DORABELLA_IS_SO_COOL}

Shared Secrets (Cryptography)

共通のnで異なる2つのeの暗号がわかっているので、Common Modulus Attackで復号する。

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

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

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

n = int(params[0].split(' ')[-1])
e1 = int(params[1].split(' ')[-1])
e2 = int(params[2].split(' ')[-1])
c1 = int(params[3].split(' ')[-1])
c2 = int(params[4].split(' ')[-1])

m = commom_modulus_attack(c1, c2, e1, e2, n)
flag = long_to_bytes(m).decode()
print(flag)
TACHYON{c0mm0n_m0dulu5_att4ck!}

Forbidden Communication II (Cryptography)

暗号の画像が添付されている。

https://www.dcode.fr/symbols-ciphersを調べると、Lunar Alphabetであることがわかる。
https://www.dcode.fr/lunar-alphabet-leandro-katzでデコードする。

WELCOMETOTHEMOON
TACHYON{WELCOME_TO_THE_MOON}

Glitched (Cryptography)

RSA-CRT Fault Attackで復号する。

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

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

n = int(params[0].split(' ')[-1])
e = int(params[1].split(' ')[-1])
c = int(params[2].split(' ')[-1])
s1 = int(params[3].split(' ')[-1])
s2 = int(params[4].split(' ')[-1])

p = GCD(pow(s1, e, n) - pow(s2, e, n), n)
q = n // p

phi = (p - 1) * (q - 1)
d = inverse(e, phi)
m = pow(c, d, n)
flag = long_to_bytes(m).decode()
print(flag)
TACHYON{f4ulty_crt_l3eaks_th3rr_k3y!}

AES Stuff (Cryptography)

$ curl -X POST https://aes-challenge-thingy.vercel.app/api/oracle -d 'input=a'               
{"ciphertext":"199498d2b4c151f6629a46338299278812080649b992f7458ad4c78cac2f24bf0791fcecb5e0d1811734593e42510c60"}                                                                                                                                                                                                     
$ curl -X POST https://aes-challenge-thingy.vercel.app/api/oracle -d 'input=aa'
{"ciphertext":"29274c3076467ef322ca37c7eeba52a58296f8bf03559111d7e794f5e771b2b4b41c54cc468e9f692302f92beef06e83"}                                                                                                                                                                                                     
$ curl -X POST https://aes-challenge-thingy.vercel.app/api/oracle -d 'input=aaa'
{"ciphertext":"11ee23b154853da2ea3ce882c0feff75c345df21d442c6e26376ee7f54dc0d089ae8460dfdcc60095012c6d556d97ccf"}                                                                                                                                                                                                     
$ curl -X POST https://aes-challenge-thingy.vercel.app/api/oracle -d 'input=aaaa'
{"ciphertext":"c7a3972dec498742ba99f40cbb6fff736f01d55d1499368c359a3099dc1034c27853f45dcc9f287351fdbb34284ec0b5"}                                                                                                                                                                                                     
$ curl -X POST https://aes-challenge-thingy.vercel.app/api/oracle -d 'input=aaaaa'
{"ciphertext":"c63080a3230a2024264f186e5c449ee44d18998cf24dad82b013ecea65b7362c63b8dd7270e8cf22b3e2d252f5e1588d"}                                                                                                                                                                                                     
$ curl -X POST https://aes-challenge-thingy.vercel.app/api/oracle -d 'input=aaaaaa'
{"ciphertext":"67854aa6188f6455284ba36b904dbd80787a89487c17caefeb2bec82d4dc22de930a050abd69c40ad0d08a6883849920"}                                                                                                                                                                                                     
$ curl -X POST https://aes-challenge-thingy.vercel.app/api/oracle -d 'input=aaaaaaa'
{"ciphertext":"576014ea5adc752197e696a9607b6710b1f3dd3bbd02ea71cbc9da79e428e085940ba72ac4ad146a21e29ee3ecc7d817"}                                                                                                                                                                                                     
$ curl -X POST https://aes-challenge-thingy.vercel.app/api/oracle -d 'input=aaaaaaaa'
{"ciphertext":"3406271efc795c88f9cda60ed2105468304e53876fe783371ddc253958dc2be1096792461507dbbd5aabf17ede0e5c9800657ea140655a44782747705d422fad"}

入力文字列の後にflagを結合してパディングしてAES ECB暗号化されているようなので、以下のイメージの平文が暗号化されることになる。

0123456789abcdef
XXXXXXXXFFFFFFFF
FFFFFFFFFFFFFFFF
FFFFFFFFFFFFFFFF
PPPPPPPPPPPPPPPP

フラグを1文字ずつはみ出して暗号化することを繰り返し、ブロック単位で一致するものを探す。

0123456789abcdef
XXXXXXXXXXXXXXX?
XXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXF
FFFFFFFFFFFFFFFF
FFFFFFFFFFFFFFFF
FFFFFFFPPPPPPPPP
#!/usr/bin/env python3
import requests
import json

url = 'https://aes-challenge-thingy.vercel.app/api/oracle'

flag = ''
for i in range(40):
    for code in range(32, 127):
        if i < 16:
            pt = 'X' * (15 - i) + flag + chr(code)
        else:
            pt = flag[-15:] + chr(code)
        pt += 'X' * (47 - i)
        print('[+] pt:', pt)

        payload = {'input': pt}
        r = requests.post(url, data=payload)
        ct = json.loads(r.text)['ciphertext']
        if ct[:32] == ct[96:128]:
            flag += chr(code)
            break

print('[*] flag:', flag)

実行結果は以下の通り。

        :
        :
[+] pt: 3ed_L0l_d23d3cfyXXXXXXXX
[+] pt: 3ed_L0l_d23d3cfzXXXXXXXX
[+] pt: 3ed_L0l_d23d3cf{XXXXXXXX
[+] pt: 3ed_L0l_d23d3cf|XXXXXXXX
[+] pt: 3ed_L0l_d23d3cf}XXXXXXXX
[*] flag: TACHYON{w3lp_3cp_1s_bu5t3ed_L0l_d23d3cf}
TACHYON{w3lp_3cp_1s_bu5t3ed_L0l_d23d3cf}