zer0pts CTF 2021 write up
動画リンク
よかったらチャンネル登録してね!
成績
正の点数獲得者951チーム中62位
Crpytoが難しい問題が多く、楽しめました。
war(sa)mup
動画リンク
YouTubeのvideoIDが不正です
ここで
このように2つの暗号文があり、 franklin-reiter related message attack
という攻撃手法が有効。
具体的には、
となり、
のように2つの式は
この式のgcdを取ると、
あとは
def gcd(g1, g2):
while g2:
g1, g2 = g2, g1 % g2
return g1.monic()
PRx.<x> = PolynomialRing(Zmod(n))
g1 = x^e - c2
g2 = (x*2 + 1)^e - c1
result = -gcd(g1, g2).coefficients()[0]
print(long_to_bytes(result*2 + 1))
3-AES
動画リンク
YouTubeのvideoIDが不正です
以下の3つの鍵、3つの暗号化手法で順番に暗号化し、逆順番に復号を行う。
keys = [md5(os.urandom(3)).digest() for _ in range(3)]
AES.new(keys[0], mode=AES.MODE_ECB),
AES.new(keys[1], mode=AES.MODE_CBC, iv=iv1),
AES.new(keys[2], mode=AES.MODE_CFB, iv=iv2, segment_size=8*16),
任意の平分
暗号化時は
3つの復号化手法を連結すると、以下のようになる。(詳細は動画を参照) Dが複合、Eが暗号、数字は3種類の鍵を表している。
m = D0(D1(E2(iv2) ^ c) ^ iv1)
2021/03/11 rkm0959さん(https://twitter.com/rkm0959) にtypoを見つけていただきました! Thanks!
ここで、鍵は 2^24 通り程度しかないので、
あとは同じ要領で残り2つの鍵を漏洩させればOK(詳細は動画参照)
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
from Crypto.Util.number import *
from binascii import hexlify, unhexlify
from hashlib import md5
import os
import signal
from pwn import *
flag = b"kurenaif{123456}"
keys = [md5(os.urandom(3)).digest() for _ in range(3)]
def get_ciphers(iv1, iv2):
return [
AES.new(keys[0], mode=AES.MODE_ECB),
AES.new(keys[1], mode=AES.MODE_CBC, iv=iv1),
AES.new(keys[2], mode=AES.MODE_CFB, iv=iv2, segment_size=8*16),
]
def encrypt(m: bytes) -> bytes:
iv1, iv2 = get_random_bytes(16), get_random_bytes(16)
assert len(m) % 16 == 0
ciphers = get_ciphers(iv1, iv2)
c = m
for cipher in ciphers:
c = cipher.encrypt(c)
return c, iv1, iv2
io = remote('crypto.ctf.zer0pts.com', 10929)
# io = remote('localhost', 2000)
def decrypt(c: bytes, iv1: bytes, iv2: bytes) -> bytes:
print(io.recvuntil('>'))
io.sendline("2")
ciphertext = b":".join([hexlify(x) for x in [iv1, iv2, c]]).decode()
io.sendline(ciphertext)
hoge = io.recvline().split(b':')[-1]
return long_to_bytes(eval(b'0x' + hoge.strip()))
### key0 ###
c = long_to_bytes(0x11223344556677889900112233445566)
iv1, iv1_dash, iv2 = get_random_bytes(16), get_random_bytes(16), get_random_bytes(16)
m = decrypt(c, iv1, iv2)
m_dash = decrypt(c, iv1_dash, iv2)
key0 = 0
for i in range((1<<24)):
if i % 10000 == 0:
print(i / (1<<24))
key = md5(long_to_bytes(i)).digest()
key = AES.new(key, mode=AES.MODE_ECB)
key.encrypt(m)
if bytes_to_long(key.encrypt(m)) ^ bytes_to_long(key.encrypt(m_dash)) == bytes_to_long(iv1) ^ bytes_to_long(iv1_dash):
key0 = md5(long_to_bytes(i)).digest()
break
### key1 ###
c = long_to_bytes(0x11223344556677889900112233445566)
c_dash = long_to_bytes(0x11223344556677889900112233445577)
iv1, iv2 = get_random_bytes(16), get_random_bytes(16)
cipher = AES.new(key0, mode=AES.MODE_ECB)
m = decrypt(c, iv1, iv2)
m = cipher.encrypt(m)
m_dash = decrypt(c_dash, iv1, iv2)
m_dash = cipher.encrypt(m_dash)
key1 = 0
for i in range((1<<24)):
if i % 10000 == 0:
print(i / (1<<24))
key = md5(long_to_bytes(i)).digest()
key = AES.new(key, mode=AES.MODE_ECB)
key.encrypt(m)
left = key.encrypt(long_to_bytes(bytes_to_long(m) ^ bytes_to_long(iv1)))
right = key.encrypt(long_to_bytes(bytes_to_long(m_dash) ^ bytes_to_long(iv1)))
if bytes_to_long(left) ^ bytes_to_long(right) == bytes_to_long(c) ^ bytes_to_long(c_dash):
key1 = md5(long_to_bytes(i)).digest()
break
### key2 ###
c = long_to_bytes(0x11223344556677889900112233445566)
iv1, iv2, iv2_dash = get_random_bytes(16), get_random_bytes(16), get_random_bytes(16)
cipher0 = AES.new(key0, mode=AES.MODE_ECB)
cipher1 = AES.new(key1, mode=AES.MODE_CBC, iv=iv1)
m = decrypt(c, iv1, iv2)
m = cipher0.encrypt(m)
m = cipher1.encrypt(m)
key2 = 0
for i in range((1<<24)):
if i % 10000 == 0:
print(i / (1<<24))
key = md5(long_to_bytes(i)).digest()
key = AES.new(key, mode=AES.MODE_ECB)
if bytes_to_long(key.encrypt(iv2)) == bytes_to_long(c) ^ bytes_to_long(m):
key2 = md5(long_to_bytes(i)).digest()
break
print("key0= ", key0)
print("key1= ", key1)
print("key2= ", key2)
def local_decrypt(c: bytes, iv1: bytes, iv2: bytes) -> bytes:
assert len(c) % 16 == 0
ciphers = get_ciphers(iv1, iv2)
m = c
for cipher in ciphers[::-1]:
m = cipher.decrypt(m)
return m
print(io.recvuntil('>'))
io.sendline("3")
hoge = io.recvline()
print(hoge)
OT or NOT OT
動画リンク
YouTubeのvideoIDが不正です
入力:
出力:
未知数:
が与えられる。これらの変数の関係は以下の通り。
うまく
ここで、
とすると、
となり、
となるので、この出力結果が 1,p-1のどちらかが出てくる。
あり得る4通りでこれを試せば良い。
import os
import signal
import random
from base64 import b64encode
from Crypto.Util.number import getStrongPrime, bytes_to_long
from Crypto.Util.Padding import pad
from Crypto.Cipher import AES
flag = b"kurenaifCTF{hogehoge}"
p = getStrongPrime(1024)
key = os.urandom(32)
iv = os.urandom(AES.block_size)
aes = AES.new(key=key, mode=AES.MODE_CBC, iv=iv)
c = aes.encrypt(pad(flag, AES.block_size))
key = bytes_to_long(key)
print("Encrypted flag: {}".format(b64encode(iv + c).decode()))
print("p = {}".format(p))
print("key.bit_length() = {}".format(key.bit_length()))
def check(x,y,z,p):
invts = z*y % p
ts = pow(invts, -1, p)
return (pow(ts,2,p) * x % p) == 1 or (pow(ts,2,p) * x * -1 % p) == 1
def solve(x,y,z,p):
count = []
for i in range(2):
for j in range(2):
if check(x ^ i,y ^ j,z,p):
count.append((i,j))
assert(len(count) == 1)
return count[0]
# signal.alarm(600)
memo = 0
ans = 0
key_expect = key
ans2 = []
while key > 0:
r = random.randint(2, p-1)
s = random.randint(2, p-1)
t = random.randint(2, p-1)
print("t = {}".format(t))
a = -1 % p
b = pow(2, -1, p) % p
c = pow(t,-2,p) % p
d = 2
assert all([a > 1 , b > 1 , c > 1 , d > 1])
assert len(set([a,b,c,d])) == 4
u = pow(a, r, p) * pow(c, s, p) % p
v = pow(b, r, p) * pow(c, s, p) % p
x = u ^ (key & 1)
y = v ^ ((key >> 1) & 1)
z = pow(d, r, p) * pow(t, s, p) % p
print("x = {}".format(x))
print("y = {}".format(y))
print("z = {}".format(z))
res = solve(x,y,z,p)
assert(res == ((key&1), (key>>1)&1))
ans2.append(res[0])
ans2.append(res[1])
ans += (res[0] << memo)
memo += 1
ans += (res[1] << memo)
memo += 1
key = key >> 2
janken vs yoshiking
動画リンク
YouTubeのvideoIDが不正です
出力:
未知数:
から
ここで、フェルマーの小定理より、
なので、この
ここでもし、
となる。
import random
import signal
# from flag import flag
from Crypto.Util.number import getStrongPrime, inverse
import statistics
from ptrlib import *
HANDNAMES = {
1: "Rock",
2: "Scissors",
3: "Paper"
}
# https://tex2e.github.io/blog/crypto/DLP
# https://ctftime.org/writeup/23567
# Baby-step Giant-step法
def babystep_giantstep(g, y, p, q=None):
if q is None:
q = p - 1
m = int(q**0.5 + 0.5)
# Baby step
table = {}
gr = 1 # g^r
for r in range(m):
table[gr] = r
gr = (gr * g) % p
# Giant step
try:
gm = pow(g, -m, p) # gm = g^{-m}
except:
return None
ygqm = y # ygqm = y * g^{-qm}
for q in range(m):
if ygqm in table: # 左辺と右辺が一致するとき
return q * m + table[ygqm]
ygqm = (ygqm * gm) % p
return None
# Pohlig–Hellman法
def pohlig_hellman_DLP(g, y, p):
crt_moduli = []
crt_remain = []
for q, memo in (p-1).factor(limit=2^30):
if q > 2^30:
break
for q, memo in factor(q^memo):
q = q ^ memo
x = babystep_giantstep(pow(g,(p-1)//q,p), pow(y,(p-1)//q,p), p, q)
if (x is None) or (x <= 1):
continue
crt_moduli.append(q)
crt_remain.append(x)
x = crt(crt_remain, crt_moduli)
return x
# g = 123123123123
# y = 1094511311619717224471473901707
# p = 2 * 32803 * 196159 * 1981991353 * 47814426923 + 1 # 素数p
# x = pohlig_hellman_DLP(g, y, p)
# print(x)
# print(pow(g, x, p) == y)
def commit(m, key):
(g, p), (x, _) = key
r = random.randint(2, p-1)
c1 = pow(g, r, p)
c2 = m * pow(g, r*x, p) % p
return (c1, c2)
def decrypt(c, key):
c1, c2 = c
_, (x, p)= key
m = c2 * inverse(pow(c1, x, p), p) % p
return m
def keygen(size):
p = getStrongPrime(size)
g = random.randint(2, p-1)
x = random.randint(2, p-1)
return (g, p), (x, p)
def judge(you,me):
result = (you - me + 3) % 3
if result == 0:
return 1
elif result == 1:
return 2
elif result == 2:
return 0
def solve(factor_list, c, p):
c1 = c[0]
c2 = c[1]
res = {}
res[1] = 0
res[2] = 0
res[3] = 0
for q in factor_list:
power = (p-1)//q
if(pow(c1, power, p) == 1):
for j in range(1, 4):
# print(pow(c2, power, p))
# print(pow(j, power, p))
# print("~~~")
if pow(c2, power, p) == pow(j, power, p):
res[j] += 1
res = sorted(res.items(), key=lambda x:-x[1])
return res
def janken(res):
# 確定
if res[0][1] != res[1][1]:
yoshiking_hand = res[0][0]
for x in range(1,4):
if judge(yoshiking_hand, x) == 2:
return x
# わからん
if res[0][1] == res[1][1] == res[2][1]:
print("random sorry")
return 1
# 二通りありえる場合は負けない手を出す
x = res[0][0]
y = res[1][0]
for ans in range(1,4):
if judge(x, ans) != 0 and judge(y, ans) != 0:
return ans
# local poc
# key = keygen(1024)
# (g, p), _ = key
#
# factor_list = []
# for qq, memo in (p-1).factor(limit=2**30):
# q = qq ^ memo
# print(q)
# factor_list.append(q)
# factor_list = factor_list[:-1]
#
# for _ in range(100):
# yoshiking_hand = random.randint(1, 3)
# c = commit(yoshiking_hand, key)
# res = solve(factor_list, c, p)
# janken(res)
io = remote('crypto.ctf.zer0pts.com', 10463)
# io = remote('localhost', 2000)
print(io.recvline()) # hello let's play...
temp = io.recvline()
print(temp)
g = int(temp.split(b':')[2].split(b',')[0])
print(g)
p = int(temp.split(b':')[-1])
factor_list = []
for qq, memo in (p-1).factor(limit=2**30):
q = qq ^ memo
print(q)
factor_list.append(q)
factor_list = factor_list[:-1]
print(io.recvline()) # ROUND...
win = 0
while True:
temp = io.recvline().split(b'=')[-1] # m)commitment is...
print(temp)
c = eval(temp)
res = solve(factor_list, c, p)
print(res)
hand = str(janken(res))
print("hand: ", hand)
io.sendline(hand)
io.recvline()
io.recvline()
io.recvline()
temp = io.recvline()
print(temp)
if b'win' in temp:
win += 1
print("wincount: ", win)
if win == 100:
break
print(io.recvuntil(b'[yoshiking]: my commitment'))
print(io.recvline())
print(io.recvline())
print(io.recvline())
print(io.recvline())
print(io.recvline())
print(io.recvline())
print(io.recvline())
print(io.recvline())
print(io.recvline())
Discussion