参加了今年的CRYPTO CTF 2022,直接啥也不会,出了好多数学类的题目,但我是真的不会,想着确实挺有意思也值得学习一下,决定刷一下历届CRYPTO CTF的题目,毕竟选择了这个方向,如此出名的赛事不做一做也不太合适。
计划把历年的CRYPTO CTF按照难度等级划分,分不同的博客发布wp,一来希望能提升一下个人水平,二来发现国内的搜索引擎好像搜不到此类wp,希望与大家共同交流。
【Farm】 149solves
from sage.all import *
import string, base64, math
from flag import flag
ALPHABET = string.printable[:62] + '\\='
F = list(GF(64))
def keygen(l):
key = [F[randint(1, 63)] for _ in range(l)]
key = math.prod(key) # Optimization the key length :D
return key
def maptofarm(c):
assert c in ALPHABET
return F[ALPHABET.index(c)]
def encrypt(msg, key):
m64 = base64.b64encode(msg)
enc, pkey = '', key**5 + key**3 + key**2 + 1
for m in m64:
enc += ALPHABET[F.index(pkey * maptofarm(chr(m)))]
return enc
key = keygen(14) # I think 64**14 > 2**64 is not brute-forcible :P
enc = encrypt(flag, key)
print(f'enc = {enc}')
#enc = 805c9GMYuD5RefTmabUNfS9N9YrkwbAbdZE0df91uCEytcoy9FDSbZ8Ay8jj
这道题有一个值得关注的点是 F = list(GF(64)) 会直接初始化 F 这个列表,并且是固定的,如此一来,加密过程只涉及到了一个未知量key。
一开始我没思路,因为正如题目所言 I think 64^14 > 2^64 is not brute-forcible 😛 ,key的可能性非常大,没有爆破的可能性,看起来没有什么办法解flag啊,一脸懵的我去看看wp。
这里其实有一个隐藏的条件,那就是已知明文前半部分"CCTF",好家伙,好久不做这类题都忘了还可以这么玩了。利用已知明文攻击,来恢复key。
exp:
import string, base64, math
ALPHABET = string.printable[:62] + '\\='
F = list(GF(64))
m=b"CCTF"
m=base64.b64encode(m).decode()
enc="805c9GMYuD5RefTmabUNfS9N9YrkwbAbdZE0df91uCEytcoy9FDSbZ8Ay8jj"
def maptofarm(c):
assert c in ALPHABET
return F[ALPHABET.index(c)]
Findex=ALPHABET.find("8")
index=F[Findex]
pkey=index/maptofarm(m[0])
flag=""
for i in enc:
Findex=ALPHABET.find(i)
index=F[Findex]
mm=index/pkey
aindex=F.index(mm)
m=ALPHABET[aindex]
flag+=m
print(base64.b64decode(flag))
【KeyBase】 118solves
#!/usr/bin/env python3
from Crypto.Util import number
from Crypto.Cipher import AES
import os, sys, random
from flag import flag
def keygen():
iv, key = [os.urandom(16) for _ in '01']
return iv, key
def encrypt(msg, iv, key):
aes = AES.new(key, AES.MODE_CBC, iv)
return aes.encrypt(msg)
def decrypt(enc, iv, key):
aes = AES.new(key, AES.MODE_CBC, iv)
return aes.decrypt(enc)
def die(*args):
pr(*args)
quit()
def pr(*args):
s = " ".join(map(str, args))
sys.stdout.write(s + "\n")
sys.stdout.flush()
def sc():
return sys.stdin.readline().strip()
def main():
border = "+"
pr(border*72)
pr(border, " hi all, welcome to the simple KEYBASE cryptography task, try to ", border)
pr(border, " decrypt the encrypted message and get the flag as a nice prize! ", border)
pr(border*72)
iv, key = keygen()
flag_enc = encrypt(flag, iv, key).hex()
while True:
pr("| Options: \n|\t[G]et the encrypted flag \n|\t[T]est the encryption \n|\t[Q]uit")
ans = sc().lower()
if ans == 'g':
pr("| encrypt(flag) =", flag_enc)
elif ans == 't':
pr("| Please send your 32 bytes message to encrypt: ")
msg_inp = sc()
if len(msg_inp) == 32:
enc = encrypt(msg_inp, iv, key).hex()
r = random.randint(0, 4)
s = 4 - r
mask_key = key[:-2].hex() + '*' * 4
mask_enc = enc[:r] + '*' * 28 + enc[32-s:]
pr("| enc =", mask_enc)
pr("| key =", mask_key)
else:
die("| SEND 32 BYTES MESSAGE :X")
elif ans == 'q':
die("Quitting ...")
else:
die("Bye ...")
if __name__ == '__main__':
main()
这道题本来需要nc连过去解题的,可惜现在环境已经关了,自己跑了一下试试。
输入g可以得到enflag,输入t再输入长度为32的字符串可以得到该字符串的加密内容,特殊的是加密后会给你enc的一部分和key[:-2],我的想法是爆破key的后两位,解密enflag,但是发现没有iv
由于可以自己发送明文并加密,可以发送长度为32的明文,根据CBC的性质,第一组密文拿来当作第二组密文生成时的向量v参与异或。所以可以通过ECB模式求出还未经异或的内容,再与第二组明文异或就得到了第一组密文。第一组密文通过ECB模式解密后与第一组明文异或得到IV
r = random.randint(0, 4)
s = 4 - r
mask_key = key[:-2].hex() + ‘’ * 4
mask_enc = enc[:r] + '’ * 28 + enc[32-s:]
可以发现enc有几率暴露我们发送的明文加密后的前四个字符,所以根据这四个字符爆破出符合条件的key,再通过上述思路来求解iv,最后利用iv和key解出flag
下面是我利用flag自己生成了enc
exp:
from Crypto.Util import number
from Crypto.Cipher import AES
import os, sys, random
from pwn import xor
#iv=\x07\x9aH\xf4b\xdd\xd3\xcb\xa5\xcf\xe7x\x84\x7f\xf3!
#key=\xf0.\xda\xe0\xc0\xdb\xf38\xe00\x9d\xf1\x08\r\x94\xe9
enc_flag=bytes.fromhex("3d0abe4a4186687c7e74c93ecb3e211ea98d192e6d4fddc1f83981fe35d795f5")
enc="f063****************************56db4933ff8484ea6a17b769c3c3cd31"
key="f02edae0c0dbf338e0309df1080d****"
def bxor(s1,s2):
return b''.join(bytes([a ^ b]) for a,b in zip(s1,s2))
prefix = enc.split("*")[0]
prefix=bytes.fromhex(prefix)
def decrypt(enc, iv, key):
aes = AES.new(key, AES.MODE_CBC, iv)
return aes.decrypt(enc)
key = bytearray.fromhex(key[:-4]) + b"\x00\x00"
for k1 in range(256):
key[-2] = k1
for k2 in range(256):
key[-1] = k2
if bxor(AES.new(key, AES.MODE_ECB).decrypt(bytes.fromhex(enc[-32:])), b"A"*16)[:2] == prefix:
print("KEY=",key.hex())
# Recover full first block by xoring second block with known plaintext
block = bxor(AES.new(key, AES.MODE_ECB).decrypt(bytes.fromhex(enc[-32:])), b"A"*16)
# IV is whatever the first block was XORed with prior to encryption. Decrypt and XOR.
IV = bxor(AES.new(key, AES.MODE_ECB).decrypt(block), b"A"*16)
print("IV=",IV.hex())
print(f"Recovered key candidate: {key.hex()} with IV {IV.hex()}")
print(decrypt(enc_flag,IV,key))
附官方wp:
from pwn import *
from Crypto.Cipher import AES
def bxor(s1,s2):
return b''.join(bytes([a ^ b]) for a,b in zip(s1,s2))
r = remote("01.cr.yp.toc.tf", 17010)
r.sendlineafter("[Q]uit\n", "G")
enc_flag = bytes.fromhex(r.recvline().strip().decode().split(" = ")[1])
while True:
r.sendlineafter("[Q]uit\n", "T")
r.sendlineafter("Please send your 32 bytes message to encrypt: \n", b"A"*32)
enc = r.recvline().strip().decode().split(" = ")[1]
key = r.recvline().strip().decode().split(" = ")[1]
prefix = enc.split("*")[0]
if len(prefix) == 4:
prefix = bytes.fromhex(prefix)
break
key = bytearray.fromhex(key[:-4]) + b"\x00\x00"
for k1 in range(256):
key[-2] = k1
for k2 in range(256):
key[-1] = k2
# Decrypt the second block alone, without CBC/IV, and XOR with the two unmasked bytes.
# Result should be original plaintext "AA.....".
if bxor(AES.new(key, AES.MODE_ECB).decrypt(bytes.fromhex(enc[-32:])), b"AA") == prefix:
# Recover full first block by xoring second block with known plaintext
block = bxor(AES.new(key, AES.MODE_ECB).decrypt(bytes.fromhex(enc[-32:])), b"A"*16)
# IV is whatever the first block was XORed with prior to encryption. Decrypt and XOR.
IV = bxor(AES.new(key, AES.MODE_ECB).decrypt(block), b"A"*16)
print(f"Recovered key candidate: {key.hex()} with IV {IV.hex()}")
print(AES.new(key, AES.MODE_CBC, IV).decrypt(enc_flag))
【Symbols】70solves
公式敲多了应该一眼就能看出来是LATEX数学符号,对照明文的前四个CCTF,很明显了。一开始以为数学符号有在表中的顺序,发现latex数学符号太多了,后来发现是markdown语法的首字母
latex数学符号大全
\Cap \Cap \Theta \Finv { \Pi \ltimes \aleph y _ \wp \infty \therefore \heartsuit _ \Lsh \aleph \Theta \eth \Xi }
⋒
⋒
Θ
Ⅎ
{
Π
⋉
ℵ
y
_
℘
∞
∴
♡
_
↰
ℵ
Θ
ð
Ξ
}
\Cap \Cap \Theta \Finv \{ \Pi \ltimes \aleph y \_ \wp \infty \therefore \heartsuit \_ \Lsh \aleph \Theta \eth \Xi \}
⋒⋒ΘℲ{Π⋉ℵy_℘∞∴♡_↰ℵΘðΞ}
所以flag为
CCTF{Play_with_LaTeX}