鹏城杯2025

周末花了一个上午打了打今年的鹏城杯 题目比较基础 Solve 5 of 6 来此写WP,剩下的RandomAudit实在猜不出出题人的脑洞了,等有师傅公布解法再复现吧

true_or_false

神人题没把flag删干净 血手慢无

tf.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
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)

题目基于RSA-CRT故障 对于因子的签名正常 但是被替换为一个随机数 我们看接下来的两个mixed后的数据

我们对2式转移到模下 因为我们的没有任何的代数关系 可以得到 而我们再对1式转化到模下,由于 可以得到 所以说 我们如果恢复了 就可以直接得到 第二步 我们发现的范围是 而且给出了的结果 完全可以暴力穷举所有的seed 逆向LCG-MIX 还原我们的两个签名 然后求出flag ,验证是否符合flag-enc exp如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
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}

babyRSA

task.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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

给出了的小数近似形式 我们发现误差项几乎可以忽略 那么leak也就是约等于 打连分数展开即可

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
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}

weak-leak

也是flag没删干净

task.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
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
'''

爆破求解pwd 得到seq后计算diff 然后解方程就行

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
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}

解p q用如下sage代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
diff = 20224040742840345250326339919317453831304805563222287954131954259215526515254
n1 = 5584300989285538211153365890789627571870624311506728764237201442331520767215704903718501881100700113185783404202199758018541582967691088869854375384182438

x = var("x")

eq = (x+diff)*(x+1) - n1 == 0
solution = solve(eq,x)
print(solution)
'''
[
x == -85521263473824766227818846323904319418946431813031743497481757287536903337968,
x == 65297222730984420977492506404586865587641626249809455543349803028321376822713
]
'''

PECO

Task.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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)

'''


告诉了你的低777位 而明显,那么有 走Lifting的方式可以确定的低777位 打Copper分解

再解Pell方程 得到后打格子求出

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
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!!}

Budo的Pem

public.enc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
-----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-----

private.enc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
-----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-----

enc为密文也给出了 这里手撕pem应该是可以得到d的部分有效位的 可以走不同位数泄露的线性问题来打 交由LLM审计已有公私钥文件可以得到

arguments.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
"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."
]
}
}

注意到我们的e是2047bits 说明d不会很大 然后 n e c都给出了 自然想到Wiener-Attack 但是没成功 稍微估算Bounds后 尝试用Boneh-durfee来打,成功恢复私钥 exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
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}'

'''

本题一血。