CryptoCTF 2022 (part1)

easy

Baphomet

题目:

点击查看代码
from base64 import b64encode
from flag import flag

def encrypt(msg):
	ba = b64encode(msg.encode('utf-8'))
	baph, key = '', ''

	for b in ba.decode('utf-8'):
		if b.islower():
			baph += b.upper()
			key += '0'
		else:
			baph += b.lower()
			key += '1'

	baph = baph.encode('utf-8')
	key = int(key, 2).to_bytes(len(key) // 8, 'big') # key是6个字节

	enc = b''
	for i in range(len(baph)):
		enc += (baph[i] ^ key[i % len(key)]).to_bytes(1, 'big')

	return enc

enc = encrypt(flag)
f = open('flag.enc', 'wb')
f.write(enc)
f.close()

分析:
首先密文长度为48,说明key生成以后仅48位二进制数,故key仅6字节;再依据base64原理CCTF{一共40个bit,小于6 * 6,因此这部分明文可确定6个字节的密文;这和key的长度一致,所以利用此段密文可直接恢复key,有了key再恢复所有明文即可。
exp:

import base64
import itertools

with open('flag.enc', 'rb') as f:
    s = f.read()

m = b'CCTF{'
m1 = base64.b64encode(m)
m2 = ''
for b in m1.decode('utf-8'):
    if b.islower():
        m2 += b.upper()
    else:
        m2 += b.lower()
m2 = m2.encode()[:6]

key = [x ^ y for x, y in zip(m2, s)] # zip会取两者较短的为长度上限
print(key)

m2 = bytes([x ^ y for x, y in zip(itertools.cycle(key), s)])
m1 = ''
# 把base密文大小写还原
for b in m2.decode('utf-8'):
    if b.islower():
        m1 += b.upper()
    else:
        m1 += b.lower()

print(base64.b64decode(m1))
# b'CCTF{UpP3r_0R_lOwER_17Z_tH3_Pr0bL3M}'

medium-easy

polyRSA

题目

点击查看代码
from Crypto.Util.number import *
from flag import flag

def keygen(nbit = 64):
	while True:
		k = getRandomNBitInteger(nbit)
		p = k**6 + 7*k**4 - 40*k**3 + 12*k**2 - 114*k + 31377
		q = k**5 - 8*k**4 + 19*k**3 - 313*k**2 - 14*k + 14011
		if isPrime(p) and isPrime(q):
			return p, q

def encrypt(msg, n, e = 31337):
	m = bytes_to_long(msg)
	return pow(m, e, n)

p, q = keygen()
n = p * q
enc = encrypt(flag, n)
print(f'n = {n}')
print(f'enc = {enc}')

分析:
尝试一:把整个n在多项式环下表示,尝试sage的factor(),发现无法成功
尝试二:因为给了p和q的多项式形式,所以把p和q放在多项式环下,再结合n和sage的roots()求解k,得到k之后还原p和q
exp:

from Crypto.Util.number import *

n = 44538727182858207226040251762322467288176239968967952269350336889655421753182750730773886813281253762528207970314694060562016861614492626112150259048393048617529867598499261392152098087985858905944606287003243
enc = 37578889436345667053409195986387874079577521081198523844555524501835825138236698001996990844798291201187483119265306641889824719989940722147655181198458261772053545832559971159703922610578530282146835945192532
e = 31337

PR.<k> = PolynomialRing(ZZ)

p = k**6 + 7*k**4 - 40*k**3 + 12*k**2 - 114*k + 31377
q = k**5 - 8*k**4 + 19*k**3 - 313*k**2 - 14*k + 14011
n1 = p * q

f = n - n1 # 解方程
ans = f.roots()
print(ans)

k = ans[0][0]
p = p(k)
q = q(k)

d = inverse_mod(e, (p-1)*(q-1))
m = pow(enc, d, n)
print(long_to_bytes(int(m)))
# b'CCTF{F4C70r!N9_tRIcK5_aR3_fUN_iN_RSA?!!!}'

Keydream

题目:

点击查看代码
from Crypto.Util.number import *
import string
from flag import flag

def randstr(l):
	rstr = [(string.printable[:62] + '_')[getRandomRange(0, 62)] for _ in range(l)]
	return ''.join(rstr)


def encrypt(msg, l):
	while True:
		rstr = 'CCTF{it_is_fake_flag_' + randstr(l) + '_90OD_luCk___!!}'
		p = bytes_to_long(rstr.encode('utf-8'))
		q = bytes_to_long(rstr[::-1].encode('utf-8'))
		if isPrime(p) and isPrime(q):
			n = p * q
			e, m = 65537, bytes_to_long(msg.encode('utf-8'))
			c = pow(m, e, n)
			return n, c

n, c = encrypt(flag, 27)

print(f'n = {n}')
print(f'c = {c}')

分析:
coppersmith,类似于已知p高位攻击,这里是已知高位和部分低位。
exp:

from Crypto.Util.number import *

p_1 = b'CCTF{it_is_fake_flag_'
p_2 = b'1' * 27
p_3 = b'_90OD_luCk___!!}'

e = 65537
n = 23087202318856030774680571525957068827041569782431397956837104908189620961469336659300387982516148407611623358654041246574100274275974799587138270853364165853708786079644741407579091918180874935364024818882648063256767259283714592098555858095373381673229188828791636142379379969143042636324982275996627729079
c = 3621516728616736303019716820373078604485184090642291670706733720518953475684497936351864366709813094154736213978864841551795776449242009307288704109630747654430068522939150168228783644831299534766861590666590062361030323441362406214182358585821009335369275098938212859113101297279381840308568293108965668609

PR.<x> = PolynomialRing(Zmod(n))

f = bytes_to_long(p_1) * 2^(8*(len(p_2) + len(p_3))) + x * 2^(8*(len(p_3))) + bytes_to_long(p_3)
f = f.monic()
sol = f.small_roots(X = 2^(8*(len(p_2))), beta=0.4)
print(sol)
kp = ZZ(f(sol[0])) # 这里代入求解出来的是p^i,所以做gcd求p,细节之后再继续学习
p = gcd(p,n)
print(p)
q = n//p
phi = (p-1)*(q-1)
d = pow(e,-1,phi)
print(long_to_bytes(int(pow(c,d,n))))
# b'Congratz, the flag is: CCTF{h0M3_m4dE_k3Y_Dr1vEn_CrYp7O_5ySTeM!}'

Jeksign

题目:

点击查看代码
from Crypto.Util.number import *
from secret import gensol, nbit_gensol
from flag import flag

m = bytes_to_long(flag.encode('utf-8'))
print(m)

a = 1337
b = 31337

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, "Welcome crypto guys! Here we are looking for the solution of special", border)
	pr(border, "Diophantine equation: 1337(z^4 - x^2) = 31337(y^2 - z^4) in natural ", border)
	pr(border, "numbers, in each stage solve the equation in mentioned interval :)  ", border)
	pr(border*72)

	STEP, level = 0, 19

	while True:
		p, q = nbit_gensol(1337, 31337, STEP + 30)
		x, y, z = gensol(a, b, p, q)
		pr(border, f"Send a solution like `x, y, z' such that the `z' is {STEP + 30}-bit: ")
		ans = sc()
		try:
			X, Y, Z = [int(_) for _ in ans.split(',')]
			NBIT = Z.bit_length()
		except:
			die(border, 'Your input is not valid, Bye!')
		if 1337*(Z**4 - X**2) == 31337*(Y**2 - Z**4) and NBIT == STEP + 30:
			if STEP == level - 1:
				die(border, f'Congrats, you got the flag: {flag}')
			else:
				pr('| good job, try to solve the next challenge :P')
				STEP += 1
		else:
			die(border, 'your answer is not correct, Bye!!')

if __name__ == '__main__':
	main()

分析:
求不定方程\(a(z^4-x^2)=b(y^2-z^4)\)的整数解,这种方程也叫做丢番图方程,要求未知数为整数。这里只需要移项整理一下方程,得到\((a+b)z^4=ax^2+by^2\),显然令\(x=y=z^2\)即可。
exp:

from pwn import *
from Crypto.Util.number import *

io = remote('02.cr.yp.toc.tf',17113)
io.recvlines(6)
for i in range(18):
    z = getRandomNBitInteger(30+i)
    x = y = z**2
    io.sendline(str(x)+','+str(y)+','+str(z))
    print('send!')
    print(io.recvlines(2))

# 最后一轮改为交互模式,或者接收1行,否则会出错
z = getRandomNBitInteger(48)
x = y = z**2
io.sendline(str(x)+','+str(y)+','+str(z))
print('send!')
io.interactive()
# Congrats, you got the flag: CCTF{4_diOpH4nT1nE_3Qua7i0n__8Y__Jekuthiel_Ginsbur!!}

Volgo

题目:
image
分析:
想去查一下M-209密码机的相关原理,结果找不到,于是换个思路直接用加密接口对特定字符加密分析原理。
A的密文:{"cipher": "QQVAI RAMAA LXXXX QQVAI RAMAA"}
B的密文:{"cipher": "QQVAI RAMAA KXXXX QQVAI RAMAA"}
C的密文:{"cipher": "QQVAI RAMAA JXXXX QQVAI RAMAA"}
BB的密文:{"cipher": "QQVAI RAMAA KHXXX QQVAI RAMAA"}
BC的密文:{"cipher": "QQVAI RAMAA KGXXX QQVAI RAMAA"}
找一下规律可知,密文前后的QQVAI RAMAA是固定的,明文对应密文应该是放在中间部分的,对于相同位置的明文,有类似于维吉尼亚的特征:A+L=B+K=C+J B+H=C+G
那么依照这个规律写脚本即可。
exp:

import string

table = string.ascii_uppercase
flag = 'NUYXA FATCL LJSVD KOLWM NWZUG JZDPY JGOSL CBRGO BAMKL BXDZR BLXDU KUNQE CWEUO SPOZR VOJMM POXCR RPNXX SGVHW TGOWJ BDOGF AHVWF WPZGR UQKUE HASEF GKFFA QOPNQ TESLZ FACTJ JPSPL DNZYN CXSFZ'.replace(' ','')
print('A'*len(flag))
# 把31个A拿去加密得到密文c
c = 'LISWK SOPBS ZFROR JTZNY NPYZR JFCYS BZNHF VALVQ PMUXR AIHSK FCPCQ SNUYR BYYLZ QOPQR XSBLR DZIQN VSMYV RIXAB SUCFV QPRJN XJGJS SUSKA OCPRO IRNZR UZXQS IZJGN OHNYC RYAIQ YLXYC QWATO QJMDQ'.replace(' ','')
s = ''
for i in range(len(c)):
    j = 0 + table.index(c[i]) - table.index(flag[i])
    s = s + table[j]
print(s.replace('Z',' '))
# YOU KNOW HOW TO FORMAT FLAG JUST PUT UPCOMING LETTERS WITHIN CURLY BRACES FOLLOWED BY CCTF OOJMPMDDIXCLNNWFTEJUMFXKBRVVMOPSLSSLUTXVDVNDMYYPHPWFJRNJBVBOMUYR

SOTS

题目:
给定一个较大的整数n,要将其分解为两个数的平方和:
image
分析:
分解大整数为两个数的平方和问题有相关解法,算法有两个关键点:
1.两个可分解为平方和的数的乘积仍然可分解为平方数,有:
\((a^2+b^2)(c^2+d^2)=(ac+bd)^2+(ad−bc)^2=(ac−bd)^2+(ad+bc)^2.\)
2.对于\(p\)的分解即找到满足\(p=a^2+b^2\)\(a,b\),可采用以下步骤:

  • 找到一个\(x\)满足\(x^2 \equiv -1modp\)
  • \(p\)\(x\)做辗转相除,当第一次辗转相除的结果都小于\(\sqrt{p}\)的时候,两个数(除数和余数)就是分解的a和b

明白以上过程,我们有两种方法来实现:
1.yafu分解n以后,利用sagemath现成的函数得到因子的平方数,用上述1的式子进行组合得到n的平方数,之所以不直接对n使用函数是因为n较大太慢服务端会断开。
2.整个流程都自己实现,主要是对上述2的编程。
exp:

n = 4964661481345694881051413826362284279426795652190880658713143766625379537
base = [1638858152657283400617209,1815964141655266322911969,1668172688765028904231097]
sols = [two_squares(j) for j in base]

a,b = sols[0][0],sols[0][1]
c,d = sols[1][0]*sols[2][0] + sols[1][1]*sols[2][1],sols[1][0]*sols[2][1] - sols[1][1]*sols[2][0]

assert (a*c+b*d)**2 + (a*d-b*c)**2 == n
print((a*c+b*d)**2 + (a*d-b*c)**2)

base = [1638858152657283400617209,1815964141655266322911969,1668172688765028904231097]

def my_exgcd(p, x, y):
    if (x * x < p and y * y < p):
        return x, y
    else:
        return my_exgcd(p, y, x%y)
# 注意找x^2 = -1 mod p的思路是通过找到二次非剩余,即g^[(p-1)//2]=-1 mod p. 则g^[(p-1)//4]为x
def get_sol(p):
    g = 2
    while (pow(g, (p-1) // 2, p) == 1):
        g += 1
    c = pow(g, (p-1) // 4, p)
    return my_exgcd(p, p, c)

sols = [get_sol(p) for p in base]
print(sols)
a,b = sols[0][0],sols[0][1]
c,d = sols[1][0]*sols[2][0] + sols[1][1]*sols[2][1],sols[1][0]*sols[2][1] - sols[1][1]*sols[2][0]

assert (a*c+b*d)**2 + (a*d-b*c)**2 == 4964661481345694881051413826362284279426795652190880658713143766625379537
print((a*c+b*d)**2 + (a*d-b*c)**2)

Infinity castle

题目:

点击查看代码
#!/usr/bin/env python3

from Crypto.Util.number import *
from os import urandom

class TaylorSeries():

	def __init__(self, order):
		self.order = order
		
	def coefficient(self, n):
		i = 0
		coef = 1
		while i < n:
			i += 1
			coef *= (coef * (1/2-i+1)) / i
		return coef

	def find(self, center):
		sum = 0
		center -= 1
		i = 0
		while i < self.order:
			sum += (center**(1/2-i) * self.coefficient(i))
			i += 1
		return sum

def xor(cip, key):
	repeation = 1 + (len(cip) // len(key))
	key = key * repeation
	key = key[:len(cip)]
	
	msg = bytes([c ^ k for c, k in zip(cip, key)])
	return msg

# If you run these 3 functions (diamond, triage, summarize) with big numbers, they will never end
# You need to optimize them first

def diamond(num):
	output = 0
	for i in range(num):
		output += i*2 + 1
	return output

def triage(num):
	output = 0
	index = 0
	for i in range(num):
		output += (i+index)*6 + 1
		index += i
	return output

def summarize(b):
	order = getRandomRange(1000, 2000)
	t = TaylorSeries(order)
	output = 0
	
	i = 2
	while i < b:
		b1 = t.find(i)
		b2 = t.find(i+1)
		output += (b1+b2) / 2
		i += 1
	return output

KEY_SIZE = 2048
p, q = [getPrime(KEY_SIZE) for _ in '01']

e, n = 0x10001, p*q

f = open ('./message.txt', 'rb')
message = f.read()
f.close()
msg_length = len(message)
padded_msg = message + urandom(len(long_to_bytes(n)) - msg_length)
padded_msg_length = len(padded_msg)

xor_key = long_to_bytes(int(summarize(n)))[:padded_msg_length] #只取了高位
assert(len(xor_key) == 512)

enc0 = xor(padded_msg, xor_key)
assert(bytes_to_long(enc0) < n)

c0 =  abs(diamond(p) - diamond(q))
c1 =  triage(p)
c1 += 3*n*(p+q)
c1 += triage(q)

m = bytes_to_long(enc0)
enc1 = pow(m, e, n)

print(f"c0 = {c0}")
print(f"c1 = {c1}")
print(f"enc = {enc1}")

分析:
首先要分析并化简题目的几个函数:
1.diamond
\(\sum_{i=0}^{n-1} 2i+1=n^2\)
2.triage
这个手动计算比较麻烦,用wolfram计算结果:
\(\sum_{i=0}^{n-1} [6.(i+\sum_{j=0}^{i-1} j)+1]=n^3\)
3.summarize
在化简summarize之前先要了解这里泰勒级数的实现(题目有点问题,做了更正):

class TaylorSeries():

	def __init__(self, order):
		self.order = order
		
	def coefficient(self, n):
		i = 0
		coef = 1
		while i < n:
			i += 1
			coef = (coef * (1/2-i+1)) / i
		return coef

	def find(self, center):
		sum = 0
		center -= 1
		i = 0
		while i < self.order:
			sum += (center**(1/2-i) * self.coefficient(i))
			i += 1
		return sum

find()实际上就是\(f=x^{\frac{1}{2}}\)\(x_0\)处的泰勒级数,所以summarize()实际上就是多项泰勒级数的加和,而泰勒展开式本身近似为函数本身,所以summarize实际上为:
\(\frac{\sqrt{2}+\sqrt{3}}{2}+\frac{\sqrt{3}+\sqrt{3}}{2}+...+\frac{\sqrt{x-1}+\sqrt{x}}{2}=\frac{\sqrt{2}}{2}+\sqrt{3}+\sqrt{4}+...+\sqrt{x-1}+\frac{\sqrt{x}}{2}\)
用积分替换一下化简得:
\(\int_2^x \sqrt{t}\,{\rm d}x=\frac{2}{3}.x^{\frac{3}{2}}-\frac{2}{3}.2^{\frac{3}{2}}\)
分析完以后,主要有两个部分要处理:
1.

c0 =  abs(diamond(p) - diamond(q))
c1 =  triage(p)
c1 += 3*n*(p+q)
c1 += triage(q)

这部分直接对\(c_0\)开三次方即可获得\(p+q\)再联立\(c_0=(p+q)(p-q)\)可以得到\(p和q\),通过rsa解密得到\(enc_0\)
2.

msg_length = len(message)
padded_msg = message + urandom(len(long_to_bytes(n)) - msg_length)
padded_msg_length = len(padded_msg)
xor_key = long_to_bytes(int(summarize(n)))[:padded_msg_length]

这里的\(xorkey\)只取了\(n\)的高位,所以前边我们的约等化简并不会影响这里的结果
exp:

from Crypto.Util.number import *

def xor(cip, key):
	repeation = 1 + (len(cip) // len(key))
	key = key * repeation
	key = key[:len(cip)]
	
	msg = bytes([c ^^ k for c, k in zip(cip, key)])	# run in sage
	return msg


def diamond(num):
	return num**2

def triage(num):
	return num**3

def summarize(b):
	return 2/3 * b**(3/2) - 2/3 * 2**(3/2)

c0 = 194487778980461745865921475300460852453586088781074246328543689440168930873549359227127363759885970436167330043371627413542337857082400024590112412164095470165662281502211335756288082198009158469871465280749846525326701136380764079685636158337203211609233704905784093776008350120607841177268965738426317288541638374141671346404729428158104872411498098187946543666987926130124245185069569476433120128275581891425551325847219504501925282012199947834686225770246801353666030075146469275695499044424390475398423700504158154044180028733800154044746648133536737830623670383925046006108348861714435567006327619892239876863209887013290251890513192375749866675256952802329688897844132157098061758362137357387787072005860610663777569670198971946904157425377235056152628515775755249828721767845990597859165193162537676147173896835503209596955703313430433124688537275895468076469102220355973904702901083642275544954262724054693167603475188412046722656788470695566949847884250306735314144182029335732280420629131990311054229665691517003924788583771265625694414774865289407885678238795596912006567817508035434074250123869676396153982762032750080222602796273083162627489526255501643913672882350236497429678928099868244687228703074267962827621792960
c1 = 102325064080381160170299055162846304227217463467232681115953623386548494169967745781550171804781503102663445039561476870208178139548389771866145006535051362059443515034504958703659546162037904784821960707630188600064568878674788077706711506213779443920430038560373854184526850365974450688458342413544179732143225845085164110594063440979455274250021370090572731855665413325275414458572412561502408983107820534723804225765540316307539962199024757378469001612921489902325166003841336027940632451584642359132723894801946906069322200784708303615779699081247051006259449466759863245508473429631466831174013498995506423210088549372249221415401309493511477847517923201080509933268996867995533386571564898914042844521373220497356837599443280354679778455765441375957556266205953496871475611269965949025704864246188576674107448587696941054123618319505271195780178776338475090463487960063464172195337956577785477587755181298859587398321270677790915227557908728226236404532915215698698185501562405374498053670694387354757252731874312411228777004316623425843477845333936913444143768519959591070492639650368662529749434618783626718975406802741753688956961837855306815380844030665696781685152837849982159679122660714556706669865596780528277684800454866433826417980212164051043504955615794706595412705883261111953152250227679858538249797999044336210905975316421254442221408945203647754303635775048438188044803368249944201671941949138202928389951227347433255867504906597772044398973241425387514239164853656233776024571606159378910745571588836981735827902305330330946219857271646498602227088657739442867033212012876837654750348578740516798444734534291467314881324902354425889448102902750077077381896216130734894767553834949561471219923459897244690006643798812989271282475503126962274052192342840870308710338336817452628324667507548265612892611100350882163205858101959976
enc = 122235247669762131006183252122503937958296387791525458429403709404875223067116491817728568224832483391622109986550732469556761300197133827976956875865159629512476600711420561872409721582387803219651736262581445978042694384374119142613277808898398213602093802571586386354257378956087240174787723503606671543195193114158641301908622673736098768829071270132073818245595918660134745516367731595853832524328488074054536278197115937409643221809577554866060292157239061557708159310445977052686561229611117673473208278176118561352693319461471419694590218295911647368543698198762827636021268989705079848502749837879584394379300566277359149621932579222865430374652678738198451655509408564586496375811666876030847654260305392984710580761255795785508407844683687773983669843744274915862335181251050775093896006210092665809300090715190088851654138383362259256212093670748527819287681468901379286722214112321906917311154811516336259463356911326393701445497605365038857575515541024824906818473933597129846235905072394148879079996812146836910199111439031562946495046766063326815863624262541346543552652673629442370320109404700346028639853707278295255350982238521659924641921142615894039995513480511108116053798143154593343124822462519555715118822045

p_add_q = c1^(1/3)
assert p_add_q^3 == c1
assert c0 % p_add_q == 0
p_sub_q = c0 / p_add_q
assert c0 == p_add_q * p_sub_q
p = (p_add_q + p_sub_q) / 2
q = (p_add_q - p_sub_q) / 2
n = p * q

xor_key = long_to_bytes(int(summarize(n))) #只需要算个大约的值 只需要n的高位就能解密
# print(xor_key)

phi = (p-1)*(q-1)
d = inverse_mod(0x10001, phi)
enc = pow(enc, d, n)
enc = long_to_bytes(int(enc))
msg = xor(enc, xor_key)
print(msg)
posted @ 2022-08-13 21:01  ZimaB1ue  阅读(451)  评论(0编辑  收藏  举报