naby

导航

0xGame2024-week2-crypto

Crypto

LFSR-baby

from random import getrandbits
from secret import flag,seed
from hashlib import md5


def MD5(m):return md5(str(m).encode()).hexdigest()

class LFSR:
	def __init__(self, seed, Length):
		self.Length = Length
		assert seed.bit_length() < self.Length + 1
		self.Mask_seed  = getrandbits(self.Length)
		self.state = self.init_state(seed)
		self.mask  = self.init_state(self.Mask_seed)

	def init_state(self, seed):
		result = [int(i) for i in bin(seed)[2:]]
		PadLenth = self.Length - len(result)
		result += [ 0 ] * PadLenth
		assert len(result) == self.Length
		return result

	def next(self):
		output = 0
		for i in range(self.Length):
			output ^= self.state[i] & self.mask[i] 
		self.state  =  self.state[ 1: ] + [output]
		return output

	def getrandbits(self,Length):
		result = []
		for _ in range(Length):
			result.append(str(self.next()))
		return int(''.join(result),2)

assert seed.bit_length() == 128
test = LFSR(seed,128)
print(test.Mask_seed)
print(test.getrandbits(128))
print(test.getrandbits(128))

assert flag == '0xGame{' + MD5(seed) + '}'

最基础的lfsr,给了mask,我这里稍微讲解一下,顺便看看自己梳理一遍看看哪里不懂,就根据题目代码来讲,讲得会很省略,像循环周期相关的知识这里就不讲了,建议如果不懂的话,可以先自己看一下wiki里的介绍,或者其他师傅的博客,建议用上纸笔,然后自己用代码实现一下,总之就是动起手来,不要只看不练。)

参考文献:

线性反馈移位寄存器 - LFSR - CTF Wiki (ctf-wiki.org)

LFSR | DexterJie'Blog

LSFR,中文为线性反馈移位寄存器,根据名字,我们能够知道,这个函数(姑且称其为函数)是线性的,然后跟每一位都有关,一般是用来生成密钥流。

众所周知,LSFR需要一个初始值,也就是这题的seed;还需要一个系数(可以这么认为吧),也就是这里的mask。这两个值,就可以决定之后所有的密钥位。

之后我们来分析一下是如何产生比特流的。看此题中next()函数,我们可以知道,之后的比特跟 LFSR此时的状态state和mask有关,output每次异或上\(state_i\&mask_i\),然后将LFSR状态的最高位删除,在最低位添上output,这就多生成了1bit。

我们可以知道异或就是 模二加法,"与"运算可以当成乘法,所以我们可以写成式子\(state_{n+i}=\sum_{j=i}^{n+i-1}mask_j*state_j \mod 2\)

理解了生成,我们来讲解一下知道mask的情况下如何还原出一开始的seed。

前提条件:

正常情况下,mask的位数与seed的位数一样,记为n(本题为128),所以mask的最高位(前面)比特一定是1,然后我们还需要剩下一组state,也就是本题继续生成了128位的数据就够了,所以只需要seed1就够了。(扩展:如果少了那么两三位,可以直接尝试爆破)

根据上面生成的式子\(state_{n}=\sum_{j=0}^{n-1}mask_j*state_j \mod 2\),我们可以先提取出state的最后一位,这个最后一位就是通过前面n(128)位来生成的,然后就可以利用下面式子还原出求这个最后一位的state的第一位\(state_0=state_n+\sum_{j=1}^{n-1}mask_j*state_j\mod 2\)(模2群下,加法和减法相同),每次都更新state,到最后就可以还原出一开始state,也就是seed了。

from hashlib import md5
def MD5(m):return md5(str(m).encode()).hexdigest()

mask=245818399386224174743537177607796459213
mask=bin(mask)[2:]
assert mask[0]=='1'

seed=103763907686833223776774671653901476306
seed=bin(seed)[2:].zfill(128)

bitslen=len(mask)

state=seed
for i in range(bitslen):
	pre=int(state[-1])
	state="?"+state[:-1]
	for j in range(1,bitslen):
		pre=(pre+int(mask[j])*int(state[j]))%2
	state=str(pre)+state[1:]

flag=int(state,2)
print('0xGame{' + MD5(flag) + '}')
#0xGame{030ec00de18ceb4ddea5f6612d28bf39}

LFSR-easy

from random import getrandbits
from secret import flag,Mask_seed
from hashlib import md5


def MD5(m):return md5(str(m).encode()).hexdigest()

class LFSR:
	def __init__(self, Mask_seed, Length):
		self.Length = Length
		assert Mask_seed.bit_length() < self.Length + 1
		self.seed  = getrandbits(self.Length)
		self.state = self.init_state(self.seed)
		self.mask  = self.init_state(Mask_seed)

	def init_state(self, seed):
		result = [int(i) for i in bin(seed)[2:]]
		PadLenth = self.Length - len(result)
		result += [ 0 ] * PadLenth
		assert len(result) == self.Length
		return result

	def next(self):
		output = 0
		for i in range(self.Length):
			output ^= self.state[i] & self.mask[i] 
		self.state  =  self.state[ 1: ] + [output]
		return output

	def getrandbits(self,Length):
		result = []
		for _ in range(Length):
			result.append(str(self.next()))
		return int(''.join(result),2)

assert Mask_seed.bit_length() == 128
test = LFSR(Mask_seed,128)
print(test.seed)
print(test.getrandbits(128))
print(test.getrandbits(128))

assert flag == '0xGame{' + MD5(Mask_seed) + '}'

在这题中,我们没有了mask,反而是要求mask,就需要使用B-M算法,利用矩阵还原出mask。

前提条件:

需要知道2nbits的数据(如果只少了两三位可以爆破)

所以这题只用前两个就好了。

感觉这题wiki里已经够详细了,就是构造矩阵然后解决就好了,对着代码看一下,应该就不难了。

seeds=[299913606793279087601607783679841106505,192457791072277356149547266972735354901]
seed="".join([bin(i)[2:].zfill(128) for i in seeds])
key=seed
lfsr_bits=128
 
r = seed
a = []
for i in range(len(r)):
    a.append(int(r[i]))
res = []
for i in range(lfsr_bits):
    for j in range(lfsr_bits):
        if a[i+j]==1:
            res.append(1)
        else:
            res.append(0)
sn = []
for i in range(lfsr_bits):
    if a[lfsr_bits+i]==1:
        sn.append(1)
    else:
        sn.append(0)
MS = MatrixSpace(GF(2),lfsr_bits,lfsr_bits)
MSS = MatrixSpace(GF(2),1,lfsr_bits)
A = MS(res)
s = MSS(sn)
inv = A.inverse()
mask = s*inv

flag=""
for i in mask[0]:
    flag+=str(i)
flag=int(flag,2)
from hashlib import md5
def MD5(m):return md5(str(m).encode()).hexdigest()
print('0xGame{' + MD5(flag) + '}')
#0xGame{d56821feacab64cdb87c754ad06823a2}

RC4

task.py

#!/usr/local/bin/python
from os import urandom
from random import choice
from hashlib import sha256
from string import ascii_letters, digits

from util import *
from secret import flag

def proof_of_work():
    proof = ''.join([choice(ascii_letters+digits) for _ in range(20)])
    _hexdigest = sha256(proof.encode()).hexdigest()
    print(f"[+] sha256(XXXX+{proof[4:]}) == {_hexdigest}")
    x = input('[+] Plz tell me XXXX: ')
    if len(x) != 4 or sha256( (x+proof[4:]).encode() ).hexdigest() != _hexdigest:
        return False
    return True

if __name__ == '__main__':
    assert proof_of_work()
    KEY = urandom(8)
    keystream = RC4(KEY)

    print("[+] Give me the text you want to encrypt:")
    m = input('>')
    c = Encrypt(m,keystream)
    print("[+] Here are the encrypt result:")
    print(f'c = {c}')

    keystream = RC4(KEY)
    print("[+] Give you the encrypted flag:")
    c = Encrypt(flag,keystream)
    print(f'c = {c}')

util.py

from os import urandom
from secret import flag

def KSA(key):
    keylength = len(key)

    S = [i for i in range(256)]

    j = 0
    for i in range(256):
        j = (j + S[i] + key[i % keylength]) % 256
        S[i], S[j] = S[j], S[i]  # swap

    return S

def PRGA(S):
    i = 0
    j = 0
    while True:
        i = (i + 1) % 256
        j = (j + S[i]) % 256
        S[i], S[j] = S[j], S[i]  # swap

        K = S[(S[i] + S[j]) % 256]
        yield K

def RC4(key):
    S = KSA(key)
    return PRGA(S)

def Encrypt(plaintext,keystream):
    if type(plaintext) == bytes:
        pt = plaintext
    else:
        pt = bytes.fromhex(plaintext)

    result = b''
    for i in pt:
        result += bytes([i^next(keystream)])
    return result.hex()

RC4加密等于解密,不用需要key,因为当key一样时,最后经过一系列固定的变换后生成的状态也是一样的,最后只是跟明文异或而已。

所以\(key\)^\(m_1=c_1\)\(key\)^\(m_2=c_2\),这里我们能够自己构造\(m_1\),那么就可以得到key的最后状态,之后跟\(c_2\)异或就可以得到flag。

注意服务接收的16进制,我这里转递的参数是128位a,意味着到服务器上就是b'\xAA'*64,最后得到key异或的也就是0xAA

from hashlib import sha256
from string import ascii_letters, digits
import itertools
from pwn import *

r=remote("118.195.138.159",10001)
r.recvuntil(b'XXXX+')
suf=r.recv(16)
dir=ascii_letters+digits
r.recvuntil(b' == ')
proof=r.recvline().strip()


payload=""
for i,j,k,l in itertools.product(dir,repeat=4):
    s=i+j+k+l+suf.decode()
    ver=sha256(s.encode()).hexdigest().encode()
    if ver == proof:
        payload=i+j+k+l
        break
r.recvuntil(b'XXXX: ')
r.sendline(payload.encode())

r.recvline()

payload=b'a'*128
r.sendline(payload)

r.recvline()

r.recvuntil(b'c = ')
my_c=r.recvline().strip()
my_c=bytes.fromhex(my_c.decode())
r.recvuntil(b'c = ')
c=r.recvline().strip()
c=bytes.fromhex(c.decode())

for i in range(len(c)):
    print(chr(c[i]^my_c[i]^0xAA),end="")
print()
r.interactive()

RSA-IV

task.py

#!/usr/local/bin/python
from os import urandom
from hashlib import sha256
from random import choice, getrandbits
from string import ascii_letters, digits

from challenge import *
from secret import flag

def proof_of_work():
	proof = ''.join([choice(ascii_letters+digits) for _ in range(20)])
	_hexdigest = sha256(proof.encode()).hexdigest()
	print(f"[+] sha256(XXXX+{proof[4:]}) == {_hexdigest}")
	x = input('[+] Plz tell me XXXX: ')
	if len(x) != 4 or sha256( (x+proof[4:]).encode() ).hexdigest() != _hexdigest:
		return False
	return True

def choice_(num):
	if num not in [0,1,2,3]:return
	global scores
	m = getrandbits(96)

	match num:
		case 0:
			print( challenge0(m) )
		case 1:
			print( challenge1(m) )
		case 2:
			print( challenge2(m) )
		case 3:
			print( challenge3(m) )

	print('[+] input answer:')
	m_= int(input('>'))
	scores[num] = (m_==m)
	score_= sum(scores)
	print(f'[+] score:{score_}')

	if score_ == 4:
		print(f'[+] flag:{flag}')
		exit()

assert proof_of_work()
scores = [0, 0, 0, 0]
while True:
	print('[+] input choice:')
	choice_( int(input('>')) )

challenge.py

from Crypto.Util.number import getPrime, inverse, bytes_to_long

def challenge0(m):
	p = getPrime(150)
	q = getPrime(150)
	N = p * q
	e = 3
	c = pow(m, e, N)
	return (N, e, c)

def challenge1(m):
	p = getPrime(64)
	q = getPrime(64)
	N = p * q
	e = 0x10001
	dp = inverse(e, p-1)
	c = pow(m, e, N)
	return (N, e, c, dp)

def challenge2(m):
	p = getPrime(64)
	q = getPrime(64)
	N = p * q
	phi = (p-1) * (q-1)
	d = getPrime(21)
	e = inverse(d, phi)
	c = pow(m, e, N)
	return (N, e, c)

def challenge3(m):
	p = getPrime(64)
	q = getPrime(64)
	N = p * q
	e = getPrime(127)
	c = pow(m, e , N)
	e_= getPrime(127)
	c_= pow(m, e_, N)
	return (N, e, c, e_, c_)

在sagemath里跑pwntools,直接套脚本了,就不讲了

challenge0:\(m^3<n\)直接开根

challenge1:dp泄露,e较小,爆破

challenge2:d较小,e很大,维纳攻击

challenge3:共模攻击

#sagemath

from hashlib import sha256
from string import ascii_letters, digits
import itertools
from pwn import *

r=remote("118.195.138.159",10003)
r.recvuntil(b'XXXX+')
suf=r.recv(16)
dir=ascii_letters+digits
r.recvuntil(b' == ')
proof=r.recvline().strip()

payload=""
for i,j,k,l in itertools.product(dir,repeat=4):
    s=i+j+k+l+suf.decode()
    ver=sha256(s.encode()).hexdigest().encode()
    if ver == proof:
        payload=i+j+k+l
        break
r.recvuntil(b'XXXX: ')
r.sendline(payload.encode())

from Crypto.Util.number import *
from gmpy2 import *

r.recvuntil(b'choice:')
r.sendline(str(0).encode())
r.recvuntil(b'>')
N,e,c=eval(r.recvline().strip().decode())
m=int(iroot(c,e)[0])
r.sendlineafter(b'[+] input answer:',str(m).encode())

r.recvuntil(b'choice:')
r.sendline(str(1).encode())
r.recvuntil(b'>')
N, e, c, dp=eval(r.recvline().strip().decode())
k=1
while 1:
    p=(dp*e-1+k)//k
    if N%p==0:
        q=N//p
        phi=(p-1)*(q-1)
        d=gmpy2.invert(e,phi)
        m=int(pow(c,d,N))
        r.sendlineafter(b'[+] input answer:',str(m).encode())
        break
    k=k+1
 
r.recvuntil(b'choice:')
r.sendline(str(2).encode())
r.recvuntil(b'>')
N, e, c=eval(r.recvline().strip().decode())
def wienerAttack(N, e):
    cf = continued_fraction(e / N)
    convers = cf.convergents()
    for pkd in convers:
        # possible k, d
        pk, pd = pkd.as_integer_ratio()
        if pk == 0:
            continue
        
        # verify
        if (e * pd - 1) % pk != 0:
            continue
        
        # possible phi
        pphi = (e * pd - 1) // pk
        p = var('p', domain=ZZ)
        roots = solve(p ** 2 + (pphi - N - 1) * p + N, p)
        if len(roots) == 2:
            # possible p, q
            pp, pq = roots
            if pp * pq == N:
                return pp, pq, pd
    raise ValueError('Could not factor N!')
p, q, d = wienerAttack(N, e)
m=int(pow(c, d, N))
r.sendlineafter(b'[+] input answer:',str(m).encode())

r.recvuntil(b'choice:')
r.sendline(str(3).encode())
r.recvuntil(b'>')
N, e, c, e_, c_=eval(r.recvline().strip().decode())
s,s1,s2=gcdext(e,e_)
m=pow(c,s1,N)*pow(c_,s2,N)%N
r.sendlineafter(b'[+] input answer:',str(int(m)).encode())

r.interactive()

Diffie-Hellman

#!/usr/local/bin/python
from Crypto.Util.number import isPrime, getPrime
from string import ascii_letters, digits
from Crypto.Cipher import AES
from hashlib import sha256
from random import randint
from random import choice
from hashlib import md5
from os import urandom

from secret import flag

def MD5(m):return md5( str(m).encode() ).digest()

def gen(bit_length): 
	while True:
		q = getPrime(bit_length)
		p = 2*q + 1
		if isPrime(p):
			g = randint(2,p-1)
			if (pow(g,2,p) != 1) & (pow(g,q,p) != 1):
				break
	return q,g

def proof_of_work():
    proof = ''.join([choice(ascii_letters+digits) for _ in range(20)])
    _hexdigest = sha256(proof.encode()).hexdigest()
    print(f"[+] sha256(XXXX+{proof[4:]}) == {_hexdigest}")
    x = input('[+] Plz tell me XXXX: ')
    if len(x) != 4 or sha256( (x+proof[4:]).encode() ).hexdigest() != _hexdigest:
        return False
    return True

assert proof_of_work()

q,g = gen(128)
print(f'Share (q,g) : {q,g}')
Alice_PriKey = randint(1, q)
Alice_PubKey = pow(g, Alice_PriKey, q)
print(f'Alice_PubKey : {Alice_PubKey}')

Bob_PubKey = int( input("[+] Give me the Bob_PubKey\n> ") )
print(f'Bob_PubKey : {Bob_PubKey}')

Share_Key = pow(Bob_PubKey, Alice_PriKey, q)
Cipher = AES.new(MD5(Share_Key), AES.MODE_ECB)
ct = Cipher.encrypt(flag)
print(f'Alice tell Bob : {ct.hex()}')

感觉很奇怪,1的任何次方都是1,直接传过去解密就行了。

from hashlib import sha256
from string import ascii_letters, digits
import itertools
from pwn import *

r=remote("118.195.138.159",10000)
r.recvuntil(b'XXXX+')
suf=r.recv(16)
dir=ascii_letters+digits
r.recvuntil(b' == ')
proof=r.recvline().strip()

payload=""
for i,j,k,l in itertools.product(dir,repeat=4):
    s=i+j+k+l+suf.decode()
    ver=sha256(s.encode()).hexdigest().encode()
    if ver == proof:
        payload=i+j+k+l
        break
r.recvuntil(b'XXXX: ')
r.sendline(payload.encode())
print("proof over!")

print(r.recvline())

from Crypto.Cipher import AES
from hashlib import md5
def MD5(m):return md5( str(m).encode() ).digest()

r.recvuntil(b'Give me the Bob_PubKey')
r.sendline(b'1')

r.recvuntil(b'Alice tell Bob : ')
c=r.recvline().strip().decode()
print(c)
c=bytes.fromhex(c)
Cipher = AES.new(MD5("1"), AES.MODE_ECB)
flag = Cipher.decrypt(c)
print(flag)


r.interactive()

Elgamal

task.py

#!/usr/local/bin/python
from random import choice
from hashlib import sha256
from string import ascii_letters, digits

from util import Elgamal
from secret import flag

def proof_of_work():
    proof = ''.join([choice(ascii_letters+digits) for _ in range(20)])
    _hexdigest = sha256(proof.encode()).hexdigest()
    print(f"[+] sha256(XXXX+{proof[4:]}) == {_hexdigest}")
    x = input('[+] Plz tell me XXXX: ')
    if len(x) != 4 or sha256( (x+proof[4:]).encode() ).hexdigest() != _hexdigest:
        return False
    return True

assert proof_of_work()

S = Elgamal()
print(f'My Public Key (q,g,y):{S.q, S.g, S.y}')
msg = (b'Welcome_to_0xGame2024_Crypto').hex()

print(f'The input msg : {msg}')
msg = bytes.fromhex(msg)
r,s = S.Sign(msg)
print(f'And the msg signatue (r,s):{r,s}')

print("Now, it's your turn to help me sign something")
msg_ = bytes.fromhex(input('[+] Give me your message:\n>'))
r_ = int(input('[+] Give me your r:\n>'))
s_ = int(input('[+] Give me your s:\n>'))

if S.Verity(msg_,(r_,s_)) and (msg_ == msg):
	print("It looks like you know how to verify the signature. Try getting the flag.")
elif S.Verity(msg_,(r_,s_)):
	print(f'flag : {flag}')
else:
	print('Is something wrong ?')

util.py

from Crypto.Util.number import getPrime, isPrime, inverse
from hashlib import sha256
from random import randint

class Elgamal:
	def __init__(self):
		self.q, self.g = self.gen()
		self.d = randint(0, self.q - 2)
		self.y = pow(self.g, self.d, self.q)

	def gen(self): 
		#原根生成函数
		q = 8867984692712162589394753592684462893849721383808359270870591946309591420901509987341888487540800853389811701998166292427185543648905432008953442556844003
		
		while True:
			#q = getPrime(512)
			p = 2*q + 1
			if isPrime(p):
				g = randint(2,p-1)
				if (pow(g,2,p) != 1) & (pow(g,q,p) != 1):
					break
		return q,g

	def Hash(self, msg): 
		#哈希函数
		return int(sha256(msg).hexdigest(),16)

	def Sign(self, msg): 
		#签名函数
		m = self.Hash(msg)
		phi = self.q - 1

		while True:
			k = getPrime(512)
			if k < phi : break

		r = pow(self.g, k, self.q)
		s = ((m - self.d * r) * inverse(k,phi)) % (phi)
		return (r,s)

	def Verity(self, msg, Signature):
		#验签函数
		m = self.Hash(msg)
		r,s = Signature

		A = (pow(self.y, r, self.q) * pow(r, s, self.q)) % self.q
		B = pow(self.g, m, self.q)

		if A == B:
			return True
		else:
			return False

参考:https://ctf-wiki.org/crypto/signature/elgamal/#_14
忘记了中国剩余定理还有这妙用。求同余一下就出了。
说一下为什么用到中国剩余定理:像本题就是为了求不同模数下同余的情况,那么就可以用到中国剩余定理,然后中国剩余定理求出来的是模的是方程组所有模数的乘积,这时候只要服务器没有判断我们输入小于其中一个模数,那么我们就可以用中国剩余定理
大致讲一下,Elgamal数字签名可以直接看wiki里,这里就只有M不一样

\[我们知道(q,g,y,r_1,s_1,m_1),可以知道H(m_1)=M_1\\ 我们可以自己随意构造一个m_2,也可以知道H(m_2)=M_2\\ 我们最后需要证明g^{M_2}=y^{r_2}*r_2^{s_2} \quad ①\\ 我们记M_2=uM_1\mod q,u=M_2*M_1^{-1}\mod q\\ 则需要证明:g^{uM_1}=y^{r_2}*r_2^{s_2}\\ ∵g^{M_1}=y^{r_1}*r_1^{s_1}\\ 所以,我需要求得g^{uM_1}=y^{r_2}*r_2^{s_2}=(y^{r_1}*r_1^{s_1})^u\mod q\\ (y^{r_1}*r_1^{s_1})^u=y^{u*r_1}*r_1^{u*s_1}\mod q\\ 到这里,我就卡住了,后来看到wiki上说可以用中国剩余定理求r_2:\\ \begin{cases} u*r_1=r_2\mod φ(q)\\ r_1=r_2\mod q\\ \end{cases}\\ 因为服务器并没有校验我们传过去的r的大小,所以可以这样做\\ 再令s_2=u*s_1,这样我们就能得到:\\ y^{u*r_1}*r_1^{u*s_1}\mod q=y^{r_2+k φ(q)}*r_2^{s_2}=y^{r_2}*r_2^{s_2}\mod q\\ 证明①式成立。 \]

from hashlib import sha256
from string import ascii_letters, digits
import itertools
from pwn import *

r=remote("118.195.138.159",10002)
r.recvuntil(b'XXXX+')
suf=r.recv(16)
dir=ascii_letters+digits
r.recvuntil(b' == ')
proof=r.recvline().strip()


payload=""
for i,j,k,l in itertools.product(dir,repeat=4):
    s=i+j+k+l+suf.decode()
    ver=sha256(s.encode()).hexdigest().encode()
    if ver == proof:
        payload=i+j+k+l
        break
r.recvuntil(b'XXXX: ')
r.sendline(payload.encode())

from Crypto.Util.number import *
from gmpy2 import *
from sympy.ntheory.modular import crt
def Hash(msg): 
	return int(sha256(msg).hexdigest(),16)

r.recvuntil(b'(q,g,y):')
q,g,y=eval(r.recvline().strip())

m1=Hash(b'Welcome_to_0xGame2024_Crypto')

r.recvuntil(b'(r,s):')
r1,s1=eval(r.recvline().strip())

m2=b'11'
m2=Hash(bytes.fromhex(m2.decode()))

u=(m2*invert(m1,q-1))%(q-1)
x=crt([q-1,q],[r1*u,r1],check=True)
r2=int(x[0])
s2=(s1*u)%(q-1)
r.recvuntil(b'message:')
r.sendline(b'11')
r.recvuntil(b'r:')
r.sendline(str(r2).encode())
r.recvuntil(b's:')
r.sendline(str(s2).encode())


r.interactive()

posted on 2024-10-20 20:17  Naby  阅读(117)  评论(0编辑  收藏  举报