周末花了一个上午打了打今年的鹏城杯 题目比较基础 Solve 5 of 6 来此写WP,剩下的RandomAudit实在猜不出出题人的脑洞了,等有师傅公布解法再复现吧
true_or_false#
神人题没把flag删干净 血手慢无
tf.py
import json, random, hashlib
from Crypto.Util.number import getPrime, inverse, bytes_to_long, long_to_bytes
A=1103515245
B=12345
MOD=2**31
def lcg(s):
while True:
s=(A*s+B)%MOD
yield s&0xFF
def mix(b,s):
g=lcg(s)
o=bytearray()
for i in range(0,len(b),8):
t=b[i:i+8][::-1]
for x in t:
o.append(x^next(g))
return bytes(o)
BITS=512
e=65537
p=getPrime(BITS)
q=getPrime(BITS)
n=p*q
phi=(p-1)*(q-1)
d=inverse(e,phi)
##################################################################################
FLAG=b"flag{de5b8aec-6294-42dd-8038-18e0854e3d22}"
##################################################################################
m=bytes_to_long(hashlib.sha256(FLAG).digest())
flag_enc=bytes_to_long(FLAG)^m
sig_ok=pow(m,d,n)
d_p=d%(p-1)
d_q=d%(q-1)
s_p=pow(m,d_p,p)
s_q=random.randrange(q)
inv= inverse(q,p)
sig_fault=(s_q+q*((s_p-s_q)*inv%p))%n
seed=random.randrange(1,2**31)
cj={
"n":hex(n),
"e":e,
"sig_ok_mixed":mix(long_to_bytes(sig_ok),seed).hex(),
"sig_fault_mixed":mix(long_to_bytes(sig_fault),seed+123456).hex(),
"flag_enc":hex(flag_enc),
"seed_hint":seed%97
}
json.dump(cj,open("challenge.json","w"),indent=0)
py题目基于RSA-CRT故障 对于因子的签名正常 但是被替换为一个随机数 我们看接下来的两个mixed后的数据
我们对2式转移到模下 因为我们的没有任何的代数关系 可以得到
而我们再对1式转化到模下,由于 可以得到
所以说 我们如果恢复了 就可以直接得到
第二步 我们发现的范围是 而且给出了的结果 完全可以暴力穷举所有的seed 逆向LCG-MIX 还原我们的两个签名 然后求出flag ,验证是否符合flag-enc exp如下
import hashlib
from Crypto.Util.number import long_to_bytes, bytes_to_long, inverse, GCD
# --- Challenge Data ---
n = 0xb7f5f009342de5a47e1283fdeb22cd85ddcd01cd0279dedbb248f32144dbe9c2dd8c65869a0bc51f93b0db038450417d789a17d70bac364fe3e3e514386eda42d9474cc58bad900e0f3535c469601dc11fe637b82835dd9eec6662bebca8b0446d764478599d709f99ebbe6cda0613206294de045afa31b2d63801b9b507b123
e = 65537
sig_ok_mixed_hex = "863a6f1eb90320932267259c7a28b757b6ff5357214663ab7f7b6deb81d4eb50303dd4d5b99811d37b0b208471658378dd6cc95baaec9c716b14bcef24bfd6ca0d5edf5a5edf099310fac466f43c58958a438c56f0a6aee35e244b5c5aa54ea5bf832698f19e4ace0cf437aec5fa4b55e89d643cb03560647f7dbd6d916e8c4b"
sig_fault_mixed_hex = "62590781e6738820860ff4f2a883ae183d32bbc9f62adca7227184bf84ee2539dc4a86b2356e943569b8b2c071dde8f5bc06d2879bc12aaa957127103f2b57c0d4c794945d9ee0da3a77dc194b9f227a19444ac596c0e6c693eb24455889d49d22e9de540264abee4350311e226f9ac4dfa7086d63a6560b1a6c037bc1dfaf38"
flag_enc_hex = "0x666c61677b64653562384ebfbe1d7a753bf0be4e6906d7d09a758f90967faee2787c3ee4c141a2e918a1"
seed_hint = 13
# Convert inputs
sig_ok_mixed = bytes.fromhex(sig_ok_mixed_hex)
sig_fault_mixed = bytes.fromhex(sig_fault_mixed_hex)
flag_enc = int(flag_enc_hex, 16)
# LCG Constants
LCG_A = 1103515245
LCG_B = 12345
LCG_MOD = 2**31
def generate_lcg_stream(seed, count):
"""Generate the LCG byte stream efficiently."""
s = seed
stream = bytearray(count)
for i in range(count):
s = (LCG_A * s + LCG_B) & 0x7FFFFFFF
stream[i] = s & 0xFF
return stream
def unmix(mixed, seed):
"""Reverses the mix function to recover the signature."""
length = len(mixed)
stream = generate_lcg_stream(seed, length)
out = bytearray()
for i in range(0, length, 8):
block = mixed[i:i+8]
key_block = stream[i:i+8]
# In mix: t = b[::-1], o = t ^ k
# In unmix: t = o ^ k, b = t[::-1]
t = [b ^ k for b, k in zip(block, key_block)]
out.extend(t[::-1])
return bytes(out)
def solve():
mixed_byte_check = sig_ok_mixed[7]
target_limit = 0xB7
step = 97
start_seed = seed_hint
# Search loop
for seed in range(start_seed, 2**31, step):
# 1. Fast Filter: Check MSB
# We need the 8th output of the LCG (index 7)
# Unrolling LCG for just 8 steps is faster than function call
curr = seed
for _ in range(8):
curr = (LCG_A * curr + LCG_B) & 0x7FFFFFFF
k7 = curr & 0xFF
# Check if MSB of potential signature exceeds MSB of n
if (mixed_byte_check ^ k7) > target_limit:
continue
# 2. Strict Filter: Recover full signatures and check GCD (Fault Attack)
try:
s_ok_bytes = unmix(sig_ok_mixed, seed)
s_ok = bytes_to_long(s_ok_bytes)
if s_ok >= n:
continue
# We need the faulty signature too
# Fault seed is seed + 123456
s_fault_bytes = unmix(sig_fault_mixed, seed + 123456)
s_fault = bytes_to_long(s_fault_bytes)
# RSA Fault Attack: gcd(S_ok - S_fault, n) should reveal p
diff = abs(s_ok - s_fault)
if diff == 0: continue
p = GCD(diff, n)
if p > 1 and p < n:
print(f"[+] Found Seed: {seed}")
print(f"[+] Found Prime p: {p}")
return seed, p, s_ok
except Exception:
continue
return None, None, None
seed, p, sig_ok = solve()
if p:
q = n // p
phi = (p - 1) * (q - 1)
d = inverse(e, phi)
m = pow(sig_ok, e, n)
flag_int = flag_enc ^ m
flag = long_to_bytes(flag_int)
print(f"[+] Flag: {flag.decode()}")
else:
print("[-] Seed not found.")
# flag{de5b8aec-6294-42dd-8038-18e0854e3d22}pybabyRSA#
task.py
from Crypto.Util.number import *
import decimal
m=long_to_bytes("?????")
p=getPrime(1024)
while True:
q=getPrime(1024)
if p>q:
break
n=p*q
e="?????"
d=inverse(e,(p-1)*(q-1))
c=pow(m,e,n)
decimal.getcontext().prec = 1024
P=decimal.Decimal(p)
Q=decimal.Decimal(q)
leak=decimal.Decimal((3*P*P-1)/(3*P*Q))
print("c =",c)
print("leak =",leak)
print("d =",d)
#leak = 1.396995694831414203476063690838730308815841662737318558906107823553922718340982125801595368449608188770051881765292978548960520326036779130167518285237817101541807766017642530065080930654694948943506714268685400709580398894902693407016988670394423892586264077247263710263220932577837642377245651448838665854362532801659965471421937839336237670710012298796758992931116659292915200628873553198226185187089027680973673618973869464164460226697625936493428822424637497370197316811245879504779934098600596822159243994319583651080005054538419168988020562590543262648544970376255020489363894055887067948343768399654357738592577280906555896933717091837896978973488220368081406433117367524537063718421897982643644320078600517763936883820416362057895941185749296170109172249907094176821124345672294602380784325702476105763209165109703429326132417850746805701054961710623030742187505484821961670922386999933202645522248608323217011522889282323071281405301772218220381951540118124201599862330377374571641729649420917168701463539034702411
#d = 16306054997613721520756151430779642117683661431522665108784419231044104572118893098180652730976905729602478591047033305251624752030036736271198006715513694904231940253554804069707679445942892410812386221633728427239116007373836662495075237456279818311659331982404534490546781763464409713789636372508503902598331950861474527128323735250673137355260113147338636761737748874105625008482750923429512271416511835596944209137554445130949731646669691366003832655082535985891463876904334888009751956386994969339847254470145428608062575606120441725590059524749595027078238962391188809496875025237129899849787699468205026040721
#c= 7908369000608075306226552240713890041649799894903074579356627811865842237315201153498579205223600526520994811661608630888045462921547166872107507948062717836952855804806976414887413729060431265217539895710936669089248515746191716161194996469977577048602427553584286064475300979649416171469313168995504717602670924606819204605601860560767900702512753735554900344201907921239415885901489708576066483012272256175573658509614344875077232108364134161997767814675830320630271209201503987787921279932886374846298269125068817280777403718279754392091441050281244934594776307137448975055247018414699621410668188864774860026941
py给出了的小数近似形式 我们发现误差项几乎可以忽略 那么leak也就是约等于 打连分数展开即可
exp
from decimal import Decimal, getcontext
from Crypto.Util.number import long_to_bytes, isPrime, inverse
from fractions import Fraction
import math
c = 7908369000608075306226552240713890041649799894903074579356627811865842237315201153498579205223600526520994811661608630888045462921547166872107507948062717836952855804806976414887413729060431265217539895710936669089248515746191716161194996469977577048602427553584286064475300979649416171469313168995504717602670924606819204605601860560767900702512753735554900344201907921239415885901489708576066483012272256175573658509614344875077232108364134161997767814675830320630271209201503987787921279932886374846298269125068817280777403718279754392091441050281244934594776307137448975055247018414699621410668188864774860026941
d = 16306054997613721520756151430779642117683661431522665108784419231044104572118893098180652730976905729602478591047033305251624752030036736271198006715513694904231940253554804069707679445942892410812386221633728427239116007373836662495075237456279818311659331982404534490546781763464409713789636372508503902598331950861474527128323735250673137355260113147338636761737748874105625008482750923429512271416511835596944209137554445130949731646669691366003832655082535985891463876904334888009751956386994969339847254470145428608062575606120441725590059524749595027078238962391188809496875025237129899849787699468205026040721
leak_str = "1.396995694831414203476063690838730308815841662737318558906107823553922718340982125801595368449608188770051881765292978548960520326036779130167518285237817101541807766017642530065080930654694948943506714268685400709580398894902693407016988670394423892586264077247263710263220932577837642377245651448838665854362532801659965471421937839336237670710012298796758992931116659292915200628873553198226185187089027680973673618973869464164460226697625936493428822424637497370197316811245879504779934098600596822159243994319583651080005054538419168988020562590543262648544970376255020489363894055887067948343768399654357738592577280906555896933717091837896978973488220368081406433117367524537063718421897982643644320078600517763936883820416362057895941185749296170109172249907094176821124345672294602380784325702476105763209165109703429326132417850746805701054961710623030742187505484821961670922386999933202645522248608323217011522889282323071281405301772218220381951540118124201599862330377374571641729649420917168701463539034702411"
getcontext().prec = 2000
x = Decimal(leak_str)
X = Fraction(leak_str)
def convergents_from_fraction(fr: Fraction):
a, b = fr.numerator, fr.denominator
cf = []
while b:
q = a // b
cf.append(q)
a, b = b, a - q*b
p0, p1 = 1, cf[0]
q0, q1 = 0, 1
yield Fraction(p1, q1)
for i in range(1, len(cf)):
p2 = cf[i]*p1 + p0
q2 = cf[i]*q1 + q0
yield Fraction(p2, q2)
p0, p1 = p1, p2
q0, q1 = q1, q2
cand = None
for f in convergents_from_fraction(X):
p, q = f.numerator, f.denominator
if q.bit_length() < 900:
continue
if q.bit_length() > 1100:
break
if not (isPrime(p) and isPrime(q)):
continue
lhs = Decimal(leak_str)
rhs = (Decimal(p) / Decimal(q)) - (Decimal(1) / (Decimal(3)*Decimal(p)*Decimal(q)))
if abs(lhs - rhs) < Decimal(1) / (Decimal(q)*Decimal(q)):
cand = (p, q)
break
p, q = cand
if p < q:
p, q = q, p
n = p*q
phi = (p-1)*(q-1)
e = inverse(d, phi)
m = pow(c, d, n)
pt = long_to_bytes(m)
print("p bits:", p.bit_length())
print("q bits:", q.bit_length())
print("e =", e)
print("flag bytes:", pt)
# flag{th1s_1s_4_ture_fl4g}pyweak-leak#
也是flag没删干净
task.py
from Crypto.Util.number import getPrime, bytes_to_long
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from hashlib import sha256
import os, base64, random
from math import gcd
e = 65537
LCG_A, LCG_B, LCG_MOD = 1103515245, 12345, 10007
SECRET_VALUE = 1234
password = f"{random.randint(0,999999):06d}"
salt = os.urandom(8).hex()
hash_val = sha256((salt + password).encode()).hexdigest()
def gen_seq(seed,a,b,m,length):
seq=[seed % m]
for _ in range(length-1):
nxt=(a*seq[-1]+b) % m
nxt ^= (seq[-1] & 0xff)
seq.append(nxt)
return seq
lcg_seed = (int(password) ^ SECRET_VALUE) % LCG_MOD
seq = gen_seq(lcg_seed, LCG_A, LCG_B, LCG_MOD, 15)
seq_prefix = seq[:7]
seq9 = seq[9]
BITS = 256
p = getPrime(BITS)
q = getPrime(BITS)
n = p * q
n1 = p * (q + 1)
n2 = (p + 1) * q + seq9
aes_key = os.urandom(16)
mask_bytes = sha256(str(seq9).encode()).digest()[:16]
masked_key_int = bytes_to_long(aes_key) ^ bytes_to_long(mask_bytes)
cipher_rsa = pow(masked_key_int, e, n)
###################################################################################
FLAG = b"flag{7980dd68-c028-439d-8f33-3b4e4cfeeb55}"
###################################################################################
iv = os.urandom(16)
cipher = AES.new(aes_key, AES.MODE_CBC, iv)
ct = cipher.encrypt(pad(FLAG,16))
print("salt:", salt)
print("hash:", hash_val)
print("n1:", n1)
print("n2:", n2)
print("seq_prefix:", ",".join(str(x) for x in seq_prefix))
print("cipher_rsa:", cipher_rsa)
print("iv:", base64.b64encode(iv).decode())
print("ciphertext:", base64.b64encode(ct).decode())
'''
salt: f62b3e49c1f05d1c
hash: a0bcbfda9bd2f0364c6f4ad0f996465bec0da2de8cd51ee11c9c883b47779cc4
n1: 5584300989285538211153365890789627571870624311506728764237201442331520767215704903718501881100700113185783404202199758018541582967691088869854375384182438
n2: 5584300989285538211153365890789627571870624311506728764237201442331520767215684679677759040755449786845864086748368453212978360679736956915595159857669375
seq_prefix: 2993,3261,4284,5322,6307,1211,8690
cipher_rsa: 4516247026166659285144948330256302160375394741001987438893860039618683568332625137344822301939534363324551681121344467717871483193109869787946141254659256
iv: G+Mn2WPXhRztrDdD8m+1gw==
ciphertext: j9mUOuK2iz9ZHZor8BcsXNFhFRGzkw1x4a5T1GzaYJJ8VhHj+7jN0Id47fcxw/7F
'''
py爆破求解pwd 得到seq后计算diff 然后解方程就行
exp
from Crypto.Util.number import *
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from hashlib import sha256
import os, base64, random
from math import gcd
#################################################
salt = "f62b3e49c1f05d1c"
hash_str = "a0bcbfda9bd2f0364c6f4ad0f996465bec0da2de8cd51ee11c9c883b47779cc4"
for i in range (0, 1000000):
password_cur = f"{i:06d}"
hash_val = sha256((salt + str(i)).encode()).hexdigest()
if hash_val == hash_str:
print(f"found pwd {password_cur}")
password = password_cur
break
# found pwd 384457
#################################################
e = 65537
LCG_A, LCG_B, LCG_MOD = 1103515245, 12345, 10007
SECRET_VALUE = 1234
# password = 384457
def gen_seq(seed,a,b,m,length):
seq=[seed % m]
for _ in range(length-1):
nxt=(a*seq[-1]+b) % m
nxt ^= (seq[-1] & 0xff)
seq.append(nxt)
return seq
lcg_seed = (int(password) ^ SECRET_VALUE) % LCG_MOD
seq = gen_seq(lcg_seed, LCG_A, LCG_B, LCG_MOD, 15)
seq_prefix = seq[:7]
seq9 = seq[9]
print(seq_prefix)
print(f"seq9 = {seq9}")
##################################################
n1 = 5584300989285538211153365890789627571870624311506728764237201442331520767215704903718501881100700113185783404202199758018541582967691088869854375384182438
n2 = 5584300989285538211153365890789627571870624311506728764237201442331520767215684679677759040755449786845864086748368453212978360679736956915595159857669375
N2 = n2 - seq9
N1 = n1
assert N1 > N2
diff = N1 - N2 # p - q
# print(diff)
# BITS = 256
# p = getPrime(BITS)
# q = getPrime(BITS)
# n = p * q
# n1 = p * (q + 1)
# n2 = (p + 1) * q + seq9
q = 65297222730984420977492506404586865587641626249809455543349803028321376822713
p = q + diff
assert n1 == p*(q+1)
n = p*q
cipher_rsa = 4516247026166659285144948330256302160375394741001987438893860039618683568332625137344822301939534363324551681121344467717871483193109869787946141254659256
phi = (p-1)*(q-1)
d = pow(e,-1,phi)
masked_key_int = pow(cipher_rsa,d,n)
mask_bytes = sha256(str(seq9).encode()).digest()[:16]
# masked_key_int = bytes_to_long(aes_key) ^ bytes_to_long(mask_bytes)
aes_key = long_to_bytes(masked_key_int ^ bytes_to_long(mask_bytes))
iv = "G+Mn2WPXhRztrDdD8m+1gw=="
ciphertext = "j9mUOuK2iz9ZHZor8BcsXNFhFRGzkw1x4a5T1GzaYJJ8VhHj+7jN0Id47fcxw/7F"
iv = base64.b64decode(iv)
ct = base64.b64decode(ciphertext)
cipher = AES.new(aes_key, AES.MODE_CBC, iv)
flag = cipher.decrypt(ct)
print(flag)
# flag{7980dd68-c028-439d-8f33-3b4e4cfeeb55}py解p q用如下sage代码
diff = 20224040742840345250326339919317453831304805563222287954131954259215526515254
n1 = 5584300989285538211153365890789627571870624311506728764237201442331520767215704903718501881100700113185783404202199758018541582967691088869854375384182438
x = var("x")
eq = (x+diff)*(x+1) - n1 == 0
solution = solve(eq,x)
print(solution)
'''
[
x == -85521263473824766227818846323904319418946431813031743497481757287536903337968,
x == 65297222730984420977492506404586865587641626249809455543349803028321376822713
]
'''pyPECO#
Task.py
from Crypto.Util.number import *
from secret import FLAG,x,y
f0= bytes_to_long(FLAG[:len(FLAG)//2].encode())
f1= bytes_to_long(FLAG[len(FLAG)//2:].encode())
m=getPrime(1876)
p=getPrime(1024)
q=getPrime(1024)
print(p,q)
n=p*q
e=65537
c=pow(m,e,n)
print('n=',n)
print('c=',c)
print('gift1=',1293023064232431070902426583269468463*x**2-105279230912868770223946474836383391725923*y**2)
print('gift2=',(p**7+q**13)&(2**777-1))
assert (x*f0+y*f1)%m <2**99
'''
n= int(18443962106578943927922829208562388331564422618353954662348987125496135728205879853444693999188714508145409575298801277623433658530589571956301880815632542860363148763704636874275223979061507756787642735086825973011622866458454405794279633717255674221895468734500735123736684346340314680683830866884050311047424068122453972745273167956795195575475691048908906061023817574695902603984554911326264947716547564759877947888574515784489778380086664649338093680740990860192640619047071160362288611331225632270531304525264824445326394068892806774552310748255977040249822464839809344521107040968321810533993659358229305320413)
c= int(8176283809770578639445916571748890916863681496488338436815389781344271720445865752568007651231910205530735296305471880971422173915403956857863330698931559658909826642456860761540607878553228782799635976463090037022164739976302533892173751687781100980039065722082091714141141136171701360981540040678479802206949078162548124224838019262997441233919136963696523351831737708850863538007579105976954619102728135600542584651031405327214877358323388674864043740117718200022790892542634633918493245432384562983429810936975869853596007429259749282607844407676244954057886824475948603911174707176467261179324130051317766768127)
gift1= int(1293023064232431070902426583269468463)
gift2= int(26161714402997656593966327522661504448812191236385246127313450633226841096347099194721417620572738092514050785292503472019045698167235604357096118735431692892202119807587271344465029467089266358735895706496467947787464475365718387614)
'''
py告诉了你的低777位 而明显,那么有
走Lifting的方式可以确定的低777位 打Copper分解
再解Pell方程 得到后打格子求出
exp
from Crypto.Util.number import long_to_bytes
from sage.all import *
n_val = 18443962106578943927922829208562388331564422618353954662348987125496135728205879853444693999188714508145409575298801277623433658530589571956301880815632542860363148763704636874275223979061507756787642735086825973011622866458454405794279633717255674221895468734500735123736684346340314680683830866884050311047424068122453972745273167956795195575475691048908906061023817574695902603984554911326264947716547564759877947888574515784489778380086664649338093680740990860192640619047071160362288611331225632270531304525264824445326394068892806774552310748255977040249822464839809344521107040968321810533993659358229305320413
c_val = 8176283809770578639445916571748890916863681496488338436815389781344271720445865752568007651231910205530735296305471880971422173915403956857863330698931559658909826642456860761540607878553228782799635976463090037022164739976302533892173751687781100980039065722082091714141141136171701360981540040678479802206949078162548124224838019262997441233919136963696523351831737708850863538007579105976954619102728135600542584651031405327214877358323388674864043740117718200022790892542634633918493245432384562983429810936975869853596007429259749282607844407676244954057886824475948603911174707176467261179324130051317766768127
gift1 = 1293023064232431070902426583269468463
gift2 = 26161714402997656593966327522661504448812191236385246127313450633226841096347099194721417620572738092514050785292503472019045698167235604357096118735431692892202119807587271344465029467089266358735895706496467947787464475365718387614
A, B = gift1, 105279230912868770223946474836383391725923
e = 65537
def solve():
print("[*] 1. Recovering p and q...")
# 2-adic lifting
R = Zp(2, prec=777)
PR_Zp = PolynomialRing(R, 'xp')
xp = PR_Zp.gen()
f_lift = xp**20 - R(gift2)*xp**13 + R(n_val)**13
roots_zp = f_lift.roots()
if not roots_zp:
print("[-] No roots in Zp")
return
p_known = Integer(roots_zp[0][0].lift())
# Coppersmith
PR_Mod = PolynomialRing(Zmod(n_val), name='x')
x_var = PR_Mod.gen()
f_cop = (2**777 * x_var + p_known).monic()
res = f_cop.small_roots(X=2**250, beta=0.45)
if not res:
print("[-] Coppersmith failed")
return
p = int(res[0]) * 2**777 + p_known
if n_val % p != 0:
print("[-] Factorization failed")
return
q = n_val // p
print(f" p = {p}")
# --- Step 2: Decrypt m ---
print("[*] 2. Decrypting m...")
phi = (p-1)*(q-1)
d = inverse_mod(e, phi)
m = int(power_mod(c_val, d, n_val))
# print(f" m = {m}")
# --- Step 3: Solve x, y ---
print("[*] 3. Solving Pell for x, y...")
g_val = gcd(A, B)
A1 = A // g_val
B1 = B // g_val
t = squarefree_part(A1)
D = B1 * t
print(f" Pell D = {D}")
K = QuadraticField(D, 'w')
unit = K.units()[0]
x_sol, y_sol = 0, 0
cur = unit
for k in range(1, 2000):
# X = trace/2.
tr = cur.trace()
nm = cur.norm()
if tr % 2 == 0:
X_curr = tr // 2
rem = (X_curr**2 - nm)
if rem % D == 0:
Y2 = rem // D
if is_square(Y2):
Y_curr = isqrt(Y2)
if nm == 1:
x_sol = abs(Integer(X_curr))
y_temp = abs(Integer(Y_curr))
y_sol = y_temp * isqrt(A1 * t)
print(f" Found solution at k={k}")
break
cur *= unit
if x_sol == 0:
print("[-] Failed to find Pell solution within limit.")
return
# Verify
if A * x_sol**2 - B * y_sol**2 == A:
print(" Equation verified.")
else:
print(f"[-] Verification failed: LHS != RHS")
return
print("[*] 4. Lattice Attack...")
M = Matrix(ZZ, [
[1, 0, x_sol],
[0, 1, y_sol],
[0, 0, m]
])
# LLL reduction
L = M.LLL()
found = False
for row in L:
# Check third component (diff) is small
if abs(row[2]) < 2**100:
r0 = abs(row[0])
r1 = abs(row[1])
try:
# Convert back to bytes
p0 = long_to_bytes(int(r0))
p1 = long_to_bytes(int(r1))
# Check for flag markers
cand1 = p0 + p1
if b'flag{' in cand1:
print(f"\n[+] FLAG: {cand1.decode(errors='ignore')}")
found = True
break
cand2 = p1 + p0
if b'flag{' in cand2:
print(f"\n[+] FLAG: {cand2.decode(errors='ignore')}")
found = True
break
except:
pass
if not found:
print("[-] Flag not found in lattice basis.")
if __name__ == "__main__":
solve()
# flag{p4ssword_b1g_snake!!}pyBudo的Pem#
public.enc
-----BEGIN PUBLIC KEY-----
MIIECQIBA???????????????????????????????????????????????????????
????????????????????????????????????????????????????????????????
????????????????????????????????????????????????????????????????
????????????????????????????????????????????????????????????????
Gwp5tfe98QiKT3ehCxOtijZx2/+SABMTf8Xil1pYHNh7zIUV2HO/rsQzTLHDGxYg
Yxb5LL5En66BFXQRPl6y7QFT0fzLgKAhDoB24cX79q+rvqc60YBcChsgxwzywTv+
AwKCAQBwVQnsKD3JTju+hcoeNoWFhnIfPRqNZ4E0UeFgcozIfnOl8Wo8TtPvtVPt
4V1UAd4ywmXMSQKmZmo7fY6YRLnLKh/8RHnfDQ8e6ZJxhJOtR8sonfIGM3oGOg+w
119moJmBIKAie0rAEADu4OjmABKqarYT+5Kzc5Iu4yhbbL1Cmjv4pjFyCQ4UaxnP
GQrxWr5lc+mau/0BaV+ADCz0cGPvf+yeaWD5GWgkO2cyrkMaPvQI6gJ+PnzURuM9
Qw8Y7brYxl91SwZKg8wP+B15RybFwffTn/kaec06fhj+JvPPoj3jNJ/LRNcISgXn
mu2YkK3q6TLT53idAggyQyNiseEl
-----END PUBLIC KEY-----pyprivate.enc
-----BEGIN RSA PRIVATE KEY-----
MIIEbQIBAAKCAQEA2B2xlQJMm9A92FZlatEZ+hmgp3EgvxDZxAL06z3HmLYR/dnC
CcF9h5OfqS+w6MEUcyMifdLt2BtA9oKlTeEDXwrwOmYOwbNNtoiE0Dji4a+qt5VG
1HJQ/TDSpwp//YPxE481PslPWL8rNEj4xNMyQZ4+GjLPcx3O22bsH8QK/f46Ba0t
HPT3ZqZCy2RpC0XitkZWOvtt/fWZ34SDi3EfGwp5tfe98QiKT3ehCxOtijZx2/+S
????????????????????????????????????????????????????????????????
????????????????????????????????????????????????????????????????
hnIfPRqNZ4E0UeFgc???????????????????????????????????????????????
????????????????????????????????????????????????????????????????
????????????????????????????????????????????????????????????????
????????????????????????????????????????????????????????????????
???????????????????????????????wffTn/kaec06fhj+JvPPoj3jN????????
??????????AekKyk0x2ElckRZItD36A1lteOEova6bUGC???????????????????
????????????????????????????????????????????????????????????????
????????????????????????????????????????????????????????????????
????????????????????????????????????????????????????????????????
???8bztiKLsHDI0Cg???????????????????????????????????????????????
????????????????????????????????????????????????????????????????
????????????????????????????????????????
-----END RSA PRIVATE KEY-----pyenc为密文也给出了 这里手撕pem应该是可以得到d的部分有效位的 可以走不同位数泄露的线性问题来打 交由LLM审计已有公私钥文件可以得到
arguments.json
{
"n_decimal": "27282116371983762041912669226171934834213267111869663414643046433946308990099670619902779534028098258662491783696548704814110569031913533951522948899897952050753641129753560031453499125038644663810374945611628785371618348480443687057347952494765550645132106424275386344675708722795562530893084586263715603616605019932670052317546124250273398628671233505778162949122797472901844999122236752056581074033257933303752665270582493695571812447018768673719950432810902857287900402430788862530621394430578906291537999146794893832511289918245846963416265417641139236739819022441678736012415250371091507897123724260254368398851",
"n_hex": "0xd81db195024c9bd03dd856656ad119fa19a0a77120bf10d9c402f4eb3dc798b611fdd9c209c17d87939fa92fb0e8c1147323227dd2edd81b40f682a54de1035f0af03a660ec1b34db68884d038e2e1afaab79546d47250fd30d2a70a7ffd83f1138f353ec94f58bf2b3448f8c4d332419e3e1a32cf731dcedb66ec1fc40afdfe3a05ad2d1cf4f766a642cb64690b45e2b646563afb6dfdf599df84838b711f1b0a79b5f7bdf1088a4f77a10b13ad8a3671dbff920013137fc5e2975a581cd87bcc8515d873bfaec4334cb1c31b16206316f92cbe449fae811574113e5eb2ed0153d1fccb80a0210e8076e1c5fbf6afabbea73ad1805c0a1b20c70cf2c13bfe03",
"n_bitlen": 2048,
"e_hex": "0x705509ec283dc94e3bbe85ca1e36858586721f3d1a8d67813451e160728cc87e73a5f16a3c4ed3efb553ede15d5401de32c265cc4902a6666a3b7d8e9844b9cb2a1ffc4479df0d0f1ee992718493ad47cb289df206337a063a0fb0d75f66a0998120a0227b4ac01000eee0e8e60012aa6ab613fb92b373922ee3285b6cbd429a3bf8a63172090e146b19cf190af15abe6573e99abbfd01695f800c2cf47063ef7fec9e6960f91968243b6732ae431a3ef408ea027e3e7cd446e33d430f18edbad8c65f754b064a83cc0ff81d794726c5c1f7d39ff91a79cd3a7e18fe26f3cfa23de3349fcb44d7084a05e79aed9890adeae932d3e7789d020832432362b1e125",
"e_bitlen": 2047,
"ciphertext_hex": "0x73f2749dcd758a2b35923e69f4033862760b6f302851a2937b2a34107500f9783f1507acf91c97477c1534426bb52bad03235d82b92c0eebc924ff50ca5cf5e959e48979fb27510f0e12082430b19ab44b9a79d396d3d4bec760a31b25b69ac177342de166539cb2e619227ff3df81ebfee4f3373a6508c6fe9b2c1de0de3a25b85fabcd3c8b1b41a25fa8e8c65774cc269ee12a8b6bbdd298e8a13705181abd3fb4bcd218543a90cada26f52f74d92f0dfdd73808aba299ae0b1a7b3cb22f043ebb618d7a2aa8b5657566bbf67fd923c7b566b205f8744fcc9b8bc300ccc4b851dd3740ba1d4c486a2b70d99a798f229169f044dea5ff6dc12f7a409f827060",
"ciphertext_length_bytes": 256,
"ciphertext_file": "budo的pem/附件/enc",
"private_key_notes": {
"d_known_bytes": 0,
"d_known_fragments": [],
"d_expected_offset_in_der": "private exponent starts at byte 527 of the DER (after version, 257-byte n, and 256-byte e)",
"observations": [
"The visible base64 fragments in private.pem (e.g. hnIfPRqNZ4E0UeFg, wffTn/kaec06fhj+JvPPoj3j, AekKyk0x2ElckRZItD36A1lteOEova6bUGC=, 8bztiKLsHDI0Cg==) were checked.",
"The first two fragments map inside the public exponent e region (offsets ~288 and ~480), so they do NOT belong to d.",
"No confirmed bytes of d are currently known; the entire d field remains to be recovered."
]
}
}
json注意到我们的e是2047bits 说明d不会很大 然后 n e c都给出了 自然想到Wiener-Attack 但是没成功 稍微估算Bounds后 尝试用Boneh-durfee来打,成功恢复私钥 exp
N = 27282116371983762041912669226171934834213267111869663414643046433946308990099670619902779534028098258662491783696548704814110569031913533951522948899897952050753641129753560031453499125038644663810374945611628785371618348480443687057347952494765550645132106424275386344675708722795562530893084586263715603616605019932670052317546124250273398628671233505778162949122797472901844999122236752056581074033257933303752665270582493695571812447018768673719950432810902857287900402430788862530621394430578906291537999146794893832511289918245846963416265417641139236739819022441678736012415250371091507897123724260254368398851
e = 14180624331525991413806961940205749159059672195526010812302727853797689314317592739705685551298732523630959991899621053483135891357764554237827830483836439016974587279272031674430927658131895070464916701588233589386349049620755733138785652009983671996549157331713576664774799256183477206330524171028474266411634935294668960034971282728570749735946140241122767282799298230610201636191394577538375846828013584508852185295950919801912280565423819423712437302673912737370268540642137115217185193479116171916803844236143555349715053108779636638218404547424967160100212508958775263973108754654389190280868923617834114867493
c = 14636964350393359609162480071693999278424170850521418014592634475368954740257722156318146008124421034391718151019460812215072523840099747455968976360400269581104958562930327324869542471914127830001738413650837056131113436668118358931480677504661152014613609817383701345299194960493886698742336070131174755606047330529525675289484270164725610612640457245718226388033419976771021747030807638114791143554563992877218086656655150210121236387483020886409296935418792488701360340836701327850914585894390509753507489933191701772030511488180126795446738798925310535928731053916469437151653598380019231518939442304479597326432
"""
Setting debug to true will display more informations
about the lattice, the bounds, the vectors...
"""
debug = False
"""
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 = 0
for ii in range(BB.dimensions()[0]):
if BB[ii, ii] >= modulus:
nothelpful += 1
print(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 function
if current == -1 or BB.dimensions()[0] <= dimension_min:
return BB
# we start by checking from the end
for ii in range(current, -1, -1):
# if it is unhelpful:
if BB[ii, ii] >= bound:
affected_vectors = 0
affected_vector_index = 0
# let's check if it affects other vectors
for jj in range(ii + 1, BB.dimensions()[0]):
# if another vector is affected:
# we increase the count
if BB[jj, ii] != 0:
affected_vectors += 1
affected_vector_index = jj
# level:0
# if no other vectors end up affected
# we remove it
if 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 else
elif affected_vectors == 1:
affected_deeper = True
for kk in range(affected_vector_index + 1, BB.dimensions()[0]):
# if it is affecting even one vector
# we give up on this one
if 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 one
if 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 happened
return 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 May
finds a solution if:
* d < N^delta
* |x| < e^delta
* |y| < e^0.5
whenever 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 + 1
polZ = Q(pol).lift()
UU = XX * YY + 1
# x-shifts
gg = []
for kk in range(mm + 1):
for ii in range(mm - kk + 1):
xshift = x ^ ii * modulus ^ (mm - kk) * polZ(u, x, y) ^ kk
gg.append(xshift)
gg.sort()
# x-shifts list of monomials
monomials = []
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 monomials
for jj in range(1, tt + 1):
for kk in range(floor(mm / tt) * jj, mm + 1):
monomials.append(u ^ kk * y ^ jj)
# construct lattice B
nn = 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 lattice
if helpful_only:
# automatically remove
BB = remove_unhelpful(BB, monomials, modulus ^ mm, nn - 1)
# reset dimension
nn = BB.dimensions()[0]
if nn == 0:
print("failure")
return 0, 0
# check if vectors are helpful
if debug:
helpful_vectors(BB, modulus ^ mm)
# check if determinant is correctly bounded
det = 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, -1
else:
print("det(L) < e^(m*n) (good! If a solution exists < N^delta, it will be found)")
# display the lattice basis
if debug:
matrix_overview(BB, modulus ^ mm)
# LLL
if 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 & 2
if debug:
print("looking for independent vectors in the lattice")
found_polynomials = False
for pol1_idx in range(nn - 1):
for pol2_idx in range(pol1_idx + 1, nn):
# for i and j, create the two polynomials
PR.<w,z> = PolynomialRing(ZZ)
pol1 = pol2 = 0
for 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)
# resultant
PR.<q> = PolynomialRing(ZZ)
rr = pol1.resultant(pol2)
# are these good polynomials?
if rr.is_zero() or rr.monomials() == [1]:
continue
else:
# print("found them, using vectors", pol1_idx, "and", pol2_idx)
found_polynomials = True
break
if found_polynomials:
break
if not found_polynomials:
# print("no independant vectors could be found. This should very rarely happen...")
return 0, 0
rr = rr(q, q)
# solutions
soly = rr.roots()
if len(soly) == 0:
# print("Your prediction (delta) is too small")
return 0, 0
soly = soly[0][0]
ss = pol1(q, soly)
solx = ss.roots()[0][0]
#
return solx, soly
delta = .271 # this means that d < N^delta
m = 8 # size of the lattice (bigger the better/slower)
t = int((1 - 2 * delta) * m) # optimization from Herrmann and May
X = 2 * floor(N ^ delta) # this _might_ be too much
Y = floor(N ^ (1 / 2)) # correct if p, q are ~ same size
P.<x,y> = PolynomialRing(ZZ)
A = int((N + 1) / 2)
pol = 1 + x * (A + y)
solx, soly = boneh_durfee(pol, e, m, t, X, Y)
d = int(pol(solx, soly) / e)
print(d)
m = power_mod(c, d, N)
print(bytes.fromhex(hex(m)[2:]))
'''
det(L) < e^(m*n) (good! If a solution exists < N^delta, it will be found)
2818724236741671359128769187179810547909044666675265260768069231661668260817660718866645913151917874535631116172290215144275909425092025869246764203201125241729277109
b'flag{RSA_key_rebuild_success}'
'''py本题一血。