ASIS CTF Quals 2020 Writeup

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

Welcome (TRIVIA, WARM-UP)

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

ASIS{H3llo_eVery0n3_4nd_wi5h_y0u_all_7he_fUn!}

Baby RSA (CRYPTO, WARM-UP)

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

p, q: 512bit素数
e, n = 65537, p*q
phi = (p-1)*(q-1)
d = inverse(e, phi)
r: 12~19ランダム整数
d = (1 << r) * K + 1

s, t: 1~min(p, q)ランダム整数
t_p = pow(s*p + 1, (d-1)/(1 << r), n)
t_q = pow(t*q + 4, (d-1)/(1 << r), n)

このコードから以下のことがわかる。

t_p % p = 1

t_p - 1 はpの倍数のため、nとの最大公約数はpになる。pを算出できれば、qも算出でき、復号できる。

from Crypto.Util.number import *

with open('output.txt', 'r') as f:
    params = f.read().rstrip().split('\n')

n = int(params[0].split(' = ')[1])
t_p = int(params[1].split(' = ')[1])
t_q = int(params[2].split(' = ')[1])
enc = int(params[3].split(' = ')[1])

e = 65537

p = GCD(t_p - 1, n)
q = n // p

phi = (p - 1) * (q - 1)
d = inverse(e, phi)
m = pow(enc, d, n)
flag = long_to_bytes(m)
print flag
ASIS{baby___RSA___f0r_W4rM_uP}

Elliptic Curve (CRYPTO)

$ nc 76.74.178.201 9531 
Please submit a printable string X, such that sha512(X)[-6:] = f07013 and len(X) = 36
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA.5N`
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ hi! There are three integer points such that (x, y), (x+1, y), and +
+ (x+2, y) lies on the elliptic curve E. You are given one of them!! +
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
| One of such points is: P = (61271155690342622475777210240075729710621342984654171453275106768277721560730, 10304534416315078468281913128100940831515761000045990722608570805012377026464)
| Send the 36783583177328169872379599156505622641753749954083930952113653353114099382996 * P :

通常の楕円曲線暗号の式はこうなっている。

y^2 = x^3 + ax + b

3点のy^2は以下の式になる。

x^3     + ax     + b
(x+1)^3 + a(x+1) + b
(x+2)^3 + a(x+2) + b

どれもmod mでは同じになるので、差分を取ってみる。

3x^2+3x+1  + a  mの倍数
6x^2+12x+8 + a*2 mの倍数

以下もそれぞれmの倍数になる。

6x^2+6x+2 + a*2
6x^2+12x+8 + a*2

再度差分をとる。

6x+6 -> mの倍数

数値の大きさを考えると、x + 1がmになる。あとは逆算して、Pがx, x+1, x+2なのか総当たりして、整合の取れるa, bを割り出す。楕円曲線を割り出せば、掛け算は簡単にできる。

#!/usr/bin/env sage
import socket
import re
import itertools
from hashlib import *
from string import *

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

def check(px, py, a, b, m):
    left = pow(py, 2, m)
    for i in range(3):
        right =(pow(px + i, 3, m) + a * (px + i) + b) % m
        if left != right:
            return False
    return True

def get_param(P):
    px, py = P[0], P[1]
    for i in range(3):
        px, py = P[0] - i, P[1]
        m = px + 1
        a = (- (3 * pow(px, 2, m) + 3 * px + 1)) % m
        b = (pow(py, 2, m) - pow(px, 3, m) - a * px) % m
        if check(px, py, a, b, m):
            return a, b, m

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('76.74.178.201', 9531))

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

pattern = 'that (.+)\(X\)\[-6:\] = (.+) and len\(X\) = (.+)'
m = re.search(pattern, data)
alg = m.group(1)
h_tail = m.group(2)
length = int(m.group(3))

for c in itertools.product(printable, repeat=4):
    X = 'A' * (length - 4) + ''.join(c)
    if alg == 'md5':
        h = md5(X).hexdigest()
    elif alg == 'sha1':
        h = sha1(X).hexdigest()
    elif alg == 'sha224':
        h = sha224(X).hexdigest()
    elif alg == 'sha256':
        h = sha256(X).hexdigest()
    elif alg == 'sha384':
        h = sha384(X).hexdigest()
    elif alg == 'sha512':
        h = sha512(X).hexdigest()
    if h[-6:] == h_tail:
        print X
        s.sendall(X + '\n')
        break

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

P = eval(data.split('\n')[4].split(' = ')[1])
mul = int(data.split('\n')[5].split(' ')[3])

a, b, m = get_param(P)
print '[+] a =', a
print '[+] b =', b
print '[+] m =', m

F = FiniteField(m)
E = EllipticCurve(F, [a, b])
P = E.point(P)
Q = mul * P
ans = '(%d, %d)' % (Q[0], Q[1])
print ans
s.sendall(ans + '\n')

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

実行結果は以下の通り。

Please submit a printable string X, such that sha224(X)[-6:] = 267912 and len(X) = 12
AAAAAAAA0N(L
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ hi! There are three integer points such that (x, y), (x+1, y), and +
+ (x+2, y) lies on the elliptic curve E. You are given one of them!! +
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
| One of such points is: P = (65066134629160492064348712349740186777271493389550937509098916844574681320586, 48248814918191461731826799414487116812840317323816727541195008776899801930943)
| Send the 820108665267417045281300596834085575361835494744709817713694502692897533236 * P :
[+] a = 65066134629160492064348712349740186777271493389550937509098916844574681320586
[+] b = 53990383394489977569370811874725037797601425434476441150827563908083881493406
[+] m = 65066134629160492064348712349740186777271493389550937509098916844574681320587
(34467009194367032598721374049670461242650808585495568505537602087481887486236, 9994827249345438784791406640639494428843057321974593620798547257160588804430)
+ Congratz! You got the flag: ASIS{4n_Ellip71c_curve_iZ_A_pl4Ne_al9ebr4iC_cUrv3}
ASIS{4n_Ellip71c_curve_iZ_A_pl4Ne_al9ebr4iC_cUrv3}

Jazzy (CRYPTO)

$ nc 76.74.178.201 31337
------------------------------------------------------------------------
|          ..:: Jazzy semantically secure cryptosystem ::..            |
|           Try to break this cryptosystem and find the flag!          |
------------------------------------------------------------------------
| Options:                                                             |
|	[E]ncryption function                                          |
|	[F]lag (encrypted)!                                            |
|	[P]ublic key                                                   |
|	[D]ecryption oracle                                            |
|	[Q]uit                                                         |
|----------------------------------------------------------------------|
E
def encrypt(msg, pubkey):
	h = len(bin(len(bin(pubkey)[2:]))[2:]) - 1	# dirty log :/
	m = bytes_to_long(msg)
	if len(bin(m)[2:]) % h != 0:
		m = '0' * (h - len(bin(m)[2:]) % h) + bin(m)[2:]
	else:
		m = bin(m)[2:]
	t = len(m) // h
	M = [m[h*i:h*i+h] for i in range(t)]
	r = random.randint(1, pubkey)
	s_0 = pow(r, 2, pubkey)
	C = []
	for i in range(t):
		s_i = pow(s_0, 2, pubkey)
		k = bin(s_i)[2:][-h:]
		c = bin(int(M[i], 2) ^ int(k, 2))[2:].zfill(h)
		C.append(c)
		s_0 = s_i
	enc = int(''.join(C), 2)
	return (enc, pow(s_i, 2, pubkey))

F
encrypt(flag, pubkey) = (285758784511988734996792473393119507219268400756160874488063750970012467915603914014511645886290587479644916689702765288982490530761182115010199238742235569617597696074084710949703041926451477768488923143163705793951552963523691597617034555469758028567143898017221627069705001555110077L, 11896097786012729204042896056711423330078393488205177168674589714389521254117566447810993885197638822768474361823019961382063217167203871544286134412708823199961452263320874375426857360765259524437045571031538041889334087041205316752479519452535747774811510959598049621167541077584122625837505724476433494244407626451743303338695473369869483530950286291805711592895773930535900800442181458942182852016952168916670632553498445201350945714177114220766208169020336848779278287955888503441256633459790389912147651603467159704565649410170203574852996088637779083925921241270667395841563487878790181620271210991198827612699L)
P
pubkey = 18040131314759667900457887678811020338614452709739086777151261886360982985518169561129314249248164816441006297459740894967672603373312703776907273574074206737224822094608881506549036557750755680495923316080676241717075216668353540054463958510613526526645837706530243811433203943369331718185791900249855413712334649759751651752486518265906541150975961029763480805875588043774798010489437893678347331269414867612772205562413709274511832250021458133791314427494532479509859686223786822752348579164492316978993634125041564682289063366281011085245142709161055404879441983281236867346195208490289706759726107124209265927453
D
| send an pair of integers, like (c, x), that you want to decrypt: 
(285758784511988734996792473393119507219268400756160874488063750970012467915603914014511645886290587479644916689702765288982490530761182115010199238742235569617597696074084710949703041926451477768488923143163705793951552963523691597617034555469758028567143898017221627069705001555110077, 11896097786012729204042896056711423330078393488205177168674589714389521254117566447810993885197638822768474361823019961382063217167203871544286134412708823199961452263320874375426857360765259524437045571031538041889334087041205316752479519452535747774811510959598049621167541077584122625837505724476433494244407626451743303338695473369869483530950286291805711592895773930535900800442181458942182852016952168916670632553498445201350945714177114220766208169020336848779278287955888503441256633459790389912147651603467159704565649410170203574852996088637779083925921241270667395841563487878790181620271210991198827612699)
| this decryption is NOT allowed :P

暗号化の処理はこうなっている。

・h = pubkeyのbit長のbinの長さ-1
・m = msgの数値
・mのbinの長さがhの倍数になるよう先頭に0パディングし、mのbinをmにセットする。
・M: hバイトブロックの配列
・r: 1~pubkeyのランダム整数
・s_0 = pow(r, 2, pubkey)
・C: 空配列
・ブロック単位に以下を実行
 ・s_i = pow(s_0, 2, pubkey)
 ・k = bin(s_i)[2:][-h:]
 ・c = bin(int(M[i], 2) ^ int(k, 2))[2:].zfill(h)
 ・C配列にcを追加
 ・s_0 = s_i
・enc = int(''.join(C), 2)
・(enc, pow(s_i, 2, pubkey))を返却

復号の方法はあるのかもしれないが、Deryption oracleの機能があるので、これを使う。flagの後ろにhビット適当なビットを付け暗号化することを考える。encは適当なビットをhビット分つけ、2つ目の要素はpow(flagの暗号化の2つ目の要素, 2, pubkey)にすることによって、最後に1ブロック追加して復号する。その後最後のブロックを切り落とせば、フラグになる。

import socket
from Crypto.Util.number import long_to_bytes

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

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('76.74.178.201', 31337))

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

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

print 'F'
s.sendall('F\n')
data = recvuntil(s, '\n').rstrip()
print data
enc = eval(data.split(' = ')[1])

print 'P'
s.sendall('P\n')
data = recvuntil(s, '\n').rstrip()
print data
pubkey = int(data.split(' = ')[1])

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

h = len(bin(len(bin(pubkey)[2:]))[2:]) - 1
s_i = enc[1]
k = bin(s_i)[2:][-h:]
c = bin(0 ^ int(k, 2))[2:].zfill(h)
try_enc0 = int(bin(enc[0])[2:] + c, 2)
try_enc1 = pow(enc[1], 2, pubkey)

try_enc = '(%d, %d)' % (try_enc0, try_enc1)
print try_enc
s.sendall(try_enc + '\n')
data = recvuntil(s, '\n').rstrip()
print data

try_dec = int(data.split(' ')[-1])
m = int(bin(try_dec)[2:-h], 2)
flag = long_to_bytes(m)
print '\nflag =', flag

実行結果は以下の通り。

------------------------------------------------------------------------
|          ..:: Jazzy semantically secure cryptosystem ::..            |
|           Try to break this cryptosystem and find the flag!          |
------------------------------------------------------------------------
| Options:                                                             |
|       [E]ncryption function                                          |
|       [F]lag (encrypted)!                                            |
|       [P]ublic key                                                   |
|       [D]ecryption oracle                                            |
|       [Q]uit                                                         |
|----------------------------------------------------------------------|
E
def encrypt(msg, pubkey):
        h = len(bin(len(bin(pubkey)[2:]))[2:]) - 1      # dirty log :/
        m = bytes_to_long(msg)
        if len(bin(m)[2:]) % h != 0:
                m = '0' * (h - len(bin(m)[2:]) % h) + bin(m)[2:]
        else:
                m = bin(m)[2:]
        t = len(m) // h
        M = [m[h*i:h*i+h] for i in range(t)]
        r = random.randint(1, pubkey)
        s_0 = pow(r, 2, pubkey)
        C = []
        for i in range(t):
                s_i = pow(s_0, 2, pubkey)
                k = bin(s_i)[2:][-h:]
                c = bin(int(M[i], 2) ^ int(k, 2))[2:].zfill(h)
                C.append(c)
                s_0 = s_i
        enc = int(''.join(C), 2)
        return (enc, pow(s_i, 2, pubkey))

F
encrypt(flag, pubkey) = (8940461672871239904396425210700665180657020465062541424029649906697472058250469651625821009153594478927461459532465576604445511763388192757139578270624506967716574274109099577107091212260513412997524304533801744556391690382207973669584797229092692055232614328377194044613021282005374073L, 3985029591321212142027572568617540370962282303225246496445833927207837883485559990969344567945607188130778286620710335474559434388362148061626139763513264168427324412985299050661646804654783252354484383127209126608396936040956420419446409631693594866624082051933348963212434810974998599187512068545652459182045834487418200289375258861133097864647387858644910844050199349346498764197263314983627084690252102482320177288142732354418219511888499030670861507391303887717168625280559640704029228845601132185486839340571767534227257434838551037846236558450858285808865600613762878191923064139275962090745084604141463362187L)
P
pubkey = 9971199606729552628222257386990532090096760501090853093092182992223314447514207252045974609200181096675069707649549137130420568806519428741568433539282864905090260769752010050789746214176691579274689573106767413433003319737254568194329462679338338601616884560418513288068808387192900799931032976223086150059485299659117003903212308255416372731428233125537622063104850039714453784515567794688318277809980781539402354379856503568481181430295326145146988300521030767102697111116802000019944684734704240579331396628961585375399188303320716579263836607609381854120101976947186976524148392714937486336105984234463154682213
D
| send an pair of integers, like (c, x), that you want to decrypt:
(9155032753020149662101939415757481144992788956224042418206361504458211387648480923264840713373280746421720534561244750442952204045709509383310928149119495134941772056687717966957661401354765734909464887842612986425745090951380965037654832362590916664558197072258246701683733792773503051403, 1369813811910032868431403621033793846859607333872495643113969203078728246802372650645236351677618184631495926474948943808806827706625702448780601256912555209388949999726522586529989918763637360844603360324623910588769647291649033527175764367863039743471562763773101395463886809524055943301361399914228909333960773219184289265135207953989057403689783020056819265887315346195218033640972295791693350828062346541508865734958926817700643333736331187348628551718917200317756734945549243368148740035302255079436542526202564752616545748949708812197907849817605752961146203430606092765551948821822880484277931424658334420915)
| the decrypted message is: 23885573558187132942244816671712487138942420248121706838741164907657524845957633817140643875962135979817699302378095948610723200260054545827725186829201209741258267835315040457645335063210274437739420225908272783355589184781925137083935650510066490690335507268451389132152854994628158464

flag = ((((......:::::: Great! the flag is: ASIS{BlUM_G0ldwaS53R_cryptOsySt3M_Iz_HI9hlY_vUlNEr4bl3_70_CCA!?} ::::::......))))
ASIS{BlUM_G0ldwaS53R_cryptOsySt3M_Iz_HI9hlY_vUlNEr4bl3_70_CCA!?}

Survey (COOL-DOWN)

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

ASIS{Can_I_See_you??!POSIX}