CRYPTO CTF 2021 easy

参加了今年的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}

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Paintrain

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值