2024 网鼎杯 青龙&白虎

WriteUp 2天前 admin
51 0 0

青龙

crypto001

from Crypto.Util.number import *
from secret import flag

p = getPrime(512)
q = getPrime(512)
n = p * q
d = getPrime(299)
e = inverse(d,(p-1)*(q-1))
m = bytes_to_long(flag)
c = pow(m,e,n)
hint1 = p >> (512-70)
hint2 = q >> (512-70)

print(f"n = {n}")
print(f"e = {e}")
print(f"c = {c}")
print(f"hint1 = {hint1}")
print(f"hint2 = {hint2}")

已知 p,q 高位的bd_attack,纯原题了,详解可以参考 2023 CryptoCTF-Insights 一题

CryptoCTF -Insights

Van1sh,公众号:Van1sh2023 CryptoCTF(二)

crypto002

# coding: utf-8
#!/usr/bin/env python2

import gmpy2
import random
import binascii
from hashlib import sha256
from sympy import nextprime
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from Crypto.Util.number import long_to_bytes
from FLAG import flag
#flag = 'wdflag{123}'

def victory_encrypt(plaintext, key):
    key = key.upper()
    key_length = len(key)
    plaintext = plaintext.upper()
    ciphertext = ''

    for i, char in enumerate(plaintext):
        if char.isalpha():
            shift = ord(key[i % key_length]) - ord('A')
            encrypted_char = chr((ord(char) - ord('A') + shift) % 26 + ord('A'))
            ciphertext += encrypted_char
        else:
            ciphertext += char

    return ciphertext

victory_key = "WANGDINGCUP"
victory_encrypted_flag = victory_encrypt(flag, victory_key)

p = 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f
a = 0
b = 7
xG = 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798
yG = 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8
G = (xG, yG)
n = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141
h = 1
zero = (0,0)

dA = nextprime(random.randint(0, n))

if dA > n:
    print("warning!!")

def addition(t1, t2):
    if t1 == zero:
        return t2
    if t2 == zero:
        return t2
    (m1, n1) = t1
    (m2, n2) = t2
    if m1 == m2:
        if n1 == 0 or n1 != n2:
            return zero
        else:
            k = (3 * m1 * m1 + a) % p * gmpy2.invert(2 * n1 , p) % p
    else:
        k = (n2 - n1 + p) % p * gmpy2.invert((m2 - m1 + p) % p, p) % p
    m3 = (k * k % p - m1 - m2 + p * 2) % p
    n3 = (k * (m1 - m3) % p - n1 + p) % p
    return (int(m3),int(n3))

def multiplication(x, k):
    ans = zero
    t = 1
    while(t <= k):
        if (k &t )>0:
            ans = addition(ans, x)
        x = addition(x, x)
        t <<= 1
    return ans

def getrs(z, k):
    (xp, yp) = P
    r = xp
    s = (z + r * dA % n) % n * gmpy2.invert(k, n) % n
    return r,s

z1 = random.randint(0, p)
z2 = random.randint(0, p)
k = random.randint(0, n)
P = multiplication(G, k)
hA = multiplication(G, dA)
r1, s1 = getrs(z1, k)
r2, s2 = getrs(z2, k)

print("r1 = {}".format(r1))
print("r2 = {}".format(r2))
print("s1 = {}".format(s1))
print("s2 = {}".format(s2))
print("z1 = {}".format(z1))
print("z2 = {}".format(z2))

key = sha256(long_to_bytes(dA)).digest()
cipher = AES.new(key, AES.MODE_CBC)
iv = cipher.iv
encrypted_flag = cipher.encrypt(pad(victory_encrypted_flag.encode(), AES.block_size))
encrypted_flag_hex = binascii.hexlify(iv + encrypted_flag).decode('utf-8')

print("Encrypted flag (AES in CBC mode, hex):", encrypted_flag_hex)

题目是一个很明显的套娃题,先用维吉尼亚密码把 flag 加密(密钥已知),然后又用 ECDSA 对两条消息做了一个签名,其中临时密钥重用,并且签名私钥的哈希又用作了 AES 的加密密钥,加密了 前面的 flag 密文。

所以解题步骤也是显而易见。先是临时密钥重用攻击获取 ECDSA 签名私钥,然后对密文 AES解密,维吉尼亚解密,就可以了。

临时密钥重用攻击

s = (z + r * dA % n) % n * gmpy2.invert(k, n) % n

签名公式  

其中 已知, 不变

所以

# sagemath
r1 = 
r2 = 
s1 = 
s2 = 
z1 = 
z2 = 

k = (z1-z2)/(s1-s2) % n
d = (s1*int(k)-z1) / int(r1) % n

AES 解密

c = ''
c = bytes.fromhex(c)
iv = c[:16]
c = c[16:]
key = sha256(long_to_bytes(d)).digest()
cipher = AES.new(iv= iv, key=key, mode = AES.MODE_CBC)
victory_encrypted_flag = cipher.decrypt(c)

维吉尼亚解密,+ shift 改成 – shift 就好了。

def victory_decrypt(plaintext, key):
    key = key.upper()
    key_length = len(key)
    plaintext = plaintext.upper()
    ciphertext = ''

    for i, char in enumerate(plaintext):
        if char.isalpha():
            shift = ord(key[i % key_length]) - ord('A')
            encrypted_char = chr((ord(char) - ord('A') - shift) % 26 + ord('A'))
            ciphertext += encrypted_char
        else:
            ciphertext += char

    return ciphertext

victory_key = "WANGDINGCUP"


key_length=len(victory_key)
victory_encrypted_flag = ''


flag = victory_decrypt(victory_encrypted_flag, victory_key)
print(flag.lower())

白虎

crypto01

from sage.all import *
from Crypto.Util.number import *

block_size = 64
rounds = 14

P_permutation = Permutations(list(range(block_size))).random_element()
inverse_P_permutation = [P_permutation.index(i) for i in range(block_size)]

MASK = 0b1110001001111001000110010000100010101111101100101110100001001001
IV = 7

b2i = lambda x: Integer(sum([x[i] * 2**i for i in range(len(x))]))

def pad(x):
    padlen = block_size - len(x) % block_size
    return x + bytes([padlen] * padlen)

def P(x):
    bit_x = x.bits()
    if len(bit_x) < block_size:
        bit_x.extend([0] * (block_size - len(bit_x)))
    bit_x = [bit_x[P_permutation[i]] for i in range(block_size)]
    return b2i(bit_x)

def P_inv(x):
    bit_x = x.bits()
    if len(bit_x) < block_size:
        bit_x.extend([0] * (block_size - len(bit_x)))
    bit_x = [bit_x[inverse_P_permutation[i]] for i in range(block_size)]
    return b2i(bit_x)

def S(x):
    x1 = x
    x2 = x << IV & MASK
    x3 = x << IV & ((1 << block_size) - 1) | MASK
    return x1 ^ x2 ^ x3

def encrypt(message_block, key):
    ret = message_block
    for i in range(rounds):
        ret = P_inv(S(P(ret)) ^ key)
    return ret

from secret import flag
from hashlib import md5

message = pad(flag)
message = [Integer(bytes_to_long(message[i:i+8])) for i in range(0, len(message), 8)]

key = int(md5(flag).hexdigest(), 16) & ((1 << block_size) - 1)

ciphertext = [encrypt(m, key) for m in message]

print(ciphertext)
print(P_permutation)

注意到 encrypt 函数里除了 P 和 P_inv 是非线性置换,剩下的 S 和 密钥异或都是线性操作。然后每一轮的 P 和 P_inv 又都相互抵消,只留一个最开始的 P 和最后的 P_inv。

这里又知道一组明文(flag的格式,外加爆破一个字节)和密文,那我们就先对明文 P 一个,再对密文 P 一个,然后添加约束条件, ,就齐活了。

from z3 import *

def encryptwhithoutP(message_block, key):
    ret = message_block
    for i in range(rounds):
        ret = (S(ret) ^ key)
    return ret

m=
c=
key = BitVec("key",64)
s = Solver()
s.add(encryptwhithoutP(P(m),key) == P(c))
if s.check() == sat:
    m = s.model()
    print(m)

有了一个密钥,再用同样的方法把明文约束出来就行了。

2024 网鼎杯 青龙&白虎


crypto02

from Crypto.Util.number import getPrime, isPrime, GCD, inverse

nbits = 2048
gbits = 1000
g = getPrime(int(gbits))
while True:
    a = getPrime(int(nbits*0.5)-gbits)
    p = 2*g*a + 1
    if isPrime(p):
        break

while True:
    b = getPrime(int(nbits*0.5)-gbits)
    q = 2*g*b + 1
    if p!=q and isPrime(q):
        break
N = p*q
e = 65537

def str2int(s):
    return int(s.encode('latin-1').hex(),16)

def int2str(i):
    tmp=hex(i)[2:]
    if len(tmp)%2==1:
        tmp='0'+tmp
    return bytes.fromhex(tmp).decode('latin-1')

with open('pubkey.txt','w'as f:
    f.write(str(e)+'n')
    f.write(str(N)+'n')

with open('flag.txt'as f:
    plain = str2int(f.read())

c = pow(plain,e,N)
with open('cipher.txt','wb'as f:
    f.write(int2str(c).encode('latin-1'))

19 年的原题,夸张了说实话。[密码学学习笔记 之 浅析Pollard’s rho algorithm及其应用 | Van1sh的小屋]

Btw,这里的map是 ,后面有一篇新的论文指出,的 map 要更好些。参考 Common Prime RSA 笔记 | 独奏の小屋:[hasegawaazusa.github.io/common-prime-rsa.html]


原文始发于微信公众号(Van1sh):2024 网鼎杯 青龙&白虎

版权声明:admin 发表于 2024年11月12日 下午4:13。
转载请注明:2024 网鼎杯 青龙&白虎 | CTF导航

相关文章