当前位置: 首页 > news >正文

2025第一届轩辕杯--Crypto--WriteUp

2025第一届轩辕杯–Crypto–WriteUp

Crypto

easyrsa

task

e = 65537
n = 1000000000000000000000000000156000000000000000000000000005643
c = 418535905348643941073541505434424306523376401168593325605206

exp

from Crypto.Util.number import inverse, long_to_bytese = 65537
n = 1000000000000000000000000000156000000000000000000000000005643
c = 418535905348643941073541505434424306523376401168593325605206# yafu
p = 1000000000000000000000000000099
q = 1000000000000000000000000000057phi_n = (p - 1) * (q - 1)
d = inverse(e, phi_n)
m = pow(c, d, n)flag = long_to_bytes(m)
print(b'flag{' + flag + b'}') # b'xuanyuanbei_easy_rsa!'
# flag{xuanyuanbei_easy_rsa!}

简单编码

exp

ciphertext = "ABBAABB ABBABAB ABABAAA ABABAAB ABBBBAA ABBAABA ABABBAA ABBAAAA ABBAAAB ABBABAB ABBBAAA ABAABBB ABABBAA ABABABB ABABBAA ABBABBB ABBABAA ABABABA ABAABAB ABBBAAA ABBBABA ABABBAB ABBBBAA ABABBAB ABBBAAA ABBABAB ABBAABA ABABAAA ABABABA AABBAB ABBBABB ABBAABA ABBABAB AABABA ABBBBAA ABBBAAB ABBAABA AABBAB ABABBAA ABBAAAB ABBBAAA ABBABAB ABBABAA ABABABB ABBBABA ABABABB ABBAABB ABBABAA ABBABAB ABBABAB ABABAAA ABBBABA AABABB ABABBAB AABBAB ABABAAA ABBAAAB ABBBBAB ABBBAAA ABABABA ABBAAAA ABABAAB ABABABB ABBABBA ABBABAB AABABA ABBABAA ABBBABA ABBBABA AABBAA ABBBBAA ABBAAAA ABABBBB ABBABAB ABABABB ABAABBB ABBAAAA ABABAAA ABABABB ABBABAA ABBABBA ABABABA ABAABAB ABABABA AABABB ABABBAB ABBBBAA ABBBBAB ABBBAAA ABABAAB ABBABBB ABABAAB ABBAAAA ABAABAB ABBBABB ABBABAA ABBABAB ABABABA ABAABAB ABBBABA ABBAABA AABBAB ABABBAA ABAABAB ABBBAAA ABBABAB ABBBABA ABAABBB ABABBBA ABABABB ABABBAA ABBABBB ABBABAA ABAABAB ABABABA ABBBAAB ABABBAA ABBAABA ABABBAA ABAABAB ABBBAAA ABBABAB ABBABBB ABBBABB ABBBABA ABABBAA ABBABAB ABABABA ABBAABA ABAABAB ABBAABA ABBABBB ABBBAAA ABBAABA ABBBBAA ABBAAAA ABBABAA ABABBAB ABBABAA ABAABBB ABABABA ABABABB ABABABB AABBAB ABBAAAB ABBBBAB ABABABA ABBBABA AABBAB ABABABA ABBABAB ABBBAAB ABBBAAA ABBAAAB ABBBBAA ABBBBAA ABBABAA ABBAABA AABBAB ABBBABA"
my_dict = {"A":"1", "B":"0"} # 注:因为所有的均为A开头,所以A-->1; B-->0
temp = "".join([" " if c == " " else my_dict[c] for c in ciphertext])
print(temp)

此后,利用随波逐流:
二进制转字符–>base32–>URL–>base64–>栅栏(5)

flag{c04d6e34aab689c5c0e68eb51753c843e032efa7c16427f8642ee07ab946e981}

dp

task

n = 110231451148882079381796143358970452100202953702391108796134950841737642949460527878714265898036116331356438846901198470479054762675790266666921561175879745335346704648242558094026330525194100460497557690574823790674495407503937159099381516207615786485815588440939371996099127648410831094531405905724333332751 dp = 3086447084488829312768217706085402222803155373133262724515307236287352098952292947424429554074367555883852997440538764377662477589192987750154075762783925 c = 59325046548488308883386075244531371583402390744927996480498220618691766045737849650329706821216622090853171635701444247741920578127703036446381752396125610456124290112692914728856924559989383692987222821742728733347723840032917282464481629726528696226995176072605314263644914703785378425284460609365608120126 e = 65537

analysis

dp泄露,RSA攻击基础题型。

exp

from Crypto.Util.number import *n = 110231451148882079381796143358970452100202953702391108796134950841737642949460527878714265898036116331356438846901198470479054762675790266666921561175879745335346704648242558094026330525194100460497557690574823790674495407503937159099381516207615786485815588440939371996099127648410831094531405905724333332751 
dp = 3086447084488829312768217706085402222803155373133262724515307236287352098952292947424429554074367555883852997440538764377662477589192987750154075762783925 
c = 59325046548488308883386075244531371583402390744927996480498220618691766045737849650329706821216622090853171635701444247741920578127703036446381752396125610456124290112692914728856924559989383692987222821742728733347723840032917282464481629726528696226995176072605314263644914703785378425284460609365608120126 
e = 65537for i in range(1, e):                   if(dp * e - 1) % i == 0:if n % (((dp * e - 1) // i) + 1) == 0:   p = ((dp * e - 1) // i) + 1q = n // (((dp * e - 1) // i) + 1)phi = (q - 1) * (p - 1)            d = inverse(e, phi)         m = pow(c, d, n)               print(long_to_bytes(m))
# flag{C5G0_1s_the_8eSt_FPS_G@m3}

babyrsa

task

from Crypto.Util.number import *
from gmpy2 import *
from random import choice
flag = b"flag{****************************}"
m = bytes_to_long(flag)
p = getPrime(256)
q = getPrime(256)
n = p*q
d = getPrime(130)
phi = (p-1)*(q-1)
e = invert(d, phi)
c = pow(m, e, n)
print(f'n = {n}')
print(f'c = {c}')
# print(f'e = {e}')def gen(bits):while True:p = 2while p.bit_length() < bits:p *= choice(sieve_base)if isPrime(p - 1):return p - 1p1 = gen(256)
q1 = gen(256)
n1 = p1 * q1
c1 = p1 + eprint(f'n1 = {n1}')
print(f'c1 = {c1}')'''n = 10037257627154486608196774801095855162090578704439233219876490744017222686494761706171113312036056644757212254824459536550416291797454693336043852190135363
c = 6723803125309437675713195914771839852631361554645954138639198200804046718848872479140347495288135138109762940384847808522874831433140182790750890982139835
n1 = 151767047787614712083974720416865469041528766980347881592164779139223941980832935534609228636599644744364450753148219193621511377088383418096756216139022880709
c1 = 6701513605196718137208327145211106525052740242222174201768345944717813148931922063338128366155730924516887607710111701686062781667128443135522927486682574
'''

analysis

采用了RSA对密文进行了加密,准确给出了n,c。接着再使用了RSA对e进行了隐藏。

我们需要针对于第二次的RSA进行破解恢复e,之后我们就拿到了第一次RSA的n,e,c据此进行明文m的恢复。虽然n的位数不是很高,但是针对于直接分解,都是有很明确的攻击方向的。

第二次加密的算法利用浏览器搜索可以直接搜到p+1光滑的攻击方法,此后第一个搜索的话应该可以搜索到winer攻击。但是根据验证发现并不满足winer攻击的条件,但是可以顺着winer攻击的低解密指数的方向寻找,boneh and durfee攻击找个模板代码进行数据替换即可。

exp

p+1光滑攻击

from itertools import count
from math import gcd, isqrt
from tqdm import trangedef mlucas(v, a, n):v1, v2 = v, (v ** 2 - 2) % nfor bit in bin(a)[3:]: v1, v2 = ((v1 ** 2 - 2) % n, (v1 * v2 - v) % n) if bit == "0" else ((v1 * v2 - v) % n, (v2 ** 2 - 2) % n)return v1def primegen():yield 2yield 3yield 5yield 7yield 11yield 13ps = primegen()  # yay recursionp = ps.__next__() and ps.__next__()q, sieve, n = p ** 2, {}, 13while True:if n not in sieve:if n < q:yield nelse:next, step = q + 2 * p, 2 * pwhile next in sieve:next += stepsieve[next] = stepp = ps.__next__()q = p ** 2else:step = sieve.pop(n)next = n + stepwhile next in sieve:next += stepsieve[next] = stepn += 2def ilog(x, b):  # greatest integer l such that b**l <= x.l = 0while x >= b:x /= bl += 1return ldef attack(n):for v in count(1):for p in primegen():e = ilog(isqrt(n), p)if e == 0:breakfor _ in trange(e):v = mlucas(v, p, n)g = gcd(v - 2, n)if 1 < g < n:return int(g), int(n // g)  # g|nif g == n:break
n1 = 151767047787614712083974720416865469041528766980347881592164779139223941980832935534609228636599644744364450753148219193621511377088383418096756216139022880709
print(attack(n1))
# (647625598040937990477179775340017395831855498212348808173836982264933068647233, 234343806431846981391062476356400447729334179333927516463017977438646752515331973)

boneh and durfee攻击

from Crypto.Util.number import *
import gmpy2n = 10037257627154486608196774801095855162090578704439233219876490744017222686494761706171113312036056644757212254824459536550416291797454693336043852190135363
c = 6723803125309437675713195914771839852631361554645954138639198200804046718848872479140347495288135138109762940384847808522874831433140182790750890982139835
n1 = 151767047787614712083974720416865469041528766980347881592164779139223941980832935534609228636599644744364450753148219193621511377088383418096756216139022880709
c1 = 6701513605196718137208327145211106525052740242222174201768345944717813148931922063338128366155730924516887607710111701686062781667128443135522927486682574# print(n1.bit_length()) 526
################################################### p + 1光滑p1 = 647625598040937990477179775340017395831855498212348808173836982264933068647233
q1 = 234343806431846981391062476356400447729334179333927516463017977438646752515331973
assert p1 * q1 == n1  and isPrime(p1) and isPrime(q1)
e1 = c1 - p1
e2 = c1 - q1
print(f"e1 = {e1}")
print(f"e2 = {e2}")"""
e1 = 647625598040937990477179775340017395831855498212348808173836982264933068647233
e2 = 6701513605196718137208327145211106525052740242222174201768345944717813148697578256906281384764668448160487159980777522352135265204110465696876174971350601
"""
# sage
from Crypto.Util.number import *
# from __future__ import print_function
import time############################################
# Config
##########################################"""
Setting debug to true will display more informations
about the lattice, the bounds, the vectors...
"""
debug = True"""
Setting strict to true will stop the algorithm (and
return (-1, -1)) if we don't have a correct
upperbound on the determinant. Note that this
doesn't necesseraly mean that no solutions
will be found since the theoretical upperbound is
usualy far away from actual results. That is why
you should probably use `strict = False`
"""
strict = False"""
This is experimental, but has provided remarkable results
so far. It tries to reduce the lattice as much as it can
while keeping its efficiency. I see no reason not to use
this option, but if things don't work, you should try
disabling it
"""
helpful_only = True
dimension_min = 7 # stop removing if lattice reaches that dimension############################################
# Functions
########################################### display stats on helpful vectors
def helpful_vectors(BB, modulus):nothelpful = 0for ii in range(BB.dimensions()[0]):if BB[ii,ii] >= modulus:nothelpful += 1print(nothelpful, "/", BB.dimensions()[0], " vectors are not helpful")# display matrix picture with 0 and X
def matrix_overview(BB, bound):for ii in range(BB.dimensions()[0]):a = ('%02d ' % ii)for jj in range(BB.dimensions()[1]):a += '0' if BB[ii,jj] == 0 else 'X'if BB.dimensions()[0] < 60:a += ' 'if BB[ii, ii] >= bound:a += '~'print(a)# tries to remove unhelpful vectors
# we start at current = n-1 (last vector)
def remove_unhelpful(BB, monomials, bound, current):# end of our recursive functionif current == -1 or BB.dimensions()[0] <= dimension_min:return BB# we start by checking from the endfor ii in range(current, -1, -1):# if it is unhelpful:if BB[ii, ii] >= bound:affected_vectors = 0affected_vector_index = 0# let's check if it affects other vectorsfor jj in range(ii + 1, BB.dimensions()[0]):# if another vector is affected:# we increase the countif BB[jj, ii] != 0:affected_vectors += 1affected_vector_index = jj# level:0# if no other vectors end up affected# we remove itif affected_vectors == 0:print("* removing unhelpful vector", ii)BB = BB.delete_columns([ii])BB = BB.delete_rows([ii])monomials.pop(ii)BB = remove_unhelpful(BB, monomials, bound, ii-1)return BB# level:1# if just one was affected we check# if it is affecting someone elseelif affected_vectors == 1:affected_deeper = Truefor kk in range(affected_vector_index + 1, BB.dimensions()[0]):# if it is affecting even one vector# we give up on this oneif BB[kk, affected_vector_index] != 0:affected_deeper = False# remove both it if no other vector was affected and# this helpful vector is not helpful enough# compared to our unhelpful oneif affected_deeper and abs(bound - BB[affected_vector_index, affected_vector_index]) < abs(bound - BB[ii, ii]):print("* removing unhelpful vectors", ii, "and", affected_vector_index)BB = BB.delete_columns([affected_vector_index, ii])BB = BB.delete_rows([affected_vector_index, ii])monomials.pop(affected_vector_index)monomials.pop(ii)BB = remove_unhelpful(BB, monomials, bound, ii-1)return BB# nothing happenedreturn BB""" 
Returns:
* 0,0   if it fails
* -1,-1 if `strict=true`, and determinant doesn't bound
* x0,y0 the solutions of `pol`
"""
def boneh_durfee(pol, modulus, mm, tt, XX, YY):"""Boneh and Durfee revisited by Herrmann and Mayfinds a solution if:* d < N^delta* |x| < e^delta* |y| < e^0.5whenever delta < 1 - sqrt(2)/2 ~ 0.292"""# substitution (Herrman and May)PR.<u, x, y> = PolynomialRing(ZZ)Q = PR.quotient(x*y + 1 - u) # u = xy + 1polZ = Q(pol).lift()UU = XX*YY + 1# x-shiftsgg = []for kk in range(mm + 1):for ii in range(mm - kk + 1):xshift = x^ii * modulus^(mm - kk) * polZ(u, x, y)^kkgg.append(xshift)gg.sort()# x-shifts list of monomialsmonomials = []for polynomial in gg:for monomial in polynomial.monomials():if monomial not in monomials:monomials.append(monomial)monomials.sort()# y-shifts (selected by Herrman and May)for jj in range(1, tt + 1):for kk in range(floor(mm/tt) * jj, mm + 1):yshift = y^jj * polZ(u, x, y)^kk * modulus^(mm - kk)yshift = Q(yshift).lift()gg.append(yshift) # substitution# y-shifts list of monomialsfor jj in range(1, tt + 1):for kk in range(floor(mm/tt) * jj, mm + 1):monomials.append(u^kk * y^jj)# construct lattice Bnn = len(monomials)BB = Matrix(ZZ, nn)for ii in range(nn):BB[ii, 0] = gg[ii](0, 0, 0)for jj in range(1, ii + 1):if monomials[jj] in gg[ii].monomials():BB[ii, jj] = gg[ii].monomial_coefficient(monomials[jj]) * monomials[jj](UU,XX,YY)# Prototype to reduce the latticeif helpful_only:# automatically removeBB = remove_unhelpful(BB, monomials, modulus^mm, nn-1)# reset dimensionnn = BB.dimensions()[0]if nn == 0:print("failure")return 0,0# check if vectors are helpfulif debug:helpful_vectors(BB, modulus^mm)# check if determinant is correctly boundeddet = BB.det()bound = modulus^(mm*nn)if det >= bound:print("We do not have det < bound. Solutions might not be found.")print("Try with highers m and t.")if debug:diff = (log(det) - log(bound)) / log(2)print("size det(L) - size e^(m*n) = ", floor(diff))if strict:return -1, -1else:print("det(L) < e^(m*n) (good! If a solution exists < N^delta, it will be found)")# display the lattice basisif debug:matrix_overview(BB, modulus^mm)# LLLif debug:print("optimizing basis of the lattice via LLL, this can take a long time")BB = BB.LLL()if debug:print("LLL is done!")# transform vector i & j -> polynomials 1 & 2if debug:print("looking for independent vectors in the lattice")found_polynomials = Falsefor pol1_idx in range(nn - 1):for pol2_idx in range(pol1_idx + 1, nn):# for i and j, create the two polynomialsPR.<w,z> = PolynomialRing(ZZ)pol1 = pol2 = 0for jj in range(nn):pol1 += monomials[jj](w*z+1,w,z) * BB[pol1_idx, jj] / monomials[jj](UU,XX,YY)pol2 += monomials[jj](w*z+1,w,z) * BB[pol2_idx, jj] / monomials[jj](UU,XX,YY)# resultantPR.<q> = PolynomialRing(ZZ)rr = pol1.resultant(pol2)# are these good polynomials?if rr.is_zero() or rr.monomials() == [1]:continueelse:print("found them, using vectors", pol1_idx, "and", pol2_idx)found_polynomials = Truebreakif found_polynomials:breakif not found_polynomials:print("no independant vectors could be found. This should very rarely happen...")return 0, 0rr = rr(q, q)# solutionssoly = rr.roots()if len(soly) == 0:print("Your prediction (delta) is too small")return 0, 0soly = soly[0][0]ss = pol1(q, soly)solx = ss.roots()[0][0]#return solx, solydef example():############################################# How To Use This Script############################################ The problem to solve (edit the following values)## the modulusN = 10037257627154486608196774801095855162090578704439233219876490744017222686494761706171113312036056644757212254824459536550416291797454693336043852190135363# the public exponent# e = 0x19441f679c9609f2484eb9b2658d7138252b847b2ed8ad182be7976ed57a3e441af14897ce041f3e07916445b88181c22f510150584eee4b0f776a5a487a4472a99f2ddc95efdd2b380ab4480533808b8c92e63ace57fb42bac8315fa487d03bec86d854314bc2ec4f99b192bb98710be151599d60f224114f6b33f47e357517e = 6701513605196718137208327145211106525052740242222174201768345944717813148931274437740087428165253744741547590314279846187850432858954606153257994418035341e2 = 6701513605196718137208327145211106525052740242222174201768345944717813148697578256906281384764668448160487159980777522352135265204110465696876174971350601# the hypothesis on the private exponent (the theoretical maximum is 0.292)delta = .26 # this means that d < N^delta## Lattice (tweak those values)## you should tweak this (after a first run), (e.g. increment it until a solution is found)m = 5 # size of the lattice (bigger the better/slower)# you need to be a lattice master to tweak theset = int((1-2*delta) * m)  # optimization from Herrmann and MayX = 2*floor(N^delta)  # this _might_ be too muchY = floor(N^(1/2))    # correct if p, q are ~ same size## Don't touch anything below## Problem put in equationP.<x,y> = PolynomialRing(ZZ)A = int((N+1)/2)pol = 1 + x * (A + y)## Find the solutions!## Checking boundsif debug:print("=== checking values ===")print("* delta:", delta)print("* delta < 0.292", delta < 0.292)print("* size of e:", int(log(e)/log(2)))print("* size of N:", int(log(N)/log(2)))print("* m:", m, ", t:", t)# boneh_durfeeif debug:print("=== running algorithm ===")start_time = time.time()solx, soly = boneh_durfee(pol, e, m, t, X, Y)# found a solution?if solx > 0:print("=== solution found ===")if False:print("x:", solx)print("y:", soly)d = int(pol(solx, soly) / e)print("private key found:", d)c = 6723803125309437675713195914771839852631361554645954138639198200804046718848872479140347495288135138109762940384847808522874831433140182790750890982139835m = pow(c,d,N)print(long_to_bytes(int(m)))else:print("=== no solution was found ===")if debug:print(("=== %s seconds ===" % (time.time() - start_time)))if __name__ == "__main__":example()
# flag{39693fd4a45b386c28c63100cc930238259891a2}

这道题不知道有没有人尝试硬分解。

告白2009-01-23

  • 首先,我在比赛第一天就进行了附件的下载,由于我先进行了"babyrsa"的解题,针对于winer攻击,看起来就有莫名的熟悉感。之后进行winer攻击即可获得RSA加密的私钥d.之后我以为这道出在新生赛的题应该就到这里了,然后直接进行了long_to_bytes发现结果是乱码。
from Crypto.Util.number import *n= 106907120255411141276638612258492580223206670508697860345280705552076099016030935898699700187523599766269485047282325650117035914628760419926410817774570995043643433455055595591107437470658308764074450729921003648782408533657438504280874574703167028727399770901329675528708585142713643443248769642817712218371
e= 92066298664485065396027178362270794902621018857568310802765263839921592653297188141639082907410773099588833460614099675385786190965706296547920850855064908555902716021514756109564555466796584126969045436871844375174789134742417250605776973188216013735765092101366990049447374275811804264794446656219369440535
c= 72413193823586193683552385578931939035012872670413497855056244201691512354415666469936125548748032982020958495114951719066245650644060153838816623502095911253320142088319318206119073607336497914311058118174988818658610257295726356030260769061712429926392969618604615189351858925626182197332313954336604548074############################################################# winer 
import gmpy2
import libnumdef continuedFra(x, y):"""计算连分数:param x: 分子:param y: 分母:return: 连分数列表"""cf = []while y:cf.append(x // y)x, y = y, x % yreturn cf
def gradualFra(cf):"""计算传入列表最后的渐进分数:param cf: 连分数列表:return: 该列表最后的渐近分数"""numerator = 0denominator = 1for x in cf[::-1]:# 这里的渐进分数分子分母要分开numerator, denominator = denominator, x * denominator + numeratorreturn numerator, denominator
def solve_pq(a, b, c):"""使用韦达定理解出pq,x^2−(p+q)∗x+pq=0:param a:x^2的系数:param b:x的系数:param c:pq:return:p,q"""par = gmpy2.isqrt(b * b - 4 * a * c)return (-b + par) // (2 * a), (-b - par) // (2 * a)def getGradualFra(cf):"""计算列表所有的渐近分数:param cf: 连分数列表:return: 该列表所有的渐近分数"""gf = []for i in range(1, len(cf) + 1):gf.append(gradualFra(cf[:i]))return gfdef wienerAttack(e, n):""":param e::param n::return: 私钥d"""cf = continuedFra(e, n)gf = getGradualFra(cf)for d, k in gf:if k == 0: continueif (e * d - 1) % k != 0:continuephi = (e * d - 1) // kp, q = solve_pq(1, n - phi + 1, n)if p * q == n:return d
d = wienerAttack(e, n)

在这里插入图片描述

  • 但是根据task.py前面的注释,(起初以为是英文,尝试读了一下,后面发现看不懂,豆包翻译,才发现是德语,注意这里的德语,划重点)。

  • 之后我就把我解出的乱码以及提示,翻译丢给了deepseek,发现他给出的解决方法,但是都没能提交成功。之后我去通过群主大大联系出题人大大,被告知题目名称就是hint.在此告一段落……

  • 比赛重新开始的第一天,由于附件进行了更新,我在题主不会说一句废话的基础上一个字一个字地扣,首先,“打不出来给你们挂孙吧”,我就去搜孙吧,后面越看越发懵,这孙吧是啥东西呀……
    然后翻译了一下今天的德语内容:
    ![[1747724414724.png]]

  • 之后我就用过浏览器搜索2009-1-23日表白,在这里我找到了这道题的关键点:
    摩斯电码里的爱情 - analyzer - 博客园

  • 我醍醐灌顶,昨天task.py中的“除了摩斯别的都用了”,以及今天的看着对信中的数字困惑不解。我就明白了,利用winer攻击获得的私钥d进行解密m不要转字节就是原文中经过摩斯之后的内容,之后9键解密内容为:OISTTSEEOWOI,之后利用单表替换密码进行解密内容为IHLEELCCIBIH。然后吧,我就想着继续跟着文章走,应该马上就出来了,但是发现根本不是有意义的明文,并且提交不对。

  • 这个时候我很懊恼这个脑洞题,注意到我们还有一段提示没有使用,那就是栅栏,我将最后的密文和提示丢给deepseek,发现还是不行,不论是脚本还是想法,都不是正确答案,但是deepseek给我提醒了一下,其中提示中所说的碰倒了第9根和第12根板条,但是栅栏完好无损,deepseek说这两个位置可以是任意的英文字母,这个时候,我明白了,然后我就去爆破。发现还是没有找到有意义的明文。最后我就先不爆破了,我直接用_进行占位,看看能不能找到有意义的明文。(为什么是有意义的明文,因为原文章中的I love you too;我觉得出题人不会大费周章地利用随机数的)。

  • 之后我还是没有发现什么端倪,之后,我想了想,我要不要直接给deepseek,让他适当地加几个空格获取,毕竟他不会累,但是还是没有收获,这个时候,我想到了,!!!题目注释是德语写的会不会,这道题的答案也是德语,所以再次求救deepseek。这次有了新发现"ICH_LIEBE_DICH"是“我爱你”
    ![[1747725278152.png]]

  • 但是我比对这个答案跟我原来求解的字母个数(因为原文是栅栏最后一步),发现我求解的字符个数中多个L少个D.这个时候我要去看看是不是真的通过栅栏解密的,因为原来不知道德语,也看不明吧,这次带着这个相似的答案进行找,就看到了。
    ![[1747725452265.png]]

  • 这里我不知道是出题人故意少个D多个L。最后拿着flag{xxxx}去提交,发现还是不对,这个时候我就去请教群主大大,之后我尝试了如下答案之后,得到最后的flag{ich liebe dich}.
    单表替换的代码:

def decrypt_substitution(ciphertext: str) -> str:"""使用QWE=ABC顺序的单表替换进行解密参数:ciphertext: 密文字符串返回:解密后的明文字符串"""# 构建解密映射表 (QWE对应ABC,依次类推)# 获取字母表alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'# 构建替换表 (Q对应A, W对应B, E对应C, ...)# 这里使用Q作为起始点,按字母表顺序映射key = 'QWERTYUIOPASDFGHJKLZXCVBNM'# 确保key包含所有26个字母且不重复if len(set(key)) != 26 or len(key) != 26:raise ValueError("替换密钥必须包含所有26个字母且不重复")# 构建解密字典 (密文字母 -> 明文字母)decrypt_dict = {k: v for k, v in zip(key, alphabet)}# 执行解密plaintext = []for char in ciphertext.upper():if char in decrypt_dict:plaintext.append(decrypt_dict[char])else:# 非字母字符保持不变plaintext.append(char)return ''.join(plaintext)# 示例使用
if __name__ == "__main__":ciphertext = "Q"  # 对应明文"HELL"plaintext = decrypt_substitution(ciphertext)print(f"密文: {ciphertext}")print(f"明文: {plaintext}")

进行flag格式爆破的代码:

s1 = "ICH_LIEBE_LICH"
s2 = "ICH_LIEBE_DICH"
s1 = s1.lower()
s2 = s2.lower()
print("flag{" + s1 + "}")
print("flag{" + s2 + "}")
s1 = s1.replace("_", " ", 2)
s2 = s2.replace("_", " ", 2)
print("flag{" + s1 + "}")
print("flag{" + s2 + "}")
s1 = s1.replace(" ", "", 2)
s2 = s2.replace(" ", "", 2)
print("flag{" + s1 + "}")
print("flag{" + s2 + "}")

前面尝试了六种形式的大写,发现都不对。
注:这个时候有人问为啥要用_占位,因为随波逐粒进行栅栏的时候有时候会忽略空格,为了少些脚本,我利用_占位。

DIladila

analysis

给出的分组密码没有采用CBC等分组密码的工作模式,因此我们可以采用逐四字符的穷举攻击,这对于正常的密码学而言是最有效的方式.

至于这个是不是预期解,等出题人大大放出wp吧。

不过经过不断修改代码逻辑,可以根据二分算法的思想,缩小需要爆破的范围,最后可以很会地爆破出来flag。

exp

from Crypto.Util.number import *def ror(val, r_bits, max_bits=16):return (val >> r_bits) | ((val << (max_bits - r_bits)) & (2**max_bits - 1))def decrypt(xys,key):may_xys = []for xy in xys:x,y = xytmp_y = bin(x^y)[2:].zfill(16)may_y = int(tmp_y[-2:]+tmp_y[:14],2)tmp_x = x^keymay_xs = []for xh in range(2 ** 9):for xl in range(2 ** 7):may_x = int(bin(xh)[2:].zfill(9)+bin(xl)[2:].zfill(7),2)if (ror(may_x, 7) + may_y) & 0xFFFF == tmp_x:may_xys.append((may_x, may_y))return may_xysciphertext = [
(57912, 19067),
(38342, 34089),
(16842, 41652),
(30292, 50979),
(9137, 57458),
(29822, 64285),
(33379, 14140),
(16514, 4653)
]
flag=b''
for c in ciphertext:keys = [0x1234, 0x5678, 0x9abc, 0xdef0]keys = keys[::-1]xys=[c]for _ in range(4):xys=decrypt(xys, keys[_])for xy in xys:x,y=xyflag+=(long_to_bytes(x)[::-1]+long_to_bytes(y)[::-1])
print(flag)
# flag{You_DIladila_Crypto_Matser}

古典密码

anlysis

  • 给了四种加密,但是说五种,应该另一种就是凯撒。因为凯撒是仿射的特殊情况。单表替换、维吉尼亚、仿射、凯撒的顺序会影响最后的结果。但是栅栏只改变顺序。所以放在第几次解密都可以。
  • 其中单表替换的密码表比价容易看出来,同时维吉尼亚的密钥也很容易看出来。接着就是5、8以及4、5.由于仿射加密需要两个密钥,即a,b同时需要满足gcd(a, 26) = 1这就表示5、8是仿射加密的密钥,而4、5是栅栏加密的密钥,在经过尝试后发现先5后4进行栅栏解密可以得到flag{}的形式。
  • 我只需要爆破4!=24种解密顺序,每次凯撒解密的时候都进行25次密钥的爆破,之后进行先5后4的栅栏。最终寻找有没有flag就可以了。

exp

import itertools
from string import ascii_lowercase, ascii_uppercasedef caesar_decrypt(ciphertext, shift):decrypted = []for char in ciphertext:if char.islower():decrypted.append(chr((ord(char) - ord('a') - shift) % 26 + ord('a')))elif char.isupper():decrypted.append(chr((ord(char) - ord('A') - shift) % 26 + ord('A')))else:decrypted.append(char)return ''.join(decrypted)def vigenere_decrypt(ciphertext, key):decrypted = []key_index = 0for char in ciphertext:if char.isalpha():key_char = key[key_index % len(key)]key_shift = ord(key_char.lower()) - ord('a')if char.islower():decrypted.append(chr((ord(char) - ord('a') - key_shift) % 26 + ord('a')))else:decrypted.append(chr((ord(char) - ord('A') - key_shift) % 26 + ord('A')))key_index += 1else:decrypted.append(char)return ''.join(decrypted)def affine_decrypt(ciphertext, a, b):a_inv = 0for i in range(26):if (a * i) % 26 == 1:a_inv = ibreakdecrypted = []for char in ciphertext:if char.islower():decrypted.append(chr((a_inv * (ord(char) - ord('a') - b)) % 26 + ord('a')))elif char.isupper():decrypted.append(chr((a_inv * (ord(char) - ord('A') - b)) % 26 + ord('A')))else:decrypted.append(char)return ''.join(decrypted)def qwe_decrypt(ciphertext):qwe_mapping = {'q': 'a', 'w': 'b', 'e': 'c', 'r': 'd', 't': 'e', 'y': 'f', 'u': 'g', 'i': 'h', 'o': 'i', 'p': 'j','a': 'k', 's': 'l', 'd': 'm', 'f': 'n', 'g': 'o', 'h': 'p', 'j': 'q', 'k': 'r', 'l': 's', 'z': 't','x': 'u', 'c': 'v', 'v': 'w', 'b': 'x', 'n': 'y', 'm': 'z'}decrypted = []for char in ciphertext:if char.isalpha():if char.islower():decrypted.append(qwe_mapping.get(char, char))else:decrypted.append(qwe_mapping.get(char.lower(), char).upper())else:decrypted.append(char)return ''.join(decrypted)def main():ciphertext = "njih{ddolYScoikOWrlctrcc}"vigenere_key = "nxtcctf"affine_a = 5affine_b = 8transformations = [('caesar', lambda x, s: caesar_decrypt(x, s)),('vigenere', lambda x, k: vigenere_decrypt(x, k)),('affine', lambda x, a, b: affine_decrypt(x, a, b)),('qwe', lambda x: qwe_decrypt(x))]# 尝试所有可能的变换顺序for order in itertools.permutations(transformations, 4):# 尝试所有可能的凯撒偏移for caesar_shift in range(26):# 应用变换顺序current_text = ciphertextfor name, func in order:if name == 'caesar':current_text = func(current_text, caesar_shift)elif name == 'vigenere':current_text = func(current_text, vigenere_key)elif name == 'affine':current_text = func(current_text, affine_a, affine_b)elif name == 'qwe':current_text = func(current_text)# 检查是否包含flag格式if 'flag{' in current_text:print(f"找到可能的flag!")print(f"变换顺序: {[t[0] for t in order]}")print(f"凯撒偏移: {caesar_shift}")print(f"明文: {current_text}")print()if __name__ == "__main__":main()    
"""
找到可能的flag!
变换顺序: ['vigenere', 'affine', 'qwe', 'caesar']
凯撒偏移: 3
明文: flag{nxtcNBflagNBctfflag}
"""

相关文章:

  • [250521] DBeaver 25.0.5 发布:SQL 编辑器、导航器全面升级,新增 Kingbase 支持!
  • Android本地语音识别引擎深度对比与集成指南:Vosk vs SherpaOnnx
  • python代码绘制某只股票最近90天的K线图、均线、量能图
  • 线上 Linux 环境 MySQL 磁盘 IO 高负载深度排查与性能优化实战
  • CentOS 10:启动telnet服务
  • 网络爬虫(Web Crawler)详解
  • ​C++性能优化的7大核心策略与实战案例
  • HTML5 Video (视频) 深入解析
  • Linux 内核音视频架构(V4L2 )介绍
  • uni-app(2):页面
  • 笔记:NAT
  • 2024正式版企业级在线客服系统源码+语音定位+快捷回复+图片视频传输+安装教程
  • HTTP相关内容
  • Flink CDC 3.4 发布, 优化高频 DDL 处理,支持 Batch 模式,新增 Iceberg 支持
  • debian系统redis-dump安装
  • 如何成为更好的自己?
  • 轻量级高性能Rust HTTP服务器库Hyperlane,助力现代网络服务开发
  • maven之pom.xml
  • flink 提交流程
  • Ntfs!ATTRIBUTE_RECORD_HEADER结构$INDEX_ROOT=0x90的一个例子
  • 网站 百度地图/找个免费网站这么难吗
  • 常州网站建设/推广合作
  • 常山网站建设/中国网站排名前100
  • 淄博乐达网站建设吧/网络营销具有哪些优势和吸引力
  • vps远程桌面服务器租用/seo还有用吗
  • 大方县住房城乡建设局网站/重庆seo团队