RSA_Quartic_Quandary

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
#python
from Crypto.Util.number import *
from gmpy2 import *

n = 125997816345753096048865891139073286898143461169514858050232837657906289840897974068391106608902082960171083817785532702158298589600947834699494234633846206712414663927142998976208173208829799860130354978308649020815886262453865196867390105038666506017720712272359417586671917060323891124382072599746305448903
e = 65537
c = 16076213508704830809521504161524867240789661063230251272973700316524961511842110066547743812160813341691286895800830395413052502516451815705610447484880112548934311914559776633140762863945819054432492392315491109745915225117227073045171062365772401296382778452901831550773993089344837645958797206220200272941
s = 35935569267272146368441512592153486419244649035623643902985220815940198358146024590300394059909370115858091217597774010493938674472746828352595432824315405933241792789402041405932624651226442192749572918686958461029988244396875361295785103356745756304497466567342796329331150560777052588294638069488836419744297241409127729615544668547101580333420563318486256358906310909703237944327684178950282413703357020770127158209107658407007489563388980582632159120621869165333921661377997970334407786581024278698231418756106787058054355713472306409772260619117725561889350862414726861327985706773512963177174611689685575805282

p_q = iroot(iroot((s + 2 * n ** 2), 2)[0] + 2 * n, 2)[0]
phi = n - p_q + 1
d = inverse(e, phi)
print(long_to_bytes(powmod(c, d, n)))

欧几里得

Paillier加密的加法同态性:

攻击条件:

  • $m_2 = \text{bytes_to_long}(\text{os.urandom}(2) \times 35)$
  • $\text{os.urandom}(2) \in [0, 65535]$,可爆破获取$m_2$

模数处理:

  • 因$m_1$和$m_2$较小不溢出,所以有没有$n$的差别只在负数上,处理一下负数情况不报错就行

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#python
from Crypto.Util.number import *
from tqdm import *

c = 1426774899479339414711783875769670405758108494041927642533743607154735397076811133205075799614352194241060726689487117802867974494099614371033282640015883625484033889861

for i in trange(65536):
byte_pair = i.to_bytes(2, 'big')
repeated_bytes = byte_pair * 35
m2 = bytes_to_long(repeated_bytes)
m = abs(c - m2)
if b'palu' in long_to_bytes(m):
print(long_to_bytes(m))

易如反掌

给定4组RSA参数 $(N_i, E_i)$,其中:

  • $N_i = p_i \times q_i$($p_i, q_i$ 为1024位素数)
  • $\Phi_i = (p_i^2 - 1)(q_i^2 - 1)$
  • $E_i \equiv d^{-1} \mod \Phi_i$($d$ 为固定的800位素数)

由于$\Phi_i \approx N_i^2$,可构建方程:

选择放大系数 $M = 2^{1000}$,构造:

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
# sage

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

N = [23796646026878116589547283793150995927866567938335548416869023482791889761195291718895745055959853934513618760888513821480917766191633897946306199721200583177442944168533218236080466338723721813833112934172813408785753690869328477108925253250272864647989241887047368829689684698870160049332949549671046125158024445929082758264311584669347802324514633164611600348485747482925940752960745308927584754759033237553398957651216385369140164712159020014009858771182426893515016507774993840721603911101735647966838456333878426803669855790758035721418868768618171692143354466457771363078719423863861881209003100274869680348729, 19552522218179875003847447592795537408210008360038264050591506858077823059915495579150792312404199675077331435544143983146080988327453540449160493126531689234464110427289951139790715136775261122038034076109559997394039408007831367922647325571759843192843854522333120187643778356206039403073606561618190519937691323868253954852564110558105862497499849080112804340364976236598384571278659796189204447521325485338769935361453819608921520780103184296098278610439625935404967972315908808657494638735904210709873823527111315139018387713381604550946445856087746716671838144925662314348628830687634437271225081272705532826343, 20588310030910623387356293638800302031856407530120841616298227518984893505166480372963166394317326422544430837759332223527939420321960057410073228508230111170414845403161052128790464277007579491219950440477721075788978767309211469555824310913593208232853272958011299985202799390532181335087622499894389777412111445377637396650710486263652440053717323053536700098339137819966260269752816515681602936416736576044630343136577023173210517247609888936337876211461528203642347119434700140264859102502126842250671976238033270367185358966766106988830596616311824691409766437473419074865115209866730272194297815209976737570183, 18468380817178794606027384089796802449939260582378979728469492439450780893746976934315768186829245395964644992296264093276556001477514083927556578752836255491334765496791841945178275793885002188397918857222419803612711637177559554489679414049308077300718317502586411333302434329130562745942681716547306138457088216901181646333860559988117376012816579422902808478175975263110581667936249474308868051767856694498210084853797453949193117835061402537058150493808371384063278793041752943930928932275052745657700368980150842377283198946138726219378646040515809994704174471793592322237777371900834531014326150160506449286179]
E = [229904181453273080302209653709086531153804577507365859149808244958841045687064628362978517491609413507875726243121473678430010600891588643092042173698830147997497783886459583186019270582236955524620567373560535686287255124958954671737097645556109314142383275516997850786599322033792080045303427363366927030304214333894247469120513426641296678531965795930756543043851154646310114366477311633838078242963665452936523438928643273392454483600446242320078010627755587492056369779661382734170244060951095344418599686788550312205964136120979823565225768814898285224838691541122088693411388097496320157113230752327025862802020421665288007529320920942060329299409362236414929126050037144149017275031336018100081931062647888329912802477032857776085190828105602067426203163344931483638271679183910241511044338001446584634203146294743522375846913845041274967653508735863706778364499099286484552570083394223973734909997825522191349543295855925973354640349809770822075226834555111927586299176453943116511915434890643239957459427390624136283086434711471863737451011157026905191204496081860277138227247744470804087252965368757930797560277881668806206419629425126031049566579233056222579590529869798537893505779097868221221068867624660759084762471141, 374749619911728044650812367560174497001343067563440477135516664935394734686391543012901514676044211541958613458868769659861216149364768233000844624035620893309356372294598009760824255187442531508754966566917198975934706398309982525100772311586501118200858124845012643495006029930202324305874402291277845166060497038915773767003006049720519011634861166208163030159519901867416488082395270295488885724507937683469910251316231210838654273986152493722244271430422693265608430755620420680629979226285393465423870727975987787149515374769359243334743541460110042872587610309611770320600248289328406805995688596910226273861759369388105641549933915686192055533242723330981192183310876306968103333706140401422550917946410378174896274789619184565321544130428008804628699594759946577979319393247067750024729672029363433673084437510430506410293512293930056667971242862448029841846596288648691077795207341975907335202945548990662460491169957175452745622341245617265849042542964819126377775749222973138584978725470886059043251544634105653274564085280013340679259157119014619894553239015777411757887293044706448625760604242512494466386343040583010961386979963779928616733980046763291988848903515836247301007113187121999960487508948748354549628160741, 111738429639840672983162926852338651562094139707285850255632987705635459657893186493838711733560515475806567653354737245246745810892238414756414117557971683747269900627524702653772058841085258035513296218047505149691384287812041721130367506731427022265277885965948486359682023555050085264531256406043361391744086539522028829421284667293339869140564699750714145488199268791908205712660933607330454849730499840287271163350865799682565216636393526339218836244889719975150503253630419647851422620890082315396457329065508602521784001607236788620811397449483104884860551374031790663030220424841642241965983726516537123807061999084476076850833658360594525986997125319941689903869138176347916707622148840226672408554102717625456819726220575710494929111642866840516339713870850732638906870325693572445316904688582043485093120585767903009745325497085286577015692005747499504730575062998090846463157669448943725039951120963375521054164657547731579771203443617489609201617736584055562887243883898406182052632245189418568410854530995044542628531851356363297989653392057214167031332353949367816700838296651167799441279086074308299608106786918676697564002641234952760724731325383088682051108589283162705846714876543662335188222683115878319143239781, 185935167438248768027713217055147583431480103445262049361952417166499278728434926508937684304985810617277398880507451351333771783039360671467147075085417403764439214700549777320094501151755362122677245586884124615115132430034242191429064710012407308619977881929109092467325180864745257810774684549914888829203014922855369708286801194645263982661023515570231007900615244109762444081806466412714045462184361892356485713147687194230341085490571821445962465385514845915484336766973332384198790601633964078447446832581798146300515184339036127604597014458389481920870330726947546808739829589808006774479656385317205167932706748974482578749055876192429032258189528408353619365693624106394913101463023497175917598944803733849984703912670992613579847331081015979121834040110652608301633876167262248103403520536210279949844194696898862249482809107840303473964914083996538912970715834110371196970613332286296427286356036576876121010776933023901744994067564045429384172315640135483480089769992730928266885675143187679290648773060781987273082229827156531141515679114580622348238382074084270808291251400949744720804368426414308355267344210055608246286737478682527960260877955900464059404976906697164610891962198768354924180929300959036213841843941]

M = 2^1000
L = Matrix(ZZ, [
[M, E[0], E[1], E[2], E[3]],
[0, -N[0]^2, 0, 0, 0],
[0, 0, -N[1]^2, 0, 0],
[0, 0, 0, -N[2]^2, 0],
[0, 0, 0, 0, -N[3]^2],
])

L = L.LLL()
for row in L:
if row[0] % M == 0:
d = abs(row[0] // M)
if 1 < d < 2^800:
print("Found d:", d)
flag = "palu{" + hashlib.md5(str(d).encode()).hexdigest() + "}"
print(flag)
break

循环锁链

这纯脑洞题,一开始傻了忘了flag头是palu,想到是循环异或了用flag试了一辈子也没试出来,加密逻辑很简单,就是第一位明文和第二位明文异或当作第一位密文,然后依次重复这个过程,最后一位明文和第一位明文异或是最后一位密文,根据已知的明文palu推回去即可:

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#python

import sys

def decrypt(c: bytes, prefix: bytes) -> bytes:
N = len(c)
p = bytearray(N)
L = len(prefix)
p[:L] = prefix
for i in range(N - 1):
if i + 1 >= L:
p[i + 1] = c[i] ^ p[i]
return bytes(p)


with open('flag.enc', 'rb') as f:
ciphertext = f.read()
known_prefix = b"palu{"
plaintext = decrypt(ciphertext, known_prefix)
flag = plaintext.decode('utf-8')

print(flag)

星际广播站

题目描述:

1
这是使用flask框架编写的RSA广播的空间站系统,你有办法登录并解密密文吗

说实话一打开是一个web界面看傻了,查看源码发现:

1
2
3
4
5
6
7
8
9
10
11
function downloadAppFile(filename) {
const iframe = document.createElement('iframe');
iframe.style.display = 'none';
document.body.appendChild(iframe);
const downloadUrl = `/file/download?path=${encodeURIComponent(filename)}`;
iframe.src = downloadUrl;

setTimeout(() => {
document.body.removeChild(iframe);
}, 2000);
}

很明显的任意文件下载漏洞,由于这是使用flask框架编写的,所以下载一下app.py文件看看:

1
downloadAppFile("app.py");

app.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
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
import sqlite3
import os
import hashlib
from flask import Flask, render_template, request, redirect, url_for, session, flash, g,send_file, abort
from Crypto.PublicKey import RSA
from Crypto.Util.number import bytes_to_long, getPrime
import string
from secret import flag

app = Flask(__name__)
app.secret_key = os.urandom(24) # 用于 session 管理

# 修改数据库路径
# Ensure this line points to the data directory
DATABASE = 'data/users.db'
E = getPrime(7)
NUM_USERS = 128


# assert NUM_USERS >= E
print(f"E: {E}")
FLAG = flag.encode('utf-8')

def get_db():
db = getattr(g, '_database', None)
if db is None:
# Ensure the directory exists when connecting
db_dir = os.path.dirname(DATABASE)
if not os.path.exists(db_dir):
os.makedirs(db_dir, exist_ok=True)
db = g._database = sqlite3.connect(DATABASE)
db.row_factory = sqlite3.Row # 让查询结果可以通过列名访问
return db

@app.teardown_appcontext
def close_connection(exception):
db = getattr(g, '_database', None)
if db is not None:
db.close()

import random
from gmssl import sm3, func

def sm3_hash(data):
"""计算数据的 SM3 哈希值"""
if isinstance(data, str):
data = data.encode('utf-8')
hash_bytes = sm3.sm3_hash(func.bytes_to_list(data))
print(f"计算 {data} 的 SM3 哈希值为: {hash_bytes}")
return hash_bytes

def generate_rsa_pair(message, e):
"""生成 RSA 公钥 N 和对应的密文 C"""
key = RSA.generate(1024)
n = key.n
m_long = bytes_to_long(message)
c = pow(m_long, e, n)
return n, c

def init_db():
"""初始化数据库,创建表并填充用户数据"""
db_path = os.path.join('/app', DATABASE) # 在容器内的绝对路径
db_dir = os.path.dirname(db_path)
os.makedirs(db_dir, exist_ok=True) # 确保容器内的目录存在

# 检查数据库文件是否存在于容器内的预期路径
if os.path.exists(db_path):
print(f"数据库文件 {db_path} 已存在,跳过初始化。")
# 即使文件存在,也要确保表结构是最新的
# 可以考虑在这里添加检查表是否存在的逻辑,如果不存在则创建
# return # 如果确定存在就跳过,否则继续执行建表逻辑

print(f"初始化数据库 {db_path}...")
with app.app_context():
db = get_db()
cursor = db.cursor()
# 检查 users 表是否存在
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='users';")
table_exists = cursor.fetchone()

if not table_exists:
print("创建 users 表...")
cursor.execute('''
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
password_hash TEXT NOT NULL,
n TEXT NOT NULL -- 存储大整数 N
)
''')
db.commit() # 提交建表操作
else:
print("users 表已存在。")
# 检查是否需要填充数据
cursor.execute("SELECT COUNT(*) FROM users")
user_count = cursor.fetchone()[0]
if user_count >= NUM_USERS:
print(f"数据库中已有 {user_count} 个用户,跳过填充。")
return # 如果用户数量足够,则跳过填充

# --- 填充用户数据的逻辑 ---
print("开始填充用户数据...")
generated_n_set = set()
# 获取当前数据库中的用户数量
cursor.execute("SELECT COUNT(*) FROM users")
current_user_count = cursor.fetchone()[0]
users_added_this_run = 0

# 从 current_user_count + 1 开始生成用户,直到达到 NUM_USERS
for i in range(current_user_count, NUM_USERS):
username = str(i + 1)
random.seed(username)

characters = string.ascii_letters + string.digits
password = "".join(random.choices(characters, k=6))
password_hash = sm3_hash(password)

n, c = None, None
attempts = 0
max_attempts = NUM_USERS * 5
while attempts < max_attempts:
n_candidate, c_candidate = generate_rsa_pair(FLAG, E)
if n_candidate not in generated_n_set:
# 还需要检查数据库中是否已存在此 N
cursor.execute("SELECT 1 FROM users WHERE n = ?", (str(n_candidate),))
n_exists_in_db = cursor.fetchone()
if not n_exists_in_db:
n = n_candidate
c = c_candidate
generated_n_set.add(n)
break
attempts += 1

if n is None:
print(f"警告:无法为用户 {username} 生成唯一的 N,已尝试 {max_attempts} 次。")
continue

try:
cursor.execute("INSERT INTO users (username, password_hash, n) VALUES (?, ?, ?)",
(username, password_hash, str(n)))
users_added_this_run += 1
print(f"添加用户 {username} 到数据库。")
except sqlite3.IntegrityError:
print(f"用户名 {username} 已存在或 N 值冲突,跳过。")
except Exception as e:
print(f"添加用户 {username} 时出错: {e}")

db.commit()
print(f"数据库初始化/填充完成,本次运行添加了 {users_added_this_run} 个用户。")

@app.route('/')
def index():
if 'username' in session:
return redirect(url_for('dashboard'))
return render_template('index.html')

@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
username = request.form['username']
password_hash_attempt = sm3_hash(request.form['password_hash'])
db = get_db()
cursor = db.cursor()
cursor.execute("SELECT password_hash FROM users WHERE username = ?", (username,))
user = cursor.fetchone()

if user and user['password_hash'] == password_hash_attempt:
session['username'] = username
flash('登录成功!', 'success')
return redirect(url_for('dashboard'))
else:
flash('无效的用户名或密码。', 'error')
return redirect(url_for('login'))


if 'username' in session:
return redirect(url_for('dashboard'))
return render_template('index.html')

@app.route('/dashboard')
def dashboard():
if 'username' not in session:
flash('请先登录。', 'error')
return redirect(url_for('login'))

username = session['username']
db = get_db()
cursor = db.cursor()
cursor.execute("SELECT n FROM users WHERE username = ?", (username,))
user_data = cursor.fetchone()

if not user_data:
# 用户在 session 中但数据库中找不到?异常情况
session.pop('username', None)
flash('发生错误,请重新登录。', 'error')
return redirect(url_for('login'))

n = user_data['n']
# c = user_data['c']
m = bytes_to_long(FLAG)
c = str(pow(m, E, int(n)))
return render_template('dashboard.html', username=username, n=n, c=c, e=E)

@app.route('/logout')
def logout():
session.pop('username', None)
flash('您已成功登出。', 'success')
return redirect(url_for('login'))



@app.route('/file/download', methods=['GET'])
def download_file():
path = request.args.get('path', '')

if not path:
return "Error: No path parameter provided", 400

try:

if not os.path.isabs(path):
path = os.path.join(os.path.dirname(__file__), path)

if not os.path.exists(path) or not os.path.isfile(path):
return f"Error: File not found: {path}", 404

return send_file(
path,
as_attachment=True,
download_name=os.path.basename(path),
mimetype='application/octet-stream'
)

except Exception as e:
return f"Error: {str(e)}", 500


if __name__ == '__main__':
init_db() # 启动时检查并初始化数据库
app.run(host='0.0.0.0', port=8000, debug=True)

我们观察到有:

1
DATABASE = 'data/users.db'


1
2
3
4
5
username = str(i + 1)
random.seed(username)

characters = string.ascii_letters + string.digits
password = "".join(random.choices(characters, k=6))

这里我们知道了数据库文件的位置可以获取到数据库文件,并且发现密码是由用户名作为种子使用random函数得来的,那么用户名固定密码就固定,这里我们就已知了密码。
下一下数据库文件:

1
downloadAppFile("data/users.db"); 

登录一下获取到了e为71,现在要做的就是读取数据库获取所有的n,登录e个用户获取到e个c进行低加密指数广播攻击即可

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
import random
import string
import requests
import re
import sqlite3
from typing import Dict, List, Tuple
from gmpy2 import iroot, invert
from Crypto.Util.number import long_to_bytes


def generate_password(username: str) -> str:
"""根据用户名生成密码"""
random.seed(username)
characters = string.ascii_letters + string.digits
return ''.join(random.choices(characters, k=6))


def get_all_n_from_db(db_path: str) -> Dict[str, int]:
"""从本地数据库获取所有用户的n值"""
try:
with sqlite3.connect(db_path) as conn:
cursor = conn.cursor()
cursor.execute("SELECT username, n FROM users")
return {row[0]: int(row[1]) for row in cursor.fetchall()}
except sqlite3.Error as e:
print(f"数据库错误: {e}")
return {}


def collect_c_values(users: Dict[str, int], base_url: str) -> Dict[int, int]:
"""通过HTTP请求收集c值"""
c_data = {}

for username, n in users.items():
password = generate_password(username)
session = requests.Session()

# 登录获取session
try:
login_resp = session.post(
f"{base_url}/login",
data={"username": username, "password_hash": password},
allow_redirects=False,
timeout=5
)

if login_resp.status_code != 302 or 'dashboard' not in login_resp.headers.get('Location', ''):
print(f"[!] 用户 {username} 登录失败")
continue
except Exception as e:
print(f"[!] 用户 {username} 登录请求异常: {str(e)}")
continue

# 获取dashboard页面
try:
dashboard_resp = session.get(f"{base_url}/dashboard", timeout=5)
if dashboard_resp.status_code != 200:
print(f"[!] 用户 {username} 访问dashboard失败")
continue

# 精确匹配c值
c_match = re.search(r'<span class="data-value">(\d+)</span>', dashboard_resp.text)
if c_match:
c_data[n] = int(c_match.group(1))
print(f"[+] 成功获取用户 {username} 的 c={c_data[n]}")
else:
print(f"[!] 用户 {username} 的c值解析失败")
except Exception as e:
print(f"[!] 用户 {username} 请求异常: {str(e)}")

return c_data


def chinese_remainder_theorem(moduli: List[int], residues: List[int]) -> int:
"""实现中国剩余定理"""
total = 0
product = 1

for m in moduli:
product *= m

for m, r in zip(moduli, residues):
p = product // m
total += r * invert(p, m) * p

return total % product


if __name__ == '__main__':
# 从本地数据库获取所有n值
print("[+] 正在从本地数据库读取n值...")
all_users = get_all_n_from_db('users.db')
print(f"[+] 成功读取 {len(all_users)} 个用户的n值")

# 收集c值
e = 71
URL = 'http://challenge.qsnctf.com:32237' # 修复了URL中的空格
print(f"[+] 开始收集 {e} 个用户的c值...")

c_data = collect_c_values(all_users, URL)

if len(c_data) < e:
print(f"[!] 警告:只收集到 {len(c_data)} 个c值,需要至少 {e} 个")

n_list = list(c_data.keys())[:e]
c_list = [c_data[n] for n in n_list]

print("[+] 开始解密流程...")
M = chinese_remainder_theorem(n_list, c_list)
m = iroot(M, e)[0]
flag = long_to_bytes(m)
print(flag)

轮回密码

题目描述:

1
在佛经残卷中发现神秘密文,据传加密者用"六道轮回"之法将真言藏于时空循环中。

encode.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import base64


def samsara_encrypt(text, key_word):
cycle_step = len(key_word) % 6 + 1

phase1 = bytes([(c >> cycle_step) | ((c << (8 - cycle_step)) & 0xFF) for c in text])

phase2 = base64.b85encode(phase1)

phase3 = bytes([(c >> cycle_step) | ((c << (8 - cycle_step)) & 0xFF) for c in phase2])

return bytes([phase3[i] ^ key_word[i % len(key_word)] for i in range(len(phase3))])


if __name__ == "__main__":
flag = b"palu{********}" # 可替换flag
key = b""
cipher = samsara_encrypt(flag, key)

# 修复点:使用latin-1编码处理二进制数据
print("轮回密文:", cipher.decode('latin-1')) # 输出示例:¨×èÄÅÉØÛÚ

flag.txt:

1
轮回密文: y¦_›6>X¬y–!,!n¡mSaÜñüë—9¼6™

对着逆向就行了,直接问ai都可以
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
import base64


def samsara_decrypt(cipher_text, key_word):
# 步骤1: 与关键词异或
step1 = bytes([cipher_text[i] ^ key_word[i % len(key_word)] for i in range(len(cipher_text))])

# 计算循环位移量
cycle_step = len(key_word) % 6 + 1

# 步骤2: 逆向循环右移(即循环左移cycle_step位)
step2 = bytes([((c << cycle_step) | (c >> (8 - cycle_step))) & 0xFF for c in step1])

# 步骤3: Base85解码
step3 = base64.b85decode(step2)

# 步骤4: 再次逆向循环右移(即循环左移cycle_step位)
return bytes([((c << cycle_step) | (c >> (8 - cycle_step))) & 0xFF for c in step3])


if __name__ == "__main__":
# 密文和密钥
cipher_text = "y¦_›6>X¬y–!,!n¡mSaÜñüë—9¼6™".encode('latin-1')
key_word = b"Bore" # 需要填入正确的密钥

# 解密
plain_text = samsara_decrypt(cipher_text, key_word)
print("解密结果:", plain_text.decode('utf-8', errors='replace'))

# palu{reincarnation_cipher}

文件查看器*

app.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
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
from flask import Flask, render_template, request, redirect, url_for, session, flash, send_from_directory, make_response
import os
from gmssl import sm4


def xor(byte1, byte2):
result = bytes(x ^ y for x, y in zip(byte1, byte2))
return result


key = os.urandom(16)
iv = os.urandom(16)
sm4_encryption = sm4.CryptSM4()

app = Flask(__name__, template_folder='templates', static_folder='static')
app.secret_key = b'palupalupalupalupalupalupalupalupalu'
# 保留原始的用户数据访问对象 (DAO)
class UserDAO(object):
def __init__(self):
self.users: dict[str, str] = {}

def getPassword(self, uid: str) -> str:
if uid not in self.users:
raise Exception('用户不存在')
return self.users[uid]

def create(self, uid: str, password: str):
if uid in self.users:
raise Exception('用户已注册')
self.users[uid] = password
return

DAO = UserDAO()
DAO.create('demoUser', '123456')

# --- Flask 路由 ---

@app.route('/')
def index():
if 'uid' in session:
return redirect(url_for('profile'))
return redirect(url_for('login'))

@app.route('/register', methods=['GET', 'POST'])
def register():
if request.method == 'POST':
uid = request.form.get('uid')
if 'admin' in uid:
flash('不可以是admin哦', 'error')
return redirect(url_for('register'))
password = request.form.get('password')
confirm_password = request.form.get('confirm_password')

if not uid or not password:
flash('用户ID和密码不可为空', 'error')
return redirect(url_for('register'))

if password != confirm_password:
flash('两次输入的密码不一致', 'error')
return redirect(url_for('register'))

try:
DAO.create(uid, password)
flash('注册成功,请登录', 'success')
return redirect(url_for('login'))
except Exception as e:
flash(str(e.args[0]), 'error')
return redirect(url_for('register'))

return render_template('register.html')

@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
uid = request.form.get('uid')
password = request.form.get('password')

if not uid or not password:
flash('用户ID和密码不可为空', 'error')
return redirect(url_for('login'))

try:
stored_password = DAO.getPassword(uid)
if stored_password != password:
raise Exception('用户ID或密码错误')

user_level = 'admin' if uid == 'admin' else 'guest'

sm4_encryption.set_key(key, sm4.SM4_ENCRYPT)
token_payload = f"{uid}:{user_level}".encode('utf-8')
token = sm4_encryption.crypt_cbc(iv, token_payload).hex()

session['uid'] = uid
flash(f'登录成功,您的 token 是: {token}', 'success')
# 创建响应对象并设置 cookie
response = make_response(redirect(url_for('profile')))
response.set_cookie('auth_token', token, httponly=True, samesite='Lax') # 设置 cookie
return response

except Exception as e:
flash(str(e.args[0]), 'error') # 显示更具体的错误信息
return redirect(url_for('login'))

return render_template('login.html')

@app.route('/profile')
def profile():
if 'uid' not in session:
return redirect(url_for('login'))

# 可以在这里添加解密 token 显示信息的逻辑,但暂时只显示用户名
username = session.get('uid')
return render_template('profile.html', username=username)

@app.route('/logout')
def logout():
session.pop('uid', None)
flash('您已成功登出', 'info')
return redirect(url_for('login'))

@app.route('/file', methods=['GET', 'POST'])
def read_file_page():
if 'uid' not in session :
flash('请先登录', 'warning')
return redirect(url_for('login'))
print(session)

file_content = None
error_message = None
file_path_requested = ''

if request.method == 'POST':
token = request.cookies.get('auth_token') # 从 cookie 获取 token
file_path = request.form.get('path')
file_path_requested = file_path # 保留用户输入的路径以便回显
if not file_path:
error_message = '路径不可为空'
else:
try:
# 保留原始的 SM4 CBC 令牌验证逻辑
sm4_encryption.set_key(key, sm4.SM4_DECRYPT)
token_decrypted = sm4_encryption.crypt_cbc(iv, bytes.fromhex(token))
decrypted_str = token_decrypted.decode('utf-8', errors='ignore')
parts = decrypted_str.split(':')[-2:]

uid_from_token, lv = parts

if 'admin' in lv:
print(f"管理员 {uid_from_token} 尝试读取: {file_path}")
try:

with open(file_path, 'r', encoding='utf-8') as f:
file_content = f.read()
except FileNotFoundError:
error_message = '文件未找到'
except Exception as e:
print(f"读取文件错误: {e}")
error_message = '读取文件失败'
else:
error_message = '非管理员,无权限读取服务器文件'
except ValueError as e:
error_message = f'token非法: {e}'
except Exception as e:
print(f"Token 解密/验证错误: {e}")
error_message = 'token无效或已过期'

if error_message:
flash(error_message, 'error')

return render_template('file_viewer.html', file_content=file_content, file_path_requested=file_path_requested)

# 移除 Flask-RESTX 的 404 处理,使用 Flask 默认或自定义模板
@app.errorhandler(404)
def page_not_found(e):
# 可以创建一个 templates/404.html 页面
return render_template('404.html'), 404

if __name__ == '__main__':
# 确保 static 文件夹存在
if not os.path.exists('static'):
os.makedirs('static')
# 确保 templates 文件夹存在
if not os.path.exists('templates'):
os.makedirs('templates')
# 404 页面
if not os.path.exists('templates/404.html'):
with open('templates/404.html', 'w', encoding='utf-8') as f:
f.write('<!doctype html><title>404 Not Found</title><h1>页面未找到</h1><p>您访问的页面不存在。</p><a href="/">返回首页</a>')

if not os.path.exists('static/style.css'):
with open('static/style.css', 'w', encoding='utf-8') as f:
f.write('''
body { font-family: sans-serif; margin: 20px; background-color: #f4f4f4; }
.container { max-width: 600px; margin: auto; background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 0 10px rgba(0,0,0,0.1); }
h1 { color: #333; }
.form-group { margin-bottom: 15px; }
label { display: block; margin-bottom: 5px; }
input[type="text"], input[type="password"] { width: calc(100% - 22px); padding: 10px; border: 1px solid #ddd; border-radius: 4px; }
button { background-color: #5cb85c; color: white; padding: 10px 15px; border: none; border-radius: 4px; cursor: pointer; }
button:hover { background-color: #4cae4c; }
a { color: #0275d8; text-decoration: none; }
a:hover { text-decoration: underline; }
.flash-messages { margin-bottom: 15px; }
.alert { padding: 10px; border-radius: 4px; margin-bottom: 10px; }
.alert-error { background-color: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; }
.alert-success { background-color: #d4edda; color: #155724; border: 1px solid #c3e6cb; }
.alert-info { background-color: #d1ecf1; color: #0c5460; border: 1px solid #bee5eb; }
.alert-warning { background-color: #fff3cd; color: #856404; border: 1px solid #ffeeba; }
.file-content { margin-top: 20px; padding: 15px; background-color: #eee; border: 1px solid #ddd; border-radius: 4px; white-space: pre-wrap; word-wrap: break-word; }
nav { margin-bottom: 20px; text-align: right; }
nav span { margin-right: 15px; }
''')

app.run(debug=True, host='0.0.0.0', port=10002)

在 app.py 中,token 的生成是将用户 ID 和用户级别(如 :guest 或 :admin)拼接后,使用 SM4 的 CBC 模式进行加密得到的,所以我们可以利用CBC 模式的特性来构造一个新的 token,使得解密后的用户级别变为 :admin。然后拿着新的token重新发包过去就可以了。

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
BLOCK_SIZE = 16

def pkcs7_pad(data, block_size):
padding_len = block_size - (len(data) % block_size)
padding = bytes([padding_len] * padding_len)
return data + padding


def xor_bytes(b1, b2):
return bytes(x ^ y for x, y in zip(b1, b2))


auth_token = 'c575072409e12c59db9ce9fdf3bcda799808caf067156983cf80b58086d38e09'
A = auth_token[:32]
C_old = pkcs7_pad(':guest'.encode(), BLOCK_SIZE)
B = xor_bytes(bytes.fromhex(A), C_old)
C_new = pkcs7_pad(':admin'.encode(), BLOCK_SIZE)
new_token = xor_bytes(B, C_new).hex() + auth_token[32:]
print(new_token)

发包